我在我的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))
; fork()
,打开与远程服务器的连接并处理请求; GET
请求,则向服务器发送原始请求,当服务器正在发送数据时,将数据从服务器发送到客户端; CONNECT
请求,则将字符串200 ok
发送到客户端,并使用select()
轮询客户端套接字描述符和服务器套接字描述符;如果我从服务器套接字读取数据,请将此数据发送给客户端;否则,如果我从客户端套接字读取数据,请将此数据发送到服务器。好处是这个代理工作,坏的是现在我必须收集统计数据;这很糟糕,因为我工作的水平我无法获得我感兴趣的数据。我不关心有效载荷,我只需要检查IP和TCP标头我关心的标志。
例如,我对:
感兴趣首先,我会在TCP标头中检查SYN标志,SYN / ACK然后是最后一个ACK;至于第二个,当我char buffer[1500]
或send()
一个完整的数据包时,每当recv()
填充数据时,我只会向我的一个计数器+1。
我意识到这是不对的:SOCK_STREAM
没有包的概念,它只是一个连续的字节流!我在第7和第8点使用的char buffer[1500]
具有有用的统计数据,我可以将其容量设置为4096字节,但我无法跟踪发送或接收的TCP数据包,因为TCP已经段,而非数据包。
我无法解析char buffer[]
在TCP标头中寻找SYN标志,因为IP和TCP标头从标题中被剥离(因为I' m工作,指定为IPPROTO_TCP
标志)如果我理解的话,char buffer[]
只包含有效载荷,对我来说没用。
所以,如果我的工作水平太高,我应该更低一些:一旦我看到一个简单的raw
套接字嗅探器,其中unsigned char buffer[65535]
被强制转换为struct ethhdr, iphdt, tcphdr
并且它可以看到所有 所有标题的标志,我感兴趣的所有统计数据!
欢乐之后,令人失望的是:由于raw
套接字在低级别上工作,他们没有一些对我的代理人至关重要的概念; raw
个套接字不能bind
,listen
和accept
;我的代理正在侦听固定端口,但是raw
套接字不知道端口是什么,它属于TCP级别,而是bind
到setsockopt
的指定接口
所以,如果我socket(PF_INET, SOCK_RAW, ntohs(ETH_P_ALL))
我应该能够在{7}和.8处解析我recv()
和send()
的缓冲区,但我应该使用{{ 1}}和recvfrom()
...但所有这些听起来都很混乱,它包含了对我的代码进行了很好的重构。
如何保持代理结构(sendto()
到固定端口和接口)的完整性并增加我对IP和TCP标头的视线?
答案 0 :(得分:2)
我的建议是在例如应用程序的另一个线程中打开一个原始套接字。嗅探所有流量并按地址和端口号过滤掉相关数据包。基本上你想要实现自己的数据包嗅探器:
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连接的本地和远程地址和端口号。您需要按这些过滤数据包。