我有一些机器有多个网络接口,每个接口有一个或多个 IP 地址。这些 IP 地址可能位于也可能不在同一子网中。例如,结果ip a
可能如下所示:
1: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 11:11:11:11:11:11 brd ff:ff:ff:ff:ff:ff
inet 10.0.1.2/24 brd 10.0.1.255 scope global eno1
valid_lft forever preferred_lft forever
inet 10.12.1.3/24 brd 10.12.1.255 scope global secondary eno1:0
valid_lft forever preferred_lft forever
2: eno2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 22:22:22:22:22:22 brd ff:ff:ff:ff:ff:ff
inet 10.0.1.4/24 brd 10.0.1.255 scope global eno2
valid_lft forever preferred_lft forever
inet 192.168.1.3/24 brd 192.168.1.255 scope global secondary eno2:0
valid_lft forever preferred_lft forever
2: eno3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 33:33:33:33:33:33 brd ff:ff:ff:ff:ff:ff
inet 172.23.1.2/24 brd 172.23.1.255 scope global eno3
valid_lft forever preferred_lft forever
我想要的是,对进入一个接口的流量的响应通过同一接口发出;并且还能够告诉应用程序通过一个特定接口发送流量,无论目的地如何。
现在情况并非如此;例如,如果我ping 10.0.1.2
来自192.168.1.0/24
网络中的另一台机器,响应将通过 eno2 路由,这会导致各种问题。
我怎么做?我可以设置多个路由表吗?我可以使用接口名称或其他内容标记传入数据包吗?
答案1
备注:您的设置需要知识产权网络 10.0.1.0/24 和 192.168.1.0/24 位于同一网络上以太网网络。如果不是这种情况,您必须提供详细的网络拓扑,仅提供一台主机是不够的。我准备编辑这个答案以匹配它,即使问题已经可以在这里重现和修复。这就是为什么在最后的模型设置中,我连接了两个桥:诺1和埃诺2在同一个局域网内。我还添加了 IP 为 10.0.1.1/24 / 192.168.1.1/24 的路由器。
请注意,我将多次提到两个设置:rp_filter
和arp_filter
,不要混淆他们。
具有相同解决方案的两个不同问题:
- ARP通量(注意:链接中的解决方案中提供的两个参数已被 的使用所取代
arp_filter
),存在是因为默认情况下 Linux 并不真正遵循 ARP 的路由规则。当两个接口上的地址位于同一 IP LAN 中时,这会导致问题。这个问题应该得到解决,否则你可能会遇到奇怪的问题,持续几分钟。因此arp_filter
必须激活它才能匹配路由。它在这里不需要额外的努力就可以工作,因为解决下一问题的策略路由也将解决这一问题。 - 路由问题,因为默认情况下可以直接通过以下方式看到到 192.168.0/24 的路由埃诺2, 所以诺1永远不会考虑回复。设置后
rp_filter
,通过 eno1 的传入流量将被丢弃,甚至无法应答。如果没有设置或松动,则查询到10.0.1.2上诺1通过错误的接口得到答复:埃诺2。
总结一下:
修复ARP外向的行为将修复异常传入IP问题,对10.0.1.2的传入请求来自哪里埃诺2。这可以通过降低看到诺1在下面的模型设置中,一两分钟后(路由器上的 ARP 缓存过期)将收到对 10.0.1.2 的 ping 请求,并且所有答案都可以使用埃诺2。现在带回来诺1up 不会恢复传入的 IP。这是因为路由器仍然在 ARP 缓存 10.0.1.2 中埃诺2的 MAC 地址,并将通过首先对该地址执行单播查询来刷新,而不是诺1的 MAC 地址。暂时降低埃诺2并刷新路由器上的 ARP(或执行 DAD/盖普请求使用
arping
在主机上)将恢复到之前的情况(如果 则没有答案rp_filter=1
)。强调我的,从linux/文档/网络/ip-sysctl.txt的
arp_filter
:arp_filter - BOOLEAN
1 - 允许您在同一子网上拥有多个网络接口
,并应答每个接口的 ARP
基于内核是否会从
ARP 的 IP路由数据包出该接口(因此,您必须使用
基于源的路由才能使其工作)。换句话说,它允许控制
哪些卡(通常是 1 个)将响应 arp 请求。0 -(默认)
内核可以使用其他接口的地址响应 arp 请求。这可能看起来是错误的,但通常是
有道理的,因为它增加了成功沟通的机会。
Linux 上的 IP 地址由整个主机拥有,而不是通过
特定的接口。仅对于负载平衡等更复杂的设置
,此行为才会导致问题。如果conf/{all,interface}/arp_filter至少其中之一
设置为TRUE,则接口的arp_filter将被启用,
否则它将被禁用修理外向的IP 路由问题,您需要额外的路由表,每个 IP 需要一个路由表来修复。每个有问题的IP都应该有一个特定的规则来选择该IP使用其特定的路由表,而该路由表不会包含在内,从而忽略其他本地IP的相关路由,而是强制指定的接口。
如果您打算部署此类设置,您可能应该进行一些自动化操作。由于这种问题通常需要每个 IP 一个路由表来修复,并且现代内核上可以有 2^32 个路由表,您甚至可以将整个 IPv4 映射到路由表(考虑到少数特殊表的数量足够低)以避免被映射和覆盖,因为 IP 0.xxx/8 被保留)。我添加了一些 shell 函数来缓解这个问题。这些路由表编号不需要名称,但可以选择以/etc/iproute2/rt_tables
相同的方式命名,并且出于相同的目的,可以在 中命名 IP 地址/etc/hosts
。这里不需要。
现在希望已经得到解释,纠正方法如下:
在主机上运行以下脚本。
fix.sh
:
#!/bin/sh
map_localip_to_interface () {
ip -brief address show to "$1" | awk '{ print $1 }'| sed 's/@.*$//'
}
map_ip_to_table () {
printf '%s\n' "$1" | awk -F. '{ print (((($1*256)+$2)*256)+$3)*256+$4 }'
}
#not used here
map_table_to_ip () {
local ip
local n="$1"
for i in 1 2 3 4; do
ip="$(($n%256)).$ip"
n=$(($n/256))
done
printf '%s\n' "$ip" | sed 's/\.$//'
}
ip rule add from 10.0.1.2 table $(map_ip_to_table 10.0.1.2)
ip rule add from 10.0.1.4 table $(map_ip_to_table 10.0.1.4)
ip rule add from 192.168.1.3 table $(map_ip_to_table 192.168.1.3)
ip route add table $(map_ip_to_table 10.0.1.2) 10.0.1.0/24 dev $(map_localip_to_interface 10.0.1.2)
ip route add table $(map_ip_to_table 10.0.1.2) default via 10.0.1.1 dev $(map_localip_to_interface 10.0.1.2)
ip route add table $(map_ip_to_table 10.0.1.4) 10.0.1.0/24 dev $(map_localip_to_interface 10.0.1.4)
ip route add table $(map_ip_to_table 10.0.1.4) default via 10.0.1.1 dev $(map_localip_to_interface 10.0.1.4)
ip route add table $(map_ip_to_table 192.168.1.3) 192.168.1.0/24 dev $(map_localip_to_interface 192.168.1.3)
ip route add table $(map_ip_to_table 192.168.1.3) default via 192.168.1.1 dev $(map_localip_to_interface 192.168.1.3)
sysctl -q -w net.ipv4.conf.eno1.arp_filter=1
sysctl -q -w net.ipv4.conf.eno2.arp_filter=1
这将导致:
# ip rule
0: from all lookup local
32763: from 192.168.1.3 lookup 3232235779
32764: from 10.0.1.4 lookup 167772420
32765: from 10.0.1.2 lookup 167772418
32766: from all lookup main
32767: from all lookup default
# ip route show table all |grep 'table [1-9]'
default via 10.0.1.1 dev eno1 table 167772418
10.0.1.0/24 dev eno1 table 167772418 scope link
default via 192.168.1.1 dev eno2 table 3232235779
192.168.1.0/24 dev eno2 table 3232235779 scope link
default via 10.0.1.1 dev eno2 table 167772420
10.0.1.0/24 dev eno2 table 167772420 scope link
# sysctl net.ipv4.conf.eno{1,2}.arp_filter
net.ipv4.conf.eno1.arp_filter = 1
net.ipv4.conf.eno2.arp_filter = 1
所有这些使得对于任何有问题的 IP,在使用它时,路由堆栈会切换到专用路由表,这将强制使用正确的网络接口。 ARP 也会遵循同样的原理。
请注意,相同的设置允许本地启动的传出 IP 正常工作。如果 192.168.1.100 是某个 Web 服务器:
ping -I 10.0.1.2 192.168.1.100 # through eno1 and via router
ping -I 10.0.1.4 192.168.1.100 # through eno2 and via router
ping -I 192.168.1.3 192.168.1.100 # through eno2 directly
同样,如果在 192.168.1.100 上使用 socat (1.7.3.2) 运行此命令:
socat tcp4-listen:5555,reuseaddr,fork exec:'printenv SOCAT_PEERADDR'
然后在多宿主主机上,您可以得到回复,告诉您一切顺利:
# socat tcp4:192.168.1.100:5555 -
192.168.1.3
# socat tcp4:192.168.1.100:5555,bind=10.0.1.2 -
10.0.1.2
# socat tcp4:192.168.1.100:5555,bind=10.0.1.4 -
10.0.1.4
您可以验证tcpdump
流量是否通过了预期的接口。
如果您在其他 IP 方面遇到更多路由问题,您现在应该知道该怎么办。
笔记:
- 我在这里留下了 10.12.1.3,但因为它已经打开了诺1现在已经有了
arp_filter=1
,它可能还应该接收自己的路由表和规则才能正常工作。 - 如果主机也进行路由,则可能需要改进规则和路由表。你必须进行测试。
样机设置:
以 root 身份运行以下脚本setup.sh
。现在这个问题可以这样重现:
术语1:
ip netns exec mhost tcpdump -l -e -n -s0 -p -i eno1
术语2:
ip netns exec mhost tcpdump -l -e -n -s0 -p -i eno2
第3项:
ip netns exec h1921681100 ping -n 10.0.1.2
不会有任何回复。至少这样做:
ip netns exec mhost sysctl -w net.ipv4.conf.eno1.rp_filter=2
将允许不对称路由,ping 来自诺1并离开至埃诺2(或全部通过埃诺2如果“ARP 通量”效应生效)。
运行上面的脚本fix.sh
将修复所有问题:
ip netns exec mhost sh fix.sh
setup.sh
:
#!/bin/sh
if ip netns id | grep -qv '^ *$' ; then
printf 'ERROR: leave netns "%s" first\n' $(ip netns id) >&2
exit 1
fi
hosts='mhost router h1921681100'
nets='net1001 net1921681 net10121 net172231'
for ns in $hosts $nets; do
ip netns del $ns 2>/dev/null || :
ip netns add $ns
ip netns exec $ns sysctl -q -w net.ipv6.conf.default.disable_ipv6=1
ip netns exec $ns sysctl -q -w net.ipv4.conf.default.rp_filter=1
ip netns exec $ns sysctl -q -w net.ipv4.conf.all.rp_filter=1
done
for ns in $hosts; do
ip -n $ns link set lo up
done
bmac=1
for ns in $nets; do
ip -n $ns link add bridge0 address 02:00:00:00:00:$(printf '%02d' $bmac) type bridge
ip -n $ns link set bridge0 up
bmac=$(($bmac+1))
done
link_lan () {
[ "$1" = "$2" ] && return 1
ip -n $2 link add p-$1 type veth peer netns $1 name p-$2 2>/dev/null || return 1
ip -n $2 link set p-$1 master bridge0 up
ip -n $1 link set p-$2 master bridge0 up
}
link_lan net1001 net1921681
ip -n h1921681100 link add eth0 type veth peer netns net1921681 name p-h1921681100
ip -n h1921681100 link set eth0 up
ip -n net1921681 link set p-h1921681100 master bridge0 up
ip -n h1921681100 address add 192.168.1.100/24 broadcast 192.168.1.255 dev eth0
ip -n h1921681100 route add default via 192.168.1.1
ip netns exec router sysctl -q -w net.ipv4.conf.default.forwarding=1
ip -n router link add eth10011 type veth peer netns net1001 name p-router10011
ip -n router link set eth10011 up
ip -n net1001 link set p-router10011 master bridge0 up
ip -n router address add 10.0.1.1/24 broadcast 10.0.1.255 dev eth10011
ip -n router link add eth1921681 type veth peer netns net1921681 name p-router1921681
ip -n router link set eth1921681 up
ip -n net1921681 link set p-router1921681 master bridge0 up
ip -n router address add 192.168.1.1/24 broadcast 192.168.1.255 dev eth1921681
ip netns exec mhost sysctl -q -w net.ipv4.conf.default.forwarding=0
ip -n mhost link add eno1 type veth peer netns net1001 name p-mhosteno1
ip -n mhost link set eno1 up
ip -n net1001 link set p-mhosteno1 master bridge0 up
ip -n mhost address add 10.0.1.2/24 broadcast 10.0.1.255 dev eno1
ip -n mhost address add 10.12.1.3/24 broadcast 10.12.1.255 dev eno1
ip -n mhost link add eno2 type veth peer netns net1921681 name p-mhosteno2
ip -n mhost link set eno2 up
ip -n net1921681 link set p-mhosteno2 master bridge0 up
ip -n mhost address add 10.0.1.4/24 broadcast 10.0.1.255 dev eno2
ip -n mhost address add 192.168.1.3/24 broadcast 192.168.1.255 dev eno2
ip -n mhost link add eno3 type veth peer netns net172231 name p-mhosteno3
ip -n mhost link set eno3 up
ip -n net172231 link set p-mhosteno3 master bridge0 up
ip -n mhost address add 172.23.1.2/24 broadcast 172.23.1.255 dev eno3