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

陪她去流浪 桃子 2018年03月09日 阅读次数:6357

开篇

这是一个从去年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 去做以后,本代理软件自己的协议及其简单了。

数据包

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 配置文件目录

项目编译

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

分别进入到 clientserver 目录后,执行 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