![两个网卡上的同一 IP 一半的流量丢失](https://linux22.com/image/748493/%E4%B8%A4%E4%B8%AA%E7%BD%91%E5%8D%A1%E4%B8%8A%E7%9A%84%E5%90%8C%E4%B8%80%20IP%20%E4%B8%80%E5%8D%8A%E7%9A%84%E6%B5%81%E9%87%8F%E4%B8%A2%E5%A4%B1.png)
我有一台机器连接到一台稍微损坏的科学仪器上,我正在尝试解决这些问题。该链接是点对点的,2x25G 以太网,不涉及其他设备。该设备以每个接口一个流的方式传输 UDP 数据,我可以配置目标 IP 和 MAC 地址,但该设备的源 IP 地址对于两个接口都是相同的。使用 tcpdump 我可以看到流量到达正确的接口,并且两个接口的目标 IP 和 MAC 地址都是正确的。
但我只能监听其中一个 IP 并获取数据,如果我监听另一个 IP,则没有流量。内核显然会丢弃流量,但我不知道原因,也不知道如何调试。
该机器运行的是 ubuntu 18.04,IP 为 10.50.5.1/16 和 10.50.5.129/16,设备的 IP 为 10.50.1.10。路由:
10.50.0.0/16 dev data0 proto kernel scope link src 10.50.5.1
10.50.0.0/16 dev data1 proto kernel scope link src 10.50.5.129
有什么建议么?
我可以在 netstat 中看到数据包没有被丢弃,那么它们去了哪里?
Kernel Interface table
Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
data0 9600 202320 0 0 0 29 0 0 0 BMRU
data1 9600 203600 0 0 0 31 0 0 0 BMRU
答案1
由于尝试使用两个不同的接口和路由到达同一目的地,因此会出现路由问题。默认情况下,路由是按目的地进行的,而不是按源进行的。默认情况下,Linux 使用弱宿主模型(但 Ubuntu 还设置了反向路径过滤器,见下文)将选择相同的接口:在列表中首先出现的同等候选接口。因此,此处源 IP 地址 10.50.5.1 和 10.50.5.129 都将使用数据0到达 10.50.1.10,因为它目前是路由条目中两个相等条目中的第一个。
否则需要基于源的路由(又名策略路由) 在 Linux 上:让源也用于确定到目的地的路由。
出于类似的原因,在您当前的配置中:在同一个 IP LAN (10.50.0.0/16) 中有两个 IP 地址,Linux 将使用其任何 MAC 地址从其任何接口回答 LAN 中的 ARP 查询。最后,除非像在您的设置中使用永久 ARP 条目而不是执行请求的设备一样,否则通常只有一个接口可以接收所有传入流量(它甚至可能是“错误的”接口)。
最糟糕的是,即使仪器发送到正确的接口和正确的 IP 地址目的地,Ubuntu 的默认设置是严格反向路径启用,丢弃在“错误”接口上收到的数据包。这就是你丢失数据的原因:接口(尝试现代命令ip -statistics link
)收到了它们,但路由堆栈之后就丢弃了它们。要验证这一点,只需询问内核的路由堆栈如何看待发送和接收 IP 数据包:
发送
# ip route get from 10.50.5.1 10.50.1.10 10.50.1.10 from 10.50.5.1 dev data0 uid 0 cache # ip route get from 10.50.5.129 10.50.1.10 10.50.1.10 from 10.50.5.129 dev data0 uid 0 cache
两者都将使用数据0,忽略数据1。
接收
# ip route get from 10.50.1.10 iif data0 10.50.5.1 local 10.50.5.1 from 10.50.1.10 dev lo table local cache <local> iif data0 # ip route get from 10.50.1.10 iif data1 10.50.5.129 RTNETLINK answers: Invalid cross-device link
接收数据1被严格反向路径过滤器禁止。
您可以选择禁用反向路径,但如果您需要在特定接口上发送,那么这无论如何都无济于事。
下面我提出一种更清洁的方法。
首先,为了避免与 ARP 设置混淆(以及“ARP 通量”考虑到这些因素,请考虑这两个接口的用途:点对点 IP 接口,即使在以太网链路层上使用也是如此。因此,无需使用整个 /16 LAN:只需点对点路由即可。
# ip address flush dev data0
# ip address flush dev data1
# ip address add 10.50.5.1 peer 10.50.1.10/32 dev data0
# ip address add 10.50.5.129 peer 10.50.1.10/32 dev data1
上面两个命令大多是 的快捷方式ip address add 10.50.5.N/32 dev dataX; ip route add 10.50.1.10/32 dev dataX
。
您可以选择保留 /16 网络设置(例如:如果最终它们用于实际 LAN 使用,后面还有一堆设备),但这也需要切换arp_filter=1
在接口上以确保安全。
要解决基于源的路由问题:使用指向附加路由表的规则,这些路由表将具有主路由表中部分路由的部分副本。我为这些路由表选择了任意值 1000 和 1001(并使用任意固定规则优先级 10000 10001,即使不需要)。关于表 1000 的所有内容实际上都不是必需的(因为它是默认的),但它无论如何都更干净。
ip route add table 1000 10.50.1.10/32 dev data0 src 10.50.5.1
ip route add table 1001 10.50.1.10/32 dev data1 src 10.50.5.129
ip rule add pref 10000 from 10.50.5.1 lookup 1000
ip rule add pref 10001 from 10.50.5.129 lookup 1001
主表仍将确定默认使用哪个接口(可能数据0): 任何未绑定到特定 IP 地址的工具都将使用此 IP 地址(因此 10.50.5.1)作为默认值,如果绑定到 10.50.5.1 也是如此。但现在任何绑定到 10.50.5.129 的工具都将正确使用双向流量数据1。
# ip route get 10.50.1.10
10.50.1.10 dev data0 src 10.50.5.1 uid 0
cache
# ip route get from 10.50.5.1 10.50.1.10
10.50.1.10 from 10.50.5.1 dev data0 table 1000 uid 0
cache
# ip route get from 10.50.5.129 10.50.1.10
10.50.1.10 from 10.50.5.129 dev data1 table 1001 uid 0
cache
现在,传入的数据数据1不会被反向路径过滤器丢弃:
# ip route get from 10.50.1.10 iif data0 10.50.5.1
local 10.50.5.1 from 10.50.1.10 dev lo table local
cache <local> iif data0
# ip route get from 10.50.1.10 iif data1 10.50.5.129
local 10.50.5.129 from 10.50.1.10 dev lo table local
cache <local> iif data1
笔记:
附加路由表是手动填充的,而不是由内核填充的。如果关闭某个接口(或删除某个 IP),然后将其恢复(或重新添加 IP),则相应的表 1000 或 1001 将被刷新,并且不会重新填充:必须手动将其添加回去(或使用能够执行此操作的网络配置工具)。