Iptables+Tproxy+RedSocks(TCP/UDP)透明代理原理淺析

這兩天閒着沒事簡單研究了下關於透明代理的方面的東西,有一些感想來記錄下。

先來簡單說下透明代理的大體流程:

1.用戶將流量發送的網關服務器

2.網關通過設置iptables、ip路由策略方式將感興趣的流量截獲並重定向代理應用程序

3.代理應用程序通過某些方式獲取原始目標的IP地址和port等信息 最後根據需求來定製實現不同的協議代理(如socks5)

...

一、第一步沒什麼好說 直接跳過 。

二、 流量轉發

主要有兩種方案來重定向流量:

1.iptables+redirect 

2.iptables+tproxy

因爲這兩種方式的原理不同,會影響第三步的代理應用的實現方式。首先方法一是採用的DNAT的方式來轉發流量的,這意味着代理程序監聽到的是目標連接地址是本地地址。而方案二中確實不改變目標源地址,但是其實目標端口還是變了。

先貼一下常用的iptables的設置規則,以redsocks爲例:

#全局TCP代理規則    iptables+REDIRECT  
sudo iptables -t nat -N SSTCP
sudo iptables -t nat -A SSTCP -d x.x.x.x -j RETURN #SS Server tcp via
sudo iptables -t nat -A SSTCP -d 0.0.0.0/8 -j RETURN
sudo iptables -t nat -A SSTCP -d 10.0.0.0/8 -j RETURN
sudo iptables -t nat -A SSTCP -d 127.0.0.0/8 -j RETURN
sudo iptables -t nat -A SSTCP -d 169.254.0.0/16 -j RETURN
sudo iptables -t nat -A SSTCP -d 172.16.0.0/12 -j RETURN
sudo iptables -t nat -A SSTCP -d 192.168.0.0/16 -j RETURN
sudo iptables -t nat -A SSTCP -d 224.0.0.0/4 -j RETURN
sudo iptables -t nat -A SSTCP -d 240.0.0.0/4 -j RETURN
sudo iptables -t nat -A SSTCP -p tcp -j REDIRECT –to-ports 12345
sudo iptables -t nat -A PREROUTING -p tcp -j SSTCP

#局部UDP代理規則  手動指定IP端口 可解決DNS污染
#sudo iptables -t nat -N SSDNS,
#sudo iptables -t nat -A SSDNS -p udp –dport 53 -j REDIRECT –to-ports 10053
#sudo iptables -t nat -A PREROUTING -p udp -j SSDNS

#全局UDP代理規則 iptables+TPROXY
sudo ip route add local 0.0.0.0/0 dev lo table 100
sudo ip rule add fwmark 1 table 100
sudo iptables -t mangle -N SSUDP
sudo iptables -t mangle -A SSUDP -d 0.0.0.0/8 -j RETURN
sudo iptables -t mangle -A SSUDP -d 10.0.0.0/8 -j RETURN
sudo iptables -t mangle -A SSUDP -d 127.0.0.0/8 -j RETURN
sudo iptables -t mangle -A SSUDP -d 169.254.0.0/16 -j RETURN
sudo iptables -t mangle -A SSUDP -d 172.16.0.0/12 -j RETURN
sudo iptables -t mangle -A SSUDP -d 192.168.0.0/16 -j RETURN
sudo iptables -t mangle -A SSUDP -d 224.0.0.0/4 -j RETURN
sudo iptables -t mangle -A SSUDP -d 240.0.0.0/4 -j RETURN
sudo iptables -t mangle -A SSUDP -p udp -j TPROXY –on-port 10053 –tproxy-mark 0x01/0x01
sudo iptables -t mangle -A PREROUTING -p udp -j SSUDP

三.  應用實現(以redsocks爲例)

先說說第一種:iptables+redirect 

iptables+redirect 這種一般是用來重定向TCP數據的 所以代理程序可以通過getsockopt (s, SOL_IP, SO_ORIGINAL_DST, &dstaddr, &n)函數來獲取原始目標地址和端口信息,大概的原理應該是通過查詢系統ip_conntrack結構來獲得原始目標地址和端口信息。但這個SO_ORIGINAL_DST參數並適用於UDP協議,有些遺憾。所以纔有了的tproxy的用武之地。不過這種方式還是可以用在某些特定的UDP協議上如DNS,因爲可以手動設置一個IP和端口作爲原始目標DNS服務器地址,這樣就可以解決DNS污染的問題。在redsocks的配置文件中可以這樣設置:

  1. redudp {  
  2.         //`ip' and `port'  for redsocks to listen  
  3.         local_ip = 0.0.0.0;  
  4.         local_port = 10053;  
  5.   
  6.         // `ip' and `port' of socks5 proxy server.  
  7.         ip = 127.0.0.1;  
  8.         port = 7456;  
  9.   
  10.         //`ip' and `port'  of DNS server .  
  11.         dest_ip = 8.8.8.8;  
  12.         dest_port = 53;  
  13.   
  14.         udp_timeout = 30;  
  15.         udp_timeout_stream = 180;  
  16. }  

第二種:iptables+tproxy

這種方式感覺會稍微複雜一點,但更通用,可以用來代理UDP協議。首先我們知道tproxy轉發數據的時候,並不會改變原始的源地址和目標地址但卻改變了目標端口 。所以這裏有兩個問題要解決:

1.套接字如何監聽到非本地IP地址 

2.如何獲取的原始目標的端口 

第一問題比較粗暴的解決方式是創建的一個底層的套接字來抓包 ,但明顯不夠優雅,所以的一般的代理應用是先用setsockopt函數爲套接字設置IP_TRANSPARENT標識,再去監聽0.0.0.0地址這樣的方式來實現監聽任意IP。

第二個問題的方式解決的也比較巧妙 先調用setsockopt (s, IPPROTO_IP, IP_RECVORIGDSTADDR, &n, sizeof(int))函數爲套接字設置IP_RECVORIGDSTADDR標識,然後通過recvmsg函數從tproxy那邊接受發過來的msghdr結構體信息,並循環遍歷cmsghdr成員最終獲取到原始目標的地址和端口,也就是說tproxy會向msghdr(附屬數據結構)填入原始目標ip和端口信息,再通過socket發送給代理應用。

這兩個問題解決了基本上後面的應用實現就隨意發揮了。在使用tproxy的時候,不要忘了添加一個local路由信息將流量發到本地網卡。

最後貼關於兩個方案的測試代碼:

方案一:iptables+redirect 

//TCP.C
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <limits.h>
#include <linux/netfilter_ipv4.h>

int handle_client (int c, struct sockaddr_in *clntaddr);
int tunnel_transparently (int c, struct sockaddr_in *clntaddr, struct sockaddr_in *dstaddr);

int main (int argc, char **argv)
{
        int                     s;
        int                     c;
        short int               port;
        struct sockaddr_in      servaddr;
        struct sockaddr_in      clntaddr;
        int                     n;
        int                     ret;
        struct msghdr           msg;
        char                    cntrlbuf[64];
        struct iovec            iov[1];
        char                    *endptr;

        if (argc < 2)
        {
                printf ("usage: %s <port>\n", argv[0]);
                return -1;
        }

        port = strtol (argv[1], &endptr, 0);
        if (*endptr || port <= 0)
        {
                fprintf (stderr, "invalid port number %s.\n", argv[1]);
                return -2;
        }

        if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0)
        {
                fprintf (stderr, "error creating listening socket.\n");
                return -3;
        }

        n=1;
        setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
        setsockopt(s, SOL_SOCKET, SO_BROADCAST, &n, sizeof(n));

        /* Enable TPROXY IP preservation */
        n=1;
        ret = setsockopt (s, SOL_IP, IP_TRANSPARENT, &n, sizeof(int));
        if (ret != 0)
        {
                fprintf (stderr, "error setting transparency for listening socket. err (#%d %s)\n", errno, strerror(errno));
                close (s);
                return -4;
        }

        memset (&servaddr, 0, sizeof (servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
        servaddr.sin_port = htons (port);


        if (bind (s, (struct sockaddr *) &servaddr, sizeof (servaddr)) < 0)
        {
                fprintf (stderr, "error calling bind()\n");
                return -6;
        }

        listen (s, 1024);

        while (1)
        {
                n=sizeof(clntaddr);
                if ((c = accept (s, (struct sockaddr *)&clntaddr, &n)) < 0)
                {
                        fprintf (stderr, "error calling accept()\n");
                        break;
                }

                handle_client (c, &clntaddr);
        }

        close (s);

        return 0;
}



int handle_client (int c, struct sockaddr_in *clntaddr)
{
        struct sockaddr_in      dstaddr={0,};
        int                     ret;
        int                     n;

        /* get original destination address */
        n=sizeof(struct sockaddr_in);
        //ret = getsockopt (c, SOL_IP, IP_ORIGDSTADDR, &dstaddr, &n); // IP_ORIGDSTADDR = 20   tproxy ??
        ret = getsockopt (c, SOL_IP, SO_ORIGINAL_DST, &dstaddr, &n); // SO_ORIGINAL_DST = 80   redir 

        if (ret != 0)
        {
                fprintf (stderr, "error getting original destination address. err (#%d %s)\n", errno, strerror(errno));
                close (c);
                return -1;
        }

        dstaddr.sin_family = AF_INET;
        printf ("original destination address %X:%d\n", dstaddr.sin_addr.s_addr, dstaddr.sin_port);

        ret = tunnel_transparently (c, clntaddr, &dstaddr);
        if (ret <= 0)
        {
                close (c);
                return -2;
        }

        close (c);
        return 0;
}

int tunnel_transparently (int c, struct sockaddr_in *clntaddr, struct sockaddr_in *dstaddr)
{
        int     d;
        int     n;
        int     ret;

        if (clntaddr == NULL || dstaddr == NULL)
        {
                return -1;
        }

        d = socket (AF_INET, SOCK_STREAM, 0);
        if (d == -1)
        {
                fprintf (stderr, "error creating socket (#%d %s)\n", errno, strerror(errno));
                return -2;
        }

        n=1;
        ret = setsockopt (d, SOL_IP, IP_TRANSPARENT, &n, sizeof(int));
        if (ret != 0)
        {
                fprintf (stderr, "error setting transparency towards destination. err (#%d %s)\n", errno, strerror(errno));
                close (d);
                return -3;
        }

        ret = bind (d, (struct sockaddr *)clntaddr, sizeof (struct sockaddr_in));
        if (ret != 0)
        {
                fprintf (stderr, "error binding to client . err (#%d %s)\n", errno, strerror(errno));
                close (d);
                return -4;
        }

        ret = connect (d, (struct sockaddr *)dstaddr, sizeof (*dstaddr));
        if (ret != 0)
        {
                fprintf (stderr, "error connecting to detination. err (#%d %s)\n", errno, strerror(errno));
                close (d);
                return -5;
        }
        // TODO: send / recv
        //

        close (d);

        return 0;
}
方案二 :iptables+tproxy

//UDP.C
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>

#define MAX_RECV_BUF    (1000)

int handle_msg (struct msghdr *msg);
int send_transparently (struct msghdr *msg, struct sockaddr_in *dstaddr);

int main (int argc, char **argv)
{
        int                     s;
        short int               port;
        struct sockaddr_in      servaddr;
        struct sockaddr_in      clntaddr;
        int                     n;
        int                     ret;
        struct msghdr           msg;
        char                    cntrlbuf[64];
        struct iovec            iov[1];
        char                    buffer[MAX_RECV_BUF];
        char                    *endptr;

		if (argc < 2)
        {
                printf ("usage: %s <port>\n", argv[0]);
                return -1;
        }

				port = strtol (argv[1], &endptr, 0);
        if (*endptr || port <= 0)
        {
                fprintf (stderr, "invalid port number %s.\n", argv[1]);
                return -2;
        }

		if ((s = socket (AF_INET, SOCK_DGRAM, 0)) < 0)
        {
                fprintf (stderr, "error creating listening socket.\n");
                return -3;
        }

		n=1;
        setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
        setsockopt(s, SOL_SOCKET, SO_BROADCAST, &n, sizeof(n));

		n=1;
        ret = setsockopt (s, SOL_IP, IP_TRANSPARENT, &n, sizeof(int));
        if (ret != 0)
        {
                fprintf (stderr, "error setting transparency for listening socket. err (#%d %s)\n", errno, strerror(errno));
                close (s);
                return -4;
        }
        n=1;
        ret = setsockopt (s, IPPROTO_IP, IP_RECVORIGDSTADDR, &n, sizeof(int));
        if (ret != 0)
        {
                fprintf (stderr, "error setting the listening socket to IP_TRANSPARENT. err (#%d %s)\n", errno, strerror(errno));
                close (s);
                return -5;
        }

		memset (&servaddr, 0, sizeof (servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
        servaddr.sin_port = htons (port);

		if (bind (s, (struct sockaddr *) &servaddr, sizeof (servaddr)) < 0)
        {
                fprintf (stderr, "error calling bind()\n");
                return -6;
        }

		while (1)
        {
                msg.msg_name = &clntaddr;
                msg.msg_namelen = sizeof(clntaddr);
                msg.msg_control = cntrlbuf;
                msg.msg_controllen = sizeof(cntrlbuf);
                iov[0].iov_base = buffer;
                iov[0].iov_len = sizeof (buffer);
                msg.msg_iov = iov;
                msg.msg_iovlen = 1;
                ret = recvmsg (s, &msg, 0);
                if (ret <= 0)
                {
                        fprintf (stderr, "error calling recvmsg(). err (#%d %s)\n", errno, strerror(errno));
                        break;
                }

				msg.msg_iov[0].iov_len = ret;
                handle_msg (&msg);
        }

				close (s);

				return 0;
}

int handle_msg (struct msghdr *msg)
{
        struct sockaddr_in      *clntaddr;
        struct sockaddr_in      dstaddr={0,};
        struct cmsghdr          *cmsg;
        int                     ret;
        int                     found=0;

		clntaddr =  msg->msg_name;
        printf ("recvd msg from %X:%d\n", clntaddr->sin_addr.s_addr, clntaddr->sin_port);

		/* get original destination address */
        for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg))
        {
                if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVORIGDSTADDR)
                {
                        memcpy (&dstaddr, CMSG_DATA(cmsg), sizeof (struct sockaddr_in));
                        dstaddr.sin_family = AF_INET;
                        printf ("original dst address %X:%d\n", dstaddr.sin_addr.s_addr, dstaddr.sin_port);
                        found = 1;
                }
        }

		if (! found)
        {
                return -1;
        }

			ret = send_transparently (msg, &dstaddr);
        if (ret <= 0)
        {
                return -2;
        }

		return 0;
}

int send_transparently (struct msghdr *msg, struct sockaddr_in *dstaddr)
{
        int     d;
        int     n;
        int     ret;

		if (msg == NULL || dstaddr == NULL)
        {
                return -1;
        }

		d = socket (AF_INET, SOCK_DGRAM, 0);
        if (d == -1)
        {
                fprintf (stderr, "error creating socket (#%d %s)\n", errno, strerror(errno));
                return -2;
        }

		n=1;
        ret = setsockopt (d, SOL_IP, IP_TRANSPARENT, &n, sizeof(int));
        if (ret != 0)
        {
                fprintf (stderr, "error setting transparency towards destination. err (#%d %s)\n", errno, strerror(errno));
                close (d);
                return -3;
        }

		ret = bind (d, (struct sockaddr *)msg->msg_name, sizeof (struct sockaddr_in));
        if (ret != 0)
        {
                fprintf (stderr, "error binding to client . err (#%d %s)\n", errno, strerror(errno));
                close (d);
                return -4;
        }

		ret = sendto (d, msg->msg_iov[0].iov_base, msg->msg_iov[0].iov_len, 0, (struct sockaddr *)dstaddr, sizeof (*dstaddr));
        if (ret <= 0)
        {
                fprintf (stderr, "error sending to detination. err (#%d %s)\n", errno, strerror(errno));
                close (d);
                return -5;
        }

		close (d);

		return 0;
}

一些參考鏈接:

http://lists.netfilter.org/pipermail/netfilter-devel/2002-March/007305.html

https://stackoverflow.com/questions/5615579/how-to-get-original-destination-port-of-redirected-udp-message

https://ask.helplib.com/sockets/post_1007734

http://blog.csdn.net/dog250/article/details/7518054/

http://blog.csdn.net/dog250/article/details/13161945

先寫這麼多吧。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章