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

使用C语言sendto()发送自定义TCP数据包失败排查求助

问题排查与代码修复

首先,你的代码存在几个关键问题,导致数据包无法被正确发送或被网络栈丢弃,Wireshark自然抓不到。下面逐一分析并给出修复方案:


1. Sendto长度参数错误(最致命的问题)

你的代码中sendto使用了ip->iph_len作为发送长度,但ip->iph_len是通过htons()转换后的网络序值(比如实际40字节的头部会被转成0x2800,即十进制10240),而sendto的第三个参数需要的是主机序的字节数。这会导致内核尝试发送10240字节的缓冲区,而非实际的40字节有效头部,数据包直接被丢弃。

修复:使用主机序的实际长度:

int packet_total_len = sizeof(struct ipheader) + sizeof(struct tcpheader);
// 或者用ntohs(ip->iph_len)转回到主机序
if(sendto(sd, buffer, packet_total_len, 0, (struct sockaddr *)&din, sizeof(din)) < 0)

2. TCP头部结构定义错误

你定义的tcpheader中位域存在重复和顺序错误,导致TCP头部格式不符合标准,数据包被网络栈视为无效并丢弃。比如你重复定义了头部长度相关的位域,且标志位的位域顺序与TCP标准格式不匹配。

修复:重新定义TCP头部结构,用更清晰的方式处理标志位,避免位序问题:

#pragma pack(push, 1)
struct tcpheader {
    unsigned short int tcph_srcport;
    unsigned short int tcph_destport;
    unsigned int tcph_seqnum;
    unsigned int tcph_acknum;
    unsigned char tcph_offset:4, tcph_reserved:4; // TCP头部第13字节:4位头部长度,4位保留位
    unsigned char tcph_flags; // 单独字节存储标志位,避免位序混淆
    unsigned short int tcph_win;
    unsigned short int tcph_chksum;
    unsigned short int tcph_urgptr;
};
// TCP校验和需要的伪头部
struct tcp_pseudo_header {
    unsigned int source_ip;
    unsigned int dest_ip;
    unsigned char reserved;
    unsigned char protocol;
    unsigned short tcp_length;
};
#pragma pack(pop)

然后设置SYN标志位:

tcp->tcph_flags = 0x02; // SYN标志位(二进制00000010)

3. TCP校验和未正确计算(缺少伪头部)

TCP校验和的计算必须包含IP伪头部,而你的代码既没有构造伪头部,还错误地依赖内核计算校验和。当设置IP_HDRINCL后,内核不会帮你计算TCP校验和,必须手动计算。

修复:添加TCP校验和计算函数,并正确计算:

unsigned short tcp_checksum(struct ipheader *ip, struct tcpheader *tcp) {
    struct tcp_pseudo_header psh;
    char checksum_buffer[PCKT_LEN];
    int tcp_segment_len = ntohs(ip->iph_len) - sizeof(struct ipheader);

    // 填充伪头部
    psh.source_ip = ip->iph_sourceip;
    psh.dest_ip = ip->iph_destip;
    psh.reserved = 0;
    psh.protocol = IPPROTO_TCP;
    psh.tcp_length = htons(tcp_segment_len);

    // 拼接伪头部+TCP头部到缓冲区
    memset(checksum_buffer, 0, PCKT_LEN);
    memcpy(checksum_buffer, &psh, sizeof(psh));
    memcpy(checksum_buffer + sizeof(psh), tcp, tcp_segment_len);

    return csum((unsigned short *)checksum_buffer, sizeof(psh) + tcp_segment_len);
}

然后在代码中调用:

// 计算TCP校验和
tcp->tcph_chksum = 0; // 先清零
tcp->tcph_chksum = tcp_checksum(ip, tcp);

4. IP校验和计算优化

你的IP校验和计算虽然逻辑正确,但最好先清零校验和字段,再进行计算,确保结果准确:

// 先清零IP校验和字段
ip->iph_chksum = 0;
// 计算IP校验和(仅计算IP头部)
ip->iph_chksum = csum((unsigned short *)ip, sizeof(struct ipheader));

完整修复后的代码

// Run as root or SUID 0, just datagram no data/payload
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

// Packet length
#define PCKT_LEN 8192

#pragma pack(push, 1)
// IP header's structure
struct ipheader {
    unsigned char iph_ihl:4, 
    iph_ver:4;
    unsigned char iph_tos;
    unsigned short int iph_len;
    unsigned short int iph_ident;
    unsigned short int iph_offset;
    unsigned char iph_ttl;
    unsigned char iph_protocol;
    unsigned short int iph_chksum;
    unsigned int iph_sourceip;
    unsigned int iph_destip;
};

/* Structure of a TCP header */
struct tcpheader {
    unsigned short int tcph_srcport;
    unsigned short int tcph_destport;
    unsigned int tcph_seqnum;
    unsigned int tcph_acknum;
    unsigned char tcph_offset:4, tcph_reserved:4;
    unsigned char tcph_flags;
    unsigned short int tcph_win;
    unsigned short int tcph_chksum;
    unsigned short int tcph_urgptr;
};

// TCP伪头部,用于计算校验和
struct tcp_pseudo_header {
    unsigned int source_ip;
    unsigned int dest_ip;
    unsigned char reserved;
    unsigned char protocol;
    unsigned short tcp_length;
};
#pragma pack(pop)

// Simple checksum function
unsigned short csum(unsigned short *buf, int len) {
    unsigned long sum;
    for(sum=0; len>0; len--)
        sum += *buf++;
    sum = (sum >> 16) + (sum &0xffff);
    sum += (sum >> 16);
    return (unsigned short)(~sum);
}

// 计算TCP校验和(包含伪头部)
unsigned short tcp_checksum(struct ipheader *ip, struct tcpheader *tcp) {
    struct tcp_pseudo_header psh;
    char checksum_buffer[PCKT_LEN];
    int tcp_segment_len = ntohs(ip->iph_len) - sizeof(struct ipheader);

    // 填充伪头部
    psh.source_ip = ip->iph_sourceip;
    psh.dest_ip = ip->iph_destip;
    psh.reserved = 0;
    psh.protocol = IPPROTO_TCP;
    psh.tcp_length = htons(tcp_segment_len);

    // 拼接伪头部和TCP段
    memset(checksum_buffer, 0, PCKT_LEN);
    memcpy(checksum_buffer, &psh, sizeof(psh));
    memcpy(checksum_buffer + sizeof(psh), tcp, tcp_segment_len);

    return csum((unsigned short *)checksum_buffer, sizeof(psh) + tcp_segment_len);
}

int main(int argc, char *argv[]) {
    int sd;
    char buffer[PCKT_LEN];
    struct ipheader *ip = (struct ipheader *) buffer;
    struct tcpheader *tcp = (struct tcpheader *) (buffer + sizeof(struct ipheader));
    struct sockaddr_in sin, din;
    int one = 1;
    const int *val = &one;
    int packet_total_len = sizeof(struct ipheader) + sizeof(struct tcpheader);

    memset(buffer, 0, PCKT_LEN);

    if(argc != 5) {
        printf("- Invalid parameters!!!\n");
        printf("- Usage: %s <source hostname/IP> <source port> <target hostname/IP> <target port>\n", argv[0]);
        exit(-1);
    }

    sd = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);
    if(sd < 0) {
        perror("socket() error");
        exit(-1);
    }
    else
        printf("socket()-SOCK_RAW and tcp protocol is OK.\n");

    // 先设置IP_HDRINCL,告知内核我们自定义IP头部
    if(setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(one)) < 0) {
        perror("setsockopt() error");
        exit(-1);
    }
    else
        printf("setsockopt() is OK\n");

    // 填充地址信息
    sin.sin_family = AF_INET;
    din.sin_family = AF_INET;
    sin.sin_port = htons(atoi(argv[2]));
    din.sin_port = htons(atoi(argv[4]));
    sin.sin_addr.s_addr = inet_addr(argv[1]);
    din.sin_addr.s_addr = inet_addr(argv[3]);

    // 填充IP头部
    ip->iph_ihl = 5;
    ip->iph_ver = 4;
    ip->iph_tos = 16;
    ip->iph_len = htons(packet_total_len);
    ip->iph_ident = htons(54321);
    ip->iph_offset = 0;
    ip->iph_ttl = 64;
    ip->iph_protocol = IPPROTO_TCP;
    ip->iph_sourceip = inet_addr(argv[1]);
    ip->iph_destip = inet_addr(argv[3]);
    // 计算IP校验和
    ip->iph_chksum = 0;
    ip->iph_chksum = csum((unsigned short *)ip, sizeof(struct ipheader));

    // 填充TCP头部
    tcp->tcph_srcport = htons(atoi(argv[2]));
    tcp->tcph_destport = htons(atoi(argv[4]));
    tcp->tcph_seqnum = htonl(1);
    tcp->tcph_acknum = 0;
    tcp->tcph_offset = 5; // TCP头部长度:5*4=20字节
    tcp->tcph_flags = 0x02; // SYN标志位
    tcp->tcph_win = htons(32767);
    tcp->tcph_urgptr = 0;
    // 计算TCP校验和
    tcp->tcph_chksum = 0;
    tcp->tcph_chksum = tcp_checksum(ip, tcp);

    printf("Using:::::Source IP: %s port: %u, Destination IP: %s port: %u.\n", argv[1], atoi(argv[2]), argv[3], atoi(argv[4]));

    // 发送数据包
    unsigned int count;
    for(count = 0; count < 20; count++) {
        if(sendto(sd, buffer, packet_total_len, 0, (struct sockaddr *)&din, sizeof(din)) < 0) {
            perror("sendto() error");
            exit(-1);
        }
        else
            printf("Count #%u - sendto() is OK\n", count);
        sleep(2);
    }

    close(sd);
    return 0;
}

验证方法

编译修复后的代码,以root权限运行:

gcc rawtcp.c -o rawtcp
sudo ./rawtcp 192.168.1.152 1000 192.168.1.151 1000

此时用Wireshark过滤tcp.port == 1000,应该可以看到你发送的SYN数据包了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:53:22