nftables

来自 ArchWiki

nftables 是一个 netfilter 项目,旨在取代现有的 {ip,ip6,arp,eb}tables 框架。它提供了一个新的数据包过滤框架、一个新的用户空间实用程序 (nft) 以及一个用于 {ip,ip6}tables 的兼容层。它使用 netfilter 现有的钩子、连接跟踪系统、用户空间排队组件和日志子系统。

它由三个主要组件组成:内核实现、libnl netlink 通信和 nftables 用户空间前端。内核提供 netlink 配置接口以及运行时规则集评估,libnl 包含与内核通信的底层函数,而 nftables 前端是用户通过 nft 进行交互的工具。

您还可以访问官方 nftables 维基页面以获取更多信息。

安装

安装 用户空间实用程序包 nftables 或 git 版本 nftables-gitAUR

或者,安装 iptables-nft,它包含 nftables 作为依赖项,将自动卸载 iptablesbase 元包的间接依赖项),并防止同时使用 iptablesnftables 时发生冲突。有关详细信息,请参阅 #使用 iptables-nft

注意: iptables-nft 的工作原理是提供 iptables 命令的实现,这些命令实际上创建并作用于 nftables 规则。但是,使用旧的 iptables-legacy 工具创建的规则是独立的对象,如果它们存在,iptables-nft 将会警告您。

前端

提示: 大多数 iptables 前端 不直接或间接支持 nftables,但可能会引入支持。[1] firewalld 是一个图形前端,同时支持 nftables 和 iptables。[2]。另一个是 ufw
  • firewalld (firewall-cmd) — 用于配置网络和防火墙区域以及设置和配置防火墙规则的守护程序和控制台接口。
https://firewalld.org/ || firewalld
  • nft-blackhole — 用于通过国家和黑名单在 nftables 中阻止 IP 的脚本/守护程序。
https://github.com/tomasz-c/nft-blackhole || nft-blackholeAUR
  • ufw — Ufw 代表 Uncomplicated Firewall(简易防火墙),是一个用于管理 netfilter 防火墙的程序。
https://help.ubuntu.com/community/UFW || ufw
  • reaction — 一个守护程序,用于扫描程序输出中重复的模式并采取行动。是 fail2ban 的轻量级替代品。
https://framagit.org/ppom/reaction || reactionAUR

用法

提示: 如果您已经有 iptables 规则,您可以将 iptables 规则转换为 nftables 规则,请参阅 [3]

nftables 不区分在命令行中创建的临时规则和从文件加载或保存的永久规则。

所有规则都必须使用 nft 命令行实用程序创建或加载。

有关如何使用,请参阅 #配置 部分。

当前规则集可以使用以下命令打印

# nft list ruleset

删除所有规则集,使系统没有防火墙

# nft flush ruleset

通过重启 nftables.service/etc/nftables.conf 读取规则集。

简单防火墙

nftables 附带一个简单且安全的防火墙配置,存储在 /etc/nftables.conf 文件中。

启动或启用时,nftables.service 将从该文件加载规则。

配置

nftables 用户空间实用程序 nft 在将规则集交给内核之前执行大部分规则集评估。规则存储在链中,链又存储在表中。以下部分说明如何创建和修改这些结构。

要从文件读取输入,请使用 -f/--file 选项

# nft --file filename

请注意,任何已加载的规则都不会自动清空。

有关所有命令的完整列表,请参阅 nft(8)

本文或本节需要扩充。

原因: 澄清 iptables 和 nftables 中表之间的区别:在后者中,它们具有“命名空间”性质,并且可以包含不同的链类型。[4](在 Talk:Nftables 中讨论)

表包含 #链。与 iptables 中的表不同,nftables 中没有内置表。表的数量和名称由用户决定。但是,每个表只有一个地址族,并且仅适用于此族的包。表可以指定以下五个族之一

nftables 族 iptables 实用程序
ip iptables
ip6 ip6tables
inet iptables 和 ip6tables
arp arptables
bridge ebtables

ip(即 IPv4)是默认族,如果未指定族,将使用 ip。

要创建适用于 IPv4 和 IPv6 的规则,请使用 inetinet 允许统一 ip 和 ip6 族,从而更容易定义两者的规则。

有关地址族的完整描述,请参阅 nft(8) § 地址族

在以下所有内容中,family_type 是可选的,如果未指定,则设置为 ip

创建表

以下命令添加一个新表

# nft add table family_type table_name

列出表

要列出所有表

# nft list tables

列出表中的链和规则

要列出指定表的所有链和规则

# nft list table family_type table_name

例如,要列出 inet 族的 my_table 表的所有规则

# nft list table inet my_table

删除表

要删除表

# nft delete table family_type table_name

这将销毁表中的所有链。

清空表

要从表中清空/清除所有规则

# nft flush table family_type table_name

链的目的是保存 #规则。与 iptables 中的链不同,nftables 中没有内置链。这意味着,如果没有任何链在 netfilter 框架中使用任何类型或钩子,那么将流经这些链的数据包将不会被 nftables 触及,这与 iptables 不同。

链有两种类型。基本链是来自网络堆栈的数据包的入口点,其中指定了钩子值。常规链可以用作跳转目标,以实现更好的组织。

请参阅流量图,该图显示了各个钩子之间的顺序。在给定的钩子中,netfilter 按数字优先级升序执行操作。

在以下所有内容中,family_type 是可选的,如果未指定,则设置为 ip

创建链

基本链

要添加基本链,必须指定类型、钩子和优先级值

# nft add chain family_type table_name chain_name '{ type chain_type hook hook_type priority priority_value ; policy policy ;}'

chain_type 可以是 filterroutenat

对于 IPv4/IPv6/Inet 地址族,hook_type 可以是 preroutinginputforwardoutputpostrouting。有关支持的 family_typechain_typehook_type 组合的列表,请参阅 nft(8) § 链

priority_value 采用优先级名称或整数值。有关标准优先级名称和值的列表,请参阅 nft(8) § 链。数字较低的链首先处理,并且可以是负数。[5]

可选地,基本链可以具有 policydrop 或默认的 accept)策略,以定义对于包含的规则中未显式接受或拒绝的数据包会发生什么。

例如,要添加一个过滤输入数据包的基本链

# nft add chain inet my_table my_chain '{ type filter hook input priority 0; }'

在以上任何命令中,将 add 替换为 create 以添加新链,但如果链已存在,则返回错误。

常规链

以下命令将名为 chain_name 的常规链添加到名为 table_name 的表中

# nft add chain family_type table_name chain_name

例如,要将名为 my_tcp_chain 的常规链添加到 inet 地址族的 my_table 表中

# nft add chain inet my_table my_tcp_chain

列出链

以下命令列出 family_type 的所有链,但不包括规则(请参阅 #列出规则

# nft list chains family_type

例如,以下命令列出 ipv6 的链

# nft list chains ip6

如果您省略 family_type,则会打印所有链。

编辑链

要编辑链,只需按名称调用它并定义要更改的规则。

# nft chain family_type table_name chain_name '{ [ type chain_type hook hook_type device device_name priority priority_value ; policy policy_type ; ] }'

例如,要将默认表的 my_input 链策略从 accept 更改为 drop

# nft chain inet my_table my_input '{ policy drop ; }'

删除链

要删除链

# nft delete chain family_type table_name chain_name

该链不得包含任何规则或作为跳转目标。

从链中清空规则

要从链中清空规则

# nft flush chain family_type table_name chain_name

规则

规则由表达式或语句构成,并包含在链中。

添加规则

要向链添加规则

# nft add rule family_type table_name chain_name handle handle_value statement

规则附加在 handle_value 处,这是可选的。如果未指定,则规则附加到链的末尾。

可以添加到任何有效列表命令的 --handle 开关必须用于确定规则句柄。此开关告诉 nft 在其输出中列出句柄。--numeric 参数对于查看某些数字输出(如未解析的 IP 地址)很有用。

# nft --handle --numeric list chain inet my_table my_input
table inet my_table {
     chain input {
          type filter hook input priority 0;
          ip saddr 127.0.0.1 accept # handle 10
     }
}

要将规则前置到位置

# nft insert rule family_type table_name chain_name handle handle_value statement

如果未指定 handle_value,则规则前置到链的开头。

表达式

通常,statement 包括要匹配的表达式,然后是裁决语句。裁决语句包括 acceptdropqueuecontinuereturnjump chain_namegoto chain_name。除了裁决语句之外,还有其他语句是可能的。有关更多信息,请参阅 nft(8)

nftables 中有各种可用的表达式,并且在很大程度上与它们的 iptables 对应项一致。最明显的区别是没有通用或隐式匹配。通用匹配是始终可用的匹配,例如 --protocol--source。隐式匹配是协议特定的,例如当数据包被确定为 TCP 时,例如 --sport

以下是不完整的可用匹配列表

  • meta(元属性,例如接口)
  • icmp(ICMP 协议)
  • icmpv6(ICMPv6 协议)
  • ip(IP 协议)
  • ip6(IPv6 协议)
  • tcp(TCP 协议)
  • udp(UDP 协议)
  • sctp(SCTP 协议)
  • ct(连接跟踪)

以下是不完整的匹配参数列表(有关更完整的列表,请参阅 nft(8)

meta:
  oif <output interface INDEX>
  iif <input interface INDEX>
  oifname <output interface NAME>
  iifname <input interface NAME>

  (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>

列出规则

以下命令列出链的所有规则

# nft list chain family_type table_name chain_name

例如,以下命令列出 inet 表中名为 my_table 的名为 my_output 的链的规则

# nft list chain inet my_table my_output

删除

单个规则只能通过其句柄删除。获取句柄的方法在 #添加规则 中显示。假设

# nft --handle --numeric list chain inet my_table my_input
table inet my_table {
     chain input {
          type filter hook input priority 0;
          ip saddr 127.0.0.1 accept # handle 10
     }
}
# nft delete rule inet my_table my_input handle 10

删除它。

可以使用 nft flush table 命令清空表中的所有链。可以使用 nft flush chainnft delete rule 命令清空单个链。

# nft flush table table_name
# nft flush chain family_type table_name chain_name
# nft delete rule family_type table_name chain_name

第一个命令清空 ip table_name 表中的所有链。第二个命令清空 family_type table_name 表中的 chain_name 链。第三个命令删除 family_type table_name 表中的 chain_name 链中的所有规则。

集合

集合可以是命名的或匿名的,由一个或多个元素组成,元素之间用逗号分隔,并用花括号括起来。匿名集合嵌入在规则中,无法更新,您必须删除并重新添加规则。例如,您不能仅从以下 dports 集合中删除“http”

# nft add rule ip6 filter input tcp dport {telnet, http, https} accept

命名集合可以更新,并且可以类型化和标记。sshguard 使用命名集合来存储被阻止主机的 IP 地址。

table ip sshguard {
       set attackers {
               type ipv4_addr
               flags interval
               elements = { 1.2.3.4 }
       }

要从集合中添加删除元素,请使用

# nft add element ip sshguard attackers { 5.6.7.8/32 }
# nft delete element ip sshguard attackers { 1.2.3.4/32 }

请注意,ipv4_addr 类型可以包含 CIDR 网络掩码(此处的 /32 不是必需的,但为了完整性而包含)。另请注意,此处由 TABLE ip sshguard { SET attackers } 定义的集合被寻址为 ip sshguard attackers

提示: systemd-networkd 连接可以配置为使用主机 IP 地址、网络前缀和接口索引填充预定义的命名集合。有关详细信息,请参阅 systemd.network(5) § [ADDRESS] SECTION OPTIONSNFTSet= 的描述,有关示例,请参阅 #使用 systemd-networkd 的动态命名集合

原子重载

清空当前规则集

# echo "flush ruleset" > /tmp/nftables 

转储当前规则集

# nft -s list ruleset >> /tmp/nftables

现在您可以编辑 /tmp/nftables 并使用以下命令应用您的更改

# nft -f /tmp/nftables

示例

工作站

/etc/nftables.conf
flush ruleset

table inet my_table {
	set LANv4 {
		type ipv4_addr
		flags interval

		elements = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 }
	}
	set LANv6 {
		type ipv6_addr
		flags interval

		elements = { fd00::/8, fe80::/10 }
	}

	chain my_input_lan {
		udp sport 1900 udp dport >= 1024 meta pkttype unicast limit rate 4/second burst 20 packets accept comment "Accept UPnP IGD port mapping reply"

		udp sport netbios-ns udp dport >= 1024 meta pkttype unicast accept comment "Accept Samba Workgroup browsing replies"

	}

	chain my_input {
		type filter hook input priority filter; policy drop;

		iif lo accept comment "Accept any localhost traffic"
		ct state invalid drop comment "Drop invalid connections"
		fib daddr . iif type != { local, broadcast, multicast } drop comment "Drop packets if the destination IP address is not configured on the incoming interface (strong host model)"
		ct state established,related accept comment "Accept traffic originated from us"

		meta l4proto ipv6-icmp accept comment "Accept ICMPv6"
		meta l4proto icmp accept comment "Accept ICMP"
		ip protocol igmp accept comment "Accept IGMP"

		udp dport mdns ip6 daddr ff02::fb accept comment "Accept mDNS"
		udp dport mdns ip daddr 224.0.0.251 accept comment "Accept mDNS"

		ip6 saddr @LANv6 jump my_input_lan comment "Connections from private IP address ranges"
		ip saddr @LANv4 jump my_input_lan comment "Connections from private IP address ranges"

		counter comment "Count any other traffic"
	}

	chain my_forward {
		type filter hook forward priority filter; policy drop;
		# Drop everything forwarded to us. We do not forward. That is routers job.
	}

	chain my_output {
		type filter hook output priority filter; policy accept;
		# Accept every outbound connection
	}

}
提示: 当使用 systemd-networkd 并连接到本地网络时,您可以使用 systemd.network(5) 选项 NFTSet= 来获取连接的网络前缀,从而避免硬编码网络子网。请参阅 #使用 systemd-networkd 的动态命名集合

服务器

/etc/nftables.conf
flush ruleset

table inet my_table {
	set LANv4 {
		type ipv4_addr
		flags interval

		elements = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 }
	}
	set LANv6 {
		type ipv6_addr
		flags interval

		elements = { fd00::/8, fe80::/10 }
	}

	chain my_input_lan {
		meta l4proto { tcp, udp } th dport 2049 accept comment "Accept NFS"

		udp dport netbios-ns accept comment "Accept NetBIOS Name Service (nmbd)"
		udp dport netbios-dgm accept comment "Accept NetBIOS Datagram Service (nmbd)"
		tcp dport netbios-ssn accept comment "Accept NetBIOS Session Service (smbd)"
		tcp dport microsoft-ds accept comment "Accept Microsoft Directory Service (smbd)"

		udp sport { bootpc, 4011 } udp dport { bootps, 4011 } accept comment "Accept PXE"
		udp dport tftp accept comment "Accept TFTP"
	}

	chain my_input {
		type filter hook input priority filter; policy drop;

		iif lo accept comment "Accept any localhost traffic"
		ct state invalid drop comment "Drop invalid connections"
		fib daddr . iif type != { local, broadcast, multicast } drop comment "Drop packets if the destination IP address is not configured on the incoming interface (strong host model)"
		ct state established,related accept comment "Accept traffic originated from us"

		meta l4proto ipv6-icmp accept comment "Accept ICMPv6"
		meta l4proto icmp accept comment "Accept ICMP"
		ip protocol igmp accept comment "Accept IGMP"

		udp dport mdns ip6 daddr ff02::fb accept comment "Accept mDNS"
		udp dport mdns ip daddr 224.0.0.251 accept comment "Accept mDNS"

		ip6 saddr @LANv6 jump my_input_lan comment "Connections from private IP address ranges"
		ip saddr @LANv4 jump my_input_lan comment "Connections from private IP address ranges"

		tcp dport ssh accept comment "Accept SSH on port 22"

		tcp dport ipp accept comment "Accept IPP/IPPS on port 631"

		tcp dport { http, https, 8008, 8080 } accept comment "Accept HTTP (ports 80, 443, 8008, 8080)"

		udp sport bootpc udp dport bootps ip saddr 0.0.0.0 ip daddr 255.255.255.255 accept comment "Accept DHCPDISCOVER (for DHCP-Proxy)"
	}

	chain my_forward {
		type filter hook forward priority filter; policy drop;
		# Drop everything forwarded to us. We do not forward. That is routers job.
	}

	chain my_output {
		type filter hook output priority filter; policy accept;
		# Accept every outbound connection
	}

}

限制速率

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

		iif lo accept comment "Accept any localhost traffic"
		ct state invalid drop comment "Drop invalid connections"
		fib daddr . iif type != { local, broadcast, multicast } drop comment "Drop packets if the destination IP address is not configured on the incoming interface (strong host model)"

		meta l4proto icmp icmp type echo-request limit rate over 10/second burst 4 packets drop comment "No ping floods"
		meta l4proto ipv6-icmp icmpv6 type echo-request limit rate over 10/second burst 4 packets drop comment "No ping floods"

		ct state established,related accept comment "Accept traffic originated from us"

		meta l4proto ipv6-icmp accept comment "Accept ICMPv6"
		meta l4proto icmp accept comment "Accept ICMP"
		ip protocol igmp accept comment "Accept IGMP"

		tcp dport ssh ct state new limit rate 15/minute accept comment "Avoid brute force on SSH"

	}

}

跳转

在配置文件中使用跳转时,必须首先定义目标链。否则可能会导致错误:Error: Could not process rule: No such file or directory

table inet my_table {
    chain web {
        tcp dport http accept
        tcp dport 8080 accept
    }
    chain my_input {
        type filter hook input priority filter;
        ip saddr 10.0.2.0/24 jump web
        drop
    }
}

不同接口的不同规则

如果您的计算机有多个网络接口,并且您想为不同的接口使用不同的规则,您可能需要使用“调度”过滤器链,然后使用特定于接口的过滤器链。例如,假设您的计算机充当家庭路由器,您想运行可通过 LAN(接口 enp3s0)访问的 Web 服务器,但不能从公共互联网(接口 enp2s0)访问,您可能需要考虑如下结构

table inet my_table {
  chain my_input { # this chain serves as a dispatcher
    type filter hook input priority filter;

    iif lo accept comment "always accept loopback"
    iifname enp2s0 jump my_input_public
    iifname enp3s0 jump my_input_private

    reject with icmpx port-unreachable comment "refuse traffic from all other interfaces"
  }
  chain my_input_public { # rules applicable to public interface interface
    ct state {established,related} accept
    ct state invalid drop
    udp dport bootpc accept
    tcp dport bootpc accept
    reject with icmpx port-unreachable comment "all other traffic"
  }
  chain my_input_private {
    ct state {established,related} accept
    ct state invalid drop
    udp dport bootpc accept
    tcp dport bootpc accept
    tcp port http accept
    tcp port https accept
    reject with icmpx port-unreachable comment "all other traffic"
  }
  chain my_output { # we let everything out
    type filter hook output priority filter;
    accept
  }
}

或者,您可以仅选择一个 iifname 语句,例如用于单个上游接口,并将所有其他接口的默认规则放在一个位置,而不是为每个接口调度。

伪装

nftables 有一个特殊的关键字 masquerade,“其中源地址自动设置为输出接口的地址”(来源)。这对于接口的 IP 地址不可预测或不稳定的情况特别有用,例如连接到许多 ISP 的路由器的上游接口。如果没有它,每次接口的 IP 地址更改时,都必须更新网络地址转换规则。

要使用它

  • 确保在内核中启用伪装(如果您使用默认内核,则为 true),否则在内核配置期间,设置 CONFIG_NFT_MASQ=m
  • masquerade 关键字只能在 nat 类型的链中使用。
  • 伪装是一种源 NAT,因此仅在输出路径中有效。

具有两个接口的计算机示例:LAN 连接到 enp3s0,公共互联网连接到 enp2s0

table inet my_nat {
  chain my_masquerade {
    type nat hook postrouting priority srcnat;
    oifname "enp2s0" masquerade
  }
}

由于表类型为 inet,IPv4 和 IPv6 数据包都将被伪装。如果您只想伪装 ipv4 数据包(因为 IPv6 的额外地址空间使得 NAT 不是必需的),则可以在 oifname "enp2s0" 前使用 meta nfproto ipv4 表达式,或者可以将表类型更改为 ip

端口转发的 NAT

此示例将伪装通过名为 eth0 的 WAN 接口退出的流量,并将端口 22 和 80 转发到 10.0.0.2。您需要通过 sysctlnet.ipv4.ip_forward 设置为 1

table nat {
    chain prerouting {
        type nat hook prerouting priority dstnat;
        iif eth0 tcp dport {22, 80} dnat to 10.0.0.2
    }
    chain postrouting {
        type nat hook postrouting priority srcnat;
        oif eth0 masquerade
    }
}

按 IP 计数新连接

使用此代码段来计数 HTTPS 连接

/etc/nftables.conf
table inet filter {
    set https {
        type ipv4_addr;
        flags dynamic;
        size 65536;
        timeout 60m;
    }

    chain input {
        type filter hook input priority filter;
        ct state new tcp dport 443 update @https { ip saddr counter }
    }
}

要打印计数器,请运行 nft list set inet filter https

动态黑洞

使用此代码段从超过每秒 10 次限制的源 IP(或 /64 IPv6 范围)丢弃所有 HTTPS 连接,持续 1 分钟。

/etc/nftables.conf
table inet dev {
    set blackhole_ipv4 {
        type ipv4_addr;
        flags dynamic, timeout;
        size 65536;
    }
    set blackhole_ipv6 {
        type ipv6_addr;
        flags dynamic, timeout;
        size 65536;
    }

    chain input {
        type filter hook input priority filter; policy accept;
        ct state new tcp dport 443 \
                meter flood_ipv4 size 128000 { ip saddr timeout 10s limit rate over 10/second } \
                add @blackhole_ipv4 { ip saddr timeout 1m }
        ct state new tcp dport 443 \
                meter flood_ipv6 size 128000 { ip6 saddr and ffff:ffff:ffff:ffff:: timeout 10s limit rate over 10/second } \
                add @blackhole_ipv6 { ip6 saddr and ffff:ffff:ffff:ffff:: timeout 1m }

        ip saddr @blackhole_ipv4 counter drop
        ip6 saddr and ffff:ffff:ffff:ffff:: @blackhole_ipv6 counter drop
    }
}

要打印被黑洞的 IP,请运行 nft list set inet dev blackhole_ipvX

提示和技巧

保存当前规则集

nft list ruleset 命令的输出也是其有效的输入文件。当前规则集可以保存到文件并在以后重新加载。

$ nft -s list ruleset | tee filename
注意: nft list 不输出变量定义,如果您在原始文件中定义了任何变量,它们将会丢失。规则中使用的任何变量都将被其值替换。

简单状态防火墙

有关更多信息,请参阅 简单状态防火墙

单机

清空当前规则集

# nft flush ruleset

添加一个表

# nft add table inet my_table

添加 input、forward 和 output 基本链。input 和 forward 的策略将是 drop。output 的策略将是 accept。

# nft add chain inet my_table my_input '{ type filter hook input priority 0 ; policy drop ; }'
# nft add chain inet my_table my_forward '{ type filter hook forward priority 0 ; policy drop ; }'
# nft add chain inet my_table my_output '{ type filter hook output priority 0 ; policy accept ; }'

添加两个将与 tcp 和 udp 关联的常规链

# nft add chain inet my_table my_tcp_chain
# nft add chain inet my_table my_udp_chain

相关和已建立的流量将被接受

# nft add rule inet my_table my_input ct state related,established accept

所有环回接口流量将被接受

# nft add rule inet my_table my_input iif lo accept

丢弃任何无效流量

# nft add rule inet my_table my_input ct state invalid drop

接受 ICMP 和 IGMP

# nft add rule inet my_table my_input meta l4proto ipv6-icmp accept
# nft add rule inet my_table my_input meta l4proto icmp accept
# nft add rule inet my_table my_input ip protocol igmp accept

新的 udp 流量将跳转到 UDP 链

# nft add rule inet my_table my_input meta l4proto udp ct state new jump my_udp_chain

新的 tcp 流量将跳转到 TCP 链

# nft add rule inet my_table my_input 'meta l4proto tcp tcp flags & (fin|syn|rst|ack) == syn ct state new jump my_tcp_chain'

拒绝所有未被其他规则处理的流量

# nft add rule inet my_table my_input meta l4proto udp reject
# nft add rule inet my_table my_input meta l4proto tcp reject with tcp reset
# nft add rule inet my_table my_input counter reject with icmpx port-unreachable

此时,您应该决定要为传入连接打开哪些端口,这些端口由 TCP 和 UDP 链处理。例如,要为 Web 服务器打开连接,请添加

# nft add rule inet my_table my_tcp_chain tcp dport 80 accept

要接受 Web 服务器在端口 443 上的 HTTPS 连接

# nft add rule inet my_table my_tcp_chain tcp dport 443 accept

要接受端口 22 上的 SSH 流量

# nft add rule inet my_table my_tcp_chain tcp dport 22 accept

要接受传入的 DNS 请求

# nft add rule inet my_table my_tcp_chain tcp dport 53 accept
# nft add rule inet my_table my_udp_chain udp dport 53 accept

满意后,请务必使您的更改永久生效。

防止暴力攻击

Sshguard 是一个程序,可以检测暴力攻击并根据它临时列入黑名单的 IP 地址修改防火墙。有关如何设置 nftables 以与 Sshguard 一起使用,请参阅 Sshguard#nftables

记录流量

您可以使用 log 动作记录数据包。记录所有传入流量的最简单规则是

# nft add rule inet filter input log

有关详细信息,请参阅 nftables 维基

监控

监听所有事件,以原生 nft 格式报告。

# nft monitor

请参阅 nft(8) § 监控

规则集调试临时跟踪

meta nftrace set 1 规则集数据包跟踪 开/关。使用 monitor trace 命令来查看跟踪。

在另一个 shell 中,在交互式 shell 中“include”该文件

# nft -i
include "/root/nftables.trace"

示例,根据您的需要进行调整

/root/nftables.trace
add table ip temp-trace {comment "Temporary table!!"; flags owner;}
add chain ip temp-trace icmp-prerouting { type filter hook prerouting priority raw - 1 ; }
add rule ip temp-trace icmp-prerouting ip protocol icmp meta nftrace set 1

此文件添加一个临时表(flags owner),以便在调用(进程)交互式 nft 关闭时自动删除它。基本链需要根据您的用例进行调整。您可以使用“meta nftrace set 1”创建多个链和多个规则。“ip protocol icmp”仅用作示例,不是必需的。有很多方法可以实现类似的效果,优点是关闭交互式 shell 后,之前的状态会自动恢复,并且如果文件内部有错误,则不会执行任何操作。

请参阅 nftables 维基和一个自动化该过程并着色的 python 工具

使用 iptables-nft

本文或本节的事实准确性存在争议。

原因: 如果加载了旧的 iptables 对象,nftables 会发出警告,因此“同时使用旧的 iptables 和 nftables 并非完全可行”。(在 Talk:Nftables 中讨论)

较旧的 iptables 语言在 Linux 文档中仍然非常流行,并且很多东西仍然依赖 iptables 运行(例如 Docker 的网络)。尽管同时使用旧的 iptables 和 nftables 也完全可行,但首选使用 iptables-nft 的翻译,因为

  • 它将所有内容都放在同一个位置,使用更新、更高效、非锁定的框架。
  • 它检查冲突。

有两种方法可以将旧的 iptables 语言与 nftables 一起使用

  • iptables-translateiptables-restore-translate(以及 ip6tables、ebtables 等的反向操作)接受 iptable 语言并输出 nft 语言。它们不会更改正在运行的 nft 设置。请参阅 xtables-translate(8)
对于您以后想要维护的配置,最好使用 -translate 工具并将结果代码集成到您现有的规则中。例如,如果您在 简单状态防火墙 或更广泛的 Internet 中找到有用的东西,您可以翻译它们以放入您的 nft 配置中。
  • iptablesiptables-restore(同样 ip6tables 等的反向操作)使用上述翻译并将它们放入正在运行的 nft 设置中。它还提供像常规 iptables 那样的统计信息。请参阅 xtables-nft(8)
考虑到它们必须处理的内容,这些命令运行得相当好。它们应该在简单使用情况下“正常工作”,但有时您需要介入并进行调试。
注意: 翻译器涵盖了 iptables 语言的大部分,但并非全部。某些 iptables 规则协同工作,翻译器需要上下文才能正确翻译,因此如果某些行翻译成空,请不要惊慌。

使用 systemd-networkd 的动态命名集合

systemd-networkd 的连接可以使用 NFTSet= 选项(请参阅 systemd.network(5) § [ADDRESS] SECTION OPTIONS)来使用主机 IP 地址、网络前缀和接口索引填充预定义的命名集合。这允许避免在 /etc/nftables.conf 中硬编码它们。

例如,要在单独的 my_input_lan 链中处理来自本地网络(IP 地址通过 DHCP 或 SLAAC 分配)的连接

/etc/nftables.conf
...
table inet my_table {

	set eth_ipv4_prefix {
		type ipv4_addr
		flags interval
		comment "Populated by systemd-networkd"
	}
	set eth_ipv6_prefix {
		type ipv6_addr
		flags interval
		comment "Populated by systemd-networkd"

		elements = { fe80::/10 }
	}
	set eth_ifindex {
		type iface_index
		comment "Populated by systemd-networkd"
	}
...
	chain my_input {
		type filter hook input priority filter; policy drop;

		iif @eth_ifindex ip6 saddr @eth_ipv6_prefix jump my_input_lan comment "Connections from LAN"
		iif @eth_ifindex ip saddr @eth_ipv4_prefix jump my_input_lan comment "Connections from LAN"
	}
...
}
/etc/systemd/network/my-network.network
...

[DHCPv4]
NFTSet=prefix:inet:my_table:eth_ipv4_prefix
NFTSet=ifindex:inet:my_table:eth_ifindex

[DHCPv6]
NFTSet=prefix:inet:my_table:eth_ipv6_prefix
NFTSet=ifindex:inet:my_table:eth_ifindex

[IPv6AcceptRA]
NFTSet=prefix:inet:my_table:eth_ipv6_prefix
NFTSet=ifindex:inet:my_table:eth_ifindex
...

故障排除

使用 Docker

注意
  • 使用以下设置,即使使用 --net host --privileged,您也无法在容器内使用 AF_BLUETOOTH 等协议。
  • 无根 Docker 容器已经在单独的网络命名空间中运行。您可能不需要做任何事情。

使用 nftables 可能会干扰 Docker 网络(也可能干扰其他容器运行时)。您可以在互联网上找到各种解决方法,这些解决方法要么涉及修补 iptables 规则并确保定义的服务启动顺序,要么完全禁用 dockers iptables 管理,这使得使用 docker 非常受限制(想想端口转发或 docker-compose)。

一种可靠的方法是让 docker 在单独的网络命名空间中运行,它可以在其中做任何想做的事情。最好不要使用 iptables-nft 来防止 docker 混合 nftables 和 iptables 规则。

使用以下 docker 服务 drop-in 文件

/etc/systemd/system/docker.service.d/netns.conf
[Service]
PrivateNetwork=yes
PrivateMounts=No

# cleanup
ExecStartPre=-nsenter -t 1 -n -- ip link delete docker0

# add veth
ExecStartPre=nsenter -t 1 -n -- ip link add docker0 type veth peer name docker0_ns
ExecStartPre=sh -c 'nsenter -t 1 -n -- ip link set docker0_ns netns "$$BASHPID" && true'
ExecStartPre=ip link set docker0_ns name eth0

# bring host online
ExecStartPre=nsenter -t 1 -n -- ip addr add 10.0.0.1/24 dev docker0
ExecStartPre=nsenter -t 1 -n -- ip link set docker0 up

# bring ns online
ExecStartPre=ip addr add 10.0.0.100/24 dev eth0
ExecStartPre=ip link set eth0 up
ExecStartPre=ip route add default via 10.0.0.1 dev eth0

如果 10.0.0.* IP 地址不适合您的设置,请进行调整。

使用以下 postrouting 规则为 docker0 启用 IP 转发并设置 NAT

iifname docker0 oifname eth0 masquerade

然后,确保内核 IP 转发已启用。

现在,您可以使用 nftables 为 docker0 接口设置防火墙和端口转发,而不会受到任何干扰。

另请参阅