# Conditions

> moat conditions: protocol tests, header fields for Ethernet, VLAN, IPv4, IPv6, TCP, UDP and ICMP, comparisons, TCP flags, and payload slices and contains.

Source: https://moatfw.dev/docs/conditions

A condition combines protocol tests, comparisons on header fields and
payload matches with `and`, `or`, `not` and parentheses.

## Protocol tests

`eth` `ip` `ip4` `ip6` `tcp` `udp` `icmp` `icmp6`

## Fields

| Field | Type | Present when |
|-------|------|--------------|
| `eth.type` | 16-bit integer | always |
| `vlan.id` | 12-bit integer | the packet has one 802.1Q tag |
| `ip.src`, `ip.dst` | address | IPv4 or IPv6 |
| `ip.proto`, `ip.ttl` | 8-bit integer | IPv4 or IPv6 (`ip.ttl` is the hop limit on IPv6) |
| `ip.len` | 16-bit integer | IPv4 or IPv6 (on IPv6 the payload length plus 40, held at 65535) |
| `ip.frag` | condition | IPv4 or IPv6; true for any fragment |
| `tcp.sport`, `tcp.dport`, `tcp.window` | 16-bit integer | TCP |
| `tcp.flags` | set of flags | TCP |
| `tcp.mss` | 16-bit integer | TCP with an MSS option among its first 8 options |
| `udp.sport`, `udp.dport`, `udp.len` | 16-bit integer | UDP |
| `icmp.type`, `icmp.code` | 8-bit integer | ICMP or ICMPv6 |

> A test on a header the packet does not have is false, the negated forms
> too. `not udp.dport == 53` also matches every packet that is not UDP;
> write `udp and not udp.dport == 53` if that is the intent.

## Comparisons

| Left side | Operators | Right side |
|-----------|-----------|------------|
| integer field | `== != < <= > >=` | integer |
| integer field | `in`, `not in` | integer, range or list |
| address field | `== !=` | address |
| address field | `in`, `not in` | set, prefix, address or list of them |
| `tcp.flags` | `==`, `!=` | list of flags (`{}` means no flags) |
| `tcp.flags` | `has` | non-empty list of flags |
| `payload[OFF:LEN]` | `==`, `!=` | integer or string of exactly LEN bytes |
| `payload`, `payload[OFF:LEN]` | `contains`, `not contains` | string or `0x...` bytes, 1 to 32 bytes |

## Matching the payload

`payload[OFF:LEN]` compares a slice at a fixed place. `payload contains`
finds text or bytes anywhere in the first 256 bytes, or in a window of up
to 512.

```moat
rule source_query {
    when udp.dport == 27015 and payload[0:4] == 0xffffffff
    then limit 10/s per ip.src burst 20
}

rule admin_probe {
    when tcp.dport == 80 and payload contains "GET /admin"
    then drop
}
```

A slice reads LEN bytes (1 to 32) starting OFF bytes after the transport
header, ending no later than byte 1500. Packets too short for the slice
do not match.

`payload[OFF:LEN] contains TEXT` looks in a window of LEN bytes from OFF,
and the window must end by byte 512. `not contains` is true only when the
packet carries at least OFF plus the text's length in payload bytes, so a
SYN, a bare ACK or an empty datagram is neither.

The end of the payload comes from the IP length, so the Ethernet padding
of a short frame is not payload: `payload contains` never matches padding
bytes, and an empty datagram padded to 60 bytes still has no payload.

A file can use at most 16 distinct text and window pairs: the kernel
checks each search on its own when loading. Using the same text and
window in several rules counts once.

## IPv6 and VLANs

On IPv6, moat skips up to four hop-by-hop, routing, fragment and
destination options headers. `vlan.id` is the first 802.1Q or 802.1ad
tag; with two tags the inner one is not read and the packet has no IP
fields.

> With rx VLAN offload on, which is how most NICs ship, the driver strips
> the 802.1Q tag before XDP runs. On Linux 6.8 or newer, in native mode,
> with a driver that reports stripped tags (veth, mlx5, ice), `moat load`
> builds the program to ask the driver for the tag, so `vlan.id` still
> matches; a tag left in the frame wins. Otherwise tagged frames reach the
> rules untagged and `vlan.id` never matches: turn the offload off with
> `ethtool -K eth0 rxvlan off`. `moat load` warns when the rules test
> `vlan.id` in that case, and `moat doctor --iface` says which case an
> interface is in.

Objects from `moat build` and `moat test` never ask the driver for the
tag, since that ties a program to one interface.
