如何使用 nftables 将端口 80 上的请求重定向到 localhost:3000?

如何使用 nftables 将端口 80 上的请求重定向到 localhost:3000?

我希望将到达 192.168.0.1:80 的网络流量重定向到 127.0.0.1:3000。并且,我还希望处理响应的映射。我的完整 NAT 和过滤表规则粘贴如下。

我能够在端口 80 上接收连接。但是,我无法将流量重定向到 localhost:3000。

add table inet filter
add chain inet filter input { type filter hook input priority 0; policy accept; }
add chain inet filter forward { type filter hook forward priority 0; policy accept; }
add chain inet filter output { type filter hook output priority 0; policy accept; }
add rule inet filter input ct state related,established  counter accept
add rule inet filter input ip protocol icmp counter accept
add rule inet filter input iifname "lo" counter accept
add rule inet filter input ct state new  tcp dport 80 counter accept
add rule inet filter input ct state new  tcp dport 4489 counter accept
add rule inet filter input ct state new  tcp dport 8080 counter accept
add rule inet filter input iifname "tun0" ct state new  tcp dport 139 counter accept
add rule inet filter input iifname "tun0" ct state new  tcp dport 445 counter accept
add rule inet filter input ct state new  udp dport 1194 counter accept
add rule inet filter input counter reject with icmp type host-prohibited
add rule inet filter forward counter reject with icmp type host-prohibited
add table nat
add chain nat prerouting { type nat hook prerouting priority -100; }
add chain nat postrouting { type nat hook postrouting priority 100; }
add rule nat prerouting redirect
add rule nat prerouting tcp dport 80 redirect to 3000
add rule nat prerouting iifname eth0 tcp dport { 80, 443 } dnat 127.0.0.1:3000
add rule nat postrouting oifname eth0 snat to 192.168.0.1

答案1

我会努力解决并完成OP 自己的工作答案以及其他评论,其中包括一些剩余的问题:

  • 为什么net.ipv4.conf.eth0.route_localnet=1需要?
  • 为什么需要允许端口 3000eth0而不是

并且还将解决其中出现的一个小的安全问题。

首先,这里是关于 Netfilter 和通用网络中数据包流的强制性示意图:

Netfilter 和常规网络中的数据包流

该示意图iptables记住,但是nftables可以(并且在大多数默认规则集中)在相同的位置使用相同的钩子。

当数据包到达网络层(IP 第 3 层)时,它会由各种子系统处理。通常只有路由堆栈,但这里网络过滤器为其自身提供钩子(连接跟踪(包括初始数据包后的 NAT 处理)或nftables

网络过滤器连接跟踪) 或者nftables不关心路由(除非例如nftables使用与),它们把这个留给路由栈:它们操纵地址和端口,nftables然后检查可用的属性,如接口、地址和端口。

所以:

  • 新连接中的数据包(因此也穿越ip nat 预路由) 来自eth0例如,源地址为 192.0.2.2 和端口 45678,目标地址为 192.168.0.1 和端口 80(或 443)。

  • ip nat 预路由 基因规则匹配并告知网络过滤器(它是连接跟踪子系统)将目标地址更改为 127.0.0.1,将目标端口更改为 3000。这不会改变数据包的任何其他属性。特别是数据包仍然来自eth0

  • 路由堆栈(示意图中的路由决策)不依赖于网络过滤器,因此在逻辑上保持独立,并且不知道之前的更改。它现在必须处理来自 192.0.2.2 和目的地 127.0.0.1 的数据包。

    这是一个异常:它将允许为环回保留的地址范围在“互联网上”被看到,如RFC 1122

    (G) { 127, <any> }

    内部主机环回地址。此形式的地址不得出现在主机外部。

    明确处理在 Linux 内核中路由堆栈:将其视为火星目的地(即丢弃数据包)除非通过使用放松route_localnet=1在相关接口上。这就是为什么net.ipv4.conf.eth0.route_localnet=1必须针对此特定情况进行设置。

  • 同样,下一个nftables规则,这次来自过滤器输入钩子,看到一个带有输入接口的数据包仍然eth0但现在目标端口为 3000。因此它必须允许目标端口 3000,并且没有更多端口来允许 80(或 443)接受它。因此规则应缩短为:

    iifname "eth0" tcp dport {4489, 3000} counter accept
    

    因为它永远不会看到来自eth0目标 TCP 端口为 80 或 443:在之前的 nat 预路由钩子中,它们都已更改为端口 3000。此外,为了便于解释,假设看到此类数据包,它们将被接受,但由于端口 80 或 443 上没有侦听进程(它在端口 3000 上侦听),因此 TCP 堆栈将发出 TCP 重置以拒绝连接。

    此外路由堆栈加强 127.0.0.0/8 和接口(进一步放松route_localnet=1),如前所述,这并不重要网络过滤器或者nftables不介意路由。此外,如果是这样的话,对于输入界面这将是来源地址没有改变,而不是目的地与以下地址相关的输出接口甚至没有真正的意义输入路径:oifoifname不能在这里使用。事实上,过滤输入钩子已经意味着评估的数据包到达主机当地的过程,如示意图所示。

    更新:实际上,出于安全原因,应该进一步更改先前给出的规则:允许端口 3000,但不仅仅允许目的地 127.0.0.1。因此,与 192.168.0.1:3000 的连接可以收到 TCP RST,这暗示这里有一些特殊情况,而不是没有收到任何回复。为了解决这种情况:

    • 或者使用这个(其中包括一个看起来非常奇怪的第二条规则):

      iifname "eth0" tcp dport 4489 counter accept
      iifname "eth0" ip daddr 127.0.0.1 tcp dport 3000 counter accept
      

      因为route_localnet=1,仍然允许同一 192.168.0.0/24 LAN 中的经过调整的系统通过在线路上发送带有 127.0.0.1 的数据包来访问服务,即使这样做可能没有任何好处。例如,另一个 Linux 系统,使用以下 4 个命令:

      sysctl -w net.ipv4.conf.eth0.route_localnet=1
      ip address delete 127.0.0.1/8 dev lo # can't have 127.0.0.1 also local
      ip route add 127.0.0.1/32 via 192.168.0.1 # via, that way no suspicious ARP *broadcast* for 127.0.0.1 will be seen elsewhere.
      socat tcp4:127.0.0.1:3000 -
      
    • 或者,也可以保护上述情况,这种方式更通用,也更受欢迎:

      iifname "eth0" tcp dport 4489 counter accept
      ct status dnat counter accept
      
      • 它保持不相关的端口 4489/tcp 和以前一样被允许
      • ct status dnat 火柴如果数据包先前已被主机 DNAT 过:因此它将接受任何先前的更改,而不必明确重申它是哪个端口(仍然可以说明它或其他任何内容以进一步缩小接受的范围):现在端口值为 3000没必要再明确说明。
      • 因此它也不会允许直接连接到端口 3000,因为这种情况不会被 DNATed。
  • 只是为了完整起见:同样的事情发生在输出和回复的(不完全)相反的顺序中。net.ipv4.conf.eth0.route_localnet=1允许最初生成的从 127.0.0.1 到 192.0.2.2 的传出数据包不被视为火星来源(=> 删除)在输出路径中路由决策,然后它们才有机会被“反 DNAT”回到最初的源地址 (192.168.0.1)网络过滤器连接跟踪) 独自的。


当然,使用防火墙route_localnet=1是一种放松安全性的做法(与适当的防火墙规则无关,但并非所有系统都使用防火墙),并且需要相关使用知识(例如:复制nftables如果没有该设置,其他地方的单独规则集将不再起作用route_localnet=1)。

既然上述解释中已经解决了安全问题(请参阅“更新”),如果允许应用程序监听 192.168.0.1(或任何地址)而不是仅仅 127.0.0.1,则可以在不启用的情况下route_localnet=1通过更改 来完成等效配置ip nat prerouting

iif eth0 tcp dport { 80, 443 } counter dnat 127.0.0.1:3000

到:

iif eth0 tcp dport { 80, 443 } counter dnat to 192.168.0.1:3000

或者简单地:

  iif eth0 tcp dport { 80, 443 } counter redirect to :3000

两者没有太大区别:redirect将目标更改为接口上主机的主 IP 地址eth0即 192.168.0.1,因此大多数情况的行为都会相同。

答案2

为了让它工作,我决定求助于阅读文档这一古老的艺术。当它不起作用时,我用我新获得的知识摆弄了一切,让它工作,但我不知道为什么这个配置有效。

使用以下 nftables 配置并sysctl -w net.ipv4.conf.eth0.route_localnet=1在 shell 提示符下输入,我可以通过连接到与 eth0 关联的假设外部 IP 地址 (192.168.0.1:80) 来连接到在 localhost:3000 上监听的服务。但是,我不清楚为什么这样做有效。

iifname "eth0" tcp dport {4489, 80, 443, 3000} counter accept为了使其正常工作,必须在行中包含端口 3000 。

flush ruleset

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

                ct state established,related accept

                iif lo counter accept

                icmp type echo-request counter accept

                iifname "eth0" tcp dport {4489, 80, 443, 3000} counter accept
  }
}

table ip nat {
        chain prerouting  {

                type nat hook prerouting priority -100;

                iif eth0 tcp dport { 80, 443 } counter dnat 127.0.0.1:3000
        }
}

答案3

iptables-translate如果您已经有了一个可以运行的 iptables 规则并且想要查看其 nftables 等效项,则可以使用。

例如,此重定向的有效 iptables 规则为:

-t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 3000

将其输入iptables-translate您将获得:

[root@vmtest-centos8 ~]# iptables-translate -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 3000
nft add rule ip nat PREROUTING tcp dport 80 counter redirect to :3000

对此不需要其他 nat 规则,尽管听起来您可能还想设置其他重定向。对它们执行相同的操作。

相关内容