如何在我的 HTTP 代理中查看 TCP、IP headers?
How to see TCP, IP headers in my HTTP proxy?
我在 Ubuntu 14.04 x86_64 上使用以下方案实现了一个分叉 HTTP 代理(我报告的基本代码和伪代码只是为了展示这个概念):
socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
;
bind(socketClient,(struct sockaddr*)&addr, sizeof(addr))
;
listen(socketClient, 50)
;
newSocket = accept(socketClient, (struct sockaddr*)&cliAddr, sizeof(cliAddr))
;
- 从客户端获取请求,解析它以解析 IP 地址中请求的主机名;
fork()
,打开到远程服务器的连接并处理请求;
- child流程:如果是
GET
请求,将原始请求发送到服务器,在服务器发送数据的同时,将数据从服务器发送到客户端;
- 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
套接字不能 bind
、listen
和 accept
;我的代理正在侦听固定端口,但是 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 连接的本地和远程地址以及端口号。您将希望通过这些过滤数据包。
我在 Ubuntu 14.04 x86_64 上使用以下方案实现了一个分叉 HTTP 代理(我报告的基本代码和伪代码只是为了展示这个概念):
socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
;bind(socketClient,(struct sockaddr*)&addr, sizeof(addr))
;listen(socketClient, 50)
;newSocket = accept(socketClient, (struct sockaddr*)&cliAddr, sizeof(cliAddr))
;- 从客户端获取请求,解析它以解析 IP 地址中请求的主机名;
fork()
,打开到远程服务器的连接并处理请求;- child流程:如果是
GET
请求,将原始请求发送到服务器,在服务器发送数据的同时,将数据从服务器发送到客户端; - 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
套接字不能 bind
、listen
和 accept
;我的代理正在侦听固定端口,但是 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 连接的本地和远程地址以及端口号。您将希望通过这些过滤数据包。