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

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是更合适的选择。

四、测试注意事项

  1. 确保以root用户运行程序(raw socket需要管理员权限)
  2. Wireshark要捕获你发送数据包对应的网卡(比如本地测试选lo网卡,外部测试选物理网卡如eth0)
  3. 先测试发送到本地地址(127.0.0.1),更容易排查问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:44:38