Theory
HTTP Proxy
有关原理,这里HTTP 代理原理及实现(一)、HTTP 代理原理及实现(二)描述得很清楚,总结如下:
- HTTP代理分为两类
- 普通代理:Web代理服务器扮演中间人角色,对于连接到它的客户端它是服务端,对于要连接的服务端它是客户端
- 隧道代理:通过Web代理服务器用隧道方式传输基于TCP的协议,即:客户端使用HTTP CONNECT方法告知Web代理服务器的目标地址与TCP端口,随后Web代理服务器在与目标完成TCP三次握手后,返回HTTP给客户端连接就绪报文,此时便建立了原始数据的任意、双向通信,直到连接关闭为止(Web代理服务器转发TCP数据流)
TCP transparent proxy in Linux
Linux的iptables / nftables (即netifilter)提供了REDIRECT extension,允许在PREROUTING,或者OUTPUT chain将任意端口流量重定向至指定端口,比如将80端口的TCP数据流重定向到1080端口:
1 | # iptables |
Note: iptables-translate
可将iptables
语法翻译成nftables
语法
同时可以使用getsockopt()
获取数据流的原目标端口与地址:
1 | // IPv4 |
Demonstration
一个go代码的演示,使用iptables / nftables的REDIRECT模块捕获TCP 80端口的流量,同时使用HTTP CONNECT方法对其进行代理访问。
Prepare
squid能够扮演Web代理服务器角色,默认情况下它仅允许发起443端口的CONNECT请求,可以通过注释http_access deny CONNECT !SSL_ports
允许建立任意端口的隧道代理。
使用$ curl --proxy http://<host>:<port> <destination>
测试HTTP代理的可用性。
HTTP [CONNECT方法的报文首部3如下:
1 | CONNECT <host>:<port> HTTP/1.1\r\n |
其中Proxy-Authorization
是可选项,用于认证。basic
类型的<credentials>
为<username>:<password>
的base64类型编码。
squid设置认证的配置如下:
1 | auth_param basic program /usr/lib64/squid/basic_ncsa_auth /etc/squid/passwords |
Note:
basic_ncsa_auth
位置由具体Linux发行版确定,可能位于/usr/lib/squid/basic_ncsa_auth
下/etc/squid/passwords
含有认证信息,由包名apache2-utils中的htpasswd
程序创建
Impelmentation
Key
核心技术要点有两处:
- 获取原始请求的目标地址与端口
- 构造HTTP CONNECT报文
代码如下:
1 | // get original destination:port |
Note: "Proxy-Connection: Keep-Alive\r\n"
非必须
详细代码在这里
踩坑记录
one
golang的Transport
类型并不好用,文档很少,俩issues(net/http: document how to create client CONNECT requests, net/http: support bidirectional stream for CONNECT method)提到了如何使用。
截止2020.10.18,它不支持Host
字段的格式为<dest addr>:<port>
,前缀必须为//
/ http://
/ https://
(基于部分net/http
源码分析得到的结论),也就是说:__无法使用golang提供的Transport
建立任意基于HTTP隧道模式的TCP隧道__。
two
无法利用以下代码获取正确的端口:
1 | var origAddr net.TCPAddr |
因端口类型为2字节,而int为4字节,在做转换时会出错:
- 对于
80 (0x0050)
端口,会得到20480 (0x5000)
- 对于
443 (0x01bb)
端口,会得到47873 (0xbb01)
three
golang的net/http/response
的Response.Header.Write()
不会带上HTTP首部的:
导致curl在测试时一直等待http报文响应。