C语言中sendto()无法发送自定义UDP数据包的问题排查
看起来你遇到的核心问题是raw socket发送的UDP数据包被内核丢弃或未正确路由,同时编译警告也可能引发潜在的未定义行为。我们一步步拆解解决:
一、先解决编译警告(消除隐式声明风险)
你的代码缺少几个关键头文件,导致memset、exit、inet_addr等函数被隐式声明,这可能引发不可预测的运行错误。需要添加:
#include <stdlib.h> // 用于 exit() 函数 #include <string.h> // 用于 memset() 函数 #include <arpa/inet.h> // 用于 inet_addr()、htons() 等网络字节序转换函数
另外,你的csum函数里sum变量未声明,也要补上初始化:
unsigned short csum(unsigned short *buf, int nwords) { unsigned long sum = 0; // 初始化sum变量 for(; nwords>0; nwords--) sum += *buf++; sum = (sum >> 16) + (sum &0xffff); sum += (sum >> 16); return (unsigned short)(~sum); }
二、核心问题:数据包无法被捕获的原因
1. sendto() 目标地址参数完全搞反了
你调用sendto()时传入的是源地址结构体sin,但这个参数要求是目标地址结构体din!sendto的第五个参数是数据包要发送到的远端地址,搞反后内核不知道该把数据包发向哪里,自然无法被Wireshark捕获。
错误代码:
sendto(sd, buffer, ip->iph_len, 0, (struct sockaddr *)&sin, sizeof(sin))
正确代码:
sendto(sd, buffer, ntohs(ip->iph_len), 0, (struct sockaddr *)&din, sizeof(din))
2. IP头长度未转换为网络字节序
IP头中的iph_len字段要求是大端字节序,但你直接赋值了主机字节序的sizeof结果,这会导致内核或目标主机无法正确解析IP包长度,直接丢弃数据包。
修正:
ip->iph_len = htons(sizeof(struct ipheader) + sizeof(struct udpheader));
3. IP头结构体定义错误
你的struct ipheader中,iph_flag和iph_offset的拆分不符合IP头规范:IP头里的标志位(3位)和片偏移(13位)是一个16位的连续字段,你拆成两个独立变量会导致IP头结构对齐错误,内核无法解析这个包。同时iph_ihl的位数也错了(应该是4位,不是5位),版本和头部长度的位域顺序也搞反了。
修正后的IP头结构体:
struct ipheader { unsigned char iph_ver:4, iph_ihl:4; // 高4位是版本,低4位是头部长度 unsigned char iph_tos; unsigned short int iph_len; unsigned short int iph_ident; unsigned short int iph_flag_offset; // 合并标志和片偏移为16位字段 unsigned char iph_ttl; unsigned char iph_protocol; unsigned short int iph_chksum; unsigned int iph_sourceip; unsigned int iph_destip; };
可以给标志位设置合理值,比如禁止分片:
ip->iph_flag_offset = htons(0x4000); // 0x4000表示DF(不分片)标志位
4. UDP校验和未正确处理
UDP校验和计算需要包含IP伪头部(源IP、目标IP、协议类型、UDP长度),你当前的csum函数只计算了IP头+UDP头,会导致校验和错误。如果暂时不需要校验和,可以直接把UDP校验和设为0(UDP允许无校验和):
udp->udph_chksum = 0;
三、关于是否改用write()
不需要改用write(),sendto()更适合raw socket场景——它可以直接指定目标地址,而write()通常用于已经通过bind()或connect()绑定了固定目标的socket,raw socket一般不需要提前绑定,所以sendto是更合适的选择。
四、测试注意事项
- 确保以root用户运行程序(raw socket需要管理员权限)
- Wireshark要捕获你发送数据包对应的网卡(比如本地测试选lo网卡,外部测试选物理网卡如eth0)
- 先测试发送到本地地址(127.0.0.1),更容易排查问题
内容的提问来源于stack exchange,提问作者Whoami

