ProxyServer源代码分析目的:
阶段一:
* 了解HTTP ProxyServer使用的基本网络函数。
* 了解HTTP ProxyServer如何处理客户端的GET和POST请求。
* 了解HTTP ProxyServer在有两个网卡的情况下如何转发请求。
* 了解HTTP ProxyServer的Cache的基本原理。
阶段二:
* 了解Socks5 ProxyServer的基本工作模型与实现函数。
* 了解Socks5 ProxyServer是如何支持UDP的。
1.csproxy
过程分析:
分析了一下csproxy的源代码,这是一个中国人写的多线程Proxy,很简单。
* main.c是入口,直接调用start_server。
*
server.c中,start_server函数是一个主线程,先调用open_socket启动监听服务(注意open_socket只有一个输入参
数port,那IP如何指定的?跟踪到函数内部,发现IP是INADDR_ANY,即在所有网卡上监听)。遇到新请求启动新的处理线程
read_client_str。
*
thread.c中,read_client_str线程先是调用read_from_net(sock.c)获取客户端请求,然后调用
fenxi_http_string(http.c)分析客户端的请求(GET/POST/CONNECT),继而得到str,host,port和
path。然后调用open_connect(sock.c)连接到远程服务器,如成功连接,将得到远程服务器的SOCKET(fd_web)。再调用
write_from_net(sock.c),使用请求参数str向远程服务器提交请求。然后再调用web_server(web.c)。
*
web_server有两个参数:fd_s和fd_c。分别表示远程服务器的SOCKET和客户端的SOCKET。该函数通过循环调用
read_from_net(fd_s, &data)和write_from_net(fd_c, data,
read_len)将远程服务器返回的页面逐次返回给客户端,完成代理过程。
* 在代码中,并没有看到想像中的将数据包从第一块网卡转发到第二块网卡,从open_socket和open_connect来看,这种转换是自动的。
函数分析:
* server.c中,启动新线程用的是_beginthread。
* sock.c中,open_socket函数打开一个端口,用了四个标准函数:WSAStartup,socket,bind,listen。
* open_connect函数用来连接远程服务器,使用了标准函数WSAStartup,socket,gethostbyname,connect。
* accept_client使用了标准函数accept。
* read_from_net使用了标准函数recv。
* write_from_net使用了标准函数send。
* http.c中,有get,post,conn函数分别对端的GET,POST,CONNECT请求进行解析。
* 另外,还有一个cache.c,基本原理是把cache写入到文件中,但在代码中没有看到应用。
另外,代码中出现了一些FD_xxx的函数,Google了一下,帖:
FD_ZERO,FD_ISSET这些都是套节字结合操作宏 看看MSDN上的select函数, 这是在select io 模型中的核心,用来管理套节字IO的,避免出现无辜锁定.
int select( int nfds,fd_set FAR *readfds, fd_set
FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout ); 第一个参数不管,是兼容目的,最后的是超时标准,select是阻塞操作 当然要设置超时事件. 接着的三个类型为fd_set的参数分别是用于检查套节字的可读性,可写性,和列外数据性质. 我举个例子 比如recv(), 在没有数据到来调用它的时候,你的线程将被阻塞 如果数据一直不来,你的线程就要阻塞很久.这样显然不好. 所以采用select来查看套节字是否可读(也就是是否有数据读了) 步骤如下 socket s; ..... fd_set set; while(1) { FD_ZERO(&set);//将你的套节字集合清空 FD_SET(s, &set);//加入你感兴趣的套节字到集合,这里是一个读数据的套节字s select(0,&set,NULL,NULL,NULL);//检查套节字是否可读, //很多情况下就是是否有数据(注意,只是说很多情况) //这里select是否出错没有写 if(FD_ISSET(s, &set) //检查s是否在这个集合里面, { //select将更新这个集合,把其中不可读的套节字去掉 //只保留符合条件的套节字在这个集合里面 recv(s,...); } //do something here }
了解一下吧。另可参考此文章:http://mawnja.blog.163.com/blog/static/212061982008684928510/ 2.proxy
老美的代码看起来味道就是不一样,很专业,东西用得也很玄乎。
这个proxy有简单的filter功能,可以设置简单的accept/deny规则。
过程分析:
* proxy.c:工作模型与csproxy差不多,accept以后起一个线程proxy(proxy.c)。
* 看了一下proxy线程,发现只是简单地read然后write,这应该不是一个HTTP Proxy。
* 注意,这个Readme里面提到两个网卡的组网,因此此例可以解释如何在两个网卡之间转发请求。
*
注意到在main()函数中,已经开始监听了:tcp_listen (srcip, srcport,
&addrlen),监听的这个网卡是通过用户输入IP地址指定的,然后在proxy线程里使用tcp_connect (destip,
destport)来连接目的主机。看到这里就觉得奇怪了,tcp_listen和tcp_connect如何能根据IP来判断使用哪个网卡通信?
* 跳到tcp_connect中去看就明白了,使用了遍历的办法,如果遍历到某一块网卡,成功建立连接了,就表示找对网卡了,退出循环:
/* * Loop through, calling socket and connect for each IP, creating * a linked list of addrinfo structures, once a successful connection * has been made, break out of loop. We then free memory and return * a socket descriptor. */ do { sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sockfd < 0) continue; if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0) break; /* Break on success */ close(sockfd); } while ( (res = res->ai_next) != NULL); /* Build linked list */tcp_listen的代码类似。
注意,代码中的关键是如何枚举网卡的。注意网卡资源信息是使用res交互的,查看res的定义,发现是一个addrinfo的struct。
参考MSDN:
3.transproxy
这个proxy已经在运行脚本tproxyrun中明确表示支持多网卡。
待来看看是如何支持的。
过程分析:
* Take for example the network configuration of a FreeBSD or Linux box acting as
a dialin server (or terminal server), and another FreeBSD or Linux box acting
as a Squid (or any other) proxy cache. Normally users would have to
configure their browser to access the proxy. This transparent proxy
will automatically intercept HTTP accesses and re-direct them to the
Squid (or any other) proxy server. The users need not even know that
a proxy is being used, it's that transparent.
也就是说这不是一个代理,而只是一个透明网关。
4.tinyproxy.c
这个应该是一个比较典型的HTTP Proxy了,有一定的应用价值,因此应该仔细研究一下。
过程分析:
* 入口tinyproxy.c。
5.squid
见《ProxyServer:squid》
阅读(1092) | 评论(0) | 转发(0) |