透明代理:redsocks2_shadowsocks

redsocks2 + shadowsocks

几点说明

  • socket四要素:srcIP + srcPort + destIP + destPort唯一确定一个socket连接。server端accept监听到连接请求后,通常会另起一个子进程处理,但accept返回的新socket,与accept监听的端口还是一样的,都是server_port,而不是新开一个port(no new port is opened),参考How does the socket API accept() function work?

  • iptables SNAT/DNAT:--to更改TCP/IP包中的IP和Port,通过第三层TCP/IP协议将数据包送达目标机器

  • 1
    iptables -t nat -A POSTROUTING -s 172.16.36.0/24 -j SNAT --to $PROXY_IP:$PROXY_PORT
  • 默认网关:通过链路层MAC地址将数据包送达目标机器。TCP/IP协议中没有关于默认网关IP的字段,通过ARP协议获取到默认网关IP对应的MAC地址,数据包发送出去时,在第二层链路层添加MAC地址,这样数据包就会被默认网关接收到

  • iptables redirect:会把原来的目标地址和端口改掉。REDIRECT使用了ip_conntrack模块,可以通过函数接口获取原来目的地址

    if you redirect TCP traffic, the ip_conntrack provides a getsockopt() to get the original destination address.

    REDIRECT实际上也是一种特殊的NAT

1
2
3
4
5
6
7
8
9
10
11
This target is only valid in the nat table, in the PREROUTING and OUTPUT
chains, and user-defined chains which are only called from those
chains. It redirects the packet to the machine itself by changing the
destination IP to the primary address of the incoming interface
(locally-generated packets are mapped to the 127.0.0.1 address). It
takes one option
--to-ports port[-port]
This specifies a destination port or range of ports to use:
without this, the destination port is never altered. This is
only valid if the rule also specifies -p tcp or -p udp.

redsocks2

TCP/IP中的目标地址一旦修改,就无法将数据包发送给真正的目标对象,而所有的socket监听,只有目标地址是自己才能接收,上层应用想代理流量,必须处理这一对矛盾,而iptables的redirect刚好派上用场

redsocks通过修改iptables,将流量redirect到自己监听的端口号,并且通过getdestaddr获取原目标ip和port,然后遵循代理协议格式将流量转发给代理

redsocks2在redsocks基础上进行增强,支持智能代理:只有直接连接失败的才通过代理访问

目前来看,只有使用redsocks真正实现透明代理

项目下载:

方案细节

能刷openwrt固件的路由都容易集成redsocks和shadowsocks,不过路由一般容量受限,例如4M内存+16M Flash,影响发挥

刚好有一块cubieboard2板,装Ubuntu12.04,可以用来布置整套透明代理:redsocks2 + shadowsocks

再从路由器的DHCP配置中将gateway地址修改为cubieboard的IP地址即可,这样局域网所有机器都通过cubieboard代理上网

其他方案

下面列出几种尝试过的方案

pcap_dnsproxy + nginx

  • 基本思路:通过pcap_dnsproxy将需要代理的url IP地址解析为自己的代理服务器地址,流量通过代理服务器nginx反向代理进行访问

  • 问题:浏览器https访问时,会提示网站证书问题,因为我们的nginx透明代理扮演中间人的角色,不被客户端信任。
    wget 可以用–no-check-certificate忽略证书,casperjs用–ignore-ssl-errors=yes忽略

  • dnsproxy Host.ini配置:

1
2
3
4
5
6
[Hosts]
52.197.180.130 .*\.google.com.*
52.197.180.130 .*\.gstatic.com
52.197.180.130 .*\.googleusercontent.com
52.197.180.130 .*\.googleadservices.com
52.197.180.130 .*\.doubleclick.com

语法类似/etc/hosts文件,但可以使用正则匹配,方便很多

  • nginx 443端口配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
resolver 8.8.8.8;
listen 443 ssl;
#server_name ssl.pickbox.cc;
ssl_certificate /opt/nginx/ssl/example_com.crt;
ssl_certificate_key /opt/nginx/ssl/example_com.key;
ssl_verify_client off;
ssl_session_timeout 5m;
location / {
proxy_pass $scheme://$host$request_uri;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
access_log logs/access.p.log;
}

需要生成cert文件,用gencert.sh脚本生成

80端口配置类似

iptables

http://www.cnblogs.com/lexus/archive/2012/02/14/2351939.html

下载

该方法同样有https证书问题,看看加深对socket四要素理解吧

1
2
3
4
5
6
7
8
9
10
#!/bin/sh
PROXY_IP=192.168.1.10
PROXY_PORT=3128
LAN_IP=`nvram get lan_ipaddr`
LAN_NET=$LAN_IP/`nvram get lan_netmask`
iptables -t nat -A PREROUTING -i br0 -s $LAN_NET -d $LAN_NET -p tcp --dport 80 -j ACCEPT
iptables -t nat -A PREROUTING -i br0 -s ! $PROXY_IP -p tcp --dport 80 -j DNAT --to $PROXY_IP:$PROXY_PORT
iptables -t nat -I POSTROUTING -o br0 -s $LAN_NET -d $PROXY_IP -p tcp -j SNAT --to $LAN_IP
iptables -I FORWARD -i br0 -o br0 -s $LAN_NET -d $PROXY_IP -p tcp --dport $PROXY_PORT -j ACCEPT

This solution described in the previous section redirects packets to the proxy server using Network Address Translation to modify the actual packets. The result is that packets arriving at the proxy have a source IP address of the router rather than the original client. As a result, it’s not possible to see the IP address of the originating client in the proxy logs, nor is it possible to apply access rules in the proxy based on the originating client IP address.

1
2
3
4
5
6
7
#!/bin/sh
PROXY_IP=192.168.1.10
iptables -t mangle -A PREROUTING -j ACCEPT -p tcp --dport 80 -s $PROXY_IP
iptables -t mangle -A PREROUTING -j MARK --set-mark 3 -p tcp --dport 80
ip rule add fwmark 3 table 2
ip route add default via $PROXY_IP dev br0 table 2

还需要改一下port,否则数据包还是转发到proxy机器的80端口上

1
iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port [PROXY_PORT]