之前我利用redsocks项目,在网关上实现了透明SOCKS5代理。
redsocks要求在网关上利用iptables将TCP流量改写给redsocks端口,这样redsocks收到流量后就会按照SOCKS5协议向代理服务器转发数据。
实现redsocks的唯一难点就是redsocks如何知道原始的TCP目标地址,因为经过iptables做REDIRECT改写后,数据包的目标地址已经被修改为redsocks自身的监听地址了。
既然redsocks能做到,我们也一定有办法做到,答案就是linux的getsockopt系统调用,做过linux网络编程的同学一定都非常熟悉了。getsockopt支持一个选项,可以获取到socket改写前的原始目标地址,包括ip和port。
我用Golang实现了一个demo:https://github.com/owenliang/go-orig-dst/blob/master/main.go,下面我简单讲解一下。
体验demo这个demo类似于redsocks,只不过它收到TCP连接后只是打印了一下原始目标IP地址,然后就关闭了连接。
首先运行程序,它监听在8080端口:
1 |
go run main.go |
然后telnet直连它,将打印出目标IP地址就是demo的监听地址:
1 2 3 4 5 6 |
pi@raspberrypi:~ $ telnet localhost 8080 Trying ::1... Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Connection closed by foreign host. |
连接被立即断开,这符合demo程序的逻辑。
在demo服务端,打印出了目标地址:
1 2 |
pi@raspberrypi:~/raid1/golang/go-orig-dst $ go run main.go 原始目标地址:127.0.0.1:8080 |
接下来,我们要测试利用iptables做REDIRECT拦截TCP流量到Demo服务端,再看一下能否获取到原始的目标地址。
因此,先后添加如下2条iptables规则:
1 2 |
sudo iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 --dport 8080 -j ACCEPT sudo iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 8080 |
- 从本机发往本机8080端口的TCP流量直接放行,也就是telnet localhost 8080将直连到Demo。
- 而从本机发往本机非8080端口的流量,将做REDIRECT改写给8080。
因此,Demo能收到2种流量:
- telnet localhost 8080的直连流量。
- telnet localhost 7000这样的流量,将被改写给localhost 8080。
这时候,我们尝试尝试telnet localhost 7000:
1 2 3 4 5 6 |
pi@raspberrypi:~ $ telnet localhost 7000 Trying ::1... Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Connection closed by foreign host. |
会发现Demo打印了如下内容:
1 2 |
pi@raspberrypi:~/raid1/golang/go-orig-dst $ go run main.go 原始目标地址:127.0.0.1:7000 |
也就是说,Demo成功获取了REDIRECT之前的原始目标地址,而不是127.0.0.1:8080这个改写后的地址。
实现Demo首先就是监听TCP连接,一旦得到TCP连接就获取socket文件描述符(fd),因为后续要调用getsockopt获取fd的原始目标地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
if listener, err = net.Listen("tcp4", "127.0.0.1:8080"); err == nil { var conn net.Conn for { // 接收连接 if conn, err = listener.Accept(); err != nil { break } // 反射到TCP连接 var tcpConn *net.TCPConn tcpConn, _ = conn.(*net.TCPConn) // 获取socket文件描述符 var file *os.File file, _ = tcpConn.File() fd := file.Fd() |
阅读后续内容?
你必须付费加入我的知识星球,为有效知识付费是对作者最好的回报。
二维码见下方 或者 右侧边栏。