发送巨型以太网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
相关产品推荐
相关产品推荐

