如何在我的 HTTP 代理中查看 TCP、IP headers?

How to see TCP, IP headers in my HTTP proxy?

我在 Ubuntu 14.04 x86_64 上使用以下方案实现了一个分叉 HTTP 代理(我报告的基本代码和伪代码只是为了展示这个概念):

  1. socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  2. bind(socketClient,(struct sockaddr*)&addr, sizeof(addr));
  3. listen(socketClient, 50);
  4. newSocket = accept(socketClient, (struct sockaddr*)&cliAddr, sizeof(cliAddr));
  5. 从客户端获取请求,解析它以解析 IP 地址中请求的主机名;
  6. fork(),打开到远程服务器的连接并处理请求;
  7. child流程:如果是GET请求,将原始请求发送到服务器,在服务器发送数据的同时,将数据从服务器发送到客户端;
  8. child 进程:否则,如果它是一个 CONNECT 请求,则将字符串 200 ok 发送到客户端,并使用 select() 轮询客户端套接字描述符和服务器套接字描述符;如果我从服务器套接字读取数据,将此数据发送给客户端;否则,如果我从客户端套接字读取数据,则将此数据发送到服务器。

好的是这个代理可以用,坏的是现在我必须收集统计信息;这很糟糕,因为我的工作水平无法获取我感兴趣的数据。我不关心负载,我只需要检查 IP 和 TCP headers我关心的标志。

例如,我感兴趣的是:

至于第一个,我会检查 TCP header SYN 标志,SYN/ACK 然后是最后一个 ACK​​;至于第二个,当我 send()recv() 一个完整的数据包时,每次 char buffer[1500] 充满数据时,我都会对我的计数器 +1。

我意识到这是不正确的:SOCK_STREAM没有包的概念,它只是一个连续的字节流!我在第 7 点和第 8 点使用的 char buffer[1500] 具有有用的统计信息,我可以将其容量设置为 4096 字节,但我无法跟踪发送或接收的 TCP 数据包,因为 TCP ,而不是数据包

我也无法解析 char buffer[] 在 TCP header 中寻找 SYN 标志,因为 IP 和 TCP headers 已从 header 中剥离(因为我正在处理的级别,用 IPPROTO_TCP 标志指定)并且,如果我理解得很好,char buffer[] 只包含有效载荷,对我来说没用。

因此,如果我的工作级别太高,我应该降低级别:有一次我看到一个简单的 raw 套接字嗅探器,其中 unsigned char buffer[65535] 被强制转换为 struct ethhdr, iphdt, tcphdr它可以看到 all all 和 header 的标志,所有我感兴趣的统计数据!

欣喜之后,是失望:由于 raw 套接字在低级别上工作,因此它们没有一些对我的代理至关重要的概念; raw 套接字不能 bindlistenaccept;我的代理正在侦听固定端口,但是 raw 套接字不知道端口是什么,它属于 TCP 级别,它们 bind 到指定接口 setsockopt.

所以,如果我 socket(PF_INET, SOCK_RAW, ntohs(ETH_P_ALL)) 我应该能够解析缓冲区 recv()send() 在 .7 和 .8,但我应该使用 recvfrom()sendto()...但是所有这些听起来很混乱,它包含对我的代码进行很好的重构。

如何保持我的代理结构完整(bind, listen, accept 固定端口和接口)并增加我对 IP 和 TCP headers 的视野?

我的建议是在应用程序的另一个线程中打开一个原始套接字。嗅探所有流量并按地址和端口号过滤掉相关数据包。基本上你想实现你自己的数据包嗅探器:

int sniff()
{
    int sockfd;
    int len;
    int saddr_size;
    struct sockaddr saddr;
    unsigned char buffer[65536];

    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
    if (sockfd < 0) {
        perror("socket");
        return -1;
    }
    while (1) {
        saddr_size = sizeof(saddr);
        len = recvfrom(sockfd, buffer, sizeof(buffer), 0, &saddr, &saddr_size);
        if (len < 0) {
            perror("recvfrom");
            close(sockfd);
            return -1;
        }

        // ... do the things you want to do with the packet received here ...
    }
    close(sockfd);
    return 0;
}

如果您知道哪个接口将用于代理的流量,您也可以将该原始套接字绑定到特定接口。例如,绑定到 "eth0":

setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 4);

使用 getpeername()getsockname() 函数调用查找您的 TCP 连接的本地和远程地址以及端口号。您将希望通过这些过滤数据包。