本文以QQ為對像,教你如何寫一個SOCK5 PROXY
本章主要介紹Launch_TCP()的工作原理
一、握手過程
===================
先看看Proxy的輸出結果:
< TCP/IP Session - START >
RECV ==> 3 bytes: (0x5)(0x1)(0x0)
SEND ==> 2 bytes: (0x5)(0x0)
RECV ==> 10 bytes: (0x5)(0x3)(0x0)(0x1)(0x0)(0x0)(0x0)(0x0)(0x6)(0x32)
SEND ==> 10 bytes: (0x5)(0x0)(0x0)(0x1)(0x7f)(0x0)(0x0)(0x1)(0x22)(0x6b)
< TCP/IP Session - END >
如果不需要身份驗證的話,SOCK5 PROXY和客戶端的握手過程只有四句,
由於SOCK5協議包括很多內容,這裏只對本例中所用到的部份作出說明,
餘者可參考rfc1928.txt
以下逐句分析:
1. 第一句,客戶端→PROXY
(0x5)版本號
(0x1)代表有1 byte的資料
(0x0)登入模式,0x0代表不用身份驗證,0x2代表需要usrname/password
2. 第二句,PROXY→客戶端
(0x5)版本號
(0x0)成功
3. 第三句,客戶端→PROXY
(0x5)版本號
(0x3)要求使用的協議類型,0x3代表UDP
(0x0)保留字
(0x1)地址類型,0x1代表IPv4,0x3代表Domain name,0x4代表IPv6
(0x0)(0x0)(0x0)(0x0)這4個bytes代表客戶端的地址
(0x6)(0x32)客戶端用作UDP傳輸的端口號
4. 第四句,PROXY→客戶端
(0x5)版本號
(0x0)成功
(0x0)保留字
(0x1)地址類型
PROXY提供的UDP SOCKET地址和端口號,一定要準確無誤,客戶端需要連接到這個地址.
(0x7f)(0x0)(0x0)(0x1)
(0x22)(0x6b)
●註:如果地址類型為Domain name(0x3)的話,第1 byte是域名長度,之後n bytes就是地址
二、源代碼
===================
void Launch_TCP( int service_port, const char *udp_proxy_ip, int udp_proxy_port, short *clt_udp_port )
{
//port is NOT network orders
struct sockaddr_in servaddr,clientaddr;
int clientlen;
int listenfd, connfd;
int n;
//定義socket, bind, listen, accept,關於這些操作的資料太多了,不詳述
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(service_port);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd < 0) {
p_error("socket error");
exit(-1);
}
if( bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0 ) {
p_error("bind error");
exit(-1);
}
if( listen(listenfd, 5) < 0 ) {
p_error("listen error");
exit(-1);
}
connfd = accept( listenfd, (struct sockaddr *)&clientaddr, &clientlen );
if( connfd < 0 ) {
p_error("accept error");
exit(-1);
}
printf("< TCP/IP Session - START >/n/n");
//接受第一句請求
n = recv( connfd, buf, BUFSZ, 0 );
debug_showbin( buf, n, "RECV", "/n" );
//目前我們只支持無身份驗證的請求,即"05 01 00"
if( buf[0]==0x5 && buf[1]==0x1 && buf[2]==0x0) {
buf[0] = 0x5;
buf[1] = 0x0;
//返回"05 00",代表成功
send( connfd, buf, 2, 0 );
debug_showbin( buf, 2, "SEND", "/n/n" );
} else {
p_error("Session ERROR!/n");
exit(-1);
}
//接受第二句請求
n = recv( connfd, buf, BUFSZ, 0 );
debug_showbin( buf, n, "RECV", "/n" );
//只處理UDP請求(0x03)
if( buf[0]==0x5 && buf[1]==0x3 ) { //Client request a UDP Proxy
short udp_port;
long udp_ip;
//提取並儲存客戶端的UDP端口號
int seg=4;
if( buf[3] == 0x3 )
seg = buf[4]+1;
memcpy( clt_udp_port, &buf[4+seg], 2 );
*clt_udp_port = ntohs( *clt_udp_port );
buf[0] = 0x5;
buf[1] = 0x0;
buf[2] = 0x0;
buf[3] = 0x1;
//把本機UDP SOCKET的IP和PORT返回給QQ
udp_ip = inet_addr( udp_proxy_ip );
udp_port = htons( udp_proxy_port );
memcpy( &buf[4], &udp_ip, 4 );
memcpy( &buf[8], &udp_port, 2 );
send(connfd, buf, 10, 0 );
debug_showbin( buf, 10, "SEND", "/n/n" );
} else {
p_error("Session ERROR: Client doesn't need a UDP Proxy!/n");
exit(-1);
}
//握手過程完成
close(connfd);
close(listenfd);
printf("< TCP/IP Session - END >/n/n");
}
三、測試
===================
1.現在可以先把程序編譯,運行後程序將會在accept()這句搪塞,直至有請求連入.
2.打開QQ,在[系統參數->網絡設置]中選取使用SOCK5代理,在地址欄填入127.0.0.1或localhost,端口填8888,切記要把用戶名和密碼欄清空,因我們的程序只能處理無身份驗證的請求(即握手的第一句為"05 01 00"),如果用戶名和密碼欄不為空的話,QQ將會發送"05 01 02"至Proxy.
3.按一下[測試],看看成不成功,再自己研究一下握手的內容:)
下載本章的源代碼 → mysock5_2.c