Update farmbot_os app to support eap + cleanup
parent
87c3ecf83b
commit
a2339546e1
|
@ -36,7 +36,18 @@ defmodule Farmbot.Config.NetworkInterface do
|
|||
|
||||
def changeset(config, params \\ %{}) do
|
||||
config
|
||||
|> cast(params, @required_fields ++ [:ssid, :psk, :security, :ipv4_method, :ipv4_address, :ipv4_gateway, :ipv4_subnet_mask, :domain, :name_servers])
|
||||
|> cast(params, @required_fields ++ [:ssid,
|
||||
:psk,
|
||||
:security,
|
||||
:identity,
|
||||
:password
|
||||
:ipv4_method,
|
||||
:ipv4_address,
|
||||
:ipv4_gateway,
|
||||
:ipv4_subnet_mask,
|
||||
:domain,
|
||||
:name_servers
|
||||
])
|
||||
|> validate_required(@required_fields)
|
||||
|> validate_inclusion(:type, ["wireless", "wired"])
|
||||
|> unique_constraint(:name)
|
||||
|
|
|
@ -2,9 +2,9 @@ defmodule Farmbot.System.ConfigStorage.Migrations.AddEapSettings do
|
|||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table("network_interfaces") do
|
||||
add :identity, :string
|
||||
add :password, :string
|
||||
alter table("network_interfaces") do
|
||||
add(:identity, :string)
|
||||
add(:password, :string)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,8 @@ defmodule Farmbot.OS.IOLayer do
|
|||
|
||||
# Reporting commands
|
||||
def read_status(_args, _body) do
|
||||
Farmbot.BotState.fetch()
|
||||
Farmbot.AMQP.BotStateNGTransport.force()
|
||||
Farmbot.AMQP.BotStateTransport.force()
|
||||
:ok
|
||||
end
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
|
||||
"muontrap": {:hex, :muontrap, "0.4.0", "f3c48f5e2cbb89b6406d28e488fbd0da1ce0ca00af332860913999befca9688a", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"nerves": {:hex, :nerves, "1.3.4", "9523cc1936f173c99cf15a132c2b24f9c6f1a5cfe3327bbcd518ff7e441327d3", [:mix], [{:distillery, "2.0.10", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"nerves_hub_cli": {:hex, :nerves_hub_cli, "0.5.1", "9e00c23678c4f34c05b7c2fda4ad04c79a129336ea8644f8e8ce827d35bedfb5", [:mix], [{:nerves_hub_core, "~> 0.2", [hex: :nerves_hub_core, repo: "hexpm", optional: false]}, {:pbcs, "~> 0.1", [hex: :pbcs, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"nerves_hub_core": {:hex, :nerves_hub_core, "0.2.0", "ee627e0c5fd8c2511cf6e975d914c5993fabf4a1de1febca54d83910a2f476c3", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.1 or ~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
|
|
|
@ -1,214 +0,0 @@
|
|||
defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
|
||||
use GenServer
|
||||
require Farmbot.Logger
|
||||
|
||||
@interface Application.get_env(:farmbot, :captive_portal_interface, "wlan0")
|
||||
@address Application.get_env(:farmbot, :captive_portal_address, "192.168.25.1")
|
||||
@mdns_domain "farmbot-setup.local"
|
||||
|
||||
@dnsmasq_conf_file "dnsmasq.conf"
|
||||
@dnsmasq_pid_file "dnsmasq.pid"
|
||||
|
||||
def start_link() do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
Farmbot.Logger.busy(3, "Starting captive portal.")
|
||||
ensure_interface(@interface)
|
||||
|
||||
host_ap_opts = [
|
||||
ssid: build_ssid(),
|
||||
key_mgmt: :NONE,
|
||||
mode: 2
|
||||
]
|
||||
|
||||
ip_opts = [
|
||||
ipv4_address_method: :static,
|
||||
ipv4_address: @address,
|
||||
ipv4_subnet_mask: "255.255.0.0"
|
||||
]
|
||||
settings = [networks: [host_ap_opts ++ ip_opts]]
|
||||
Nerves.Network.setup(@interface, settings)
|
||||
|
||||
dhcp_opts = [
|
||||
gateway: @address,
|
||||
netmask: "255.255.255.0",
|
||||
range: {dhcp_range_begin(@address), dhcp_range_end(@address)},
|
||||
domain_servers: [@address]
|
||||
]
|
||||
|
||||
{:ok, dhcp_server} = DHCPServer.start_link(@interface, dhcp_opts)
|
||||
|
||||
dnsmasq =
|
||||
case setup_dnsmasq(@address, @interface) do
|
||||
{:ok, dnsmasq} ->
|
||||
dnsmasq
|
||||
|
||||
{:error, _} ->
|
||||
Farmbot.Logger.error(1, "Failed to start DnsMasq")
|
||||
nil
|
||||
end
|
||||
|
||||
Farmbot.Leds.blue(:slow_blink)
|
||||
init_mdns(@mdns_domain)
|
||||
update_mdns(@address)
|
||||
{:ok, %{dhcp_server: dhcp_server, dnsmasq: dnsmasq}}
|
||||
end
|
||||
|
||||
def terminate(_, state) do
|
||||
Farmbot.Logger.busy(3, "Stopping captive portal GenServer.")
|
||||
|
||||
Farmbot.Logger.busy(3, "Stopping mDNS.")
|
||||
Mdns.Server.stop()
|
||||
|
||||
Farmbot.Logger.busy(3, "Stopping DHCP GenServer.")
|
||||
GenServer.stop(state.dhcp_server, :normal)
|
||||
|
||||
stop_dnsmasq(state)
|
||||
|
||||
Nerves.Network.teardown(@interface)
|
||||
end
|
||||
|
||||
def handle_info({_port, {:data, _data}}, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp dhcp_range_begin(address) do
|
||||
[a, b, c, _] = String.split(address, ".")
|
||||
Enum.join([a, b, c, "2"], ".")
|
||||
end
|
||||
|
||||
defp dhcp_range_end(address) do
|
||||
[a, b, c, _] = String.split(address, ".")
|
||||
Enum.join([a, b, c, "10"], ".")
|
||||
end
|
||||
|
||||
defp ensure_interface(interface) do
|
||||
unless interface in Nerves.NetworkInterface.interfaces() do
|
||||
Farmbot.Logger.debug(
|
||||
2,
|
||||
"Waiting for #{interface}: #{inspect(Nerves.NetworkInterface.interfaces())}"
|
||||
)
|
||||
|
||||
Process.sleep(100)
|
||||
ensure_interface(interface)
|
||||
end
|
||||
end
|
||||
|
||||
defp build_ssid do
|
||||
{:ok, hostname} = :inet.gethostname()
|
||||
|
||||
if String.starts_with?(to_string(hostname), "farmbot-") do
|
||||
to_string('farmbot-' ++ Enum.take(hostname, -4))
|
||||
else
|
||||
to_string(hostname)
|
||||
end
|
||||
end
|
||||
|
||||
defp setup_dnsmasq(ip_addr, interface) do
|
||||
dnsmasq_conf = build_dnsmasq_conf(ip_addr, interface)
|
||||
File.mkdir_p!("/tmp/dnsmasq")
|
||||
:ok = File.write("/tmp/dnsmasq/#{@dnsmasq_conf_file}", dnsmasq_conf)
|
||||
|
||||
dnsmasq_cmd =
|
||||
"dnsmasq -k --dhcp-lease " <>
|
||||
"/tmp/dnsmasq/#{@dnsmasq_pid_file} " <> "--conf-dir=/tmp/dnsmasq"
|
||||
|
||||
dnsmasq_port = Port.open({:spawn, dnsmasq_cmd}, [:binary])
|
||||
get_dnsmasq_info(dnsmasq_port, ip_addr, interface)
|
||||
end
|
||||
|
||||
defp get_dnsmasq_info(nil, ip_addr, interface) do
|
||||
Farmbot.Logger.warn(1, "dnsmasq failed to start.")
|
||||
Process.sleep(1000)
|
||||
setup_dnsmasq(ip_addr, interface)
|
||||
end
|
||||
|
||||
defp get_dnsmasq_info(dnsmasq_port, ip_addr, interface) when is_port(dnsmasq_port) do
|
||||
case Port.info(dnsmasq_port, :os_pid) do
|
||||
{:os_pid, dnsmasq_os_pid} ->
|
||||
{dnsmasq_port, dnsmasq_os_pid}
|
||||
|
||||
nil ->
|
||||
Farmbot.Logger.warn(1, "dnsmasq not ready yet.")
|
||||
Process.sleep(1000)
|
||||
setup_dnsmasq(ip_addr, interface)
|
||||
end
|
||||
end
|
||||
|
||||
defp build_dnsmasq_conf(ip_addr, interface) do
|
||||
"""
|
||||
interface=#{interface}
|
||||
address=/#/#{ip_addr}
|
||||
server=/farmbot/#{ip_addr}
|
||||
local=/farmbot/
|
||||
domain=farmbot
|
||||
"""
|
||||
end
|
||||
|
||||
defp stop_dnsmasq(state) do
|
||||
case state.dnsmasq do
|
||||
{dnsmasq_port, dnsmasq_os_pid} ->
|
||||
Farmbot.Logger.busy(3, "Stopping dnsmasq")
|
||||
Farmbot.Logger.busy(3, "Killing dnsmasq PID.")
|
||||
:ok = kill(dnsmasq_os_pid)
|
||||
Port.close(dnsmasq_port)
|
||||
Farmbot.Logger.success(3, "Stopped dnsmasq.")
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
Farmbot.Logger.debug(3, "Dnsmasq not running.")
|
||||
:ok
|
||||
end
|
||||
rescue
|
||||
e ->
|
||||
Farmbot.Logger.error(3, "Error stopping dnsmasq: #{Exception.message(e)}")
|
||||
:ok
|
||||
end
|
||||
|
||||
defp kill(os_pid), do: :ok = cmd("kill -9 #{os_pid}")
|
||||
|
||||
defp cmd(cmd_str) do
|
||||
[command | args] = String.split(cmd_str, " ")
|
||||
|
||||
System.cmd(command, args, into: IO.stream(:stdio, :line))
|
||||
|> print_cmd()
|
||||
end
|
||||
|
||||
defp print_cmd({_, 0}), do: :ok
|
||||
|
||||
defp print_cmd({_, num}) do
|
||||
Farmbot.Logger.error(2, "Encountered an error (#{num})")
|
||||
:error
|
||||
end
|
||||
|
||||
defp init_mdns(mdns_domain) do
|
||||
Mdns.Server.add_service(%Mdns.Server.Service{
|
||||
domain: mdns_domain,
|
||||
data: :ip,
|
||||
ttl: 120,
|
||||
type: :a
|
||||
})
|
||||
end
|
||||
|
||||
defp update_mdns(ip) do
|
||||
ip_tuple = to_ip_tuple(ip)
|
||||
Mdns.Server.stop()
|
||||
|
||||
# Give the interface time to settle to fix an issue where mDNS's multicast
|
||||
# membership is not registered. This occurs on wireless interfaces and
|
||||
# needs to be revisited.
|
||||
:timer.sleep(100)
|
||||
|
||||
Mdns.Server.start(interface: ip_tuple)
|
||||
Mdns.Server.set_ip(ip_tuple)
|
||||
end
|
||||
|
||||
defp to_ip_tuple(str) do
|
||||
str
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> List.to_tuple()
|
||||
end
|
||||
end
|
|
@ -173,7 +173,7 @@ defmodule Farmbot.Target.Configurator.Router do
|
|||
security == "WPA-PSK" ->
|
||||
render_page(conn, "/config_wireless_step_2_PSK", opts)
|
||||
|
||||
security == "WPA-PSK" ->
|
||||
security == "WPA-PSK" ->
|
||||
render_page(conn, "/config_wireless_step_2_PSK", opts)
|
||||
|
||||
security == "NONE" ->
|
||||
|
|
|
@ -38,14 +38,31 @@ defmodule Farmbot.Target.Network do
|
|||
case Map.fetch!(settings, :security) do
|
||||
"WPA-PSK" -> :"WPA-PSK"
|
||||
"WPA2-PSK" -> :"WPA-PSK"
|
||||
"WPA-EAP" -> :"WPA-EAP"
|
||||
"NONE" -> :NONE
|
||||
end
|
||||
|
||||
[
|
||||
ssid: ssid,
|
||||
psk: psk,
|
||||
key_mgmt: key_mgmt
|
||||
] ++ validate_advanced(settings)
|
||||
key_mgmt: key_mgmt,
|
||||
scan_ssid: 1
|
||||
] ++ validate_eap(settings) ++ validate_advanced(settings)
|
||||
end
|
||||
|
||||
def validate_eap(%{security: "WPA-EAP"} = settings) do
|
||||
identity = Map.fetch!(settings, :identity)
|
||||
password = Map.fetch!(settings, :password)
|
||||
|
||||
[
|
||||
pairwise: :"CCMP TKIP",
|
||||
group: :"CCMP TKIP",
|
||||
eap: :PEAP,
|
||||
identity: identity,
|
||||
password: password,
|
||||
phase1: "peapver=auto",
|
||||
phase2: "MSCHAPV2"
|
||||
]
|
||||
end
|
||||
|
||||
def validate_advanced(%{ipv4_address: "static"} = settings) do
|
||||
|
@ -53,7 +70,8 @@ defmodule Farmbot.Target.Network do
|
|||
ipv4_address_method: :static,
|
||||
ipv4_address: Map.fetch!(settings, :ipv4_address),
|
||||
ipv4_gateway: Map.fetch!(settings, :ipv4_gateway),
|
||||
ipv4_subnet_mask: Map.fetch!(settings, :ipv4_subnet_mask)
|
||||
ipv4_subnet_mask: Map.fetch!(settings, :ipv4_subnet_mask),
|
||||
nameservers: String.split(settings.name_servers, " ")
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -65,7 +83,6 @@ defmodule Farmbot.Target.Network do
|
|||
ifnames()
|
||||
|> List.delete("lo")
|
||||
|> Enum.map(fn ifname ->
|
||||
IO.puts("Looking up #{ifname}")
|
||||
{:ok, settings} = Nerves.NetworkInterface.settings(ifname)
|
||||
{ifname, settings}
|
||||
end)
|
||||
|
@ -111,7 +128,6 @@ defmodule Farmbot.Target.Network do
|
|||
|
||||
for ifname <- state.ifnames do
|
||||
Nerves.Network.teardown(ifname)
|
||||
Nerves.NetworkInterface.ifdown(ifname)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -169,7 +185,6 @@ defmodule Farmbot.Target.Network do
|
|||
for {ifname, _conf} <- state.config do
|
||||
Nerves.Network.teardown(ifname)
|
||||
Nerves.Network.setup(ifname, [])
|
||||
Nerves.NetworkInterface.setup(ifname, [])
|
||||
end
|
||||
|
||||
%{state | hostap: :down, hostap_wpa_supplicant_pid: nil}
|
||||
|
@ -203,8 +218,7 @@ defmodule Farmbot.Target.Network do
|
|||
]
|
||||
|
||||
Nerves.Network.teardown("wlan0")
|
||||
Nerves.Network.setup("wlan0", hostap_opts)
|
||||
Nerves.NetworkInterface.setup("wlan0", ip_opts)
|
||||
Nerves.Network.setup("wlan0", hostap_opts ++ ip_opts)
|
||||
{:ok, hostap_dhcp_server_pid} = DHCPServer.start_link("wlan0", dhcp_opts)
|
||||
{:ok, hostap_wpa_supplicant_pid} = wait_for_wpa("wlan0")
|
||||
|
||||
|
|
|
@ -1,304 +0,0 @@
|
|||
### WARNING(Connor) 2018-08-16
|
||||
### Do not touch anything in this file unless you understand _exactly_
|
||||
### what you are doing. If you look at it wrong, you will cause the
|
||||
### Raspberry pi to kernel panic for some reason. I have
|
||||
### no idea why. Just move along.
|
||||
### If you are in this file, please at least be kind enough as to not touch any
|
||||
### of the timing sensitive things. It _will_ break.
|
||||
|
||||
defmodule Farmbot.Target.Network.Manager do
|
||||
use GenServer
|
||||
require Farmbot.Logger
|
||||
import Farmbot.Config, only: [get_config_value: 3]
|
||||
alias Farmbot.Target.Network.NotFoundTimer
|
||||
import Farmbot.Target.Network, only: [test_dns: 0]
|
||||
|
||||
def debug_logs? do
|
||||
Application.get_env(:farmbot, :network_debug_logs, false)
|
||||
end
|
||||
|
||||
def debug_logs(bool) do
|
||||
Application.put_env(:farmbot, :network_debug_logs, bool)
|
||||
end
|
||||
|
||||
def get_ip_addr(interface) do
|
||||
GenServer.call(:"#{__MODULE__}-#{interface}", :ip)
|
||||
end
|
||||
|
||||
def get_state(interface) do
|
||||
:sys.get_state(:"#{__MODULE__}-#{interface}")
|
||||
end
|
||||
|
||||
def start_link(interface, opts) do
|
||||
GenServer.start_link(__MODULE__, {interface, opts}, name: :"#{__MODULE__}-#{interface}")
|
||||
end
|
||||
|
||||
def init({interface, opts} = args) do
|
||||
Farmbot.Logger.busy(3, "Waiting for interface #{interface} up.")
|
||||
|
||||
unless interface in Nerves.NetworkInterface.interfaces() do
|
||||
Process.sleep(1000)
|
||||
init(args)
|
||||
end
|
||||
|
||||
Farmbot.Logger.success(3, "Interface #{interface} is up.")
|
||||
Process.flag(:sensitive, true)
|
||||
s1 = get_config_value(:string, "settings", "default_ntp_server_1")
|
||||
s2 = get_config_value(:string, "settings", "default_ntp_server_2")
|
||||
Nerves.Time.set_ntp_servers([s1, s2])
|
||||
maybe_hack_tzdata()
|
||||
Nerves.Network.teardown(interface)
|
||||
Nerves.Network.setup(interface, opts)
|
||||
{:ok, _} = Elixir.Registry.register(Nerves.NetworkInterface, interface, [])
|
||||
{:ok, _} = Elixir.Registry.register(Nerves.Udhcpc, interface, [])
|
||||
{:ok, _} = Elixir.Registry.register(Nerves.WpaSupplicant, interface, [])
|
||||
|
||||
domain = node() |> to_string() |> String.split("@") |> List.last() |> Kernel.<>(".local")
|
||||
init_mdns(domain)
|
||||
|
||||
state = %{
|
||||
# These won't change
|
||||
mdns_domain: domain,
|
||||
interface: interface,
|
||||
opts: opts,
|
||||
|
||||
# These change based on
|
||||
# Events from timers and other processes.
|
||||
ip_address: nil,
|
||||
connected: false,
|
||||
ap_connected: false,
|
||||
|
||||
# Tries to reconnect after "network not found" event.
|
||||
reconnect_timer: nil,
|
||||
|
||||
# Tests internet connectivity.
|
||||
dns_timer: nil
|
||||
}
|
||||
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def terminate(_, state) do
|
||||
Nerves.Network.teardown(state.interface)
|
||||
end
|
||||
|
||||
def handle_call(:ip, _, state) do
|
||||
{:reply, state.ip_address, state}
|
||||
end
|
||||
|
||||
# When assigned an IP address.
|
||||
def handle_info({Nerves.Udhcpc, :bound, %{ipv4_address: ip}}, state) do
|
||||
Farmbot.Logger.debug(3, "Ip address: #{ip}")
|
||||
NotFoundTimer.stop()
|
||||
connected = match?({:ok, {:hostent, _, _, :inet, 4, _}}, test_dns())
|
||||
|
||||
if connected do
|
||||
init_mdns(state.mdns_domain)
|
||||
dns_timer = restart_dns_timer(state.dns_timer, 45_000)
|
||||
update_mdns(ip, state.mdns_domain)
|
||||
{:noreply, %{state | dns_timer: dns_timer, ip_address: ip, connected: true}}
|
||||
else
|
||||
{:noreply, %{state | connected: false, ip_address: ip}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{Nerves.WpaSupplicant,
|
||||
{:INFO, "WPA: 4-Way Handshake failed - pre-shared key may be incorrect"}, _},
|
||||
state
|
||||
) do
|
||||
Farmbot.Logger.error(1, "Incorrect PSK.")
|
||||
Farmbot.System.factory_reset("WIFI Authentication failed. (incorrect psk)")
|
||||
{:stop, :normal, state}
|
||||
end
|
||||
|
||||
def handle_info({Nerves.WpaSupplicant, {:"CTRL-EVENT-CONNECTED", _bssid, _status, _}, _}, state) do
|
||||
# Don't update `connected`. This is not a real test of connectivity.
|
||||
Logger.success 1, "Connected to access point."
|
||||
NotFoundTimer.stop()
|
||||
{:noreply, %{state | ap_connected: true}}
|
||||
end
|
||||
|
||||
def handle_info({Nerves.WpaSupplicant, {:"CTRL-EVENT-DISCONNECTED", _}, _}, state) do
|
||||
# stored in minutes
|
||||
reconnect_timer = if state.connected, do: restart_connection_timer(state)
|
||||
maybe_refresh_token()
|
||||
NotFoundTimer.start()
|
||||
|
||||
new_state = %{
|
||||
state
|
||||
| ap_connected: false,
|
||||
connected: false,
|
||||
ip_address: nil,
|
||||
reconnect_timer: reconnect_timer
|
||||
}
|
||||
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
def handle_info({Nerves.WpaSupplicant, :"CTRL-EVENT-CONNECTED", _}, state) do
|
||||
# Don't update `connected`. This is not a real test of connectivity.
|
||||
Farmbot.Logger.success(1, "Connected to access point.")
|
||||
NotFoundTimer.stop()
|
||||
{:noreply, %{state | ap_connected: true}}
|
||||
end
|
||||
|
||||
def handle_info({Nerves.WpaSupplicant, :"CTRL-EVENT-DISCONNECTED", _}, state) do
|
||||
# stored in minutes
|
||||
reconnect_timer = if state.connected, do: restart_connection_timer(state)
|
||||
maybe_refresh_token()
|
||||
NotFoundTimer.start()
|
||||
|
||||
new_state = %{
|
||||
state
|
||||
| ap_connected: false,
|
||||
connected: false,
|
||||
ip_address: nil,
|
||||
reconnect_timer: reconnect_timer
|
||||
}
|
||||
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
def handle_info({Nerves.WpaSupplicant, {:"CTRL-EVENT-SSID-TEMP-DISABLED", %{duration: 20}}, _}, state) do
|
||||
reconnect_timer = if state.connected, do: restart_connection_timer(state)
|
||||
maybe_refresh_token()
|
||||
NotFoundTimer.start()
|
||||
new_state = %{state |
|
||||
ap_connected: false,
|
||||
connected: false,
|
||||
ip_address: nil,
|
||||
reconnect_timer: reconnect_timer
|
||||
}
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
def handle_info(:reconnect_timer, %{ap_connected: false} = state) do
|
||||
Farmbot.Logger.warn(1, "Wireless network not found still. Trying again.")
|
||||
{:stop, :reconnect_timer, state}
|
||||
end
|
||||
|
||||
def handle_info(:reconnect_timer, %{ap_connected: true} = state) do
|
||||
Farmbot.Logger.success(1, "Wireless network reconnected.")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(:dns_timer, %{connected: true} = state) do
|
||||
case test_dns() do
|
||||
{:ok, {:hostent, _host_name, _aliases, :inet, 4, _}} ->
|
||||
# Farmbot is still connected. NBD
|
||||
{:noreply, %{state | dns_timer: restart_dns_timer(nil, 45_000)}}
|
||||
|
||||
{:error, err} ->
|
||||
maybe_refresh_token()
|
||||
Farmbot.Logger.warn(3, "Farmbot was disconnected from the internet: #{inspect(err)}")
|
||||
{:noreply, %{state | connected: false, dns_timer: restart_dns_timer(nil, 20_000)}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(:dns_timer, %{ip_address: nil} = state) do
|
||||
Farmbot.Logger.warn(3, "Farmbot still disconnected from the internet")
|
||||
{:noreply, %{state | connected: false, dns_timer: restart_dns_timer(nil, 20_000)}}
|
||||
end
|
||||
|
||||
def handle_info(:dns_timer, state) do
|
||||
case test_dns() do
|
||||
{:ok, {:hostent, _host_name, aliases, :inet, 4, _}} ->
|
||||
# If we weren't previously connected, send a log.
|
||||
Farmbot.Logger.success(3, "Farmbot was reconnected to the internet: #{inspect(aliases)}")
|
||||
maybe_refresh_token()
|
||||
new_state = %{state | connected: true, dns_timer: restart_dns_timer(nil, 45_000)}
|
||||
{:noreply, new_state}
|
||||
|
||||
{:error, err} ->
|
||||
Farmbot.Logger.warn(3, "Farmbot was disconnected from the internet: #{inspect(err)}")
|
||||
maybe_refresh_token()
|
||||
{:noreply, %{state | connected: false, dns_timer: restart_dns_timer(nil, 20_000)}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(_event, state) do
|
||||
# Farmbot.Logger.warn 3, "unhandled network event: #{inspect event}"
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp cancel_timer(timer) do
|
||||
# If there was a timer, cancel it.
|
||||
if timer do
|
||||
# Farmbot.Logger.warn 3, "Cancelling Network timer"
|
||||
Process.cancel_timer(timer)
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
defp restart_dns_timer(timer, time) when is_integer(time) do
|
||||
cancel_timer(timer)
|
||||
Process.send_after(self(), :dns_timer, time)
|
||||
end
|
||||
|
||||
defp restart_connection_timer(state) do
|
||||
cancel_timer(state.reconnect_timer)
|
||||
Nerves.Network.teardown(state.interface)
|
||||
Process.sleep(5000)
|
||||
Nerves.Network.setup(state.interface, state.opts)
|
||||
Process.send_after(self(), :reconnect_timer, 30_000)
|
||||
end
|
||||
|
||||
defp maybe_refresh_token do
|
||||
if Process.whereis(Farmbot.Bootstrap.AuthTask) do
|
||||
Farmbot.Bootstrap.AuthTask.force_refresh()
|
||||
else
|
||||
Farmbot.Logger.warn(1, "AuthTask not running yet")
|
||||
end
|
||||
end
|
||||
|
||||
defp init_mdns(mdns_domain) do
|
||||
Mdns.Server.add_service(%Mdns.Server.Service{
|
||||
domain: mdns_domain,
|
||||
data: :ip,
|
||||
ttl: 120,
|
||||
type: :a
|
||||
})
|
||||
end
|
||||
|
||||
defp update_mdns(ip, _mdns_domain) do
|
||||
ip_tuple = to_ip_tuple(ip)
|
||||
Mdns.Server.stop()
|
||||
|
||||
# Give the interface time to settle to fix an issue where mDNS's multicast
|
||||
# membership is not registered. This occurs on wireless interfaces and
|
||||
# needs to be revisited.
|
||||
:timer.sleep(100)
|
||||
|
||||
Mdns.Server.start(interface: ip_tuple)
|
||||
Mdns.Server.set_ip(ip_tuple)
|
||||
end
|
||||
|
||||
defp to_ip_tuple(str) do
|
||||
str
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> List.to_tuple()
|
||||
end
|
||||
|
||||
@fb_data_dir Application.get_env(:farmbot_ext, :data_path)
|
||||
@tzdata_dir Application.app_dir(:tzdata, "priv")
|
||||
def maybe_hack_tzdata do
|
||||
case Tzdata.Util.data_dir() do
|
||||
@fb_data_dir ->
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
Farmbot.Logger.debug(3, "Hacking tzdata.")
|
||||
objs_to_cp = Path.wildcard(Path.join(@tzdata_dir, "*"))
|
||||
|
||||
for obj <- objs_to_cp do
|
||||
File.cp_r(obj, @fb_data_dir)
|
||||
end
|
||||
|
||||
Application.put_env(:tzdata, :data_dir, @fb_data_dir)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,264 +0,0 @@
|
|||
defmodule Farmbot.Target.Network do
|
||||
@moduledoc "Bring up network."
|
||||
|
||||
import Farmbot.Config, only: [get_config_value: 3, get_all_network_configs: 0]
|
||||
alias Farmbot.Config.NetworkInterface
|
||||
alias Farmbot.Target.Network.Manager, as: NetworkManager
|
||||
alias Farmbot.Target.Network.NotFoundTimer
|
||||
alias Farmbot.Target.Network.ScanResult
|
||||
|
||||
use Supervisor
|
||||
require Farmbot.Logger
|
||||
|
||||
@doc "List available interfaces. Removes unusable entries."
|
||||
def get_interfaces(tries \\ 5)
|
||||
def get_interfaces(0), do: []
|
||||
|
||||
def get_interfaces(tries) do
|
||||
case Nerves.NetworkInterface.interfaces() do
|
||||
["lo"] ->
|
||||
Process.sleep(100)
|
||||
get_interfaces(tries - 1)
|
||||
|
||||
interfaces when is_list(interfaces) ->
|
||||
interfaces
|
||||
# Delete unusable entries if they exist.
|
||||
|> List.delete("usb0")
|
||||
|> List.delete("lo")
|
||||
|> List.delete("sit0")
|
||||
|> Map.new(fn interface ->
|
||||
{:ok, settings} = Nerves.NetworkInterface.status(interface)
|
||||
{interface, settings}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Scan on an interface."
|
||||
def scan(iface) do
|
||||
do_scan(iface)
|
||||
|> ScanResult.decode()
|
||||
|> ScanResult.sort_results()
|
||||
|> ScanResult.decode_security()
|
||||
|> Enum.filter(&Map.get(&1, :ssid))
|
||||
|> Enum.map(&Map.update(&1, :ssid, nil, fn ssid -> to_string(ssid) end))
|
||||
|> Enum.reject(&String.contains?(&1.ssid, "\\x00"))
|
||||
|> Enum.uniq_by(fn %{ssid: ssid} -> ssid end)
|
||||
end
|
||||
|
||||
defp wait_for_results(pid) do
|
||||
Nerves.WpaSupplicant.request(pid, :SCAN_RESULTS)
|
||||
|> String.trim()
|
||||
|> String.split("\n")
|
||||
|> tl()
|
||||
|> Enum.map(&String.split(&1, "\t"))
|
||||
|> reduce_decode()
|
||||
|> case do
|
||||
[] ->
|
||||
Process.sleep(500)
|
||||
wait_for_results(pid)
|
||||
|
||||
res ->
|
||||
res
|
||||
end
|
||||
end
|
||||
|
||||
defp reduce_decode(results, acc \\ [])
|
||||
defp reduce_decode([], acc), do: Enum.reverse(acc)
|
||||
|
||||
defp reduce_decode([[bssid, freq, signal, flags, ssid] | rest], acc) do
|
||||
decoded = %{
|
||||
bssid: bssid,
|
||||
frequency: String.to_integer(freq),
|
||||
flags: flags,
|
||||
level: String.to_integer(signal),
|
||||
ssid: ssid
|
||||
}
|
||||
|
||||
reduce_decode(rest, [decoded | acc])
|
||||
end
|
||||
|
||||
defp reduce_decode([[bssid, freq, signal, flags] | rest], acc) do
|
||||
decoded = %{
|
||||
bssid: bssid,
|
||||
frequency: String.to_integer(freq),
|
||||
flags: flags,
|
||||
level: String.to_integer(signal),
|
||||
ssid: nil
|
||||
}
|
||||
|
||||
reduce_decode(rest, [decoded | acc])
|
||||
end
|
||||
|
||||
defp reduce_decode([_ | rest], acc) do
|
||||
reduce_decode(rest, acc)
|
||||
end
|
||||
|
||||
def do_scan(iface) do
|
||||
pid = :"Nerves.WpaSupplicant.#{iface}"
|
||||
|
||||
if Process.whereis(pid) do
|
||||
Nerves.WpaSupplicant.request(pid, :SCAN)
|
||||
wait_for_results(pid)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def get_level(ifname, ssid) do
|
||||
r = Farmbot.Target.Network.scan(ifname)
|
||||
|
||||
if res = Enum.find(r, &(Map.get(&1, :ssid) == ssid)) do
|
||||
res.level
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Tests if we can make dns queries."
|
||||
def test_dns(hostname \\ nil)
|
||||
|
||||
def test_dns(nil) do
|
||||
case get_config_value(:string, "authorization", "server") do
|
||||
nil ->
|
||||
test_dns(get_config_value(:string, "settings", "default_dns_name"))
|
||||
|
||||
url when is_binary(url) ->
|
||||
%URI{host: hostname} = URI.parse(url)
|
||||
test_dns(hostname)
|
||||
end
|
||||
end
|
||||
|
||||
def test_dns(hostname) when is_binary(hostname) do
|
||||
test_dns(to_charlist(hostname))
|
||||
end
|
||||
|
||||
def test_dns(hostname) do
|
||||
:ok = :inet_db.clear_cache()
|
||||
# IO.puts "testing dns: #{hostname}"
|
||||
case :inet.parse_ipv4_address(hostname) do
|
||||
{:ok, addr} -> {:ok, {:hostent, hostname, [], :inet, 4, [addr]}}
|
||||
_ -> :inet_res.gethostbyname(hostname)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO Expand this to allow for more settings.
|
||||
def to_network_config(config)
|
||||
|
||||
def to_network_config(%NetworkInterface{type: "wireless"} = config) do
|
||||
Farmbot.Logger.debug(3, "wireless network config: ssid: #{config.ssid}")
|
||||
Nerves.Network.set_regulatory_domain(config.regulatory_domain)
|
||||
case config.security do
|
||||
"WPA-EAP" ->
|
||||
opts = [
|
||||
ssid: config.ssid,
|
||||
scan_ssid: 1,
|
||||
key_mgmt: :"WPA-EAP",
|
||||
pairwise: :"CCMP TKIP",
|
||||
group: :"CCMP TKIP",
|
||||
eap: :PEAP,
|
||||
identity: config.identity,
|
||||
password: config.password,
|
||||
phase1: "peapver=auto",
|
||||
phase2: "MSCHAPV2"
|
||||
]
|
||||
ip_settings = ip_settings(config)
|
||||
{config.name, [networks: [opts ++ ip_settings]]}
|
||||
"WPA-PSK" ->
|
||||
opts = [ssid: config.ssid, psk: config.psk, key_mgmt: :"WPA-PSK", scan_ssid: 1]
|
||||
ip_settings = ip_settings(config)
|
||||
{config.name, [networks: [opts ++ ip_settings]]}
|
||||
"NONE" ->
|
||||
opts = [ssid: config.ssid, psk: config.psk, scan_ssid: 1]
|
||||
ip_settings = ip_settings(config)
|
||||
{config.name, [networks: [opts ++ ip_settings]]}
|
||||
other -> raise "Unsupported wireless security type: #{other}"
|
||||
end
|
||||
end
|
||||
|
||||
def to_network_config(%NetworkInterface{type: "wired"} = config) do
|
||||
{config.name, ip_settings(config)}
|
||||
end
|
||||
|
||||
defp ip_settings(config) do
|
||||
case config.ipv4_method do
|
||||
"static" ->
|
||||
[
|
||||
ipv4_address_method: :static,
|
||||
ipv4_address: config.ipv4_address,
|
||||
ipv4_gateway: config.ipv4_gateway,
|
||||
ipv4_subnet_mask: config.ipv4_subnet_mask
|
||||
]
|
||||
|
||||
{name, Keyword.merge(opts, settings)}
|
||||
|
||||
"dhcp" ->
|
||||
{name, opts}
|
||||
end
|
||||
|> maybe_use_name_servers(config)
|
||||
|> maybe_use_domain(config)
|
||||
end
|
||||
|
||||
# This is a typo. It should have been `nameservers` not `name_servers`
|
||||
# It is however stored in the database as
|
||||
# `name_servers`, so it can not be changed without a migration.
|
||||
defp maybe_use_name_servers(opts, config) do
|
||||
if config.name_servers do
|
||||
Keyword.put(opts, :nameservers, String.split(config.name_servers, " "))
|
||||
else
|
||||
opts
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_use_domain(opts, config) do
|
||||
if config.domain do
|
||||
Keyword.put(opts, :domain, config.domain)
|
||||
else
|
||||
opts
|
||||
end
|
||||
end
|
||||
|
||||
def to_child_spec({interface, opts}) do
|
||||
worker(NetworkManager, [interface, opts], restart: :transient)
|
||||
end
|
||||
|
||||
def start_link(args) do
|
||||
Supervisor.start_link(__MODULE__, args, name: __MODULE__)
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
config = get_all_network_configs()
|
||||
Farmbot.Logger.info(3, "Starting Networking")
|
||||
s1 = Farmbot.Config.get_config_value(:string, "settings", "default_ntp_server_1")
|
||||
s2 = Farmbot.Config.get_config_value(:string, "settings", "default_ntp_server_2")
|
||||
Nerves.Time.set_ntp_servers([s1, s2])
|
||||
maybe_hack_tzdata()
|
||||
|
||||
children =
|
||||
config
|
||||
|> Enum.map(&to_network_config/1)
|
||||
|> Enum.map(&to_child_spec/1)
|
||||
# Don't know why/if we need this?
|
||||
|> Enum.uniq()
|
||||
|
||||
children = [{NotFoundTimer, []}] ++ children
|
||||
Supervisor.init(children, strategy: :one_for_one, max_restarts: 20, max_seconds: 1)
|
||||
end
|
||||
|
||||
@fb_data_dir Application.get_env(:farmbot_ext, :data_path)
|
||||
@tzdata_dir Application.app_dir(:tzdata, "priv")
|
||||
def maybe_hack_tzdata do
|
||||
case Tzdata.Util.data_dir() do
|
||||
@fb_data_dir ->
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
Farmbot.Logger.debug(3, "Hacking tzdata.")
|
||||
objs_to_cp = Path.wildcard(Path.join(@tzdata_dir, "*"))
|
||||
|
||||
for obj <- objs_to_cp do
|
||||
File.cp_r(obj, @fb_data_dir)
|
||||
end
|
||||
|
||||
Application.put_env(:tzdata, :data_dir, @fb_data_dir)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue