Packages
vintage_net
0.1.0
0.13.12
0.13.11
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.2
0.12.1
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.1
0.5.0
0.4.1
0.4.0
0.3.1
0.3.0
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Network configuration and management for Nerves
Current section
Files
Jump to
Current section
Files
lib/vintage_net.ex
defmodule VintageNet do
@moduledoc """
`VintageNet` is network configuration library built specifically for [Nerves
Project](https://nerves-project.org) devices. It has the following features:
* Ethernet and WiFi support included. Extendible to other technologies
* Default configurations specified in your Application config
* Runtime updates to configurations are persisted and applied on next boot (can
be disabled)
* Simple subscription to network status change events
* Connect to multiple networks at a time and prioritize which interfaces are
used (Ethernet over WiFi over cellular)
* Internet connection monitoring and failure detection (currently slow and
simplistic)
See
[github.com/nerves-networking/vintage_net](https://github.com/nerves-networking/vintage_net)
for more information.
"""
alias VintageNet.{Interface, Persistence, PropertyTable}
@typedoc """
A name for the network interface
Names depend on the device drivers and any software that may rename them.
Typical names on Nerves are:
* "eth0", "eth1", etc. for wired Ethernet interfaces
* "wlan0", etc. for WiFi interfaces
* "ppp0" for cellular modems
* "usb0" for gadget USB virtual Ethernet interfaces
"""
@type ifname :: String.t()
@doc """
Return a list of all interfaces on the system
NOTE: This list is currently updated every 30 seconds rather than on change.
Be patient.
"""
@spec all_interfaces() :: [ifname()]
def all_interfaces() do
for {["interface", ifname, "present"], true} <-
get_by_prefix(["interface"]) do
ifname
end
end
@doc """
Return a list of configured interface
"""
@spec configured_interfaces() :: [ifname()]
def configured_interfaces() do
for {["interface", ifname, "type"], _value} <-
get_by_prefix(["interface"]) do
ifname
end
end
@doc """
Update the settings for the specified interface
"""
@spec configure(ifname(), map()) :: :ok | {:error, any()}
def configure(ifname, config) do
# The logic here is to validate the config by converting it to a
# raw_config. We'd need to do that anyway, so just get it over with. The
# next step is to persist the config. This is important since if the
# Interface GenServer ever crashes and restarts, we want it to use this new
# config. `maybe_start_interface` might start up an Interface GenServer. If
# it does, then it will reach into reach into Persistence for the config
# and it would be bad for it to get an old config. If a GenServer isn't
# started, configure the running one.
with {:ok, raw_config} <- Interface.to_raw_config(ifname, config),
:ok <- Persistence.call(:save, [ifname, config]),
{:error, :already_started} <- maybe_start_interface(ifname) do
Interface.configure(raw_config)
end
end
@doc """
Return the settings for the specified interface
"""
@spec get_configuration(ifname()) :: map()
def get_configuration(ifname) do
Interface.get_configuration(ifname)
end
@doc """
Check if this is a valid configuration
This runs the validation routines for a settings map, but doesn't try to
apply them.
"""
@spec configuration_valid?(ifname(), map()) :: boolean()
def configuration_valid?(ifname, config) do
case Interface.to_raw_config(ifname, config) do
{:ok, _raw_config} -> true
_ -> false
end
end
@doc """
Get the current value of a network property
See `get_by_prefix/1` to get some or all properties.
"""
@spec get(PropertyTable.property(), PropertyTable.value()) :: PropertyTable.value()
def get(name, default \\ nil) do
PropertyTable.get(VintageNet, name, default)
end
@doc """
Get a list of all properties matching the specified prefix
To get a list of all known properties and their values, call
`VintageNet.get_by_prefix([])`
"""
@spec get_by_prefix(PropertyTable.property()) :: [
{PropertyTable.property(), PropertyTable.value()}
]
def get_by_prefix(prefix) do
PropertyTable.get_by_prefix(VintageNet, prefix)
end
@doc """
Subscribe to receive property change messages
Messages have the form:
```
{VintageNet, property_name, old_value, new_value, metadata}
```
"""
@spec subscribe(PropertyTable.property()) :: :ok
def subscribe(name) do
PropertyTable.subscribe(VintageNet, name)
end
@doc """
Stop subscribing to property change messages
"""
@spec unsubscribe(PropertyTable.property()) :: :ok
def unsubscribe(name) do
PropertyTable.unsubscribe(VintageNet, name)
end
@doc """
Run a command on a network interface
Commands are mostly network interface-specific. Also see the `VintageNet`
PropertyTable fo getting status or registering for status changes.
"""
@spec ioctl(ifname(), atom(), any()) :: :ok | {:ok, any()} | {:error, any()}
def ioctl(ifname, command, args \\ []) do
Interface.ioctl(ifname, command, args)
end
@doc """
Scan wireless interface for other access points
This is a utility function for calling the `:scan` ioctl.
"""
@spec scan(ifname()) :: {:ok, [VintageNet.WiFi.AccessPoint.t()]} | {:error, any()}
def scan(ifname) do
ioctl(ifname, :scan)
end
@doc """
Print the current network status
"""
@spec info() :: :ok
def info() do
version = :application.loaded_applications() |> List.keyfind(:vintage_net, 0) |> elem(2)
IO.write("""
VintageNet #{version}
All interfaces: #{inspect(all_interfaces())}
Available interfaces: #{inspect(get(["available_interfaces"]))}
""")
ifnames = configured_interfaces()
if ifnames == [] do
IO.puts("\nNo configured interfaces")
else
Enum.each(ifnames, fn ifname ->
IO.puts("\nInterface #{ifname}")
print_if_attribute(ifname, "type", "Type")
print_if_attribute(ifname, "present", "Present")
print_if_attribute(ifname, "state", "State")
print_if_attribute(ifname, "connection", "Connection")
end)
end
end
defp print_if_attribute(ifname, name, print_name) do
value = get(["interface", ifname, name])
IO.puts(" #{print_name}: #{inspect(value)}")
end
@doc """
Check that the system has the required programs installed
NOTE: This isn't completely implemented yet!
"""
@spec verify_system(keyword() | nil) :: :ok | {:error, String.t()}
def verify_system(opts \\ nil) do
opts = opts || Application.get_all_env(:vintage_net)
for ifname <- configured_interfaces() do
type = get(["interface", ifname, "type"])
apply(type, :check_system, [opts])
end
|> Enum.find(:ok, fn rc -> rc != :ok end)
end
defp maybe_start_interface(ifname) do
case VintageNet.InterfacesSupervisor.start_interface(ifname) do
{:ok, _pid} -> :ok
{:error, {:already_started, _pid}} -> {:error, :already_started}
{:error, other} -> {:error, other}
end
end
end