[抱歉,前奏太长;问题已经进行了一半。]
我有一个有效的 OpenVPN 设置,其中 VPN 服务器将路由推送回一个客户端(以下称为“路由器”),然后该客户端可以将其自己的子网公开给运行服务器的计算机以及运行 VPN 客户端的其他计算机。这只需让路由器使用SNAT
来自的目标即可实现iptables
。例如,假设 VPN 服务器和其他未区分的客户端位于 10.0.77.0/24 网络上,VPN 创建一个tun0
覆盖 192.168.252.0/24 的接口,私有子网为 192.168.33.0/24。OpenVPN 服务器配置显示(除其他内容外)
client-to-client
route 192.168.33.0 255.255.255.0
push "route 192.168.33.0 255.255.255.0"
当 Linux “路由器” 机器(比如说 192.168.33.10)连接到 VPN 时,它会被推送一条路由,因此它的路由表如下所示
192.168.33.0 * 255.255.255.0 U 0 0 0 eth1
192.168.252.0 192.168.252.5 255.255.255.0 UG 0 0 0 tun0
192.168.252.5 * 255.255.255.255 UH 0 0 0 tun0
然后配置为运行
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 192.168.252.0/24 -j SNAT --to-source 192.168.33.10
它还可以添加一些iptables
规则来创建防火墙,但上述内容足以让运行 OpenVPN 服务器(或另一个客户端)的机器连接到 192.168.33.11:数据包通过tun0
路由器发送,路由器使用 SNAT 将源 IP 设置为自己的 192.168.33.10,然后将数据包转发到其兄弟机器 192.168.33.11。回复数据包被发送到路由器,然后路由器通过隧道将它们转发回去,一切正常。例如,在 192.168.33.11 上,我可以
nc -l localhost 9999
并且从 10.0.77.13(其他 VPN 客户端)我可以
nc 192.168.33.11 9999
并建立连接。到目前为止一切顺利。
注意没有变化正在向任一网络上的物理路由器机器发送数据包;引号中的“路由器”(运行 VPN 客户端的随机机器)需要使用 SNAT,以便其网络上的其他机器能够通过 VPN 发送回复数据包。就这个问题而言,我无法“控制”任一网络:我只能添加一些具有自己的路由规则和 VPN 客户端或服务器的机器。
现在来谈谈问题:假设这两个网络(都不在公共互联网上)实际上在实际范围内重叠。因此,在此示例中,私有子网不是 192.168.33.11,而是 10.0.77.0/24。让我们假设重新编号任何一个网络都不是一个选项。因此,除了使用 SNAT 允许路由器转发数据包之外,我还需要从 OpenVPN 服务器的角度将路由器的私有网络重新映射到不同的 IP 范围。假设我选择 10.0.78.0/24 作为虚拟网络范围:
route 10.0.78.0 255.255.255.0
push "route 10.0.78.0 255.255.255.0"
并且我希望从 OpenVPN 服务器计算机到 10.0.78.11 的连接通过隧道并像以前一样使用 SNAT 进行处理,但我还希望路由器子网中的目标地址为 10.0.77.11。我该如何配置iptables
才能做到这一点?
目标NETMAP
看了就像这是我想要的,但我无法让它工作:
iptables -t nat -A PREROUTING -i tun0 -j NETMAP --to 10.0.77.0/24
iptables -t nat -A POSTROUTING -s 192.168.252.0/24 -j SNAT --to-source 10.0.77.10
NETMAP
当添加目标时,我可以从 OpenVPN 服务器机器 ping 10.0.78.10(即路由器的重新映射地址) ,因此它正在执行某些操作。
tcpdump -i tun0
在此过程中,在路由器上运行 pingICMP echo request
并显示ICMP echo reply
与预期一致。但是,如果我尝试 ping 10.0.78.11(即路由器子网上兄弟机器的重新映射地址),则不会收到回复,tcpdump
路由器上显示请求但没有回复;tcpdump
在 10.0.77.11(兄弟机器)上根本没有显示任何数据包。同样在路由器上运行:
iptables -t nat -L -v
显示数据包正在被处理,NETMAP
但没有被处理SNAT
。所以看起来NETMAP
目标以某种方式取代了SNAT
目标?
本质上我想要的是,当路由器tun0
从例如 192.168.252.5(隧道上的 OpenVPN 服务器地址)接收到发往 10.0.78.11 的数据包时,它会被重写为二方法:将目标地址更改为 10.0.77.11,将源地址更改为 10.0.77.10(选择新的源端口,以便 SNAT 可以跟踪这是哪个连接)。然后,当 10.0.77.11 向 10.0.77.10 上的虚假端口发送回复时,路由器应逆转该过程,tun0
使用虚假的源地址 10.0.78.11 将数据包发送回 192.168.252.5。可以NETMAP
这样做吗,或者任何其他目标都可以iptables
这样做吗?
我尝试了其他一些方法,但没有成功:为代理 ARP 配置路由器;将虚拟网络范围添加到路由表。但感觉这些事情应该没有必要。
更新:在这种情况下,我根本不关心 DNS——只需要联系“私有子网”中的少数几台机器,因此使用 IP 地址是可以接受的。
答案1
这真是一团糟,老实说,重新编号比创建一个丑陋的 NAT 网络(地址冲突)更划算。支持这一点所需的其他解决方案(例如具有多个区域的水平分割 DNS,可能具有自动更新)将非常困难和混乱,每个后来必须处理此网络的人都会永远诅咒你的名字并烧毁你和你的团队的肖像。
尽管如此,您遇到的问题似乎是 NAT 一侧的主机(看到了吗?我甚至无法正确描述 NAT 的两侧,因为它很混乱)没有返回另一侧主机的路径。您还必须为返回路径添加 NETMAP 规则(我假设是从 eth1 传入的数据包)。
但是等一下!这是在预路由链中完成的。因此,目标地址将在做出路由决策之前设置,这几乎就是它必须工作的方式……但是您的路由器有两个用于“冲突”网络的路由。因此,它将通过通常的方式进行优先排序(最窄匹配前缀优先;度量以打破平局),导致一些数据包被反射回它们来自的网络,而不是通过 NAT 路由。它们也将被源 NAT。不幸的是,源地址不用于路由,因此您不能使用源地址。输入接口也不用于路由,一旦做出路由决策,您就不能再次重写目标地址。
因此,您发现这无法完成。您有两个选择。最好,您可以重新编号存在网络地址冲突的子网之一,因为您是一名优秀的网络工程师,而不是一名不称职的网络工程师,并且您创建的网络不会过于复杂。重新编号 VPN 子网往往是一项简单的任务,最坏的情况是需要使用sed
,但也许您遇到了一种奇怪的情况,即重新编号任一子网都是一项极其困难的任务。好的。
如果出于某种原因您无法这样做,您将需要一个额外的路由器来配合您的水平分割 DNS。为每个子网设置一个路由器(实际上这意味着一个路由器用于 VPN 客户端,一个路由器用于网络上的主机,不幸的是,该主机使用您为 VPN 设置的前缀)。选择其他地址范围(并且它必须是您可以将其中一个子网重新编号为...)作为“外部”地址范围。
现在,假设我们将具有 VPN 主机的网络称为 A 端,将具有非 VPN 主机的网络称为 B 端。假设在路由器 A 上,您选择“外部”前缀为 192.0.2.0/24,而在路由器 B 上,它为 198.51.100.0/24。这些将是您在该子网上的主机使用的虚假前缀,用于联系另一个子网上具有冲突前缀的主机(不要使用这些前缀,它们在 RFC5737 中为文档目的而保留)。
以下规则很复杂,因为您不能将输入接口用作 POSTROUTING 链中的谓词,并且源子网不唯一,因此我们必须使用过滤规则来防止欺骗。这也令人困惑,因为 NETMAP 会根据其所在的链来决定是否更改源地址或目标地址。
在路由器 A 和 B 中,添加一条针对路由器之间点对点链路上的传入流量的规则,我将其称为 ptp0:
router-a # iptables -t nat -A PREROUTING -i ptp0 -d 198.51.100.0/24 -j NETMAP --to 10.0.77.0/24
router-a # iptables -t nat -A POSTROUTING -d 10.0.77.0/24 -j NETMAP --to 192.0.2.0/24
router-a # iptables -t filter -A FORWARD -i \!ptp0 -s 10.0.77.0/24 -j DROP
router-b # iptables -t nat -A PREROUTING -i ptp0 -d 192.0.2.0/24 -j NETMAP --to 10.0.77.0/24
router-b # iptables -t nat -A POSTROUTING -d 10.0.77.0/24 -j NETMAP --to 198.51.100.0/24
router-b # iptables -t filter -A FORWARD -i \!ptp0 -s 10.0.77.0/24 -j DROP
现在,这部分问题已经解决,因为您不再需要单个路由器在其路由表中同时包含两个前缀。任何要求您在同一个路由表中使用相同的前缀来引用两个不同网络(其中包含两个不同的主机)的解决方案都行不通;由于路由的工作方式,这本质上是不可能的。
哦,我差点忘了 - 我认为您的 SNAT 规则没有得到处理,因为没有任何东西与它匹配,但重要的是要记住,一旦连接存储在 NAT 状态表中,它就不再被 SNAT 规则处理,并且如果我没记错的话,它也不再被计入统计数据中。
如果您实际上不需要子网分离,只需使用第 2 层桥接即可。这将使您的生活变得轻松很多。
答案2
所以我创建了一个独立的snat-路由-演示使用 Vagrant。作为控制,我最初使用了简单的SNAT
,VPN 客户端直接引用服务的实际 IP,同时我解决了 OpenVPN 设置等细节问题。然后我引入了一个虚拟子网,并将NETMAP
之前的内容添加SNAT
到表中:
iptables -t nat -A PREROUTING -d <virtual-subnet> -j NETMAP --to <actual-subnet>
成功了。我以为我已经在现实环境中尝试过这个相当简单的规则,但也许我没有,或者其他东西被破坏了;无论如何,我也可以“真正”地让类似的规则发挥作用。似乎不需要使用标记,而且
iptables -t nat -L -v
现在显示两条规则均已应用。
防火墙规则限制了桥接子网中哪些主机和端口可以从 VPN 客户端访问,似乎也无需修改就可以使用实际的 IP 范围,例如
iptables -A FORWARD -p tcp -d <actual-host> --dport <some-port> -j ACCEPT
iptables -A FORWARD -s <actual-subnet> -j ACCEPT
iptables -P FORWARD DROP
答案3
我相信,在我的其他回答中,我可能在某种程度上误解了你的意图,仔细想想。
如果我理解正确的话,两个站点之间有一条 VPN 链路,它应该在两个站点之间路由流量。但不幸的是,这两个(之前独立的)站点最终都使用相同的 RFC1918 地址作为子网,对吗?这是 RFC1918 最令人烦恼的新兴特性之一。
尽管如此,最好的解决方案是重新编号其中一个子网。但是,如果您不能,您尝试执行的映射操作并不是一个糟糕的解决方案。
但是,您的规则中存在一些错误。
您的 SNAT 规则没有记录太多流量,因为它除了匹配 openvpn 客户端路由器自己的数据包之外没有匹配任何其他内容。来自 VPN 链接的数据包的源地址在 10.0.77.0/24 范围内。因此,数据包穿过 VPN 并从另一端出来,但对它们的回复被发送到本地段,而不是通过 VPN 返回,因此丢失了。
就像您需要创建一个假网络前缀来区分正向路径中的目标地址一样,反向路径地址也必须是可区分的,我猜这就是您使用 SNAT 的原因。因此,您应该修改规则(并且更具体地说明您的 NETMAP 是什么,这样您就不会重写通过 VPN 的每个子网中每个目标地址的前 3 个八位字节):
iptables -t mangle -A PREROUTING -i tun0 -j MARK --set-mark 1
iptables -t nat -A PREROUTING -i tun0 -d 10.0.78.0/24 -j NETMAP --to 10.0.77.0/24
iptables -t nat -A POSTROUTING -s 10.0.77.0/24 -m mark --mark 1 -j SNAT --to-source 10.0.77.10
mangle 规则是必需的,因为您不能将输入接口用作 POSTROUTING 规则中的谓词,因此您需要其他方法来确定数据包来自 VPN 链接并需要源 NAT。