这两天闲着没事简单研究了下关于透明代理的方面的东西,有一些感想来记录下。
先来简单说下透明代理的大体流程:
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的配置文件中可以这样设置:
第二种: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和端口信息,再通过sendmsg函数发送给代理应用。
这两个问题解决了基本上后面的应用实现就随意发挥了。在使用tproxy的时候,不要忘了添加一个local路由信息将流量发到本地网卡。
最后贴关于两个方案的测试代码:
方案一:iptables+redirect
//TCP.C
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 \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
#include
#include
#include
#include
#include
#include
#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 \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
先写这么多吧。。