Nftables syntax

From wikinotes

Unlike iptables, nftables can be described in files.

  • Tables are associated with a address-family (ip, arp, nat, ...), and contain chains of rules
  • Packets are passed through chains of rules. This determines what to do with packets.
  • Rules are composed of a matching-expression, and a statement that determines the action to be taken

The man page for nft is excellent, but I find it useful to also look at examples for more context.

Documentation

Documentation
man 8 nftables (best) https://manpages.debian.org/testing/nftables/nft.8.en.html
archwiki https://wiki.archlinux.org/index.php/Nftables
ManPage Headers
file syntax https://manpages.debian.org/testing/nftables/nft.8.en.html#INPUT_FILE_FORMATS
conntrack types (state, status, ..) https://manpages.debian.org/testing/nftables/nft.8.en.html#CONNTRACK_TYPES
expressions (ip, ct state, ...) https://manpages.debian.org/testing/nftables/nft.8.en.html#EXPRESSIONS
statements (accept, drop, masquerade, ...) https://manpages.debian.org/testing/nftables/nft.8.en.html#STATEMENTS

Tutorials

netfilter logging framework https://home.regit.org/2014/02/nftables-and-netfilter-logging-framework/

Overview

  • Tables hold chains. Tables are associated to an address-familiy (ip, ip6, arp, bridge, inet(ip+ip6).
  • Chains contain collections of rules that traffic flows through. (ex: input, forward, output)
  • Rules match traffic, and determine what to do with it. Their composition:
    • expressions/constants defines target traffic (ex: ct state established, tcp port 22)
    • statement determine action (ex: accept, drop)

Example

# flush current rules on load
flush ruleset

table inet filter {
  chain input {
    policy drop;

    # =====
    # RULES
    # =====

    #{constant}           {statement}
    tcp port 22            accept
    ip protocol icmp       drop
    ip6 nexthdr icmpv6     drop

    #{expression}         {statement}
    ct state established   accept       # conntrack
}

Defaults

NOTE:

If using the default ruleset, you should probably prepend flush ruleset to the top so that your rules are reloaded every time.

default /etc/nftables.conf


#!/usr/bin/nft -f
# ipv4/ipv6 Simple & Safe Firewall
# you can find examples in /usr/share/nftables/

table inet filter {
  chain input {
    type filter hook input priority 0;
    policy drop;

    # allow established/related connections
    ct state {established, related} accept

    # early drop of invalid connections
    ct state invalid drop

    # allow from loopback
    iifname lo accept

    # allow icmp
    ip protocol icmp accept
    meta l4proto ipv6-icmp accept

    # allow ssh
    tcp dport ssh accept

    # everything else
    reject with icmpx type port-unreachable
  }
  chain forward {
    type filter hook forward priority 0;
    policy drop;
  }
  chain output {
    type filter hook output priority 0;
    policy accept;
  }

}

# vim:set ts=2 sw=2 et:

Include

You can include other nftable files.

include ./relative/path.conf
include /absolute/path.conf
include /widldcard/*.conf
include repative/to/include/paths.conf  # see nft --include-path

Variables

define int_if1 = eth0
define int_if2 = eth1
define mosh_ports = 60000-61000

define int_ifs = { $int_if1, $int_if2 }
filter input iif $int_ifs accept

Rules

  • Rules match traffic, and determine what to do with it. Their composition:
    • expressions/constants defines target traffic (ex: ct state established, tcp port 22)
    • statement determine action (ex: accept, drop)

Expressions (matching)

expressions (all expression types)
payload expressions

(ipv4, ipv6, icmp, tcp, udp )

tcp port 22
ip protocol icmp
conntrack types ct state {new, establishd}
ct event new

Cheatsheet

Originally taken from ArchWiki

meta:
  oif <output interface NAME/INDEX>
  iif <input interface NAME/INDEX>
  oifname <output interface NAME>  # slower than oif, string matching
  iifname <input interface NAME>   # slower than lif, string matching

  (oif and iif accept string arguments and are converted to interface indexes)
  (oifname and iifname are more dynamic, but slower because of string matching)

icmp:
  type <icmp type>

icmpv6:
  type <icmpv6 type>

ip:
  protocol <protocol>
  daddr <destination address>
  saddr <source address>

ip6:
  daddr <destination address>
  saddr <source address>

tcp:
  dport <destination port>
  sport <source port>

udp:
  dport <destination port>
  sport <source port>

sctp:
  dport <destination port>
  sport <source port>

ct:
  state <new | established | related | invalid>
# =========
# Constants
# =========
tcp dport ssh #{statement}
tcp dport {22, 33, 44} #{statement}

ip protocol icmp #{statement}
ip6 nexthdr icmpv6 #{statement}

ct state invalid #{statement}
ct state {established, related} #{statement}

# =========================
# Constants can be Combined
# =========================
udp sport 1900 \
  udp dport >= 1024 \
  ip6 saddr { fd00::/8, fe80::/10 } \
  meta pkttype unicast limit rate 4/second burst 20 packets \
  accept \
  comment "Accept UPnP IGD port mapping reply"

Examples

iif lo accept \
  comment "accept everything on lo"

udp dport 53 accept \
  comment "simple protocol based rule"

tcp sport 2222 ip saddr 1.1.1.1 ip daddr 2.2.2.2 accept \
  comment "rule with src host/port and dest host"

Statements

statements
verdict statements accept
drop
nat statements masquerade(rewrite source addr to appear from here)
redirect to 8888
fwd statements fwd to eth0
log statements
nftables logging
log prefix "strange behaviour: " level "info"
limit statements limit rate over 10/second <statement>

Examples

See ArchWiki Examples