UDP发送遇EMSGSIZE错误:数据包未超PMTU/UDP上限问题
UDP发送出现EMSGSIZE异常的问题(K8s Pod环境)
代码片段
int fd = socket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP); int rc = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&a, 4); sockaddr_in *addr = new sockaddr_in; bzero(addr, sizeof(addr)); addr->sin_family=AF_INET; addr->sin_port=htons(4756); addr->sin_addr.s_addr=inet_addr("10.157.75.99"); rc = connect(fd, (struct sockaddr*)addr, 16); char buf[1600]; for (int i = 0; i < 1600; i++) buf[i] = 0; srand((unsigned)time( NULL ) ); for (int j = 0; j<100000; j++) { usleep(20000); int nn = rand()%1500; std::cout<<"the original bytes is "<<nn+100<<std::endl; rc = write(fd, (void*)buf, 100+nn); std::cout<<"sent bytes is"<<rc<<std::endl; if (rc < 0) { std::cout<<errno<<std::endl; if (errno == 90) //EMSGSIZE std::cout<<"message too long"<<std::endl; } int mtusize = 0; socklen_t len1 = sizeof(mtusize); int rc2 = getsockopt(fd, IPPROTO_IP,IP_MTU,&mtusize,&len1); if(rc2 == 0) { std::cout<<"get pmtu successful"<<std::endl; std::cout<<"pmtu size is: "<<mtusize<<std::endl; } ... }
问题现象
- 循环调用
write发送100000次UDP包,payload大小在100~1600字节之间,偶尔触发**EMSGSIZE(90)**错误(约4次) - 错误触发时,
write的count参数远小于获取到的PMTU(如示例中143字节,PMTU为1500),却返回消息过长;反之有时count超过1500字节,write却执行成功 - 仅在K8s Pod环境中出现该错误,节点上运行无问题(节点PMTU同样为1500)
错误输出示例:
the original bytes is 143 sent bytes is-1 90 message too long get pmtu successful pmtu size is: 1500
更新发现
PMTU并非始终固定为1500:
- 初始状态下PMTU为2120(本地MTU)
- 发送大于1500字节的数据包后,PMTU降至1500(路径MTU)
- 此后即使发送小于1500字节的包,也会触发EMSGSIZE错误
相关输出:
begin to write the original bytes is 545 get pmtu successful pmtu size is: 2120 get pmtu disc successful pmtu disc is: 1 begin to write the original bytes is 1565 get pmtu successful pmtu size is: 1500 get pmtu disc successful pmtu disc is: 1 begin to write the original bytes is 744 message too long get pmtu successful pmtu size is: 1500
问题分析与解决
核心原因
K8s Pod网络环境中,PMTU发现机制的异步性导致了该矛盾现象:
- 初始阶段系统使用本地MTU(2120),发送大于1500字节的包时,内核会触发PMTU探测
- 收到ICMP“需要分片但DF位已置”的消息后,内核会异步更新该socket的PMTU为1500,但这个更新不会立即同步到用户态
- 后续发送小数据包时,若内核未完成PMTU状态同步,或存在探测缓存竞态,会出现用户态获取到的PMTU为1500,但内核处理时仍处于状态过渡,进而误判数据包大小(实际总长度=payload+IP头20字节+UDP头8字节,143字节payload总长度仅171字节,远小于1500)
另外,K8s Pod的CNI网络栈对ICMP包的处理存在延迟或丢包,导致PMTU状态更新不及时,这也是仅在Pod环境出现问题的关键原因——节点网络的ICMP处理更直接,状态同步更快。
解决方法
关闭PMTU发现,允许内核分片:如果不需要路径MTU发现,可关闭DF位,让内核自动处理分片:
int df = 0; setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &df, sizeof(df));注:
IP_MTU_DISCOVER取值IP_PMTUDISC_DONT表示关闭PMTU发现,允许分片。主动限制payload大小:计算最大允许payload为
PMTU - 20(IP头) -8(UDP头),即1500-28=1472字节,确保发送的payload不超过该值,避免触发PMTU探测竞态。捕获错误并重发:捕获到EMSGSIZE错误时,短暂延迟后重发数据包,此时内核通常已完成PMTU状态更新,重发大概率成功。
内容的提问来源于stack exchange,提问作者nzs666
相关产品推荐
相关产品推荐

