# `Linx.Netlink.Rtnl.Monitor`
[🔗](https://github.com/oshlabs/linx/blob/v0.2.0/lib/linx/netlink/rtnl/monitor.ex#L1)

A GenServer that owns a multicast rtnetlink socket, decodes each broadcast
into a `Linx.Netlink.Rtnl.Monitor.Event`, and forwards it to an owner pid —
the `ip monitor` equivalent.

## Lifecycle

    {:ok, mon} = Linx.Netlink.Rtnl.Monitor.subscribe()
    # → the owner receives:
    #   {:linx_rtnl, :event, %Linx.Netlink.Rtnl.Monitor.Event{...}}
    #   {:linx_rtnl, :resync_needed}    (on ENOBUFS)
    :ok = Linx.Netlink.Rtnl.Monitor.unsubscribe(mon)

## Events are wake-ups, not deltas

Netlink multicast is lossy by design: on a busy system the kernel's send
buffer fills, frames are dropped, and the next recv returns `ENOBUFS`. So the
Monitor is a *latency* layer over reconcile, never a source of truth — a
level-triggered consumer treats every event (and every `:resync_needed`) as
"look now", then re-reads and re-diffs full state. It must not act on an
event's `:resource` directly. `RTM_NEW*`/`RTM_DEL*` decode through the *same*
codecs as `list/1`, so the structs are identical to what a re-read returns.

Unlike `Linx.Netfilter.Monitor`, there is no generation counter or
snapshot-then-tail handshake — rtnetlink has no transaction id. The
level-triggered resync (re-`list`, diff, apply) is what makes a missed event
harmless.

## Groups

By default it joins links, neighbours, and IPv4/IPv6 addresses, routes, and
rules. Override with `:groups` (a list of `RTNLGRP_*` numbers).

## ENOBUFS recovery

On `ENOBUFS` the Monitor emits `{:linx_rtnl, :resync_needed}` and keeps
reading; the owner re-syncs by reconciling. `SO_RCVBUF` defaults to 4 MiB to
reduce overflow.

# `opt`

```elixir
@type opt() ::
  {:owner, pid()}
  | {:netns, Linx.Netlink.Socket.netns()}
  | {:groups, [pos_integer()]}
  | {:rcvbuf, pos_integer()}
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `start_link`

```elixir
@spec start_link([opt()]) :: GenServer.on_start()
```

Starts a Monitor linked to the caller and subscribed to the rtnetlink
multicast groups.

Options:

  * `:owner` (required) — pid that receives `{:linx_rtnl, _}` messages.
  * `:netns` — namespace to monitor; defaults to `:host`.
  * `:groups` — `RTNLGRP_*` group numbers; defaults to links, neighbours,
    and IPv4/IPv6 addresses, routes, and rules.
  * `:rcvbuf` — `SO_RCVBUF` size in bytes; default 4 MiB.

# `stop`

```elixir
@spec stop(pid()) :: :ok
```

Stops the Monitor (closes its socket).

# `subscribe`

```elixir
@spec subscribe(pid(), [opt()]) :: GenServer.on_start()
```

Convenience: start a Monitor with `owner` (default the calling process) and
the rest of `opts`. Returns `{:ok, monitor}`.

# `unsubscribe`

```elixir
@spec unsubscribe(pid()) :: :ok
```

Alias for `stop/1`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
