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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 07:35:54