From 5e483c01e2bd45a1e57d21e6cfcd1fe71a949521 Mon Sep 17 00:00:00 2001 From: Connor Rigby Date: Fri, 27 Mar 2020 23:08:26 -0700 Subject: [PATCH] Remove usage of dnsmasq This implementes a small DNS server that can be controlled/setup by VintageNet natively, instead of shelling out to dnsmasq. It also by extension removes the usage of dnsmasq's dhcp server, replacing it with dhcpcd which is built into VintageNet as well. --- farmbot_os/config/target/dev.exs | 17 +--- farmbot_os/config/target/prod.exs | 17 +--- .../farmbot_os/configurator/captive_dns.ex | 79 +++++++++++++++++++ farmbot_os/mix.exs | 1 + .../target/configurator/captive_portal.ex | 68 ++-------------- farmbot_os/platform/target/network.ex | 5 +- .../test/farmbot_os/captive_dns_test.exs | 13 +++ 7 files changed, 103 insertions(+), 97 deletions(-) create mode 100644 farmbot_os/lib/farmbot_os/configurator/captive_dns.ex create mode 100644 farmbot_os/test/farmbot_os/captive_dns_test.exs diff --git a/farmbot_os/config/target/dev.exs b/farmbot_os/config/target/dev.exs index 890ae616..3e622a11 100644 --- a/farmbot_os/config/target/dev.exs +++ b/farmbot_os/config/target/dev.exs @@ -10,22 +10,7 @@ config :vintage_net, persistence: VintageNet.Persistence.Null, config: [ {"wlan0", %{type: VintageNet.Technology.Null}}, - {"usb0", - %{ - type: FarmbotOS.Platform.Target.Configurator.CaptivePortal, - ipv4: %{ - method: :static, - address: "192.168.25.1", - netmask: "255.255.255.0" - }, - dnsmasq: %{ - domain: "farmbot", - server: "192.168.25.1", - address: "192.168.25.1", - start: "192.168.25.2", - end: "192.168.25.10" - } - }} + {"usb0", %{type: VintageNetDirect}} ] config :mdns_lite, diff --git a/farmbot_os/config/target/prod.exs b/farmbot_os/config/target/prod.exs index 916e6c2f..271c8607 100644 --- a/farmbot_os/config/target/prod.exs +++ b/farmbot_os/config/target/prod.exs @@ -10,22 +10,7 @@ config :vintage_net, persistence: VintageNet.Persistence.Null, config: [ {"wlan0", %{type: VintageNet.Technology.Null}}, - {"usb0", - %{ - type: FarmbotOS.Platform.Target.Configurator.CaptivePortal, - ipv4: %{ - method: :static, - address: "192.168.25.1", - netmask: "255.255.255.0" - }, - dnsmasq: %{ - domain: "farmbot", - server: "192.168.25.1", - address: "192.168.25.1", - start: "192.168.25.2", - end: "192.168.25.10" - } - }} + {"usb0", %{type: VintageNetDirect}} ] config :mdns_lite, diff --git a/farmbot_os/lib/farmbot_os/configurator/captive_dns.ex b/farmbot_os/lib/farmbot_os/configurator/captive_dns.ex new file mode 100644 index 00000000..21ff89f5 --- /dev/null +++ b/farmbot_os/lib/farmbot_os/configurator/captive_dns.ex @@ -0,0 +1,79 @@ +defmodule FarmbotOS.Configurator.CaptiveDNS do + use GenServer + alias __MODULE__, as: State + + defstruct [:dns_socket, :dns_port, :ifname] + + def start_link(ifname, port) do + GenServer.start_link(__MODULE__, [ifname, port]) + end + + @impl GenServer + def init([ifname, port]) do + send(self(), :open_dns) + # use charlist here because :inet module works with charlists + {:ok, %State{dns_port: port, ifname: to_charlist(ifname)}} + end + + @impl GenServer + # open a UDP socket on port 53 + def handle_info(:open_dns, state) do + case :gen_udp.open(state.dns_port, [:binary, active: true, reuseaddr: true]) do + {:ok, socket} -> + {:noreply, %State{state | dns_socket: socket}} + + error -> + {:stop, error, state} + end + end + + # binary dns message from the socket + def handle_info( + {:udp, socket, ip, port, packet}, + %{dns_socket: socket} = state + ) do + record = DNS.Record.decode(packet) + {answers, state} = handle_dns(record.qdlist, [], state) + response = DNS.Record.encode(%{record | anlist: answers}) + _ = :gen_udp.send(socket, ip, port, response) + {:noreply, state} + end + + # recursively check for dns queries, respond to each of them with the local ip address. + + # respond to `a` with our current ip address + defp handle_dns( + [%{type: :a} = q | rest], + answers, + state + ) do + ifname = state.ifname + {:ok, interfaces} = :inet.getifaddrs() + {^ifname, ifinfo} = List.keyfind(interfaces, ifname, 0) + + addr = + Enum.find_value(ifinfo, fn + {:addr, {_, _, _, _} = ipv4_addr} -> ipv4_addr + _ -> false + end) + + answer = make_record(q.domain, q.type, 120, addr) + handle_dns(rest, [answer | answers], state) + end + + # stop recursing when qdlist is fully enumerated + defp handle_dns([], answers, state) do + {Enum.reverse(answers), state} + end + + defp make_record(domain, type, ttl, data) do + %DNS.Resource{ + domain: domain, + class: :in, + type: type, + ttl: ttl, + data: data + } + end +end +FarmbotOS.Configurator.CaptiveDNS.start_link("lo0", 4040) \ No newline at end of file diff --git a/farmbot_os/mix.exs b/farmbot_os/mix.exs index c7c030a6..1b2c4c6a 100644 --- a/farmbot_os/mix.exs +++ b/farmbot_os/mix.exs @@ -92,6 +92,7 @@ defmodule FarmbotOS.MixProject do {:cors_plug, "~> 2.0"}, {:plug_cowboy, "~> 2.1"}, {:phoenix_html, "~> 2.13"}, + {:dns, "~> 2.1"}, # Nerves stuff. {:nerves, "~> 1.5", runtime: false}, diff --git a/farmbot_os/platform/target/configurator/captive_portal.ex b/farmbot_os/platform/target/configurator/captive_portal.ex index fb92763f..9a0bcd3e 100644 --- a/farmbot_os/platform/target/configurator/captive_portal.ex +++ b/farmbot_os/platform/target/configurator/captive_portal.ex @@ -6,6 +6,7 @@ defmodule FarmbotOS.Platform.Target.Configurator.CaptivePortal do @behaviour VintageNet.Technology require FarmbotCore.Logger + alias FarmbotOS.Configurator.CaptiveDNS @impl VintageNet.Technology def normalize(%{vintage_net_wifi: _} = config) do @@ -24,7 +25,7 @@ defmodule FarmbotOS.Platform.Target.Configurator.CaptivePortal do ifname |> vintage_wifi(normalized, opts) - |> dnsmasq(opts) + |> add_captive_dns() end def to_raw_config(ifname, config, opts) do @@ -32,7 +33,7 @@ defmodule FarmbotOS.Platform.Target.Configurator.CaptivePortal do ifname |> vintage_ethernet(normalized, opts) - |> dnsmasq(opts) + |> add_captive_dns() end @impl VintageNet.Technology @@ -45,67 +46,12 @@ defmodule FarmbotOS.Platform.Target.Configurator.CaptivePortal do VintageNetWiFi.ioctl(ifname, ioctl, args) end - defp dnsmasq( - %{ifname: ifname, source_config: %{dnsmasq: config}} = raw_config, - opts - ) do - tmpdir = Keyword.fetch!(opts, :tmpdir) - killall = Keyword.fetch!(opts, :bin_killall) - dnsmasq = System.find_executable("dnsmasq") - dnsmasq_conf_path = Path.join(tmpdir, "dnsmasq.conf.#{ifname}") - dnsmasq_lease_file = Path.join(tmpdir, "dnsmasq.leases.#{ifname}") - dnsmasq_pid_file = Path.join(tmpdir, "dnsmasq.pid.#{ifname}") - - dnsmasq_conf_contents = """ - interface=#{ifname} - except-interface=lo - localise-queries - bogus-priv - bind-interfaces - listen-address=#{config[:address]} - server=#{config[:address]} - address=/#/#{config[:address]} - dhcp-option=6,#{config[:address]} - dhcp-range=#{config[:start]},#{config[:end]},12h - """ - - files = [ - {dnsmasq_conf_path, dnsmasq_conf_contents} + defp add_captive_dns(%{ifname: ifname} = raw_config) do + child_specs = [ + {CaptiveDNS, [ifname, 53]} ] - up_cmds = [ - {:run, dnsmasq, - [ - "-K", - "-l", - dnsmasq_lease_file, - "-x", - dnsmasq_pid_file, - "-C", - dnsmasq_conf_path - ]} - ] - - down_cmds = [ - {:run, killall, ["-q", "-9", "dnsmasq"]} - ] - - updated_raw_config = %{ - raw_config - | files: raw_config.files ++ files, - up_cmds: raw_config.up_cmds ++ up_cmds, - down_cmds: raw_config.down_cmds ++ down_cmds, - cleanup_files: - raw_config.cleanup_files ++ - [dnsmasq_conf_path, dnsmasq_lease_file, dnsmasq_pid_file] - } - - updated_raw_config - end - - defp dnsmasq(%{} = raw_config, _opts) do - FarmbotCore.Logger.error(1, "DNSMASQ Disabled") - raw_config + %{raw_config | child_specs: raw_config.child_specs ++ child_specs} end defp vintage_wifi(ifname, config, opts) do diff --git a/farmbot_os/platform/target/network.ex b/farmbot_os/platform/target/network.ex index ecd7c2fa..23558d28 100644 --- a/farmbot_os/platform/target/network.ex +++ b/farmbot_os/platform/target/network.ex @@ -36,10 +36,7 @@ defmodule FarmbotOS.Platform.Target.Network do address: "192.168.24.1", netmask: "255.255.255.0" }, - dnsmasq: %{ - domain: "farmbot", - server: "192.168.24.1", - address: "192.168.24.1", + dhcpd: %{ start: "192.168.24.2", end: "192.168.24.10" } diff --git a/farmbot_os/test/farmbot_os/captive_dns_test.exs b/farmbot_os/test/farmbot_os/captive_dns_test.exs new file mode 100644 index 00000000..1482951b --- /dev/null +++ b/farmbot_os/test/farmbot_os/captive_dns_test.exs @@ -0,0 +1,13 @@ +defmodule FarmbotOS.Configurator.CaptiveDNSTest do + use ExUnit.Case, async: false + + @dig System.find_executable("dig") + if @dig do + test "all dns queries resolve to local ip address" do + {:ok, dns_server} = FarmbotOS.Configurator.CaptiveDNS.start_link("lo0", 4040) + res = :os.cmd('dig -p 4040') + refute String.contains?(to_string(res), "no servers could be reached") + end + end + +end \ No newline at end of file