使用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
相关产品推荐
相关产品推荐

