180 lines
5.2 KiB
Elixir
180 lines
5.2 KiB
Elixir
defmodule Farmbot.Target.Network do
|
|
@moduledoc "Bring up network."
|
|
|
|
@behaviour Farmbot.System.Init
|
|
alias Farmbot.System.ConfigStorage
|
|
import ConfigStorage, only: [get_config_value: 3]
|
|
alias ConfigStorage.NetworkInterface
|
|
alias Farmbot.Target.Network.Manager, as: NetworkManager
|
|
alias Farmbot.Target.Network.ScanResult
|
|
|
|
use Supervisor
|
|
use 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
|
|
|> List.delete("usb0") # Delete unusable entries if they exist.
|
|
|> 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"))
|
|
|> Enum.map(fn(res) ->
|
|
case res do
|
|
[bssid, freq, signal, flags, ssid] ->
|
|
%{bssid: bssid,
|
|
frequency: String.to_integer(freq),
|
|
flags: flags,
|
|
level: String.to_integer(signal),
|
|
ssid: ssid
|
|
}
|
|
|
|
[bssid, freq, signal, flags] ->
|
|
%{bssid: bssid,
|
|
frequency: String.to_integer(freq),
|
|
flags: flags,
|
|
level: String.to_integer(signal),
|
|
ssid: nil
|
|
}
|
|
end
|
|
end)
|
|
|> case do
|
|
[] ->
|
|
Process.sleep(500)
|
|
wait_for_results(pid)
|
|
res -> res
|
|
end
|
|
end
|
|
|
|
def do_scan(iface) do
|
|
pid = :"Nerves.WpaSupplicant.#{iface}"
|
|
Nerves.WpaSupplicant.request(pid, :SCAN)
|
|
wait_for_results(pid)
|
|
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
|
|
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
|
|
Logger.debug(3, "wireless network config: ssid: #{config.ssid}")
|
|
opts = [ssid: config.ssid, key_mgmt: config.security]
|
|
case config.security do
|
|
"WPA-PSK" ->
|
|
{config.name, Keyword.merge(opts, [psk: config.psk])} |> maybe_use_advanced(config)
|
|
"NONE" ->
|
|
{config.name, opts} |> maybe_use_advanced(config)
|
|
other -> raise "Unsupported wireless security type: #{other}"
|
|
end
|
|
end
|
|
|
|
def to_network_config(%NetworkInterface{type: "wired"} = config) do
|
|
{config.name, []} |> maybe_use_advanced(config)
|
|
end
|
|
|
|
defp maybe_use_advanced({name, opts}, config) do
|
|
case config.ipv4_method do
|
|
"static" ->
|
|
settings = [ipv4_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
|
|
|
|
defp maybe_use_name_servers({name, opts}, config) do
|
|
if config.name_servers do
|
|
{name, Keyword.put(opts, :name_servers, String.split(config.name_servers, " "))}
|
|
else
|
|
{name, opts}
|
|
end
|
|
end
|
|
|
|
defp maybe_use_domain({name, opts}, config) do
|
|
if config.domain do
|
|
{name, Keyword.put(opts, :domain, config.domain)}
|
|
else
|
|
{name, opts}
|
|
end
|
|
end
|
|
|
|
def to_child_spec({interface, opts}) do
|
|
worker(NetworkManager, [interface, opts])
|
|
end
|
|
|
|
def start_link(_, opts) do
|
|
Supervisor.start_link(__MODULE__, [], opts)
|
|
end
|
|
|
|
def init([]) do
|
|
config = ConfigStorage.get_all_network_configs()
|
|
Logger.info(3, "Starting Networking")
|
|
children = config
|
|
|> Enum.map(&to_network_config/1)
|
|
|> Enum.map(&to_child_spec/1)
|
|
|> Enum.uniq() # Don't know why/if we need this?
|
|
Supervisor.init(children, strategy: :one_for_one, max_restarts: 20, max_seconds: 1)
|
|
end
|
|
end
|