Linx.Netlink.Rtnl.Route (Linx v0.1.0)

Copy Markdown View Source

rtnetlink routes — the RTM_*ROUTE messages.

list/1 reads routes; add/5, add_default/3, replace/5, delete/5 and delete_default/3 install, update, and remove them. IPv4 and IPv6 are both supported — the address family is detected from the destination and gateway, which must agree.

Address-typed fields (:dst, :gateway) on a decoded %Route{} are Linx.IP structs; verbs accept strings or Linx.IPs.

Options

add/5, replace/5, delete/5 (and their *_default forms) take an options keyword:

  • :table — routing table (1..2^32-1). Default 254 (the main table).
  • :protocol — the route's origin tag (rtm_protocol). An integer 0..255, or one of :kernel (2), :boot (3, the default), :static (4), :ra (9), :dhcp (16). This is the ownership marker a reconciler uses to manage only its own routes (see the reconcile design notes).
  • :metric — the route metric / RTA_PRIORITY (a u32); omitted when not given, which the kernel treats as metric 0.

add vs replace

  • add/5 uses NLM_F_CREATE | NLM_F_EXCL — it errors (:eexist) if a matching route already exists.

  • replace/5 uses NLM_F_CREATE | NLM_F_REPLACE — create-or-replace. It installs the route if absent and overwrites it (e.g. a changed gateway) if present. This is the idempotent upsert a reconciler applies, and the only way to change a route's mutable attributes in place.

The two table representations

struct rtmsg's table is a u8, so tables above 255 are conveyed via the separate RTA_TABLE attribute (mapped to :table_ext); the kernel sets the header byte to RT_TABLE_UNSPEC in that case. target_table/1 returns the effective table — taking :table_ext when present, falling back to :table.

Example

{:ok, sock} = Rtnl.open()

:ok = Route.add_default(sock, "10.0.0.1")
:ok = Route.add(sock, "192.168.9.0", 24, "10.0.0.254")

# Change the gateway in place (idempotent upsert):
:ok = Route.replace(sock, "192.168.9.0", 24, "10.0.0.253")

# A route in a custom table, tagged with a dedicated protocol:
:ok = Route.add(sock, "10.50.0.0", 24, "10.0.0.1", table: 100, protocol: 4)

{:ok, routes} = Route.list(sock)
{:ok, route} = Route.get(sock, "192.168.9.7")

:ok = Route.delete_default(sock, "10.0.0.1")

The wire format — struct rtmsg and the RTA_* attributes (include/uapi/linux/rtnetlink.h) — is declared with the Linx.Netlink.Codec DSL.

Summary

Types

Options for add/5, replace/5, and delete/5. See the moduledoc.

t()

Functions

Adds a route to destination/prefix via gateway.

Adds the default route (0.0.0.0/0 for IPv4, ::/0 for IPv6) via gateway.

Decodes a netlink message body into a t/0.

Deletes the route to destination/prefix via gateway.

Deletes the default route via gateway.

Encodes a t/0 into its netlink message body.

Looks up the route the kernel would use to reach destination.

Lists every route in the socket's network namespace.

Create-or-replace: installs the route if absent, overwrites it in place (e.g. a changed gateway) if present.

Returns the effective routing table for a route, handling both the in-header byte form and the RTA_TABLE extension used for tables above 255.

Types

opts()

@type opts() :: [
  table: pos_integer(),
  protocol: 0..255 | atom(),
  metric: non_neg_integer()
]

Options for add/5, replace/5, and delete/5. See the moduledoc.

t()

@type t() :: %Linx.Netlink.Rtnl.Route{
  dst: term(),
  dst_len: term(),
  family: term(),
  flags: term(),
  gateway: term(),
  oif: term(),
  priority: term(),
  protocol: term(),
  scope: term(),
  src_len: term(),
  table: term(),
  table_ext: term(),
  tos: term(),
  type: term()
}

Functions

add(socket, destination, prefix, gateway, opts \\ [])

@spec add(
  Linx.Netlink.Socket.t(),
  binary() | Linx.IP.t(),
  non_neg_integer(),
  binary() | Linx.IP.t(),
  opts()
) :: :ok | {:error, term()}

Adds a route to destination/prefix via gateway.

Strict create: errors with :eexist if a matching route already exists. destination and gateway are each a string or an Linx.IP, and must share an address family. See the moduledoc for opts.

add_default(socket, gateway, opts \\ [])

@spec add_default(Linx.Netlink.Socket.t(), binary() | Linx.IP.t(), opts()) ::
  :ok | {:error, term()}

Adds the default route (0.0.0.0/0 for IPv4, ::/0 for IPv6) via gateway.

decode(body)

@spec decode(binary()) :: t()

Decodes a netlink message body into a t/0.

delete(socket, destination, prefix, gateway, opts \\ [])

@spec delete(
  Linx.Netlink.Socket.t(),
  binary() | Linx.IP.t(),
  non_neg_integer(),
  binary() | Linx.IP.t(),
  opts()
) :: :ok | {:error, term()}

Deletes the route to destination/prefix via gateway.

On delete the kernel filters by an option only when it is set, so by default :protocol is left unspecified (RTPROT_UNSPEC) — the route is matched regardless of which protocol installed it. Pass :protocol (and :table / :metric) to narrow the match when several routes share a destination.

delete_default(socket, gateway, opts \\ [])

@spec delete_default(Linx.Netlink.Socket.t(), binary() | Linx.IP.t(), opts()) ::
  :ok | {:error, term()}

Deletes the default route via gateway.

encode(message)

@spec encode(t()) :: binary()

Encodes a t/0 into its netlink message body.

get(socket, destination)

@spec get(Linx.Netlink.Socket.t(), binary() | Linx.IP.t()) ::
  {:ok, t()} | {:error, term()}

Looks up the route the kernel would use to reach destination.

The kernel-side ip route get equivalent: an RTM_GETROUTE without NLM_F_DUMP, with RTA_DST set, so the kernel resolves the lookup and returns the matching %Route{}. For an unroutable destination the kernel returns ENETUNREACH — surfaced as {:error, %Linx.Netlink.Error{}}.

destination is a string ("10.0.0.1", "fc00::1") or an Linx.IP.

list(socket)

@spec list(Linx.Netlink.Socket.t()) :: {:ok, [t()]} | {:error, term()}

Lists every route in the socket's network namespace.

replace(socket, destination, prefix, gateway, opts \\ [])

@spec replace(
  Linx.Netlink.Socket.t(),
  binary() | Linx.IP.t(),
  non_neg_integer(),
  binary() | Linx.IP.t(),
  opts()
) :: :ok | {:error, term()}

Create-or-replace: installs the route if absent, overwrites it in place (e.g. a changed gateway) if present.

Idempotent — the reconciler's apply verb. Same arguments as add/5.

target_table(route)

@spec target_table(t()) :: non_neg_integer()

Returns the effective routing table for a route, handling both the in-header byte form and the RTA_TABLE extension used for tables above 255.