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). Default254(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(au32); omitted when not given, which the kernel treats as metric 0.
add vs replace
add/5usesNLM_F_CREATE | NLM_F_EXCL— it errors (:eexist) if a matching route already exists.replace/5usesNLM_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
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
@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.
Functions
@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.
@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.
Decodes a netlink message body into a t/0.
@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.
@spec delete_default(Linx.Netlink.Socket.t(), binary() | Linx.IP.t(), opts()) :: :ok | {:error, term()}
Deletes the default route via gateway.
Encodes a t/0 into its netlink message body.
@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.
@spec list(Linx.Netlink.Socket.t()) :: {:ok, [t()]} | {:error, term()}
Lists every route in the socket's network namespace.
@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.
@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.