CentOS 8 作为带有 nft 和firewalld 的 NAT 路由器-如何让它通过 TFTP?

CentOS 8 作为带有 nft 和firewalld 的 NAT 路由器-如何让它通过 TFTP?

我正在尝试在位于 NAT 路由器后面的网络上设置 PXE 启动(需要 TFTP)。

我的问题与网络上的许多其他问题类似,但我找到的所有答案都适用于带有 iptables 的 CentOS 7。我需要使用带有防火墙和 nft 作为后端的 CentOS 8 来执行此操作。

无法对 TFTP 流量进行 NAT,因为尽管 TFTP 助手创建了期望,但 iptables 并未将返回连接转发到客户端 https://unix.stackexchange.com/questions/579508/iptables-rules-to-forward-tftp-via-nat

这是我的简化的网络图:

     Outside NAT               Inside NAT
10.0.10.10  10.0.10.11->192.168.1.1  192.168.1.2
TFTP server --------> NAT ---------> PXE/TFTP client

TFTP 无法正常工作。使用 tcpdump,我看到 RRQ 消息成功从 192.168.1.2 传输到 10.0.10.10。响应到达路由器,但未正确进行 NAT 以到达客户端。

我尝试了 sysctl net.netfilter.nf_contrack_helper 的两个设置(更改设置后重新启动):

# sysctl -a | grep conntrack_helper
net.netfilter.nf_conntrack_helper = 0

当 nf_contrack_helper=0 时:

tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes

Initial RRQ:

14:02:27.842563 IP (tos 0x0, ttl 64, id 64642, offset 0, flags [DF], proto UDP (17), length 54)
    192.168.1.2.36799 > 10.0.10.10.69: [udp sum ok]  26 RRQ "grub2/grubx64.efi" octet

Initial RRQ after NAT:

14:02:27.842619 IP (tos 0x0, ttl 63, id 64642, offset 0, flags [DF], proto UDP (17), length 54)
    10.0.10.11.36799 > 10.0.10.10.69: [udp sum ok]  26 RRQ "grub2/grubx64.efi" octet

Response from TFTP server to NAT router:

14:02:27.857924 IP (tos 0x0, ttl 63, id 60000, offset 0, flags [none], proto UDP (17), length 544)
    10.0.10.10.60702 > 10.0.10.11.36799: [udp sum ok] UDP, length 516

(repeated several times until timeout)

当 nf_contrack_helper=1 时,传出的数据包根本就不会被 NAT:

tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes

Initial RRQ:

14:02:27.842563 IP (tos 0x0, ttl 64, id 64642, offset 0, flags [DF], proto UDP (17), length 54)
    192.168.1.2.36799 > 10.0.10.10.69: [udp sum ok]  26 RRQ "grub2/grubx64.efi" octet

(repeated several times until timeout)

nf_*_tftp 帮助程序都已加载(无论 nf_contrack_helper 设置如何):

# lsmod | grep tftp
nf_nat_tftp            16384  0
nf_conntrack_tftp      16384  3 nf_nat_tftp
nf_nat                 36864  3 nf_nat_ipv6,nf_nat_ipv4,nf_nat_tftp
nf_conntrack          155648  10 nf_conntrack_ipv6,nf_conntrack_ipv4,nf_nat,nf_conntrack_tftp,nft_ct,nf_nat_ipv6,nf_nat_ipv4,nf_nat_tftp,nft_masq,nft_masq_ipv4

上面链接的一篇文章建议使用 iptables 进行以下操作(这很有意义):

iptables -A PREROUTING -t raw -p udp --dport 69 -s 192.168.11.0/24 -d 172.16.0.0/16 -j CT --helper tftp

我如何使用带有 nft 后端的firewalld 做同等的事情。

更新:

Firewalld 配置相当复杂,因此我仅添加相关区域:

外部区域:

<?xml version="1.0" encoding="utf-8"?>
<zone>
  <source address="10.0.10.0/24"/>
  <service name="tftp-client"/>
  <service name="ssh"/>
  <masquerade/>
</zone>

内部区域:

<?xml version="1.0" encoding="utf-8"?>
<zone>
  <source address="192.168.1.0/24"/>
  <service name="dhcp"/>
  <service name="ssh"/>
  <service name="dns"/>
  <service name="tftp"/>
  <masquerade/>
</zone>

注意:内部区域的 Masquerade 是一个错误。我删除了它,但行为没有改变。

区域漂移已被禁用。

更新 2:

要回答评论者的请求:

DHCP 配置

DHCP 服务器与 NAT 路由器(网络图中的 192.168.1.1)在同一系统上运行。它是标准的 ISC DHCP,分发 IP 地址(作为固定地址;不涉及地址池)、掩码、网关、DNS 服务器等,以及 PXE Boot 下一个服务器和文件名选项。

这一切显然是有效的。tcpdump 显示客户端向服务器发送了正确的 RRQ 数据包。

响应返回到 NAT 路由器,但不会被发送到 NAT 后面的一侧。

关于 TFTP 工作原理及其如何突破 NAT 的详细信息

如果您了解 TFTP 协议,那么就会很清楚发生了什么;我只是不知道如何使用firewalld/nft/CentOS 8 来处理它。

从根本上讲,问题在于 TFTP 协议以非标准方式使用 UDP 端口。在 DNS 等基于 UDP 的“标准”协议中,响应来自服务器侦听的同一端口。

Request: client:54321 -> server:53
Response: server:53 -> client:54321

(其中 54321 可以是客户端选择的任何随机临时端口号)。

NAT 匹配这些 IP 地址和端口来识别哪个响应属于哪个请求。

TFTP 的做法不同;响应不是来自端口 69,而是来自其他随机端口。

Request (RRQ): client54321 -> server:69
Response (Data): server:12345 -> client:54321

其中 54321 再次是客户端选择的随机临时端口,而 12345 是服务器选择的随机临时端口。

因此,标准 NAT 行为将找不到与原始服务器:12345 匹配的连接,并丢弃数据包。

解决这个问题的方法涉及使用一个帮助程序——了解这个怪癖的 nf_nat_tftp 内核模块。

我只是还没能弄清楚如何使用 CentOS 8、nftables 和firewalld 来实现这一点。

对于我来说,使用 nftables 的答案是完全可以接受的,只要它不违反任何防火墙规则。

答案1

不起作用的原因

它出现防火墙可能适合处理防火墙本地服务,而不是路由服务。

所以远程文件传输协议当使用 OP 中的区域文件配置了firewalld(在 CentOS 8 上)时,设置将在最后添加这些 nft 规则(这里仅显示规则,而不是整个规则集):

table inet firewalld {
    chain filter_IN_external_allow {
        udp dport 69 ct helper set "helper-tftp-udp"
    }
    chain filter_IN_internal_allow {
        udp dport 69 ct helper set "helper-tftp-udp"
        udp dport 69 ct state { new, untracked } accept
    }
}

这些规则永远不会匹配,因此毫无用处:它们在输入路径中,而不是在前向路径中。

在运行防火墙的情况下,将这些(盲目复制的)规则添加到正确的位置:在转发路径中,将使 TFTP 正常工作:

nft insert rule inet firewalld filter_FWDI_internal_allow udp dport 69 ct helper set "helper-tftp-udp"
nft add rule inet firewalld filter_FWDI_internal_allow index 0 udp dport 69 ct state '{ new, untracked }' accept

所以最后所谓的直接选择仍然是一个选择,所以一切都存储在防火墙的配置。可惜文档有点误导:

警告:直接规则行为因 FirewallBackend 的值而异。请参阅防火墙d.direct(5)

如果不仔细阅读,你可能会认为FirewallBackend=nftables它会通过接受而表现不同nftables规则,但事实并非如此:

# firewall-cmd --version
0.8.0

# firewall-cmd --direct --add-rule inet firewalld filter_FWDI_internal_allow 0 'udp dport 69 ct helper set "helper-tftp-udp" ct state new accept'
Error: INVALID_IPV: invalid argument: inet (choose from 'ipv4', 'ipv6', 'eb')

无需进行更多测试,此“功能”已记录在那里:

https://bugzilla.redhat.com/show_bug.cgi?id=1692964

还有:

https://github.com/firewalld/firewalld/issues/555

直接规则仍然使用iptablesnftables后端。注意事项与规则评估的顺序有关。

在另一个表中处理此问题

我不再认为这样做有什么意义防火墙命令,这将添加iptables规则nftables规则。添加一个独立的表会变得更清晰。它只会在知识产权系列,因为还将添加针对特定 IPv4 网络的过滤器(互联网也可以。

handletftp.nft(待加载nft -f handletftp.nft):

table ip handletftp
delete table ip handletftp

table ip handletftp {
    ct helper helper-tftp {
        type "tftp" protocol udp
    }

    chain sethelper {
        type filter hook forward priority 0; policy accept;
        ip saddr 192.168.1.0/24 ip daddr 10.0.10.10 udp dport 69 ct helper set "helper-tftp"
    }
}

由于表不同,并且规则集永远不会被刷新,而是特定表被(原子地)删除并重新创建,因此这不会影响防火墙也不防火墙会影响它。

优先级并不重要:这个链是在之前还是之后遍历防火墙的锁链不会改变包裹的命运(仍然在防火墙)。无论顺序如何,如果数据包被防火墙它还将激活该流程的助手。

如果您选择使用nftables服务加载此表,您必须编辑它(例如systemctl edit --full nftables:),因为除了加载一些可能不充分的默认规则外,它还会刷新全部停止或重新加载的规则,扰乱防火墙

现在,TFTP 传输将工作并激活特定的帮助程序,可以conntrack在传输期间通过运行两个命令进行检查:

# conntrack -E & conntrack -E expect
[1] 3635
    [NEW] 300 proto=17 src=10.0.10.10 dst=10.0.10.11 sport=0 dport=56597 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=192.168.1.2 master-dst=10.0.10.10 sport=56597 dport=69 class=0 helper=tftp
    [NEW] udp      17 29 src=192.168.1.2 dst=10.0.10.10 sport=56597 dport=69 [UNREPLIED] src=10.0.10.10 dst=10.0.10.11 sport=69 dport=56597 helper=tftp
[DESTROY] 299 proto=17 src=10.0.10.10 dst=10.0.10.11 sport=0 dport=56597 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=192.168.1.2 master-dst=10.0.10.10 sport=56597 dport=69 class=0 helper=tftp
    [NEW] udp      17 30 src=10.0.10.10 dst=10.0.10.11 sport=42032 dport=56597 [UNREPLIED] src=192.168.1.2 dst=10.0.10.10 sport=56597 dport=42032
 [UPDATE] udp      17 30 src=10.0.10.10 dst=10.0.10.11 sport=42032 dport=56597 src=192.168.1.2 dst=10.0.10.10 sport=56597 dport=42032

上面例子中的第 3 个 NEW 条目实际上被标记为 RELATED(这是 tftp 助手的全部作用:期望某种类型的数据包被视为相关),它将被防火墙接受。

相关内容