本文以QQ為對像,教你如何寫一個SOCKS5 PROXY
本章主要介紹Launch_UDP()的工作原理
一、SOCKS5 UDP封包結構
===========================
順序為:
2 Bytes 保留字,一定要為0x0
1 Bytes Current fragment number
1 Bytes 地址類型
X Bytes 目的地地址
2 Bytes 目的地端口號
N Bytes 數據
二、源代碼
===========================
void Launch_UDP( int udp_proxy_port, const char *udp_proxy_ip, int clt_udp_port )
{
//port is NOT network orders
//記錄本機,客戶端,遠端服務器和封包來源地址
struct sockaddr_in servaddr,clientaddr,remoteaddr,inaddr;
int inlen;
int listenfd;
int n;
fd_set set;
//把接收來的數據寫在緩衝區第11個Byte之後,前10 Bytes用來存放Header
char *thisbuf = &buf[10];
int thissize = BUFSZ - 10;
printf("< UDP Session - START >/n/n");
//建立一個UDP SOCKET,注意UDP協議不需要listen, accept和conenct
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons( udp_proxy_port );
servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
memset(&remoteaddr, 0, sizeof(remoteaddr));
remoteaddr.sin_family = AF_INET;
listenfd = socket(AF_INET, SOCK_DGRAM, 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);
}
//使用select來監控Socket是否有資料可讀
FD_ZERO(&set);
FD_SET(listenfd, &set);
while( 1 ) {
if( select( listenfd+1, &set, NULL, NULL, NULL) < 0 ) {
p_error("select error");
exit(-1);
}
if( FD_ISSET( listenfd, &set ) ) {
//UDP協議可使用recvfrom()來接收數據,並獲得來源地地址
n = recvfrom( listenfd, thisbuf, thissize, 0, (struct sockaddr *)&inaddr, &inlen );
if( n >=0 ) {
debug_showip( &inaddr, "Received From", "/n" );
//資料來自客戶端
if( (thisbuf[0]==0x0) && (thisbuf[1]==0x0) &&
(htons(inaddr.sin_port)==clt_udp_port) )
{
//保存客戶端的地址
memcpy( &clientaddr, &inaddr, sizeof(clientaddr) );
if( thisbuf[3] != 0x1 ) {
//如果目的地地址類型為域名,先進行解析獲得IP再發送
struct hostent *h;
char tmp[256];
int seg;
strncpy( tmp, &thisbuf[5], thisbuf[4] );
tmp[ thisbuf[4] ] = 0;
h = gethostbyname ( tmp ); //<netdb.h>
if( h == NULL )
p_error("unknown domain name/n");
else
{
remoteaddr.sin_addr.s_addr = (*(struct in_addr*)h->h_addr).s_addr;
seg = thisbuf[4]+1;
memcpy( &remoteaddr.sin_port, &thisbuf[4+seg], 2 );
debug_showbin( thisbuf, 4+seg+2, "RECV CLIENT [ Header ]","/n");
debug_showbin( &thisbuf[4+seg+2], n-(4+seg+2), "RECV CLIENT [ Data ]","/n");
debug_showip( &remoteaddr, "Send to DOMAIN", "/n/n");
sendto( listenfd, &thisbuf[4+seg+2], n-(4+seg+2), 0, (struct sockaddr*)&remoteaddr,sizeof(remoteaddr));
}
} else {
//目的地地址為IPv4,直接把資料發送過去
memcpy( &remoteaddr.sin_port, &thisbuf[8], 2 );
memcpy( &remoteaddr.sin_addr.s_addr, &thisbuf[4], 4 );
debug_showbin( thisbuf, 10, "RECV CLIENT [ Header ]","/n");
debug_showbin( &thisbuf[10], n-10, "RECV CLIENT [ Data ]","/n");
debug_showip( &remoteaddr, "Send to IP", "/n/n");
sendto( listenfd, &thisbuf[10], n-10, 0, (struct sockaddr*)&remoteaddr,sizeof(remoteaddr));
}
}
else
{ //資料來自遠端服務器
debug_showbin(thisbuf, n, "RECV REMOTE","/n");
debug_showip(&clientaddr, "Send to CLT","/n/n");
//編寫Header
buf[0] = 0x0;
buf[1] = 0x0;
buf[2] = 0x0;
buf[3] = 0x1;
memcpy( &buf[4], &udp_proxy_ip, 4 );
memcpy( &buf[8], &udp_proxy_port, 2 );
//發送到客戶端
sendto( listenfd, buf, n+10, 0, (struct sockaddr*)&clientaddr,sizeof(clientaddr));
} } }
}
close(listenfd);
printf("< UDP Session - END >/n/n");
}
三、測試
===================
到目前為止,整個PROXY已經完成,可以用QQ來測試一下,連接後QQ與遠端服務器之間傳輸的資料都會顯示在屏幕上,我們還可以對數據進行截留,從而把煩人的廣告全去掉:)
下載本章的源代碼 → mysocks5.c