对于 IPv4,很容易创建一条规则,只接受来自同一子网主机的连接,例如(假设我的计算机是192.168.42.2
,传入连接是192.168.42.20
):
table ip firewall {
chain incoming {
type filter hook input priority 0; policy drop;
ip saddr 192.168.42.0/24 tcp dport 8080 accept
}
}
对于 IPv6,如何做到这一点?我知道总是有链路本地地址,理论上有这个规则应该工作:
ip6 saddr fe80::/64 tcp dport 8080 accept
现在的问题是我已经设置了 mDNS,它返回的地址是全局可路由地址,类似于2001:db8::1234
。 因此,我从其他主机(尽管位于同一子网)收到的数据包都带有前缀,ip6 saddr
而2001:db8
该前缀会被防火墙阻止。
我无法简单地添加一条匹配的规则2001:db8::/64
,因为该前缀来自 ISP,并且会不时更改。设置 ULA 以便获得可预测的前缀似乎也不可能,因为路由器是 ISP 强制要求的,并且它为 IPv6 提供的配置界面非常空洞。
所以......这就是为什么我正在寻找与此类似的东西:
ip6 saddr & ffff:ffff:ffff:ffff:: == ip6 daddr & ffff:ffff:ffff:ffff:: tcp dport 8080 accept
但 nftables 似乎不接受这一点。我能做些什么来解决这个问题,还是我遗漏了什么?
答案1
该功能尚不存在?
现在,nftables只能使用一个寄存器(在其虚拟状态机中):它在左侧(LHS)应用按位运算,将结果与右侧(RHS)的常量或集合进行比较。它不能在 LHS 和 RHS 中使用两个变量操作数(意思是:都来自数据包)。
这些补丁系列中正在进行有关改进此问题的工作(尚未被接受):nf-next 库 nftables:
目前按位布尔运算(AND、OR 和 XOR)只能有一个变量操作数。[...] 我们添加了对直接在内核空间中的一个寄存器上评估这些操作的支持,以及一个立即数或第二个寄存器。
这可能仍需要额外的迭代和一些时间才能实现(这个想法自 2019 年甚至更早以来就一直在流传,但目前仍未实现)。完成后,可以想象 OP 的精确规则:
ip6 saddr & ffff:ffff:ffff:ffff:: == ip6 daddr & ffff:ffff:ffff:ffff:: tcp dport 8080 accept
将会按预期工作。
解决方法
话虽如此,今天能做什么呢?
可以使用外部工具对主机更改地址做出反应并更新放因此它以主机的 IPv6 网络/网络掩码作为内容。一组可以用作 RHS。最后它不是动态的,就像变量操作数但它仍然足够动态以满足需求。
在 OP 的规则集中添加一组(我不会放完整的规则集,这超出了问题的范围。只需记住,通常ct state related,established accept
还应该允许环回接口存在,并且可以在相关时合并一些 IPv4 和 IPv6 规则的系列表inet
)ip6
。
ip6firewall.nft
:
table ip6 firewall #for idempotence
delete table ip6 firewall #for idempotence
table ip6 firewall {
set myip6net {
typeof ip6 saddr
flags interval
}
chain acceptmyip6netsrc {
ip6 saddr @myip6net counter accept
}
}
这可以从基本输入链中调用:
tcp dport 8080 jump acceptmyip6netsrc
我假设网络接口名称为eth0
。下面的脚本使用一个非常简单的事件循环,ip monitor
并将继续运行:将其用作服务,而不是在 crontab 中。每当发生地址事件时,它都会触发(大多数情况下,当刷新超时且更改的路由器广告没有发生时,它会毫无用处)。ip monitor
的输出不易解析,因此只需忽略它并使用它ip -json addr
检索实际值。该脚本还有改进的空间,但可以完成工作。
需要通常在发行版中提供的工具:
updatemyip6net.sh
:
#!/bin/sh
{ echo init; ip -6 -o monitor address dev eth0; } | while read dummy; do
myip6addr=$(ip -json -6 addr show dev eth0 scope global |
jq -j '.[].addr_info[] | if .local then .local,"/",.prefixlen,"\n", halt else empty end'
)
myip6net=$(netmask $myip6addr)
nft -f - <<EOF
flush set ip6 firewall myip6net
add element ip6 firewall myip6net { $myip6net }
EOF
done
多于,
ip -json -6 addr show dev eth0 scope global | jq -j '.[].addr_info[] | if .local then .local,"/",.prefixlen,"\n", halt else empty end'
很长,但是可以用更简单的替换它:
ip -j -6 route get 2001:4860:4860::8888 | jq -r '.[].prefsrc'
没有获取网络掩码,并且应避免在任何地方硬编码 /64。