Created
December 13, 2017 18:53
-
-
Save adkron/73fb126cd03330601e635225a0bc3a4a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Nerves.Network.DHCPManager do | |
use GenServer | |
require Logger | |
import Nerves.Network.Utils | |
alias Nerves.Network.Types | |
@moduledoc false | |
defstruct context: :removed, | |
ifname: nil, | |
settings: nil, | |
dhcp_pid: nil, | |
dhcp_retry_interval: 60_000, | |
dhcp_retry_timer: nil | |
@typedoc "Settings for starting the server." | |
@type dhcp_settings :: Nerves.Network.setup_settings | |
@typep context :: Types.interface_context | :dhcp | :dhcp_retry | |
@typedoc """ | |
The current state machine state is called "context" to avoid confusion between server | |
state and state machine state. | |
""" | |
@type t :: %__MODULE__{ | |
context: context, | |
ifname: Types.ifname | nil, | |
settings: dhcp_settings, | |
dhcp_pid: GenServer.server() | nil, | |
dhcp_retry_interval: integer, | |
dhcp_retry_timer: reference | |
} | |
@doc false | |
@spec start_link(Types.ifname, dhcp_settings, GenServer.options) :: GenServer.on_start | |
def start_link(ifname, settings, opts \\ []) do | |
GenServer.start_link(__MODULE__, {ifname, settings}, opts) | |
end | |
def init({ifname, settings}) do | |
# Register for nerves_network_interface and udhcpc events | |
{:ok, _} = Registry.register(Nerves.NetworkInterface, ifname, []) | |
{:ok, _} = Registry.register(Nerves.Udhcpc, ifname, []) | |
state = %Nerves.Network.DHCPManager{settings: settings, ifname: ifname} | |
# If the interface currently exists send ourselves a message that it | |
# was added to get things going. | |
current_interfaces = Nerves.NetworkInterface.interfaces | |
state = | |
if Enum.member?(current_interfaces, ifname) do | |
consume(state.context, :ifadded, state) | |
else | |
state | |
end | |
{:ok, state} | |
end | |
@spec handle_registry_event({Nerves.NetworkInterface, atom, %{ifname: Types.ifname}}) :: Types.ifevent | |
defp handle_registry_event({Nerves.NetworkInterface, :ifadded, %{ifname: ifname}}) do | |
Logger.debug "DHCPManager(#{ifname}) network_interface ifadded" | |
:ifadded | |
end | |
# :ifmoved occurs on systems that assign stable names to removable | |
# interfaces. I.e. the interface is added under the dynamically chosen | |
# name and then quickly renamed to something that is stable across boots. | |
defp handle_registry_event({Nerves.NetworkInterface, :ifmoved, %{ifname: ifname}}) do | |
Logger.debug "DHCPManager(#{ifname}) network_interface ifadded (moved)" | |
:ifadded | |
end | |
defp handle_registry_event({Nerves.NetworkInterface, :ifremoved, %{ifname: ifname}}) do | |
Logger.debug "DHCPManager(#{ifname}) network_interface ifremoved" | |
:ifremoved | |
end | |
# Filter out ifup and ifdown events | |
# :is_up reports whether the interface is enabled or disabled (like by the wifi kill switch) | |
# :is_lower_up reports whether the interface as associated with an AP | |
defp handle_registry_event({Nerves.NetworkInterface, :ifchanged, %{ifname: ifname, is_lower_up: true}}) do | |
Logger.debug "DHCPManager(#{ifname}) network_interface ifup" | |
:ifup | |
end | |
defp handle_registry_event({Nerves.NetworkInterface, :ifchanged, %{ifname: ifname, is_lower_up: false}}) do | |
Logger.debug "DHCPManager(#{ifname}) network_interface ifdown" | |
:ifdown | |
end | |
defp handle_registry_event({Nerves.NetworkInterface, event, %{ifname: ifname}}) do | |
Logger.debug "DHCPManager(#{ifname}): ignoring event: #{inspect event}" | |
:noop | |
end | |
# Handle Network Interface events coming in from SystemRegistry. | |
def handle_info({Nerves.NetworkInterface, _, ifstate} = event, %{ifname: ifname} = s) do | |
event = handle_registry_event(event) | |
scope(ifname) |> SystemRegistry.update(ifstate) | |
s = consume(s.context, event, s) | |
Logger.debug "DHCPManager(#{s.ifname}, #{s.context}) got event #{inspect event}" | |
{:noreply, s} | |
end | |
# Handle Udhcpc events coming from SystemRegistry. | |
def handle_info({Nerves.Udhcpc, event, info}, %{ifname: ifname} = s) do | |
Logger.debug "DHCPManager(#{s.ifname}) udhcpc #{inspect event}" | |
scope(ifname) |> SystemRegistry.update(info) | |
s = consume(s.context, {event, info}, s) | |
{:noreply, s} | |
end | |
# Comes from the timer. | |
def handle_info(:dhcp_retry, s) do | |
s = consume(s.context, :dhcp_retry, s) | |
{:noreply, s} | |
end | |
# Catch all. | |
def handle_info(event, s) do | |
Logger.debug "DHCPManager(#{s.ifname}): ignoring event: #{inspect event}" | |
{:noreply, s} | |
end | |
## State machine implementation | |
@spec goto_context(t, context) :: t | |
defp goto_context(state, newcontext) do | |
%Nerves.Network.DHCPManager{state | context: newcontext} | |
end | |
@typedoc "Event for the state machine." | |
@type event :: Types.ifevent | Nerves.Network.Udhcpc.event | |
@spec consume(context, event, t) :: t | |
defp consume(_, :noop, state), do: state | |
## Context: :removed | |
defp consume(:removed, :ifadded, state) do | |
:ok = Nerves.NetworkInterface.ifup(state.ifname) | |
{:ok, status} = Nerves.NetworkInterface.status state.ifname | |
notify(Nerves.NetworkInterface, state.ifname, :ifchanged, status) | |
state |> goto_context(:down) | |
end | |
defp consume(:removed, :ifdown, state), do: state | |
## Context: :down | |
defp consume(:down, :ifadded, state), do: state | |
defp consume(:down, :ifup, state) do | |
state | |
|> start_udhcpc | |
|> goto_context(:dhcp) | |
end | |
defp consume(:down, :ifdown, state) do | |
state | |
|> stop_udhcpc | |
end | |
defp consume(:down, :ifremoved, state) do | |
state | |
|> stop_udhcpc | |
|> goto_context(:removed) | |
end | |
## Context: :dhcp | |
defp consume(:dhcp, :ifup, state), do: state | |
defp consume(:dhcp, :ifdown, state) do | |
state | |
|> stop_udhcpc | |
|> goto_context(:down) | |
end | |
## Context: :up | |
defp consume(:up, :ifup, state), do: state | |
defp consume(:up, :dhcp_retry, state) do | |
state | |
|> start_udhcpc | |
|> goto_context(:dhcp) | |
end | |
defp consume(:up, :ifdown, state) do | |
state | |
|> stop_udhcpc | |
|> deconfigure | |
|> goto_context(:down) | |
end | |
# Catch-all handler for consume | |
defp consume(context, event, state) do | |
Logger.warn "Unhandled event #{inspect event} for context #{inspect context} in consume/3." | |
state | |
end | |
@spec stop_udhcpc(t) :: t | |
defp stop_udhcpc(state) do | |
if is_pid(state.dhcp_pid) do | |
Nerves.Network.Udhcpc.stop(state.dhcp_pid) | |
%Nerves.Network.DHCPManager{state | dhcp_pid: nil} | |
else | |
state | |
end | |
end | |
@spec start_udhcpc(t) :: t | |
defp start_udhcpc(state) do | |
state = stop_udhcpc(state) | |
{:ok, pid} = Nerves.Network.Udhcpc.start_link(state.ifname) | |
%Nerves.Network.DHCPManager{state | dhcp_pid: pid} | |
end | |
@spec deconfigure(t) :: t | |
defp deconfigure(state) do | |
:ok = Nerves.Network.Resolvconf.clear(Nerves.Network.Resolvconf, state.ifname) | |
state | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment