我有一个“理解”问题,但它确实有一个我已经简化的真实案例。考虑这个示例网络(使用简化的 IP): 从左到右:
- 三台主机 S1-S3 (IPs),分别运行三个程序 R1-R3;一台有 3 个 VLAN 的交换机和一个连接到第 4 台主机 S4 的 eth0 的 trunk 端口。所有这些都是 Ubuntu 20.04 LTS,但这可能无关紧要。
- S4 正在运行 P1,它绑定到三个 UDP 套接字,分别位于 S1、2、3 各自网络上的三个 VLAN 接口上
- P1以 10-100Hz 的速率向每个套接字上的多个端口发送单播和
OUTPUT
多播 UDP 消息。有一个 netfilter链控制这些流。
每个红色区域代表可能出现“故障”的情况。除一种情况外,所有其他情况下,UDP 数据报都会被立即丢弃:
- 虚拟接口
V10
链路断开;进程R2
未运行;或OUTPUT
链式DROP
传输 - 在每种情况下,数据包要么被 S2 默默丢弃,要么向 P1 报告错误并丢弃数据包。所有其他组播继续传输。 - 对于 S3 的情况,移除阻止后
OUTPUT
,tx
套接字的缓冲区v12
会迅速被发往的单播数据包填满12.1
,从而阻止任何进一步的单播和多播写入。
在此状态下,strace
onP1
可能看起来像这样(修剪后):
15:38:27 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:27 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:27 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:27 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = -1 EAGAIN (Resource temporarily unavailable)
15:38:30 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:30 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:30 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:30 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = -1 EAGAIN (Resource temporarily unavailable)
并ss
显示问题:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
UNCONN 0 148992 0.0.0.0:51047 0.0.0.0:* users:(("task",pid=381888,fd=9))
skmem:(r0,rb212992,t148992,tb131070,f0,w0,o0,bl0,d0)
因此,我们向套接字缓冲区 (默认 120kb) 写入了 4 * ~30kb,并EAGAIN
在缓冲区已满时报告第 4 次写入。缓冲区不断被填满,而操作系统却在等待永远不会到来的ARP
响应。S3
以上这些背后有两个问题:
- UDP 本质上是不可靠的。我们的应用程序很高兴看到数据包被丢弃,那么为什么
tx
在内核尝试解析 ARP 时会以这种方式排队呢?(考虑一下 S3 可能只是偶尔连接但其他主机可能仍然可以访问的情况v12
。) - 缓冲区似乎每 3 秒清空一次(然后迅速被新写入填充)。结果之一是,进入同一
tx
队列的任何多播都会以小爆发的方式发出,而不是以稳定的发送速率发出。当然,我们可以打开更多发送套接字,但这个值在哪里设置,这种行为是否可以调整sysctl
?
您的应用程序可能如此,但大多数其他应用程序在数据包被丢弃时并不高兴。它们通常有超时和重新传输,但这些通常比 ARP 查询成功所需的时间长得多。
例如,如果内核因为必须向网关发出 ARP 查询而丢弃 TCP SYN(而不是将其排队),则重新传输将需要 3 秒,而 ARP 响应则需要约 0.003 秒。基于 UDP 的 DNS 查询和大多数其他协议也是如此。(更不用说那些没有重新传输的协议了——UDP 上的 Syslog 没有,如果消息发送频率不够高,无法保持 ARP 缓存条目处于活动状态,那么大多数消息都会丢失。)
net.ipv4.neigh.*.unres_qlen_bytes
似乎是 sysctl(以及已弃用的.unres_qlen
)控制可以为未解析的邻居排队多少数据。相邻的
.mcast_solicit
×.retrans_time_ms
sysctls 控制在邻居条目进入“FAILED”状态并且其所有排队数据包被丢弃之前,将重试多播请求或广播 ARP 查询的次数(以及间隔)。(一旦发生这种情况,下一个数据包将触发一系列新的邻居发现尝试,同时新的数据包再次排队,从而导致默认设置下的 3 秒周期。)
如果您尝试在 Linux 源代码中找到它,请 grep并专门在 中
QUEUE_LEN_BYTES
查找。似乎队列附加到您在 中看到的相同邻居条目,尽管它没有显示每个邻居队列大小。__neigh_event_send()
net/core/neighbour.c
ip [-s] neigh