基于C Socket的代理服务器抓包转发:打印与连接问题求助
问题分析与修复方案
1. Buffer无法正常打印的问题
原代码直接用printf("%s", buffer)打印整个数据包是错误的——buffer包含二进制的IP/TCP协议头,其中存在大量非可打印字符或空字节,会导致printf提前截断输出,出现乱码或无内容。
修复方式:
- 跳过IP和TCP头,只打印HTTP负载部分;
- 若要调试整个数据包,使用十六进制打印方式避免乱码。
示例代码(打印HTTP负载):
// 计算IP头实际长度(ihl单位是4字节) int ip_header_len = ip_header->ihl * 4; struct tcphdr *tcp_header = (struct tcphdr*)(buffer + ip_header_len); // 计算TCP头实际长度(doff单位是4字节) int tcp_header_len = tcp_header->doff * 4; // HTTP负载起始位置 unsigned char *http_payload = buffer + ip_header_len + tcp_header_len; // 只打印可打印的HTTP内容 printf("HTTP Payload: "); for (int i = 0; i < data_size - ip_header_len - tcp_header_len; i++) { if (isprint(http_payload[i]) || http_payload[i] == '\r' || http_payload[i] == '\n') { putchar(http_payload[i]); } else { putchar('.'); } } printf("\n");
2. 目标服务器连接与响应的核心问题修复
原代码在转发逻辑上存在多个致命错误,以下是关键修复点:
2.1 端口提取错误
原代码提取目标端口的位置完全错误,TCP头的目标端口位于TCP头的第2-3字节,且需要转换为主机字节序:
// 修正端口提取逻辑 struct tcphdr *tcp_header = (struct tcphdr*)(buffer + ip_header_len); unsigned short dest_port = ntohs(tcp_header->dest); // 正确提取目标端口并转字节序
2.2 错误转发原始数据包到SOCK_STREAM socket
SOCK_STREAM是面向字节流的TCP socket,不需要手动处理IP/TCP头,应该只转发HTTP负载部分,而非整个原始数据包。
2.3 未处理read/write的返回值
read返回的是实际读取的字节数,不能用strlen(buffer)计算长度(响应可能包含二进制内容或空字节),必须使用返回的字节数进行转发。
2.4 资源泄漏与异常处理
- 每次循环创建的
hostSocket必须关闭,否则会耗尽文件描述符; - 黑名单拦截后不应直接终止程序,而是跳过当前包的转发即可;
- 用raw socket直接发送HTTP响应无效(需要构造完整IP/TCP头),改为在代理层面拦截后,通过正常TCP连接回发403响应(或使用iptables等更高效的方式拦截)。
3. 符合网络编程规范的实现调整
- 处理可变长度的协议头:IP头和TCP头的长度是可变的,必须通过
ihl和doff字段计算实际长度,不能直接用sizeof; - 使用安全的IP转换函数:用
inet_ntop代替手动拼接IP字符串,避免格式错误; - 权限要求:raw socket需要root权限运行,代码中应添加提示;
- 循环容错:遇到非致命错误(如连接失败)应continue,而非直接终止程序;
- 内存安全:确保在任何退出路径下释放malloc的buffer,避免内存泄漏。
修改后的完整代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <ctype.h> #define BUFFER_SIZE 65536 #define BLACKLISTED_IP "142.251.42.36" int main() { int raw_socket; struct sockaddr saddr; socklen_t saddr_len; unsigned char *buffer = (unsigned char *)malloc(BUFFER_SIZE); if (!buffer) { perror("malloc failed"); return 1; } // 创建raw socket(需要root权限) raw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); if (raw_socket < 0) { perror("Socket creation error (need root?)"); free(buffer); return 1; } printf("Sniffing packets...\n"); while (1) { saddr_len = sizeof(saddr); // 接收数据包 ssize_t data_size = recvfrom(raw_socket, buffer, BUFFER_SIZE, 0, &saddr, &saddr_len); if (data_size < 0) { perror("Packet receive error"); continue; // 继续循环,不终止程序 } // 提取IP头 struct iphdr *ip_header = (struct iphdr*)buffer; if (ip_header->version != 4) { continue; // 只处理IPv4包 } int ip_header_len = ip_header->ihl * 4; if (ip_header_len < sizeof(struct iphdr)) { continue; // 无效IP头,跳过 } // 转换IP地址为字符串(安全方式) char src_ip[INET_ADDRSTRLEN]; char dest_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &ip_header->saddr, src_ip, INET_ADDRSTRLEN); inet_ntop(AF_INET, &ip_header->daddr, dest_ip, INET_ADDRSTRLEN); printf("\nSource IP: %s\n", src_ip); printf("Destination IP: %s\n", dest_ip); // 检查黑名单 if (strcmp(dest_ip, BLACKLISTED_IP) == 0) { printf("Blocked blacklisted IP: %s\n", BLACKLISTED_IP); // 注意:用raw socket回发HTTP响应需要构造完整IP/TCP头,此处仅打印提示,实际拦截建议用iptables continue; } // 提取TCP头 struct tcphdr *tcp_header = (struct tcphdr*)(buffer + ip_header_len); int tcp_header_len = tcp_header->doff * 4; if (tcp_header_len < sizeof(struct tcphdr)) { continue; // 无效TCP头,跳过 } // 只处理HTTP请求(目标端口80) unsigned short dest_port = ntohs(tcp_header->dest); if (dest_port != 80) { continue; } // HTTP负载起始位置 unsigned char *http_payload = buffer + ip_header_len + tcp_header_len; int payload_len = data_size - ip_header_len - tcp_header_len; if (payload_len <= 0) { continue; // 无HTTP负载,跳过 } // 打印HTTP负载(调试用) printf("HTTP Request Payload:\n"); for (int i = 0; i < payload_len; i++) { if (isprint(http_payload[i]) || http_payload[i] == '\r' || http_payload[i] == '\n') { putchar(http_payload[i]); } else { putchar('.'); } } printf("\n"); // 转发到目标服务器 int hostSocket = socket(AF_INET, SOCK_STREAM, 0); if (hostSocket == -1) { perror("Error opening host socket"); continue; } struct sockaddr_in hostAddr; memset(&hostAddr, 0, sizeof(hostAddr)); hostAddr.sin_family = AF_INET; if (inet_pton(AF_INET, dest_ip, &hostAddr.sin_addr) != 1) { perror("Invalid destination IP"); close(hostSocket); continue; } hostAddr.sin_port = htons(dest_port); if (connect(hostSocket, (struct sockaddr *)&hostAddr, sizeof(hostAddr)) == -1) { perror("Connection to host server failed"); close(hostSocket); continue; } // 转发HTTP负载到目标服务器 if (write(hostSocket, http_payload, payload_len) == -1) { perror("Error writing to host server"); close(hostSocket); continue; } // 读取目标服务器响应 ssize_t resp_len = read(hostSocket, buffer, BUFFER_SIZE); if (resp_len <= 0) { perror("Error reading from host server"); close(hostSocket); continue; } printf("Received response from server (length: %zd bytes)\n", resp_len); // 注意:转发响应到客户端需要构造完整IP/TCP包,raw socket实现复杂,建议用正向代理模式而非嗅探转发 // 此处仅打印响应头部示例 printf("Response Header:\n"); for (int i = 0; i < resp_len; i++) { if (isprint(buffer[i]) || buffer[i] == '\r' || buffer[i] == '\n') { putchar(buffer[i]); } else { putchar('.'); } // 打印到空行结束 if (i >= 3 && buffer[i-3] == '\r' && buffer[i-2] == '\n' && buffer[i-1] == '\r' && buffer[i] == '\n') { break; } } printf("\n"); close(hostSocket); } // 实际循环不会到这里,仅为内存安全 close(raw_socket); free(buffer); return 0; }
额外说明
- 原始代码的“代理服务器”思路存在偏差:用raw socket嗅探并转发数据包的实现复杂度极高,需要处理TCP握手、序列号、重传等细节,建议改用正向代理模式(监听端口,接收客户端连接后转发到目标服务器),实现更简单且符合规范;
- 黑名单IP拦截更高效的方式是使用
iptables等系统工具,无需编写代码; - raw socket运行必须拥有root权限。
内容的提问来源于stack exchange,提问作者MT 16
相关产品推荐
相关产品推荐

