You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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发现机制的异步性导致了该矛盾现象:

  1. 初始阶段系统使用本地MTU(2120),发送大于1500字节的包时,内核会触发PMTU探测
  2. 收到ICMP“需要分片但DF位已置”的消息后,内核会异步更新该socket的PMTU为1500,但这个更新不会立即同步到用户态
  3. 后续发送小数据包时,若内核未完成PMTU状态同步,或存在探测缓存竞态,会出现用户态获取到的PMTU为1500,但内核处理时仍处于状态过渡,进而误判数据包大小(实际总长度=payload+IP头20字节+UDP头8字节,143字节payload总长度仅171字节,远小于1500)

另外,K8s Pod的CNI网络栈对ICMP包的处理存在延迟或丢包,导致PMTU状态更新不及时,这也是仅在Pod环境出现问题的关键原因——节点网络的ICMP处理更直接,状态同步更快。

解决方法

  1. 关闭PMTU发现,允许内核分片:如果不需要路径MTU发现,可关闭DF位,让内核自动处理分片:

    int df = 0;
    setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &df, sizeof(df));
    

    注:IP_MTU_DISCOVER取值IP_PMTUDISC_DONT表示关闭PMTU发现,允许分片。

  2. 主动限制payload大小:计算最大允许payload为PMTU - 20(IP头) -8(UDP头),即1500-28=1472字节,确保发送的payload不超过该值,避免触发PMTU探测竞态。

  3. 捕获错误并重发:捕获到EMSGSIZE错误时,短暂延迟后重发数据包,此时内核通常已完成PMTU状态更新,重发大概率成功。


内容的提问来源于stack exchange,提问作者nzs666

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 12:10:58