ETHERNET HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~~~~~
[verse]
*ether* {*daddr* | *saddr* | *type*}

.Ethernet header expression types
[options="header"]
|==================
|Keyword| Description| Type
|daddr|
Destination MAC address|
ether_addr
|saddr|
Source MAC address|
ether_addr
|type|
EtherType|
ether_type
|==================

VLAN HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~
[verse]
*vlan* {*id* | *dei* | *pcp* | *type*}

.VLAN header expression
[options="header"]
|==================
|Keyword| Description| Type
|id|
VLAN ID (VID) |
integer (12 bit)
|dei|
Drop Eligible Indicator|
integer (1 bit)
|pcp|
Priority code point|
integer (3 bit)
|type|
EtherType|
ether_type
|==================

ARP HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~
[verse]
*arp* {*htype* | *ptype* | *hlen* | *plen* | *operation* | *saddr* { *ip* | *ether* } | *daddr* { *ip* | *ether* }

.ARP header expression
[options="header"]
|==================
|Keyword| Description| Type
|htype|
ARP hardware type|
integer (16 bit)
|ptype|
EtherType|
ether_type
|hlen|
Hardware address len|
integer (8 bit)
|plen|
Protocol address len |
integer (8 bit)
|operation|
Operation |
arp_op
|saddr ether|
Ethernet sender address|
ether_addr
|daddr ether|
Ethernet target address|
ether_addr
|saddr ip|
IPv4 sender address|
ipv4_addr
|daddr ip|
IPv4 target address|
ipv4_addr
|======================

IPV4 HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~
[verse]
*ip* {*version* | *hdrlength* | *dscp* | *ecn* | *length* | *id* | *frag-off* | *ttl* | *protocol* | *checksum* | *saddr* | *daddr* }

.IPv4 header expression
[options="header"]
|==================
|Keyword| Description| Type
|version|
IP header version (4)|
integer (4 bit)
|hdrlength|
IP header length including options|
integer (4 bit) FIXME scaling
|dscp|
Differentiated Services Code Point|
dscp
|ecn|
Explicit Congestion Notification|
ecn
|length|
Total packet length |
integer (16 bit)
|id|
IP ID|
integer (16 bit)
|frag-off|
Fragment offset |
integer (16 bit)
|ttl|
Time to live|
integer (8 bit)
|protocol|
Upper layer protocol |
inet_proto
|checksum|
IP header checksum|
integer (16 bit)
|saddr|
Source address|
ipv4_addr
|daddr|
Destination address |
ipv4_addr
|======================

ICMP HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~
[verse]
*icmp* {*type* | *code* | *checksum* | *id* | *sequence* | *gateway* | *mtu*}

This expression refers to ICMP header fields. When using it in *inet*,
*bridge* or *netdev* families, it will cause an implicit dependency on IPv4 to
be created. To match on unusual cases like ICMP over IPv6, one has to add an
explicit *meta protocol ip6* match to the rule.

.ICMP header expression
[options="header"]
|==================
|Keyword|Description| Type
|type|
ICMP type field |
icmp_type
|code|
ICMP code field |
integer (8 bit)
|checksum|
ICMP checksum field |
integer (16 bit)
|id|
ID of echo request/response |
integer (16 bit)
|sequence|
sequence number of echo request/response|
integer (16 bit)
|gateway|
gateway of redirects|
integer (32 bit)
|mtu|
MTU of path MTU discovery|
integer (16 bit)
|============================

IGMP HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~
[verse]
*igmp* {*type* | *mrt* | *checksum* | *group*}

This expression refers to IGMP header fields. When using it in *inet*,
*bridge* or *netdev* families, it will cause an implicit dependency on IPv4 to
be created. To match on unusual cases like IGMP over IPv6, one has to add an
explicit *meta protocol ip6* match to the rule.

.IGMP header expression
[options="header"]
|==================
|Keyword|Description| Type
|type|
IGMP type field |
igmp_type
|mrt|
IGMP maximum response time field |
integer (8 bit)
|checksum|
IGMP checksum field |
integer (16 bit)
|group|
Group address|
integer (32 bit)
|============================

IPV6 HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~
[verse]
*ip6* {*version* | *dscp* | *ecn* | *flowlabel* | *length* | *nexthdr* | *hoplimit* | *saddr* | *daddr*}

This expression refers to the ipv6 header fields. Caution when using *ip6
nexthdr*, the value only refers to the next header, i.e. *ip6 nexthdr tcp* will
only match if the ipv6 packet does not contain any extension headers. Packets
that are fragmented or e.g. contain a routing extension headers will not be
matched. Please use *meta l4proto* if you wish to match the real transport header
and ignore any additional extension headers instead.

.IPv6 header expression
[options="header"]
|==================
|Keyword|Description| Type
|version|
IP header version (6)|
integer (4 bit)
|dscp|
Differentiated Services Code Point|
dscp
|ecn|
Explicit Congestion Notification|
ecn
|flowlabel|
Flow label|
integer (20 bit)
|length|
Payload length|
integer (16 bit)
|nexthdr|
Nexthdr protocol|
inet_proto
|hoplimit|
Hop limit|
integer (8 bit)
|saddr|
Source address|
ipv6_addr
|daddr|
Destination address |
ipv6_addr
|=======================

.Using ip6 header expressions
-----------------------------
# matching if first extension header indicates a fragment
ip6 nexthdr ipv6-frag
-----------------------------

ICMPV6 HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~~~
[verse]
*icmpv6* {*type* | *code* | *checksum* | *parameter-problem* | *packet-too-big* | *id* | *sequence* | *max-delay*}

This expression refers to ICMPv6 header fields. When using it in *inet*,
*bridge* or *netdev* families, it will cause an implicit dependency on IPv6 to
be created. To match on unusual cases like ICMPv6 over IPv4, one has to add an
explicit *meta protocol ip* match to the rule.

.ICMPv6 header expression
[options="header"]
|==================
|Keyword| Description| Type
|type|
ICMPv6 type field|
icmpv6_type
|code|
ICMPv6 code field|
integer (8 bit)
|checksum|
ICMPv6 checksum field|
integer (16 bit)
|parameter-problem|
pointer to problem|
integer (32 bit)
|packet-too-big|
oversized MTU|
integer (32 bit)
|id|
ID of echo request/response |
integer (16 bit)
|sequence|
sequence number of echo request/response|
integer (16 bit)
|max-delay|
maximum response delay of MLD queries|
integer (16 bit)
|==============================

TCP HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~
[verse]
*tcp* {*sport* | *dport* | *sequence* | *ackseq* | *doff* | *reserved* | *flags* | *window* | *checksum* | *urgptr*}

.TCP header expression
[options="header"]
|==================
|Keyword| Description| Type
|sport|
Source port|
inet_service
|dport|
Destination port|
inet_service
|sequence|
Sequence number|
integer (32 bit)
|ackseq|
Acknowledgement number |
integer (32 bit)
|doff|
Data offset |
integer (4 bit) FIXME scaling
|reserved|
Reserved area |
integer (4 bit)
|flags|
TCP flags|
tcp_flag
|window|
Window|
integer (16 bit)
|checksum|
Checksum|
integer (16 bit)
|urgptr|
Urgent pointer|
integer (16 bit)
|======================

UDP HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~
[verse]
*udp* {*sport* | *dport* | *length* | *checksum*}

.UDP header expression
[options="header"]
|==================
|Keyword| Description| Type
|sport|
Source port|
inet_service
|dport|
Destination port|
inet_service
|length|
Total packet length|
integer (16 bit)
|checksum|
Checksum|
integer (16 bit)
|================

UDP-LITE HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~~~~~~
[verse]
*udplite* {*sport* | *dport* | *checksum*}

.UDP-Lite header expression
[options="header"]
|==================
|Keyword| Description| Type
|sport|
Source port|
inet_service
|dport|
Destination port|
inet_service
|checksum|
Checksum|
integer (16 bit)
|================

SCTP HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~~
[verse]
____
*sctp* {*sport* | *dport* | *vtag* | *checksum*}
*sctp chunk* 'CHUNK' [ 'FIELD' ]

'CHUNK' := *data* | *init* | *init-ack* | *sack* | *heartbeat* |
	   *heartbeat-ack* | *abort* | *shutdown* | *shutdown-ack* | *error* |
	   *cookie-echo* | *cookie-ack* | *ecne* | *cwr* | *shutdown-complete*
	   | *asconf-ack* | *forward-tsn* | *asconf*

'FIELD' := 'COMMON_FIELD' | 'DATA_FIELD' | 'INIT_FIELD' | 'INIT_ACK_FIELD' |
	   'SACK_FIELD' | 'SHUTDOWN_FIELD' | 'ECNE_FIELD' | 'CWR_FIELD' |
	   'ASCONF_ACK_FIELD' | 'FORWARD_TSN_FIELD' | 'ASCONF_FIELD'

'COMMON_FIELD' := *type* | *flags* | *length*
'DATA_FIELD' := *tsn* | *stream* | *ssn* | *ppid*
'INIT_FIELD' := *init-tag* | *a-rwnd* | *num-outbound-streams* |
		*num-inbound-streams* | *initial-tsn*
'INIT_ACK_FIELD' := 'INIT_FIELD'
'SACK_FIELD' := *cum-tsn-ack* | *a-rwnd* | *num-gap-ack-blocks* |
		*num-dup-tsns*
'SHUTDOWN_FIELD' := *cum-tsn-ack*
'ECNE_FIELD' := *lowest-tsn*
'CWR_FIELD' := *lowest-tsn*
'ASCONF_ACK_FIELD' := *seqno*
'FORWARD_TSN_FIELD' := *new-cum-tsn*
'ASCONF_FIELD' := *seqno*
____

.SCTP header expression
[options="header"]
|==================
|Keyword| Description| Type
|sport|
Source port|
inet_service
|dport|
Destination port|
inet_service
|vtag|
Verification Tag|
integer (32 bit)
|checksum|
Checksum|
integer (32 bit)
|chunk|
Search chunk in packet|
without 'FIELD', boolean indicating existence
|================

.SCTP chunk fields
[options="header"]
|==================
|Name| Width in bits | Chunk | Notes
|type| 8 | all | not useful, defined by chunk type
|flags| 8 | all | semantics defined on per-chunk basis
|length| 16 | all | length of this chunk in bytes excluding padding
|tsn| 32 | data | transmission sequence number
|stream| 16 | data | stream identifier
|ssn| 16 | data | stream sequence number
|ppid| 32 | data | payload protocol identifier
|init-tag| 32 | init, init-ack | initiate tag
|a-rwnd| 32 | init, init-ack, sack | advertised receiver window credit
|num-outbound-streams| 16 | init, init-ack | number of outbound streams
|num-inbound-streams| 16 | init, init-ack | number of inbound streams
|initial-tsn| 32 | init, init-ack | initial transmit sequence number
|cum-tsn-ack| 32 | sack, shutdown | cumulative transmission sequence number acknowledged
|num-gap-ack-blocks| 16 | sack | number of Gap Ack Blocks included
|num-dup-tsns| 16 | sack | number of duplicate transmission sequence numbers received
|lowest-tsn| 32 | ecne, cwr | lowest transmission sequence number
|seqno| 32 | asconf-ack, asconf | sequence number
|new-cum-tsn| 32 | forward-tsn | new cumulative transmission sequence number
|==================

DCCP HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~
[verse]
*dccp* {*sport* | *dport* | *type*}

.DCCP header expression
[options="header"]
|==================
|Keyword| Description| Type
|sport|
Source port|
inet_service
|dport|
Destination port|
inet_service
|type|
Packet type|
dccp_pkttype
|========================

AUTHENTICATION HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[verse]
*ah* {*nexthdr* | *hdrlength* | *reserved* | *spi* | *sequence*}

.AH header expression
[options="header"]
|==================
|Keyword| Description| Type
|nexthdr|
Next header protocol|
inet_proto
|hdrlength|
AH Header length|
integer (8 bit)
|reserved|
Reserved area|
integer (16 bit)
|spi|
Security Parameter Index |
integer (32 bit)
|sequence|
Sequence number|
integer (32 bit)
|========================

ENCRYPTED SECURITY PAYLOAD HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[verse]
*esp* {*spi* | *sequence*}

.ESP header expression
[options="header"]
|==================
|Keyword| Description| Type
|spi|
Security Parameter Index |
integer (32 bit)
|sequence|
Sequence number|
integer (32 bit)
|===========================

IPCOMP HEADER EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~~~~
*comp* {*nexthdr* | *flags* | *cpi*}

.IPComp header expression
[options="header"]
|==================
|Keyword| Description| Type
|nexthdr|
Next header protocol|
inet_proto
|flags|
Flags|
bitmask
|cpi|
compression Parameter Index |
integer (16 bit)
|============================

RAW PAYLOAD EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~
[verse]
*@*'base'*,*'offset'*,*'length'

The raw payload expression instructs to load 'length' bits starting at 'offset' bits.
Bit 0 refers to the very first bit -- in the C programming language, this
corresponds to the topmost bit, i.e. 0x80 in case of an octet. They are useful
to match headers that do not have a human-readable template expression yet. Note
that nft will not add dependencies for Raw payload expressions. If you e.g. want
to match protocol fields of a transport header with protocol number 5, you need
to manually exclude packets that have a different transport header, for instance
by using *meta l4proto 5* before the raw expression.

.Supported payload protocol bases
[options="header"]
|==================
|Base| Description
|ll|
Link layer, for example the Ethernet header
|nh|
Network header, for example IPv4 or IPv6
|th|
Transport Header, for example TCP
|==============================

.Matching destination port of both UDP and TCP
----------------------------------------------
inet filter input meta l4proto {tcp, udp} @th,16,16 { 53, 80 }
-----------------------------------------------------------------
The above can also be written as
-----------------------------------------------------------------
inet filter input meta l4proto {tcp, udp} th dport { 53, 80 }
-----------------------------------------------------------------
it is more convenient, but like the raw expression notation no
dependencies are created or checked. It is the users responsibility
to restrict matching to those header types that have a notion of ports.
Otherwise, rules using raw expressions will errnously match unrelated
packets, e.g. mis-interpreting ESP packets SPI field as a port.

.Rewrite arp packet target hardware address if target protocol address matches a given address
----------------------------------------------------------------------------------------------
input meta iifname enp2s0 arp ptype 0x0800 arp htype 1 arp hlen 6 arp plen 4 @nh,192,32 0xc0a88f10 @nh,144,48 set 0x112233445566 accept
-----------------------------------------------------------------------------------------------

EXTENSION HEADER EXPRESSIONS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Extension header expressions refer to data from variable-sized protocol headers, such as IPv6 extension headers, TCP options and IPv4 options.

nftables currently supports matching (finding) a given ipv6 extension header, TCP option or IPv4 option.
[verse]
*hbh* {*nexthdr* | *hdrlength*}
*frag* {*nexthdr* | *frag-off* | *more-fragments* | *id*}
*rt* {*nexthdr* | *hdrlength* | *type* | *seg-left*}
*dst* {*nexthdr* | *hdrlength*}
*mh* {*nexthdr* | *hdrlength* | *checksum* | *type*}
*srh* {*flags* | *tag* | *sid* | *seg-left*}
*tcp option* {*eol* | *nop* | *maxseg* | *window* | *sack-perm* | *sack* | *sack0* | *sack1* | *sack2* | *sack3* | *timestamp*} 'tcp_option_field'
*ip option* { lsrr | ra | rr | ssrr } 'ip_option_field'

The following syntaxes are valid only in a relational expression with boolean type on right-hand side for checking header existence only:
[verse]
*exthdr* {*hbh* | *frag* | *rt* | *dst* | *mh*}
*tcp option* {*eol* | *nop* | *maxseg* | *window* | *sack-perm* | *sack* | *sack0* | *sack1* | *sack2* | *sack3* | *timestamp*}
*ip option* { lsrr | ra | rr | ssrr }

.IPv6 extension headers
[options="header"]
|==================
|Keyword| Description
|hbh|
Hop by Hop
|rt|
Routing Header
|frag|
Fragmentation header
|dst|
dst options
|mh|
Mobility Header
|srh|
Segment Routing Header
|=====================

.TCP Options
[options="header"]
|==================
|Keyword| Description | TCP option fields
|eol|
End if option list|
-
|nop|
1 Byte TCP Nop padding option |
-
|maxseg|
TCP Maximum Segment Size|
length, size
|window|
TCP Window Scaling |
length, count
|sack-perm |
TCP SACK permitted |
length
|sack|
TCP Selective Acknowledgement (alias of block 0) |
length, left, right
|sack0|
TCP Selective Acknowledgement (block 0) |
length, left, right
|sack1|
TCP Selective Acknowledgement (block 1) |
length, left, right
|sack2|
TCP Selective Acknowledgement (block 2) |
length, left, right
|sack3|
TCP Selective Acknowledgement (block 3) |
length, left, right
|timestamp|
TCP Timestamps |
length, tsval, tsecr
|============================

TCP option matching also supports raw expression syntax to access arbitrary options:
[verse]
*tcp option*
[verse]
*tcp option* *@*'number'*,*'offset'*,*'length'

.IP Options
[options="header"]
|==================
|Keyword| Description | IP option fields
|lsrr|
Loose Source Route |
type, length, ptr, addr
|ra|
Router Alert |
type, length, value
|rr|
Record Route |
type, length, ptr, addr
|ssrr|
Strict Source Route |
type, length, ptr, addr
|============================

.finding TCP options
--------------------
filter input tcp option sack-perm exists counter
--------------------

.matching TCP options
--------------------
filter input tcp option maxseg size lt 536
--------------------

.matching IPv6 exthdr
---------------------
ip6 filter input frag more-fragments 1 counter
---------------------------------------

.finding IP option
------------------
filter input ip option lsrr exists counter
---------------------------------------

CONNTRACK EXPRESSIONS
~~~~~~~~~~~~~~~~~~~~~
Conntrack expressions refer to meta data of the connection tracking entry associated with a packet. +

There are three types of conntrack expressions. Some conntrack expressions
require the flow direction before the conntrack key, others must be used
directly because they are direction agnostic. The *packets*, *bytes* and
*avgpkt* keywords can be used with or without a direction. If the direction is
omitted, the sum of the original and the reply direction is returned. The same
is true for the *zone*, if a direction is given, the zone is only matched if the
zone id is tied to the given direction. +

[verse]
*ct* {*state* | *direction* | *status* | *mark* | *expiration* | *helper* | *label* | *count* | *id*}
*ct* [*original* | *reply*] {*l3proto* | *protocol* | *bytes* | *packets* | *avgpkt* | *zone*}
*ct* {*original* | *reply*} {*proto-src* | *proto-dst*}
*ct* {*original* | *reply*} {*ip* | *ip6*} {*saddr* | *daddr*}

The conntrack-specific types in this table are described in the sub-section CONNTRACK TYPES above.

.Conntrack expressions
[options="header"]
|==================
|Keyword| Description | Type
|state|
State of the connection |
ct_state
|direction|
Direction of the packet relative to the connection |
ct_dir
|status|
Status of the connection |
ct_status
|mark|
Connection mark |
mark
|expiration|
Connection expiration time |
time
|helper|
Helper associated with the connection|
string
|label|
Connection tracking label bit or symbolic name defined in connlabel.conf in the nftables include path|
ct_label
|l3proto|
Layer 3 protocol of the connection|
nf_proto
|saddr|
Source address of the connection for the given direction |
ipv4_addr/ipv6_addr
|daddr|
Destination address of the connection for the given direction |
ipv4_addr/ipv6_addr
|protocol|
Layer 4 protocol of the connection for the given direction |
inet_proto
|proto-src|
Layer 4 protocol source for the given direction|
integer (16 bit)
|proto-dst|
Layer 4 protocol destination for the given direction |
integer (16 bit)
|packets|
packet count seen in the given direction or sum of original and reply |
integer (64 bit)
|bytes|
byte count seen, see description for *packets* keyword |
integer (64 bit)
|avgpkt|
average bytes per packet, see description for *packets* keyword |
integer (64 bit)
|zone|
conntrack zone |
integer (16 bit)
|count|
number of current connections|
integer (32 bit)
|id|
Connection id|
ct_id|
|==========================================

.restrict the number of parallel connections to a server
--------------------
nft add set filter ssh_flood '{ type ipv4_addr; flags dynamic; }'
nft add rule filter input tcp dport 22 add @ssh_flood '{ ip saddr ct count over 2 }' reject
--------------------
