[我的小项目] 一个简单的跨平台代理软件:taosocks

陪她去流浪 桃子 阅读次数:20660

开篇

这是一个从去年11月份开始的一个新项目,名字瞎起的,叫 taosocks,是一个支持 SOCKS/HTTP 协议的智能跨平台隧道代理软件,至于哪里智能了,请继续阅读。

该软件的早期版本使用C++语言写成,基于完成端口,但是只能运行在 Windows 上。算是对协议的熟悉,以及对完成端口的练习。实现基本功能之后,此版本就停止开发了。开始学习 Go语言。学习一个月Go语言之后,就开始用Go语言全部重新实现之前的所有功能。

写这个软件主要是为了学习。学习协议、Go语言等。同时作为一个后备软件,你懂的。

自己试用了三个月了已经,很稳定。暂时先发文公开在这里,后面可能会删除文章并闭源项目。

架构与原理

整体架构与工作原理应该跟 shadowsocks 相近,但是我没有参阅它的任何逻辑(我不会 Python)。

该软件分为两端:服务端和客户端。服务端运行在服务器上,客户端运行在本地。

客户端接受来自本地的连接,然后通过服务器转发其流量到目标机器。

大概类似下面图示,图画得简单,但应该很好理解:

在没有启用代理时

                 互联网(数据透明传输)
浏览器 <------------------------------------------> 网站服务器

启用代理后

                                   互联网(数据加密传输)
浏览器 <-------> 代理客户端 <------------------------------------> 代理服务端 <-------> 网站服务器
+------------------------+
       运行在本地

以上两个图示中:浏览器代指所有有代理需求的软件,网站服务器代指所有类型的目标机器。下同。

数据包与加密协议

基本前提

其实从根本上来说,本代理软件本身并没有加密任何流量数据。数据的加密依靠的是 代理客户端 到 代理服务端 的连接协议。

代理服务端它其实是被设计成是一个 HTTPS 服务器。如果用浏览器直接连接代理服务端,将会打开一个配置好的网站(准备移除此功能)。

那么,代理客户端是如何连接到代理服务端的呢?答案很简单:使用 HTTP/1.1 的 协议升级请求。

HTTPS证书

证书用于保证 HTTPS 连接的正确建立。到目前为止,暂时都是使用的自签证书(非公开安全)。

关于证书的更多详细,暂时不多做介绍。

连接协议

  • 代理客户端首先会向一个浏览器一样发一个普通的 HTTP GET 请求到代理服务端。并附带如下的请求头部:

    Connection: Upgrade
    Upgrade: taosocks/<version>
    Authorization: taosocks <key>
    

    熟悉 HTTP协议 的朋友可能一看就明白了,这其实就是个 HTTP/1.1 协议中标准的升级协议请求,Authorization则是用来登录鉴权的。就像由 HTTP/1.1 升级到 WebSocket / HTTP2.0 那样。不过这里,升级到的是我自己的协议。

  • 代理服务端在检测到这个协议升级请求后,会验证协议的名字、版本、密钥,如果均验证成功,就会升级到目标协议。简单说就是把 TCP连接 转交给后续协议维持。更简单地说,就是劫持(Hijack)。

数据传输

连接被劫持后,代理服务端就不再是一个网站服务器的角色了,而成了真真正正的「代理服务端」。

代理客户端接收来自浏览器的数据,并通过互联网直接传输到代理服务端。代理服务端则负责把代理客户端发送过来的数据转发给目标网站。而如果是目标网站发给浏览器的数据,则由代理服务端通过互联网直接传输到代理客户端。其实说白了,就是个 TCP 中继。

由此看来,把加密交给 HTTPS 去做以后,本代理软件自己的协议及其简单了。

数据包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package internal

type OpenPacket struct {
    Addr    string
}

type OpenAckPacket struct {
    Status  bool
}

type RelayPacket struct {
    Data    []byte
}

超级简单,有木有?有!有!有!(真的超级简单,不骗你。逃……

  • OpenPacket 一个地址:负责告诉代理服务端:浏览器要连接哪个网站服务器。
  • OpenAckPacket 一个布尔值:负责告诉代理客户端:这个网站服务器的连接状况。
  • RelayPacket 一个字节数组(切片):自然就是中继数据。

用户认证

用户的认证在建立到代理服务端的连接的时候就开始了,即:Authorization 的请求头部值。

如果验证成功,则升级协议。如果验证失败,忽略此升级请求,并当成普通 HTTP 请求对待。

也就是说:你无法探测一个 HTTPS 网站服务器它到底是不是一个支持代理的代理服务端。

除非,除非你同时提供了正确的:协议名字、协议版本、密钥。

过滤规则

支持的主机过滤规则:

  1. 域名
  2. IP地址
  3. CIDR

支持的过滤规则有:

  1. 直接连接
  2. 总是代理
  3. 拒绝连接

智能代理

另外,本代理软件最大的亮点是:智能代理。

智能代理会在目标网站无法通过本地访问时,自动切换到远程代理。并自动添加代理规则。 由于是智能识别,在第一次访问不能通过本地访问的网站时,可能要花费较长的时间来确定规则。但此后就非常快了,因为规则被记录了。

目前的智能算法有以下几点:

  1. 如果域名解析失败。即:无解析
  2. 如果域名解析被污染。即:解析出错误的IP地址
  3. 如果目标 HTTP 服务器无响应。即:发送了数据,但没有收到响应
  4. 如果目标 HTTPS 数据包被丢弃。即:TLS协议未完成,比如:只发送了 Client Hello,但没收到回复

示例输出

大概会有类似下面这样的输出:

2018/05/02 22:53:56 ? Dial host:www.facebook.com:443 -  dial tcp 66.220.149.18:443: getsockopt: connection refused
2018/05/02 22:53:58 > [Proxy ] www.facebook.com:443
2018/05/02 22:53:58 + 添加代理规则:www.facebook.com
2018/05/02 22:53:59 < [Proxy ] www.facebook.com:443 [TX:822, RX:342]
2018/05/02 22:54:09 ? Dial host:static.xx.fbcdn.net:443 -  dial tcp: i/o timeout
2018/05/02 22:54:11 > [Proxy ] static.xx.fbcdn.net:443
2018/05/02 22:54:11 + 添加代理规则:static.xx.fbcdn.net
2018/05/02 22:54:13 < [Proxy ] static.xx.fbcdn.net:443 [TX:1143, RX:6240]
2018/05/02 22:54:23 ? Dial host:scontent-lax3-1.xx.fbcdn.net:443 -  dial tcp: i/o timeout
2018/05/02 22:54:25 > [Proxy ] scontent-lax3-1.xx.fbcdn.net:443
2018/05/02 22:54:25 + 添加代理规则:scontent-lax3-1.xx.fbcdn.net
2018/05/02 22:54:26 ? Dial host:staticxx.facebook.com:443 -  dial tcp 69.171.240.27:443: i/o timeout
2018/05/02 22:54:27 < [Proxy ] facebook.com:443 [TX:822, RX:342]
2018/05/02 22:54:28 + 添加代理规则:staticxx.facebook.com
2018/05/02 22:54:28 > [Proxy ] staticxx.facebook.com:443
2018/05/02 22:54:29 ? Dial host:cx.atdmt.com:443 -  dial tcp 199.16.156.40:443: getsockopt: connection refused
2018/05/02 22:54:31 > [Proxy ] cx.atdmt.com:443
2018/05/02 22:54:31 + 添加代理规则:cx.atdmt.com
2018/05/02 22:54:31 + 添加代理规则:cx.atdmt.com
2018/05/02 22:54:31 > [Proxy ] cx.atdmt.com:443
2018/05/02 22:54:34 < [Proxy ] cx.atdmt.com:443 [TX:1284, RX:684]

详细配置见文后「配置文件」。

代码与编译

项目地址

一如既往,托管在 GitHub 上: taosocks - A smart tunnel proxy that helps you bypass firewalls.。

目录结构

目录 含义
client 客户端代码
server 服务端代码
internal 客户端与服务端公共文件
config 配置文件目录

项目编译

本项目未依赖任何第三方的包,编译简单。

分别进入到 client 和 server 目录后,执行 go build 命令即可。

配置文件

rules.txt

这个是代理规则文件。规定哪些地址直接连接、走代理或被拒绝。

格式:

域名,规则
IP,规则
IP段,规则

比如:

# qq.com 或以 qq.com 结尾的:直连
qq.com,direct

# 192.168.*.* 直连
192.168.0.0/16,direct

不在此规则中的主机将默认为直接访问,或根据智能代理确定访问。

rules-auto.txt

这里保存了自动添加的规则。程序退出(^C)和启动时,会自动读取里面的规则,并设置为走代理。

运行

注意:请在命令行下执行!请在项目根目录执行!

根目录结构

$ ls -l
total 15
drwxr-xr-x 1 tao 197121   0 Jan 12 09:04 client
drwxr-xr-x 1 tao 197121   0 Jan 12 09:04 config
drwxr-xr-x 1 tao 197121   0 Dec  5 09:55 internal
drwxr-xr-x 1 tao 197121   0 Jan 12 09:04 server

运行代理服务端

代理服务端仅需两个参数,其它的固定在代码里面了。

$ ./server/server -h
Usage of ./server/server:
  -key string
    	the key
  -listen string
    	listen address(host:port) (default "0.0.0.0:1081")

$ ./bin/server --key="your password"

如果没报错的话,代理服务端就已经正常运行了。同时它了是一个 HTTPS 服务器。可以用 cURL 试试(输出内容随版本变更可能不一致)。

$ curl -k https://127.0.0.1:1081
<!doctype html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>
    <h1>Welcome home!</h1>
</body>
</html>

运行代理客户端

客户端参数有好几个。

  • --listen 本地监听地址,SOCKS代理和HTTP代理共用
  • --server 代理服务端地址
  • --insecure 是否验证证书,目前请总是设置成否(有此参数即可)
  • --key 密钥

运行起来:

$ ./client/client -h
Usage of ./client/client:
  -insecure
    	don't verify server certificate (default true)
  -key string
    	login key
  -listen string
    	listen address(host:port) (default "0.0.0.0:1080")
  -server string
    	server address(host:port) (default "127.0.0.1:1081")
        
$ ./bin/client --key="your password"

设置浏览器代理/cURL试试

HTTP代理:

$ curl --proxy http://127.0.0.1:1080 https://example.com
<!doctype html>
<html>
<head>
    <title>Example Domain</title>
    <meta charset="utf-8" />
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is established to be used for illustrative examples in documents. You may use this
    domain in examples without prior coordination or asking for permission.</p>
    <p><a href="http://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

SOCKS代理:

$ curl --proxy socks5h://127.0.0.1:1082 https://example.com
<!doctype html>
<html>
<head>
    <title>Example Domain</title>
    <meta charset="utf-8" />
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is established to be used for illustrative examples in documents. You may use this
    domain in examples without prior coordination or asking for permission.</p>
    <p><a href="http://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

输出的日志

服务端:

2018/03/09 16:12:41 > example.com:443
2018/03/09 16:12:42 < example.com:443
2018/03/09 16:14:19 > example.com:443
2018/03/09 16:14:20 < example.com:443

客户端:

2018/03/09 16:12:41 > [Proxy ] example.com:443
2018/03/09 16:12:42 < [Proxy ] example.com:443 [TX:929, RX:4934]
2018/03/09 16:14:19 > [Proxy ] example.com:443
2018/03/09 16:14:20 < [Proxy ] example.com:443 [TX:929, RX:4934]

标签:代理 · 我的小项目 · Go