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

发送巨型以太网UDP帧出现Error 105,如何解决该问题?

解决Linux下RAW Socket发送大UDP包出现Error 105的问题

问题描述

在Linux系统下使用RAW socket发送8192字节的UDP数据包时,仅能成功发送少量包,最终触发Error 105;但将包大小改为1518字节时可持续传输。需求是仅使用UDP传输,不切换到TCP。

原代码

/* Note: run this program as root user */

struct ifreq ifreq_c, ifreq_i, ifreq_ip;
int sock_raw;
unsigned char *sendbuff;


 #define DESTMAC0 0x00
 #define DESTMAC1 0x0c
 #define DESTMAC2 0x29
 #define DESTMAC3 0xb7
 #define DESTMAC4 0x20
 #define DESTMAC5 0x57

 #define destination_ip 192.168.95.175

int total_len = 0, send_len;

void get_eth_index()
{
    memset(&ifreq_i, 0, sizeof(ifreq_i));
    strncpy(ifreq_i.ifr_name, "ens33",IFNAMSIZ-1);
    if ((ioctl(sock_raw, SIOCGIFINDEX, &ifreq_i)) < 0)
        printf("error in index ioctl reading");
    printf("index=%d\n", ifreq_i.ifr_ifindex);
}

void get_mac()
{
    memset(&ifreq_c, 0, sizeof(ifreq_c));
    strncpy(ifreq_c.ifr_name, "ens33",IFNAMSIZ-1);

    if ((ioctl(sock_raw, SIOCGIFHWADDR, &ifreq_c)) < 0)
       printf("error in SIOCGIFHWADDR ioctl reading");

    printf("Mac= %.2X-%.2X-%.2X-%.2X-%.2X-%.2X\n", (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[0]), (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[1]), (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[2]), (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[3]), (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[4]), (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[5]));

    printf("ethernet packaging start ... \n");

    struct ethhdr *eth = (struct ethhdr *)(sendbuff);
    eth->h_source[0] = (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[0]);
    eth->h_source[1] = (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[1]);
    eth->h_source[2] = (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[2]);
    eth->h_source[3] = (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[3]);
    eth->h_source[4] = (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[4]);
    eth->h_source[5] = (unsigned char)(ifreq_c.ifr_hwaddr.sa_data[5]);

    eth->h_dest[0]    =  DESTMAC0;
    eth->h_dest[1]    =  DESTMAC1;
    eth->h_dest[2]    =  DESTMAC2;
    eth->h_dest[3]    =  DESTMAC3;
    eth->h_dest[4]    =  DESTMAC4;
    eth->h_dest[5]    =  DESTMAC5;

    eth->h_proto = htons(ETH_P_IP);  

    printf("ethernet packaging done.\n");

    total_len += sizeof(struct ethhdr);
}

void get_data()
{
    printf("\n total length in get data  %d", total_len);
    sendbuff[total_len++] = 0xAA;
    sendbuff[total_len++] = 0xBB;
    sendbuff[total_len++] = 0xCC;
    sendbuff[total_len++] = 0xDD;
    sendbuff[total_len++] = 0xEE;

    printf("\n total length in get data  %d", total_len);

    while (total_len < 8186)
        sendbuff[total_len++]= 0xAA;

    for (int i = 0; i < total_len; i++)
        printf ("\t %d ", sendbuff[i]);
}

void get_udp()
{
    struct udphdr *uh = (struct udphdr *)(sendbuff + sizeof(struct iphdr) + sizeof(struct ethhdr));

    uh->source = htons(23451);
    uh->dest = htons(23452);
    uh->check = 0;

    total_len += sizeof(struct udphdr);
    printf("\n total length %d :    ", total_len);
    get_data();
    int length = (total_len - sizeof(struct iphdr) - sizeof(struct ethhdr));
    printf(" \n udp size length %d ", length);
    uh->len = (length);
    printf("\n udp data length : %d", uh->len);
}

void get_ip()
{
    memset(&ifreq_ip, 0, sizeof(ifreq_ip));
    strncpy(ifreq_ip.ifr_name, "ens33", IFNAMSIZ-1);
    if (ioctl(sock_raw, SIOCGIFADDR, &ifreq_ip) < 0)
    {
        printf("error in SIOCGIFADDR \n");
    }

    printf(" IP  address  %s \n", inet_ntoa((((struct sockaddr_in*)&(ifreq_ip.ifr_addr))->sin_addr)));

    struct iphdr *iph = (struct iphdr*)(sendbuff + sizeof(struct ethhdr));
    iph->ihl = 5;
    iph->version = 4;
    iph->tos = 16;
    iph->id = 2048;
    iph->ttl = 64;
    iph->protocol = 17;
    iph->saddr = inet_addr(inet_ntoa((((struct sockaddr_in *)&(ifreq_ip.ifr_addr))->sin_addr)));
    iph->daddr = inet_addr("destination_ip"); // put destination IP address
    total_len += sizeof(struct iphdr);
    printf("\n get udp");
    get_udp();
    iph->tot_len = (total_len - sizeof(struct ethhdr));
    printf("\n IP data length : %d", iph->tot_len);
    iph->check = 0;
}

int main()
{
    sock_raw = socket(PF_INET, SOCK_RAW, IPPROTO_UDP);
    if (sock_raw == -1)
    {
        printf("error in socket");
        return -1;
    }

    sendbuff = (unsigned char*)malloc(8192); // increase in case of large data.Here data is --> AA  BB  CC  DD  EE
    memset(sendbuff, 0, 8192);

    get_eth_index();  // interface number
    get_mac();
    printf("\n get IP");
    get_ip();
    printf("\n create  socket address");
    struct sockaddr_ll sadr_ll;
    sadr_ll.sll_ifindex = ifreq_i.ifr_ifindex;
    sadr_ll.sll_family = AF_INET;
    sadr_ll.sll_halen   = ETH_ALEN;
    sadr_ll.sll_addr[0]  = DESTMAC0;
    sadr_ll.sll_addr[1]  = DESTMAC1;
    sadr_ll.sll_addr[2]  = DESTMAC2;
    sadr_ll.sll_addr[3]  = DESTMAC3;
    sadr_ll.sll_addr[4]  = DESTMAC4;
    sadr_ll.sll_addr[5]  = DESTMAC5;

    int enable = 1;
    if (setsockopt(sock_raw, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
       error("setsockopt(SO_REUSEADDR) failed");

    printf("sending...\n");
    while (1)
    {
        send_len = sendto(sock_raw, sendbuff, total_len, 0, (const struct sockaddr*)&sadr_ll, sizeof(struct sockaddr_ll));
        if (send_len < 0)
        {
            sleep(1);
            printf("error in sending....sendlen=%d....errno=%d\n", send_len, errno);
            return -1;
        }
    }
}

问题根源

Error 105对应ENETDOWN,但实际触发原因是:

  • MTU限制:以太网默认MTU为1500字节(IP负载),8192字节的UDP包远超该值,必须分片传输。
  • RAW Socket类型不匹配:原代码使用PF_INET的RAW socket(网络层),但却用sockaddr_ll(链路层地址)发送,混合模式导致内核处理大报文时异常。
  • 协议头错误:IP头的分片标志、长度字段未按网络字节序设置,UDP长度未做字节序转换,目的IP引用错误。

修复方案

1. 切换到链路层RAW Socket

创建PF_PACKET类型的RAW socket,适配链路层发送:

sock_raw = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));

2. 配置IP分片允许

在IP头中设置分片标志,允许内核处理分片:

struct iphdr *iph = (struct iphdr*)(sendbuff + sizeof(struct ethhdr));
iph->frag_off = htons(0x0000); // 清除DF位,允许分片
iph->id = htons(2048); // ID字段需转为网络字节序

同时关闭PMTU发现(可选,确保分片不受路径MTU限制):

int val = 0;
setsockopt(sock_raw, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));

3. 调整发送缓冲区大小

增大发送缓冲区,避免大报文溢出:

int sndbuf_size = 16384; // 设置为报文大小的2倍
setsockopt(sock_raw, SOL_SOCKET, SO_SNDBUF, &sndbuf_size, sizeof(sndbuf_size));

4. 修正协议头错误

  • UDP长度字段转为网络字节序:
    uh->len = htons(length);
    
  • 修复目的IP的引用错误:
    iph->daddr = inet_addr("192.168.95.175");
    
  • IP总长度字段转为网络字节序:
    iph->tot_len = htons(total_len - sizeof(struct ethhdr));
    
  • 计算IP校验和(RAW socket需手动计算):
    添加校验和函数并调用:
    unsigned short checksum(unsigned short *ptr, int nbytes)
    {
        long sum;
        unsigned short oddbyte;
        unsigned short answer;
    
        sum = 0;
        while (nbytes > 1) {
            sum += *ptr++;
            nbytes -= 2;
        }
        if (nbytes == 1) {
            oddbyte = 0;
            *((unsigned char *)&oddbyte) = *(unsigned char *)ptr;
            sum += oddbyte;
        }
    
        sum = (sum >> 16) + (sum & 0xffff);
        sum += (sum >> 16);
        answer = ~sum;
        return answer;
    }
    
    // 在get_ip中调用
    iph->check = checksum((unsigned short *)iph, iph->ihl*4);
    

5. 修正sockaddr_ll配置

将sll_family改为AF_PACKET,和PF_PACKET socket匹配:

struct sockaddr_ll sadr_ll;
memset(&sadr_ll, 0, sizeof(sadr_ll));
sadr_ll.sll_ifindex = ifreq_i.ifr_ifindex;
sadr_ll.sll_family = AF_PACKET;
sadr_ll.sll_halen = ETH_ALEN;
memcpy(sadr_ll.sll_addr, (unsigned char[]){DESTMAC0, DESTMAC1, DESTMAC2, DESTMAC3, DESTMAC4, DESTMAC5}, ETH_ALEN);

验证

修复后,大UDP包会被自动分片传输,避免触发ENETDOWN错误。同时确保接收方支持IP分片(默认是开启的)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 02:15:26