歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux資訊 >> 更多Linux

Linux下的透明代理技術

  想像這麼一個場景你想截獲(hijack)一個本地的出口連接(LOCALOUT)或者轉發的連接(PREROUTING),對這個連接的兩個方向的內容做修改,比如:1、將這個連接連接到遠程socks代理(在通訊頭部加上socks通信協議部分)2、對這個連接進行記錄(用於協議分析)3、任何你能想到的折騰方式,比如我們叫他tcpgrep,:)。    那麼我們該如何做呢?    一、獲得連接    首先,我們要能獲得這個連接,很容易想到的是用iptables連接REDIRECT方法(和DNAT類似),如果你做過squid的透明代理,應該會覺得很熟悉。    #iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 8010    當然,你應該忽略一些不感興趣的連接,可以單獨建立一個chains,然後配和-j RETURN就可以做到。如果你熟悉iptables,這個應該很容易做到,我就不多說了。    現在呢?    nc -l -p 8010  (注:BSD style 的nc,如果你是GNU nc,用nc -l 8888)    然後nc www.baidu.com 80    很好,連接已經被第一個nc hijack了。但是,問題來了,他要去哪裡呢?    二、獲得ORGINAL DESTINALTION    這裡就是透明代理技術的關鍵所在了。    0、socks代理方式的這個方式並不是我們所需要的東西,因為這個需要涉及到用戶程序的修改。因為用戶需要將目的地址發送給服務器(按照socks協議),如果用戶程序本身沒有考慮支持socks協議那麼就沒有直接的辦法了。    1、方法一:做一個專用內核模塊    我曾經做過一個,在http://s5snake.gro.cLinux.org有相關信息,是專門用於socks[45]的東西。不過在kernel 2.6.9以後因為設計上的問題,對於LOCALOUT的連接就失效了,現在我已經不維護這個老的模塊了。    這種方法太吃力,而且調試困難,需要同時維護netfilter/iptables的內核態模塊和用戶態模塊,所以不推薦再使用了。    2、方法二: PRELOAD方式    通過PRELOAD一個庫,修改socks,connect函數的調用來達到目的。    tsocks就是這麼做的(http://tsocks.sourceforge.net/)    這樣的方式非常像windows下的sockscap32程序,但是有個最大的問題是,只能在本機使用,對轉發的連接就不適用了。    3、方式三:SO_ORIGINAL_DST    SO_ORIGINAL_DST是一個socket參數(SOL_IP層的)。    調用方式如下:    getsockopt (clifd, SOL_IP, SO_ORIGINAL_DST, &orig_addr, &sin_size);    clifd是hijack到的客戶socket,orig_addr是sockaddr_in結構的參數,sin_size=sizeof(sockaddr_in).    返回0如果成功,-1失敗。    如果成功orig_addr將是客戶真正需要去的方向(恩……撒個小謊,後面你會看到)。先給段代碼吧:    /*  * Simple "Hello, World!" server  * patch: demonstrate SO_ORIGINAL_DST  */    #include <stdio.h> /* */  #include <stdlib.h> /* exit() */  #include <string.h> /* memset(), memcpy() */  #include <sys/utsname.h> /* uname() */  #include <sys/types.h>  #include <sys/socket.h>  /* socket(), bind(),listen(), accept(),getsockopt() */  #include <linux/netfilter_ipv4.h>  #include <netinet/in.h>  #include <arpa/inet.h>  #include <netdb.h>  #include <unistd.h> /* fork(), write(), close() */    char message[BUFSIZ];  const int BACK_LOG = 5;    int main(int argc, char *argv[])  {  int serverSocket = 0,  on = 0,  port = 0,  status = 0,  childPid = 0;  strUCt hostent *hostPtr = NULL;  char  hostname[80] = "";  struct sockaddr_in serverName = { 0 };  struct sockaddr_in originDst = { 0 };  socklen_t     sin_size  = sizeof(originDst);    if (2 != argc)  {  fprintf(stderr, "Usage: %s portnum\n",  argv[0]);  exit(1);  }  port = atoi(argv[1]);  serverSocket = socket(PF_INET, SOCK_STREAM,  IPPROTO_TCP);  if (-1 == serverSocket)  {  perror("socket()");  exit(1);  }  on = 1;    status = setsockopt(serverSocket, SOL_SOCKET,  SO_REUSEADDR,  (const char *) &on, sizeof(on));    if (-1 == status)  {  perror("setsockopt(...,SO_REUSEADDR,...)");  }    {  struct linger linger = { 0 };    linger.l_onoff = 1;  linger.l_linger = 30;  status = setsockopt(serverSocket,  SOL_SOCKET, SO_LINGER,  (const char *) &linger,  sizeof(linger));    if (-1 == status)  {  perror("setsockopt(...,SO_LINGER,...)");  }  }    /*  * find out who I am  */    status = gethostname(hostname,  sizeof(hostname));  if (-1 == status)  {  perror("gethostname()");  exit(1);  }    hostPtr = gethostbyname(hostname);  if (NULL == hostPtr)  {  perror("gethostbyname()");  exit(1);  }    (void) memset(&serverName, 0,  sizeof(serverName));  (void) memcpy(&serverName.sin_addr,  hostPtr->h_addr,  hostPtr->h_length);    serverName.sin_addr.s_addr=htonl(INADDR_ANY);  serverName.sin_family = AF_INET;  serverName.sin_port = htons(port);  status = bind(serverSocket,  (struct sockaddr *) &serverName,  sizeof(serverName));  if (-1 == status)  {  perror("bind()");  exit(1);  }  status = listen(serverSocket, BACK_LOG);  if (-1 == status)  {  perror("listen()");  exit(1);  }    for (;;)  {  struct sockaddr_in clientName = { 0 };  int slaveSocket;  socklen_t clientLength =  sizeof(clientName);    (void) memset(&clientName, 0,  sizeof(clientName));    slaveSocket = accept(serverSocket,  (struct sockaddr *) &clientName,  &clientLength);  if (-1 == slaveSocket)  {  perror("accept()");  exit(1);  }    childPid = fork();    switch (childPid)  {  case -1: /* ERROR */  perror("fork()");  exit(1);    case 0: /* child process */    close(serverSocket);    if (-1 == getpeername(slaveSocket,  (struct sockaddr *) &clientName,  &clientLength))  {  perror("getpeername()");  }  else  {  if(getsockopt( slaveSocket, SOL_IP, SO_ORIGINAL_DST, &originDst, &sin_size) == 0){  printf("new connection:%s,%u",inet_ntoa(clientName.sin_addr),ntohs(clientName.sin_port));  printf("->%s,%u\n",inet_ntoa(originDst.sin_addr),ntohs(originDst.sin_port));  }else{  perror("getsockopt SO_ORIGINAL_DST:");  }  }    do{  read(slaveSocket,message,BUFSIZ);  write(1,message,strlen(message));  write(slaveSocket, message,strlen(message));  }while(message[0]);  close(slaveSocket);  exit(0);    default:  }  }    return 0;  }    編譯運行前,記得    #iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports [端口號]    運行後,如果一切正常,那麼你是很幸運,如果得到的是服務器運行的地址和端口,那麼你很不幸,你很可能用的是2.6.9-2.6.12之間的    內核,很明顯這是一個BUG,見:http://patchwork.netfilter.org/netfilter-devel/patch.pl?id=2676    那麼怎麼辦呢?    1、升級到2.6.13的內核,2.6.13已經合並了上面的那個patch。    2、降級到低版本的內核,前提是有SO_ORIGINAL_DST選項,並測試是否正常    3、手動分析/proc/net/ip_conntrack文件,個人分析過可行性,並認為這種方法是一種可行的補救措施,不過一直沒有動手寫_=_!    三、恩……隨你怎麼玩吧    用poll或者select做中間人,做tcp-grep游戲(s/microsft/gnu/g,哈哈),分析QQ協議,等等等等。寫個插件式的框架會更好的:)




Copyright © Linux教程網 All Rights Reserved