lib/vintage_net/connectivity/lan_checker.ex

defmodule VintageNet.Connectivity.LANChecker do
  @moduledoc """
  This GenServer monitors a network interface for LAN connectivity

  Currently LAN connectivity simply looks to see if it's possible to
  send a packet on the interface. It might or might not get to the
  desired destination on the LAN, but it won't obviously fail.

  This is an alternative to the InternetConnectivityChecker that
  actively monitors reachability to a host.
  """

  use GenServer

  alias VintageNet.RouteManager
  require Logger

  @doc """
  Start the connectivity checker GenServer
  """
  @spec start_link(VintageNet.ifname()) :: GenServer.on_start()
  def start_link(ifname) do
    GenServer.start_link(__MODULE__, ifname)
  end

  @impl GenServer
  def init(ifname) do
    state = %{ifname: ifname}
    {:ok, state, {:continue, :continue}}
  end

  @impl GenServer
  def handle_continue(:continue, %{ifname: ifname} = state) do
    VintageNet.subscribe(lower_up_property(ifname))

    case VintageNet.get(lower_up_property(ifname)) do
      true ->
        RouteManager.set_connection_status(ifname, :lan, "ifup")

      _not_true ->
        # If the physical layer isn't up, don't start polling until
        # we're notified that it is available.
        RouteManager.set_connection_status(ifname, :disconnected, "ifdown")
    end

    {:noreply, state}
  end

  @impl GenServer
  def handle_info(
        {VintageNet, ["interface", ifname, "lower_up"], _old_value, false, _meta},
        %{ifname: ifname} = state
      ) do
    # Physical layer is down. We're definitely disconnected.
    RouteManager.set_connection_status(ifname, :disconnected, "ifdown")
    {:noreply, state}
  end

  @impl GenServer
  def handle_info(
        {VintageNet, ["interface", ifname, "lower_up"], _old_value, true, _meta},
        %{ifname: ifname} = state
      ) do
    # Physical layer is up. Optimistically assume that the LAN is accessible.

    # NOTE: Consider triggering based on whether the interface has an IP address or not.
    RouteManager.set_connection_status(ifname, :lan, "ifup")

    {:noreply, state}
  end

  @impl GenServer
  def handle_info(
        {VintageNet, ["interface", ifname, "lower_up"], old_value, nil, _meta},
        %{ifname: ifname} = state
      ) do
    # The interface was completely removed!
    if old_value, do: RouteManager.set_connection_status(ifname, :disconnected, "removed!")
    {:noreply, state}
  end

  defp lower_up_property(ifname) do
    ["interface", ifname, "lower_up"]
  end
end