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

rtnetlink policy-routing rules — the FIB rules that decide which routing
table to consult for a given packet, based on source address, destination,
firewall mark and so on.

`list/1` reads rules; `add/2` and `delete/2` install and remove them. Rules
are specified as a keyword list of selectors plus the target table:

    Rtnl.Rule.add(socket, from: "10.0.0.0/24", table: 100)
    Rtnl.Rule.add(socket, fwmark: 0x1,         table: 100, priority: 200)

`:table` is required; the family is inferred from the address selectors
(`:from` / `:to`) — or defaults to IPv4 when only non-address selectors
(`:fwmark`) are used.

Address-typed fields (`:src`, `:dst`) on a decoded `%Rule{}` are `Linx.IP`
structs.

## The two table representations

`fib_rule_hdr.table` is a `u8`, so tables above 255 are conveyed via the
separate `FRA_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 = Rule.add(sock, from: "10.0.0.0/24", table: 100)
    :ok = Rule.add(sock, fwmark: 0x1, table: 100, priority: 200)

    {:ok, rules} = Rule.list(sock)
    # => [..., #Linx.Netlink.Rtnl.Rule<priority=200 fwmark=0x1 table=100>,
    #     #Linx.Netlink.Rtnl.Rule<from=10.0.0.0/24 table=100>]

    :ok = Rule.delete(sock, from: "10.0.0.0/24", table: 100)

The wire format — `struct fib_rule_hdr` and the `FRA_*` attributes
(`include/uapi/linux/fib_rules.h`) — is declared with the
`Linx.Netlink.Codec` DSL.

# `t`

```elixir
@type t() :: %Linx.Netlink.Rtnl.Rule{
  action: term(),
  dst: term(),
  dst_len: term(),
  family: term(),
  flags: term(),
  fwmark: term(),
  priority: term(),
  res1: term(),
  res2: term(),
  src: term(),
  src_len: term(),
  table: term(),
  table_ext: term(),
  tos: term()
}
```

# `add`

```elixir
@spec add(
  Linx.Netlink.Socket.t(),
  keyword()
) :: :ok | {:error, term()}
```

Adds a policy-routing rule.

Selectors (all optional unless noted):

  * `:from`     — match source `"ip/prefix"` (CIDR string).
  * `:to`       — match destination `"ip/prefix"` (CIDR string).
  * `:fwmark`   — match firewall mark (integer).
  * `:table`    — routing table to consult (1..2^32-1). **Required.**
  * `:priority` — rule priority (smaller wins; integer).

# `decode`

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

Decodes a netlink message body into a `t:t/0`.

# `delete`

```elixir
@spec delete(
  Linx.Netlink.Socket.t(),
  keyword()
) :: :ok | {:error, term()}
```

Deletes the policy-routing rule matching `opts` (same shape as `add/2`).

# `encode`

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

Encodes a `t:t/0` into its netlink message body.

# `list`

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

Lists every policy-routing rule in the socket's network namespace.

# `target_table`

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

Returns the routing-table number this rule points to.

Handles both the in-header byte form and the `FRA_TABLE` extension used for
tables above 255.

---

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