From 4a33464097fb2a602850e2efb3e982ec01a8fb1c Mon Sep 17 00:00:00 2001 From: Page Fault Date: Thu, 11 Jun 2020 04:29:02 +0000 Subject: [PATCH] aead redirection, update docs --- README.md | 112 +++++++-------------- build/client.go | 2 +- build/forward.go | 2 +- build/mysql.go | 2 +- build/nat.go | 2 +- build/other.go | 7 ++ build/server.go | 2 +- docs/content/advance/relay.md | 25 ----- docs/content/advance/router.md | 25 ++--- docs/content/basic/full-config.md | 77 +++++++------- docs/content/developer/mux.md | 11 +- docs/content/developer/overview.md | 68 ++++++++++++- docs/content/developer/plugin.md | 2 + docs/content/developer/simplesocks.md | 2 +- docs/content/developer/websocket.md | 42 ++------ easy/easy.go | 134 +++++++++++++++++++++++++ proxy/server/server.go | 8 +- test/scenario/proxy_test.go | 72 ++++++++++++- tunnel/shadowsocks/client.go | 6 +- tunnel/shadowsocks/config.go | 8 +- tunnel/shadowsocks/conn.go | 27 +++++ tunnel/shadowsocks/server.go | 38 +++++-- tunnel/shadowsocks/shadowsocks_test.go | 53 +++++++++- tunnel/simplesocks/client.go | 2 + tunnel/simplesocks/server.go | 1 + 25 files changed, 512 insertions(+), 218 deletions(-) create mode 100644 build/other.go delete mode 100644 docs/content/advance/relay.md create mode 100644 easy/easy.go create mode 100644 tunnel/shadowsocks/conn.go diff --git a/README.md b/README.md index 3505539..92d7334 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ 支持[CDN流量中转](#Websocket)(基于WebSocket over TLS/SSL)。 -支持基于ACME协议从Let's Encrypt[自动申请和更新](#证书申请)HTTPS证书,只需提供域名和邮箱。 +支持使用AEAD对Trojan流量[二次加密](#AEAD)(基于Shadowsocks AEAD) -支持可插拔的[传输层插件](#传输层插件),允许替换TLS,实现其他流量混淆和加密。 +支持可插拔的[传输层插件](#传输层插件),允许替换TLS,使用其他加密隧道传输Trojan协议流量。 -预编译的版本可在 [Release 页面](https://github.com/p4gefau1t/trojan-go/releases)下载。直接运行解压得到的执行文件即可,无其他组件依赖。 +预编译的版本可在[Release 页面](https://github.com/p4gefau1t/trojan-go/releases)下载。直接运行解压得到的执行文件即可,无其他组件依赖。 如果你遇到配置和使用方面的问题,发现了软件Bug,或是有更好的想法,欢迎加入Trojan-Go的[Telegram交流反馈群](https://t.me/trojan_go_chat)。 @@ -44,8 +44,6 @@ Trojan-Go支持并且兼容Trojan-GFW的绝大多数功能,包括但不限于 - 从数据库中的用户列表进行认证 -- TCP Keep Alive,TCP Fast Open,端口复用等TCP选项 - 同时,Trojan-Go还扩展了更多高效易用的功能特性: - 简易模式,快速部署使用 @@ -54,7 +52,7 @@ Trojan-Go支持并且兼容Trojan-GFW的绝大多数功能,包括但不限于 - 多平台和多操作系统支持,无特殊依赖 -- 多路复用,降低延迟,提升并发性能 +- 基于多路复用(smux)降低延迟,提升并发性能 - 自定义路由模块,可实现国内直连/广告屏蔽等功能 @@ -68,6 +66,8 @@ Trojan-Go支持并且兼容Trojan-GFW的绝大多数功能,包括但不限于 - 可插拔传输层,可将TLS替换为其他协议或明文传输。同时有完整的Shadowsocks混淆插件支持。 +- 支持对人类更友好的YAML配置文件格式 + ## 图形界面客户端 Trojan-Go服务端可以兼容所有Trojan-GFW的客户端,如Igniter,ShadowRocket等。 @@ -80,25 +80,7 @@ Trojan-Go服务端可以兼容所有Trojan-GFW的客户端,如Igniter,Shadow ## 使用方法 -1. 快速证书配置 - - - 自动申请证书 - - ```shell - sudo ./trojan-go -autocert request - ``` - - (**注意备份生成的证书和密钥,并确保其安全**) - - - 为证书续期 - - ```shell - sudo ./trojan-go -autocert renew - ``` - - 关于证书申请[更详细的说明](#证书申请)。 - -2. 快速启动服务器和客户端(简易模式) +1. 快速启动服务器和客户端(简易模式) - 服务端 @@ -112,13 +94,13 @@ Trojan-Go服务端可以兼容所有Trojan-GFW的客户端,如Igniter,Shadow ./trojan-go -client -remote example.com:443 -local 127.0.0.1:1080 -password your_password ``` -3. 使用配置文件启动客户端/服务端/透明代理/中继(一般模式) +2. 使用配置文件启动客户端/服务端/透明代理/中继(一般模式) ```shell ./trojan-go -config config.json ``` -4. 使用Docker部署 +3. 使用Docker部署 ```shell docker run \ @@ -203,53 +185,16 @@ client.json } ``` - - -### 自动证书申请 - -使用 - -```shell -sudo ./trojan-go -autocert request -``` - -向Let's Encrypt申请证书。 - -申请过程中,按照ACME协议要求,trojan-go需要和letsencrypt服务器交互,因此需要暂时占用本地443和80端口,此时请暂时关闭nginx,apache,或者trojan等服务。 - -Linux下,绑定80和443端口需要root权限,因此你需要使用sudo执行trojan-go才能正常证书申请流程。 - -你也可以指定自定义端口,然后使用nginx等web服务器进行443和80分流,将ACME协议流量代理到自定义端口上。 - -如果申请成功,本目录下会得到 - -- server.key 服务器私钥 - -- server.crt 经过Let's Encrypt签名的服务器证书 - -- user.key 用户Email对应的私钥 - -- domain_info.json 域名和用户Email信息 - -请备份这几个文件并且妥善保管。接下来你可以将服务器私钥和证书文件名填入你的配置文件,开启你的trojan-go服务器即可。 - -如果证书过期了,使用 - -```shell -sudo ./trojan-go -autocert renew -``` - -更新证书,确保上面提到的四个文件在trojan-go所在目录,运行后trojan-go将自动更新证书文件。 + ### WebSocket - - Trojan-Go支持使用TLS+Websocket承载Trojan协议,使得利用CDN进行流量中转成为可能。 服务器和客户端配置文件中同时添加```websocket```选项即可启用Websocket支持,例如 ```json +... "websocket": { "enabled": true, "path": "/im_a_url_path", @@ -263,9 +208,10 @@ Trojan-Go支持使用TLS+Websocket承载Trojan协议,使得利用CDN进行流 由于Trojan-GFW版本并不支持Websocket,因此,虽然开启了Websocket支持的Trojan-Go服务端可以兼容所有客户端,但是如果要使用Websocket承载流量,请确保双方都使用Trojan-Go。 + + ### 多路复用 - 在很差的网络条件下,一次TLS握手可能会花费很多时间。 @@ -276,6 +222,7 @@ Trojan-Go支持多路复用(基于[smux](https://github.com/xtaci/smux))。通 注意,这个特性和Trojan-GFW**不兼容**,出于兼容性考虑,这个特性是默认关闭的。你可以通过设置客户端的mux选项"enabled"字段启用它。如下 ```json +... "mux": { "enabled": true } @@ -285,10 +232,10 @@ Trojan-Go支持多路复用(基于[smux](https://github.com/xtaci/smux))。通 你只需要设置客户端的配置文件即可,服务端会自动检测是否启用多路复用并提供支持。 -### 路由模块 - +### 路由模块 + Trojan-Go的客户端内建一个简单实用的路由模块用以方便实现国内直连等自定义路由功能。 路由策略有三种 @@ -302,6 +249,7 @@ Trojan-Go的客户端内建一个简单实用的路由模块用以方便实现 要激活模块,在你的配置文件中添加router选项,并且设置enabled为true,例如 ```json +... "router": { "enabled": true, "bypass": [ @@ -341,6 +289,22 @@ Trojan-Go的客户端内建一个简单实用的路由模块用以方便实现 完整的选项说明参见[Trojan-Go 文档](https://p4gefau1t.github.io/trojan-go)。 + + +## AEAD加密 + +Trojan-Go允许对Trojan协议基于Shadowsocks AEAD进行加密,以保证Websocket传输流量无法被不可信的CDN识别和审查。下面是一个例子 + +```json +... +"shadowsocks": { + "enabled": true, + "password": "my_password" +} +``` + +一旦开启,服务端和客户端必须同时开启并保证密码一致。 + ## 传输层插件 @@ -352,17 +316,19 @@ Trojan-Go支持可插拔的传输层插件,并支持Shadowsocks [SIP003](https 服务端配置: ```json +... "transport_plugin": { - "enabled": true, - "type": "shadowsocks", - "command": "./v2ray-plugin", - "arg": ["-server", "-host", "www.baidu.com"] + "enabled": true, + "type": "shadowsocks", + "command": "./v2ray-plugin", + "arg": ["-server", "-host", "www.baidu.com"] } ``` 客户端配置: ```json +... "transport_plugin": { "enabled": true, "type": "shadowsocks", diff --git a/build/client.go b/build/client.go index f855ca0..6f578cb 100644 --- a/build/client.go +++ b/build/client.go @@ -1,4 +1,4 @@ -// +build client full +// +build client full standard package build diff --git a/build/forward.go b/build/forward.go index 4ad95c4..9c87964 100644 --- a/build/forward.go +++ b/build/forward.go @@ -1,4 +1,4 @@ -// +build forward full +// +build forward full standard package build diff --git a/build/mysql.go b/build/mysql.go index 357f8e4..2c546f2 100644 --- a/build/mysql.go +++ b/build/mysql.go @@ -1,4 +1,4 @@ -// +build mysql full +// +build mysql full standard package build diff --git a/build/nat.go b/build/nat.go index 6bdae9e..596f6e7 100644 --- a/build/nat.go +++ b/build/nat.go @@ -1,4 +1,4 @@ -// +build nat full +// +build nat full standard package build diff --git a/build/other.go b/build/other.go new file mode 100644 index 0000000..fc695d8 --- /dev/null +++ b/build/other.go @@ -0,0 +1,7 @@ +// +build other full + +package build + +import ( + _ "github.com/p4gefau1t/trojan-go/easy" +) diff --git a/build/server.go b/build/server.go index 8469416..90c1742 100644 --- a/build/server.go +++ b/build/server.go @@ -1,4 +1,4 @@ -// +build client full +// +build server full standard package build diff --git a/docs/content/advance/relay.md b/docs/content/advance/relay.md deleted file mode 100644 index 15d52d8..0000000 --- a/docs/content/advance/relay.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: "流量中继" -draft: false -weight: 4 ---- - -### 注意,Trojan-GFW版本不支持这个特性 - -Trojan-Go支持进行流量转发。一个典型的使用场景是,你所使用的ISP提供的网络服务,出境的线路质量并不理想。这时你可以使用国内的一些线路更好的服务器,作为中继,将你的流量转发给trojan服务器。 - -中继的配置很简单,下面是一个例子 - -```json -{ - "run_type": "relay", - "local_addr": "0.0.0.0", - "local_port": 1234, - "remote_addr": "your_trojan_server", - "remote_port": 443, -} -``` - -中继启动后,客户端连接该主机的1234端口,和直接连接Trojan服务器443端口是等效的。中继只做简单的流量转发,本地不需要任何的证书文件和密钥文件。你也可以使用socat或者iptables实现上述功能。 - -你可以使用多个中继链接起来作为多重跳板,如果你能忍受这种做法带来的延迟升高和吞吐量下降的话。 diff --git a/docs/content/advance/router.md b/docs/content/advance/router.md index e9e0b16..4f547b7 100644 --- a/docs/content/advance/router.md +++ b/docs/content/advance/router.md @@ -50,22 +50,23 @@ Trojan-Go内建的路由模块可以帮助你实现国内直连,即客户端 所需要的数据库geoip.dat和geosite.dat已经包含在release的压缩包中,直接使用即可。它们来自v2ray的[domain-list-community](https://github.com/v2ray/domain-list-community)和[geoip](https://github.com/v2ray/geoip)。你可以使用如```geosite:cn```,```geosite:bilibili```的形式来指定某一类域名,使用如```geoip:cn```,```geoip:private```的形式来指定某一类IP。所有可用的tag可以在[domain-list-community](https://github.com/v2ray/domain-list-community)仓库中找到。 -你也可以配置自己的列表文件,列表文件每一行是一个域名或者IP子网(CIDR)。例如,想要屏蔽所有example.com域名以及其子域名,以及192.168.1.0/24,只需要编写一个txt文件 - -test_list.txt - -```text -example.com -192.168.1.0/24 -``` - -然后在修改上面的block选项,添加一行指定该文件名即可 +你也可以配置自己的路由规则。例如,想要屏蔽所有example.com域名以及其子域名,以及192.168.1.0/24,添加下面的规则。 ```json "block": [ - "geosite:category-ads", - "test_list.txt" + "domain:example.com", + "cidr:192.168.1.0/24" ] ``` +支持的格式有 + +- "domain:",子域名匹配 + +- "full:",完全域名匹配 + +- "regex:",正则表达式匹配 + +- "cidr:",CIDR匹配 + 更详细的说明参考"完整的配置文件"一节。 diff --git a/docs/content/basic/full-config.md b/docs/content/basic/full-config.md index 1b68988..6b8207a 100644 --- a/docs/content/basic/full-config.md +++ b/docs/content/basic/full-config.md @@ -59,10 +59,7 @@ weight: 30 "tcp": { "no_delay": true, "keep_alive": true, - "reuse_port": false, "prefer_ipv4": false, - "fast_open": false, - "fast_open_qlen": 20 }, "mux": { "enabled": false, @@ -82,22 +79,12 @@ weight: 30 "websocket": { "enabled": false, "path": "", - "hostname": "", - "obfuscation_password": "", - "double_tls": true, - "ssl": { - "verify": true, - "verify_hostname": true, - "cert": "", - "key": "", - "key_password": "", - "prefer_server_cipher": false, - "sni": "", - "session_ticket": true, - "reuse_session": true, - "plain_http_response": "", - "key_log": "" - } + "hostname": "" + }, + "shadowsocks": { + "enabled": false, + "method": "", + "password": "" }, "transport_plugin": { "enabled": false, @@ -184,7 +171,7 @@ weight: 30 服务端必须填入```cert```和```key```,对应服务器的证书和私钥文件,请注意证书是否有效/过期。如果使用权威CA签发的证书,客户端(client/nat/forward)可以不填写```cert```。如果使用自签名或者自签发的证书,应当在的```cert```处填入服务器证书文件,否则可能导致校验失败。 -```sni```指的是证书的Common Name,如果你使用letsencrypt等机构签名的证书,这里填入你的域名。如果这一项未填,将使用```remote_addr```填充。你应当指定一个有效的SNI(和远端证书CN一致),否则客户端可能无法验证远端证书有效性从而无法连接。 +```sni```指的是TLS客户端请求中的服务器名字段,一般和证书的Common Name相同。如果你使用let'sencrypt等机构签发的证书,这里填入你的域名。如果这一项未填,将使用```remote_addr```填充。你应当指定一个有效的SNI(和远端证书CN一致),否则客户端可能无法验证远端证书有效性从而无法连接。 ```alpn```为TLS的应用层协议协商指定协议。在TLS Client/Server Hello中传输,协商应用层使用的协议,仅用作指纹伪造,并无实际作用。**如果使用了CDN,错误的alpn字段可能导致与CDN协商错误的应用层协议**。 @@ -198,8 +185,6 @@ weight: 30 - "",不使用指纹伪造 -- "auto",自动尝试并选择 - - "firefox",伪造Firefox指纹(默认) - "chrome",伪造Chrome指纹 @@ -238,7 +223,7 @@ weight: 30 - Block 封锁。不代理请求,直接关闭连接。 -在```proxy```, ```bypass```, ```block```字段中填入对应列表文件名或者geoip/geosite标签名,trojan-go即根据列表中的IP(CIDR)或域名执行相应路由策略。列表文件中每行是一个IP或者域名,trojan-go会自动识别。客户端(client)可以配置三种策略,服务端(server)只可配置block策略。 +在```proxy```, ```bypass```, ```block```字段中填入对应列表geoip/geosite或路由规则,trojan-go即根据列表中的IP(CIDR)或域名执行相应路由策略。客户端(client)可以配置三种策略,服务端(server)只可配置block策略。 ```enabled```是否开启路由模块。 @@ -256,7 +241,7 @@ weight: 30 - "as_is",只在域名列表中进行匹配。 -- "ip_if_nonmatch",在域名列表中进行匹配,如果不匹配,解析为IP后在IP列表中匹配。该策略可能导致DNS泄漏或遭到污染。 +- "ip_if_non_match",在域名列表中进行匹配,如果不匹配,解析为IP后在IP列表中匹配。该策略可能导致DNS泄漏或遭到污染。 - "ip_on_demand",域名均解析为IP,在IP列表中匹配。该策略可能导致DNS泄漏或遭到污染。 @@ -264,13 +249,7 @@ weight: 30 ### ```websocket```选项 -Websocket传输是trojan-go的特性。在**正常的直接连接代理节点**的情况下,开启这个选项不会改善你的链路速度(甚至有可能下降),也不会提升你的连接安全性。你只应该在下面两种情况下启用它: - -- 你需要利用CDN进行流量中转 - -- 你到代理节点的直接TLS连接遭到了GFW的中间人攻击 - -警告:**由于信任CDN证书并使用CDN网络进行传输,HTTPS连接对于CDN是透明的,CDN运营商可以直接审计Websocket流量传输内容。如果你使用了国内的CDN,应当假定CDN不可信任,请务必开启double_tls进行双重加密,并使用obfuscation_password进行流量混淆** +Websocket传输是trojan-go的特性。在**正常的直接连接代理节点**的情况下,开启这个选项不会改善你的链路速度(甚至有可能下降),也不会提升你的连接安全性。你只应该在需要利用CDN进行中转,或利用nginx等服务器根据路径分发的情况下,使用websocket。 ```enabled```表示是否启用Websocket承载流量,服务端开启后同时支持一般Trojan协议和基于websocket的Trojan协议,客户端开启后将只使用websocket承载所有Trojan协议流量。 @@ -278,11 +257,33 @@ Websocket传输是trojan-go的特性。在**正常的直接连接代理节点** ```hostname```Websocket握手时使用的主机名,客户端如果留空则使用```remote_addr```填充。如果使用了CDN,这个选项一般填入域名。 -```double_tls```是否开启双重TLS,默认开启。开启后在TLS+Websocket上将会再承载一次TLS连接。双重TLS的意义在于即使第一层TLS遭到中间人攻击也能保证通信安全。第二层TLS的证书校验被强制打开。客户端和服务端设置必须相同。这个选项对性能有一定影响,请自行斟酌安全性和性能的平衡。 +## ``shadowsocks`` AEAD加密选项 -```ssl```如果```double_tls```启用,这个选项用于配置第二层TLS,如果没有填写则使用全局的```ssl```填充。各字段定义与全局```ssl```相同。 +此选项用于替代弃用的混淆加密和双重TLS。如果此选项被设置启用,Trojan协议层下将插入一层Shadowsocks AEAD加密层。也即(已经加密的)TLS隧道内,所有的Trojan协议将再使用AEAD加密。注意,此选项和Websocket是否开启无关。无论Websocket是否开启,所有Trojan流量都会被再进行一次加密。 -```obfuscation_password```指定混淆密码。用于混淆内层连接以避免遭到CDN运营商识别。如果需要使用混淆,服务端和客户端必须设置相同的密码。这个选项对性能有一定影响,请自行斟酌安全性和性能的平衡。 +注意,开启这个选项将有可能降低传输性能,你只应该在不信任承载Trojan协议的传输信道的情况下,启用这个选项。例如: + +- 你使用了Websocket,经过不可信的CDN进行中转(如国内CDN) + +- 你与服务器的连接遭到了GFW针对TLS的中间人攻击 + +- 你的证书失效,无法验证证书有效性 + +等等。 + +由于使用的是AEAD,trojan-go启用了AEAD加密后依然可以正确判断是否遭到主动探测,并作出相应的响应。 + +```enabled```是否启用Shadowsocks AEAD加密Trojan协议层。 + +```method```加密方式。合法的值有: + +- "CHACHA20-IETF-POLY1305" + +- "AES-128-GCM" + +- "AES-256-GCM" + +```password```用于生成主密钥的密码。必须确保客户端和服务端一致。 ### ```transport_plugin```传输层插件选项 @@ -292,7 +293,7 @@ Websocket传输是trojan-go的特性。在**正常的直接连接代理节点** - "shadowsocks",支持符合[SIP003](https://shadowsocks.org/en/spec/Plugin.html)标准的shadowsocks混淆插件。trojan-go将在启动时按照SIP003标准替换环境变量并修改自身配置(```remote_addr/remote_port/local_addr/local_port```),使插件与远端直接通讯,而trojan-go仅监听/连接插件。 -- "plaintext",使用明文传输。选择此项,trojan-go不会修改任何地址配置(```remote_addr/remote_port/local_addr/local_port```),也不会启动```command```中插件,仅移除最底层的TLS传输层并使用TCP明文传输。此选项目的为支持nginx接管TLS并进行分流,以及高级用户进行调试测试。**请勿直接使用明文传输模式穿透防火墙。** +- "plaintext",使用明文传输。选择此项,trojan-go不会修改任何地址配置(```remote_addr/remote_port/local_addr/local_port```),也不会启动```command```中插件,仅移除最底层的TLS传输层并使用TCP明文传输。此选项目的为支持nginx等接管TLS并进行分流,以及高级用户进行调试测试。**请勿直接使用明文传输模式穿透防火墙。** - "other",其他插件。选择此项,trojan-go不会修改任何地址配置(```remote_addr/remote_port/local_addr/local_port```),但会启动```command```中插件并传入参数和环境变量。 @@ -310,14 +311,8 @@ Websocket传输是trojan-go的特性。在**正常的直接连接代理节点** ```keep_alive```是否启用TCP心跳存活检测。 -```reuse_port```是否启用端口复用。由于trojan-gfw版本对多线程支持不佳,因而服务器使用此选项开启多个进程监听同一端口以提升并发性能。trojan-go本身的并发性能足够优秀,并无必要开启此选项。该选项仅为兼容而保留。 - ```prefer_ipv4```是否优先使用IPv4地址。 -```fast_open```是否启用TCP Fast Open。开启此选项需要操作系统支持。考虑到TFO开启后的TCP封包特征明显,容易被GFW阻断,且可能存在安全性问题,trojan-go仅仅出于兼容目的在服务端实现TFO支持。 - -```fast_open_qlen```TCP Fast Open的qlen值,即允许的同时发起的未经三次握手的TFO连接数量。 - ### ```mysql```数据库选项 trojan-go兼容trojan-gfw的基于mysql的用户管理方式,但更推荐的方式是使用API。 diff --git a/docs/content/developer/mux.md b/docs/content/developer/mux.md index 2d4e554..b371c29 100644 --- a/docs/content/developer/mux.md +++ b/docs/content/developer/mux.md @@ -10,9 +10,8 @@ Trojan-Go使用[smux](https://github.com/xtaci/smux)实现多路复用。同时 |协议 |备注 | |----------------|---------------| -|真实流量| -|SimpleSocks| -|smux| -|Trojan |仅用于认证| -|TLS| -|TCP| +|真实流量 | +|SimpleSocks | +|smux | +|Trojan |用于鉴权 | +|底层协议 | | diff --git a/docs/content/developer/overview.md b/docs/content/developer/overview.md index 7811399..bbd9410 100644 --- a/docs/content/developer/overview.md +++ b/docs/content/developer/overview.md @@ -6,14 +6,72 @@ weight: 1 Trojan-Go的核心部分有 -- protocol 各个协议具体实现 +- tunnel 各个协议具体实现 -- proxy 代理核心,使用protocol的协议实现,处理入站和出站流量 +- proxy 代理核心 -- conf 配置解析模块 +- config 配置注册和解析模块 -- shadow 主动检测欺骗模块 +- redirector 主动检测欺骗模块 -- stat 用户认证和统计模块 +- statistics 用户认证和统计模块 可以在对应文件夹中找到相关源代码。 + +## tunnel.Tunnel隧道 + +Trojan-Go将所有协议(包括路由功能等)抽象为隧道(tunnel.Tunnel接口),每个隧道可开启服务端(tunnel.Server接口)和客户端(tunnel.Client)。每个服务端可以从其底层隧道中,剥离并接受流(tunnel.Conn)和包(tunnel.PacketConn)。客户端可以向底层隧道,创建流和包。 + +每个隧道并不关心其下方的隧道是什么,但是每个隧道清楚知道这个它上方的其他隧道的相关信息。 + +所有隧道需要下层提供流或包传输支持,或两者都要求提供。所有隧道必须向上层隧道提供流传输支持,但不一定提供包传输。 + +隧道可能只有服务端,也可能只有客户端,也可能两者皆有。两者皆有的隧道,可被用于作为Trojan-Go客户端和服务端间的传输隧道。 + +注意,请区分Trojan-Go的服务端/客户端,和隧道的服务端/客户端的区别。下面是一个方便理解的图例。 + +```text + + 入站 GFW 出站 +-------->隧道A服务端->隧道B客户端 ----------------> 隧道B服务端->隧道C客户端-----------> + (Trojan-Go客户端) (Trojan-Go服务端) + +``` + +最底层的隧道为传输层,即不从其他隧道获取或者创建流和包的隧道,充当上图中隧道A或者C的角色。 + +- transport,TLS和可插拔传输层 + +- socks,socks5代理,仅隧道服务端 + +- tproxy,透明代理,仅隧道服务端 + +- dokodemo,反向代理,仅隧道服务端 + +- raw,原始TCP/UDP + +这几个隧道直接从TCP/UDP Socket创建流和包,不接受为其底层添加的任何隧道。 + +其他隧道,只要下层能满足上层对包和流传输的需求,则原则上可以任何方式,任何数量进行组合和堆叠。这些隧道在上图中充当隧道B的角色,他们有 + +- trojan + +- websocket + +- mux + +- simplesocks + +- router,路由功能,仅隧道客户端 + +他们都不关心其下层隧道实现。但可以根据到来的流和包,将其分发给上层隧道。 + +# proxy.Proxy代理核心 + +代理核心的作用,监听上述隧道进行组合堆叠并形成的协议栈,将所有的入站协议栈(多个的隧道Server)中抽取的流和包,以及对应元信息,转送给出站协议栈(一个隧道Client)。 + +注意,这里的入站协议栈可以有多个,如客户端可以同时从Socks5和HTTP协议栈中抽取流和包,服务端可以同时从Websocket承载的Trojan协议,和TLS承载的Trojan协议中抽取流和包等。但是出站协议栈只能有一个。 + +为了描述入站协议栈(隧道服务端)的组合和堆叠方式,使用一棵多叉树对所有协议栈进行描述。你可以在client/forward/nat/server中看到构建树的过程。 + +而出站协议栈则比较简单,使用一个简单列表即可描述。 diff --git a/docs/content/developer/plugin.md b/docs/content/developer/plugin.md index 1872b89..4605d19 100644 --- a/docs/content/developer/plugin.md +++ b/docs/content/developer/plugin.md @@ -6,6 +6,8 @@ weight: 150 Trojan-Go鼓励开发传输层插件,以丰富协议类型,增加与GFW对抗的战略纵深。 +传输层插件的作用,是替代tansport隧道的TLS进行传输加密和混淆。 + 插件与Trojan-Go基于TCP Socket通讯,与Trojan-Go本身不存在任何耦合关系,你可以使用任何你喜欢的语言和设计模式进行开发。我们建议的参照[SIP003](https://shadowsocks.org/en/spec/Plugin.html)标准进行开发。如此开发的插件可以同时用于Trojan-Go和Shadowsocks。 Trojan-Go开启插件功能后,仅使用TCP进行传输(明文)。你的插件只需要处理入站的TCP请求即可。你可以将这些TCP流量转换成任何你喜欢的流量格式,如QUIC,HTTP,甚至是ICMP。 diff --git a/docs/content/developer/simplesocks.md b/docs/content/developer/simplesocks.md index 2ec4c53..1ae04cd 100644 --- a/docs/content/developer/simplesocks.md +++ b/docs/content/developer/simplesocks.md @@ -4,7 +4,7 @@ draft: false weight: 50 --- -SimpleSocks协议是无认证机制的简单代理协议,本质上是去除了sha224的Trojan协议。使用该协议的目的是减少多路复用时的overhead。 +SimpleSocks协议是无鉴权机制的简单代理协议,本质上是去除了sha224的Trojan协议。使用该协议的目的是减少多路复用时的overhead。 只有启用多路复用之后,被复用的连接才会使用这个协议。也即SimpleSocks总是被SMux承载。 diff --git a/docs/content/developer/websocket.md b/docs/content/developer/websocket.md index 157af6f..090339e 100644 --- a/docs/content/developer/websocket.md +++ b/docs/content/developer/websocket.md @@ -4,46 +4,20 @@ draft: false weight: 40 --- -由于使用CDN中转时,HTTPS对CDN透明,CDN可以审查Websocket传输内容,而Trojan协议明文的头部特征过于明显,而TLS握手特征也很明显。为了保证Websocket传输的隐蔽和安全,默认情况下还会进行一次AES加密(混淆层)和TLS连接(双重TLS)。其中TLS用于保证传输的安全性,AES加密仅仅只是用于混淆流量。 +由于使用CDN中转时,HTTPS对CDN透明,CDN可以审查Websocket传输内容。而Trojan协议本身是明文传输,因此为保证安全性,可添加一层Shadowsocks AEAD加密层以混淆流量特征并保证安全性。 -**如果你使用的是国内CDN,务必保证两者均开启。最坏情况下也应当保持混淆和双重TLS之一是打开的。** +**如果你使用的是中国境内运营商提供的CDN,请务必开启AEAD加密** -开启Websocket模块的客户端可以使用```obfuscation_password```字段开启混淆,以及使用```double_tls```启用双重TLS以确保信道安全性。 - -混淆层使用AES-CTR-128密码系统。加密主密钥派生自```obfuscation_password```:使用16字节的盐和sha1散列算法,使用pbkdf2对```obfuscation_password```密码进行32次迭代,派生得到16字节的主密钥。盐为硬编码的随机字节,定义为 - -```go -salt := []byte{48, 149, 6, 18, 13, 193, 247, 116, 197, 135, 236, 175, 190, 209, 146, 48} -``` - -在每次连接开始时,生成16字节的随机IV。根据主密钥和IV,使用AES-CTR-128密码系统对后续流量进行加密。Websocket承载的客户端到服务端的头部结构如下(IV随着Payload同时发送以避免长度特征): - -```text -+----+-------------------+ -| IV | Encrypted Payload | -+----+-------------------+ -| 16 | Variable | -+----+-------------------+ -``` - -服务端用同样的方法得到主密钥,接收IV后进行解密和后续的双向通讯。服务端到客户端的流量无头部,均为密文。 - -注意,这层加密作用仅仅是增加数据流的熵,混淆流量特征,而不是保护数据安全。CTR加密模式不保证数据完整性和身份认证,因此可能遭受CDN或者中间人的重放攻击。如果CDN不可信,或者遭受了基于HTTPS劫持的中间人攻击,应启用双重TLS保证数据传输安全。 - -如果使用了双重TLS,握手造成的延迟可能略有增加,但是只要开启```session_reuse```,```session_ticket```复用TLS连接,以及开启```mux```启用TLS多路复用,只会在开启Trojan-Go时的最初几秒察觉明显的延迟。 - -当Websocket握手成功,但认证失败(混淆密码错误,用户密码sha224错误,TLS设置不一致),服务端将尝试与remote_addr:remote_port的服务器进行相同的Websocket握手过程,并将此websocket连接代理给它。如果它的Websocket握手失败,服务端将关闭该Websocket连接。 +开启AEAD加密后,Websocket承载的流量将被Shadowsocks AEAD加密,头部具体格式参见Shadowsocks白皮书。 开启Websocket支持后,协议栈如下: |协议 |备注 | |-----------------|----------| -|真实流量| +|真实流量 | | |SimpleSocks |如果开启多路复用| |smux |如果开启多路复用| -|Trojan| -|TLS |如果开启双重TLS| -|混淆层 |如果开启混淆| -|Websocket| -|TLS| -|TCP| +|Trojan | | +|Shadowsocks |如果开启加密| +|Websocket | | +|传输层协议 | | diff --git a/easy/easy.go b/easy/easy.go new file mode 100644 index 0000000..f7b1d47 --- /dev/null +++ b/easy/easy.go @@ -0,0 +1,134 @@ +package easy + +import ( + "flag" + "fmt" + "github.com/p4gefau1t/trojan-go/option" + "net" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/proxy" +) + +type easy struct { + server *bool + client *bool + password *string + local *string + remote *string + cert *string + key *string +} + +func (o *easy) Name() string { + return "easy" +} + +func (o *easy) Handle() error { + if !*o.server && !*o.client { + return common.NewError("empty") + } + if *o.password == "" { + log.Fatal("empty password is not allowed") + } + log.Info("easy mode enabled, trojan-go will NOT use the config file") + if *o.client { + clientConfigFormat := ` +{ + "run_type": "client", + "local_addr": "%s", + "local_port": %s, + "remote_addr": "%s", + "remote_port": %s, + "password": [ + "%s" + ] +} + ` + if *o.local == "" { + log.Warn("client local addr is unspecified, using 127.0.0.1:1080") + *o.local = "127.0.0.1:1080" + } + localHost, localPort, err := net.SplitHostPort(*o.local) + if err != nil { + log.Fatal(common.NewError("invalid local addr format:" + *o.local).Base(err)) + } + remoteHost, remotePort, err := net.SplitHostPort(*o.remote) + if err != nil { + log.Fatal(common.NewError("invalid remote addr format:" + *o.remote).Base(err)) + } + clientConfigJSON := fmt.Sprintf(clientConfigFormat, localHost, localPort, remoteHost, remotePort, *o.password) + log.Info("generated json config:") + log.Info(clientConfigJSON) + proxy, err := proxy.NewProxyFromConfigData([]byte(clientConfigJSON), true) + if err != nil { + log.Fatal(err) + } + if err := proxy.Run(); err != nil { + log.Fatal(err) + } + + } else if *o.server { + serverConfigFormat := ` +{ + "run_type": "server", + "local_addr": "%s", + "local_port": %s, + "remote_addr": "%s", + "remote_port": %s, + "password": [ + "%s" + ], + "ssl": { + "cert": "%s", + "key": "%s" + } +} + ` + if *o.remote == "" { + log.Warn("server remote addr is unspecified, using 127.0.0.1:80") + *o.remote = "127.0.0.1:80" + } + if *o.local == "" { + log.Warn("server local addr is unspecified, using 0.0.0.0:443") + *o.local = "0.0.0.0:443" + } + localHost, localPort, err := net.SplitHostPort(*o.local) + if err != nil { + log.Fatal(common.NewError("invalid local addr format:" + *o.local).Base(err)) + } + remoteHost, remotePort, err := net.SplitHostPort(*o.remote) + if err != nil { + log.Fatal(common.NewError("invalid remote addr format:" + *o.remote).Base(err)) + } + serverConfigJSON := fmt.Sprintf(serverConfigFormat, localHost, localPort, remoteHost, remotePort, *o.password, *o.cert, *o.key) + log.Info("generated json config:") + + log.Info(serverConfigJSON) + proxy, err := proxy.NewProxyFromConfigData([]byte(serverConfigJSON), true) + if err != nil { + log.Fatal(err) + } + if err := proxy.Run(); err != nil { + log.Fatal(err) + } + } + return nil +} + +func (o *easy) Priority() int { + return 50 +} + +func init() { + option.RegisterHandler(&easy{ + server: flag.Bool("server", false, "Run a trojan-go server"), + client: flag.Bool("client", false, "Run a trojan-go client"), + password: flag.String("password", "", "Password for authentication"), + remote: flag.String("remote", "", "Remote address, e.g. 127.0.0.1:12345"), + local: flag.String("local", "", "Local address, e.g. 127.0.0.1:12345"), + key: flag.String("key", "server.key", "Key of the server"), + cert: flag.String("cert", "server.crt", "Certificates of the server"), + }) +} diff --git a/proxy/server/server.go b/proxy/server/server.go index 7219010..48c5e93 100644 --- a/proxy/server/server.go +++ b/proxy/server/server.go @@ -32,8 +32,12 @@ func init() { Server: s, } - root.BuildNext(trojan.Name).BuildNext(mux.Name).BuildNext(simplesocks.Name).IsEndpoint = true - root.BuildNext(trojan.Name).IsEndpoint = true + trojanSubTree := root + if cfg.Shadowsocks.Enabled { + trojanSubTree = trojanSubTree.BuildNext(shadowsocks.Name) + } + trojanSubTree.BuildNext(trojan.Name).BuildNext(mux.Name).BuildNext(simplesocks.Name).IsEndpoint = true + trojanSubTree.BuildNext(trojan.Name).IsEndpoint = true wsSubTree := root.BuildNext(websocket.Name) if cfg.Shadowsocks.Enabled { diff --git a/test/scenario/proxy_test.go b/test/scenario/proxy_test.go index 17eb183..77c9a50 100644 --- a/test/scenario/proxy_test.go +++ b/test/scenario/proxy_test.go @@ -79,7 +79,7 @@ func init() { ioutil.WriteFile("server.key", []byte(key), 0777) } -func TestClientServer(t *testing.T) { +func TestClientServerWebsocketSubTree(t *testing.T) { serverPort := common.PickPort("tcp", "127.0.0.1") socksPort := common.PickPort("tcp", "127.0.0.1") clientData := fmt.Sprintf(` @@ -157,6 +157,76 @@ websocket: } } +func TestClientServerTrojanSubTree(t *testing.T) { + serverPort := common.PickPort("tcp", "127.0.0.1") + socksPort := common.PickPort("tcp", "127.0.0.1") + clientData := fmt.Sprintf(` +run-type: client +local-addr: 127.0.0.1 +local-port: %d +remote-addr: 127.0.0.1 +remote-port: %d +password: + - password +ssl: + verify: false + fingerprint: firefox + sni: localhost +shadowsocks: + enabled: true + method: AEAD_CHACHA20_POLY1305 + password: 12345678 +mux: + enabled: true +`, socksPort, serverPort) + go func() { + proxy, err := proxy.NewProxyFromConfigData([]byte(clientData), false) + common.Must(err) + common.Must(proxy.Run()) + }() + + serverData := fmt.Sprintf(` +run-type: server +local-addr: 127.0.0.1 +local-port: %d +remote-addr: 127.0.0.1 +remote-port: %s +disable-http-check: true +password: + - password +ssl: + verify-hostname: false + key: server.key + cert: server.crt + sni: localhost +shadowsocks: + enabled: true + method: AEAD_CHACHA20_POLY1305 + password: 12345678 +`, serverPort, util.HTTPPort) + go func() { + proxy, err := proxy.NewProxyFromConfigData([]byte(serverData), false) + common.Must(err) + common.Must(proxy.Run()) + }() + + time.Sleep(time.Second * 2) + dialer, err := netproxy.SOCKS5("tcp", fmt.Sprintf("127.0.0.1:%d", socksPort), nil, netproxy.Direct) + + payload := util.GeneratePayload(1024) + buf := [1024]byte{} + + conn, err := dialer.Dial("tcp", util.EchoAddr) + common.Must(err) + + common.Must2(conn.Write(payload)) + common.Must2(conn.Read(buf[:])) + + if !bytes.Equal(payload, buf[:]) { + t.Fail() + } +} + func TestForward(t *testing.T) { serverPort := common.PickPort("tcp", "127.0.0.1") clientPort := common.PickPort("tcp", "127.0.0.1") diff --git a/tunnel/shadowsocks/client.go b/tunnel/shadowsocks/client.go index a789545..42fc3db 100644 --- a/tunnel/shadowsocks/client.go +++ b/tunnel/shadowsocks/client.go @@ -6,7 +6,6 @@ import ( "github.com/p4gefau1t/trojan-go/config" "github.com/p4gefau1t/trojan-go/log" "github.com/p4gefau1t/trojan-go/tunnel" - "github.com/p4gefau1t/trojan-go/tunnel/transport" "github.com/shadowsocks/go-shadowsocks2/core" ) @@ -20,8 +19,9 @@ func (c *Client) DialConn(address *tunnel.Address, tunnel tunnel.Tunnel) (tunnel if err != nil { return nil, err } - return &transport.Conn{ - Conn: c.Cipher.StreamConn(conn), + return &Conn{ + aeadConn: c.Cipher.StreamConn(conn), + Conn: conn, }, nil } diff --git a/tunnel/shadowsocks/config.go b/tunnel/shadowsocks/config.go index ab6a031..2d42d43 100644 --- a/tunnel/shadowsocks/config.go +++ b/tunnel/shadowsocks/config.go @@ -9,11 +9,17 @@ type ShadowsocksConfig struct { } type Config struct { + RemoteHost string `json:"remote_addr", yaml:"remote-addr"` + RemotePort int `json:"remote_port", yaml:"remote-port"` Shadowsocks ShadowsocksConfig `json,yaml:"shadowsocks"` } func init() { config.RegisterConfigCreator(Name, func() interface{} { - return new(Config) + return &Config{ + Shadowsocks: ShadowsocksConfig{ + Method: "AES-128-GCM", + }, + } }) } diff --git a/tunnel/shadowsocks/conn.go b/tunnel/shadowsocks/conn.go new file mode 100644 index 0000000..f362e89 --- /dev/null +++ b/tunnel/shadowsocks/conn.go @@ -0,0 +1,27 @@ +package shadowsocks + +import ( + "github.com/p4gefau1t/trojan-go/tunnel" + "net" +) + +type Conn struct { + aeadConn net.Conn + tunnel.Conn +} + +func (c *Conn) Read(p []byte) (n int, err error) { + return c.aeadConn.Read(p) +} + +func (c *Conn) Write(p []byte) (n int, err error) { + return c.aeadConn.Write(p) +} + +func (c *Conn) Close() error { + return c.aeadConn.Close() +} + +func (c *Conn) Metadata() *tunnel.Metadata { + return c.Conn.Metadata() +} diff --git a/tunnel/shadowsocks/server.go b/tunnel/shadowsocks/server.go index 6cfea25..bac054e 100644 --- a/tunnel/shadowsocks/server.go +++ b/tunnel/shadowsocks/server.go @@ -5,14 +5,17 @@ import ( "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/config" "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/redirector" "github.com/p4gefau1t/trojan-go/tunnel" - "github.com/p4gefau1t/trojan-go/tunnel/transport" "github.com/shadowsocks/go-shadowsocks2/core" + "net" ) type Server struct { - underlay tunnel.Server core.Cipher + *redirector.Redirector + underlay tunnel.Server + redirAddr net.Addr } func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) { @@ -20,8 +23,29 @@ func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) { if err != nil { return nil, common.NewError("shadowsocks failed to accept connection from underlying tunnel") } - return &transport.Conn{ - Conn: s.Cipher.StreamConn(conn), + rewindConn := common.NewRewindConn(conn) + rewindConn.SetBufferSize(1024) + defer rewindConn.StopBuffering() + + // try to read something from this connection + buf := [1024]byte{} + testConn := s.Cipher.StreamConn(rewindConn) + if _, err := testConn.Read(buf[:]); err != nil { + // we are under attack + rewindConn.Rewind() + rewindConn.StopBuffering() + s.Redirect(&redirector.Redirection{ + RedirectTo: s.redirAddr, + InboundConn: rewindConn, + }) + return nil, common.NewError("invalid aead payload") + } + rewindConn.Rewind() + rewindConn.StopBuffering() + + return &Conn{ + aeadConn: s.Cipher.StreamConn(rewindConn), + Conn: conn, }, nil } @@ -41,7 +65,9 @@ func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) { } log.Debug("shadowsocks client created") return &Server{ - underlay: underlay, - Cipher: cipher, + underlay: underlay, + Cipher: cipher, + Redirector: redirector.NewRedirector(ctx), + redirAddr: tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort), }, nil } diff --git a/tunnel/shadowsocks/shadowsocks_test.go b/tunnel/shadowsocks/shadowsocks_test.go index 8aecb64..1d587f4 100644 --- a/tunnel/shadowsocks/shadowsocks_test.go +++ b/tunnel/shadowsocks/shadowsocks_test.go @@ -2,17 +2,25 @@ package shadowsocks import ( "context" + "fmt" "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/config" "github.com/p4gefau1t/trojan-go/test/util" "github.com/p4gefau1t/trojan-go/tunnel" "github.com/p4gefau1t/trojan-go/tunnel/raw" "net" + "strconv" + "strings" + "sync" "testing" ) func TestShadowsocks(t *testing.T) { + p, err := strconv.ParseInt(util.HTTPPort, 10, 32) + common.Must(err) cfg := &Config{ + RemoteHost: "127.0.0.1", + RemotePort: int(p), Shadowsocks: ShadowsocksConfig{ Enabled: true, Method: "AES-128-GCM", @@ -35,9 +43,48 @@ func TestShadowsocks(t *testing.T) { common.Must(err) s, err := NewServer(ctx, tcpServer) common.Must(err) - conn1, err := c.DialConn(nil, nil) + + wg := sync.WaitGroup{} + wg.Add(2) + var conn1, conn2 net.Conn + go func() { + var err error + conn1, err = c.DialConn(nil, nil) + common.Must(err) + conn1.Write([]byte("12345678")) + wg.Done() + }() + go func() { + var err error + conn2, err = s.AcceptConn(nil) + buf := [8]byte{} + conn2.Read(buf[:]) + common.Must(err) + wg.Done() + }() + wg.Wait() + if !util.CheckConn(conn1, conn2) { + t.Fail() + } + + go func() { + var err error + conn2, err = s.AcceptConn(nil) + if err == nil { + t.Fail() + } + }() + // test redirection + conn3, err := tcpClient.DialConn(nil, nil) common.Must(err) - conn2, err := s.AcceptConn(nil) + n, err := conn3.Write(util.GeneratePayload(1024)) common.Must(err) - util.CheckConn(conn1, conn2) + fmt.Println("write:", n) + buf := [1024]byte{} + n, err = conn3.Read(buf[:]) + common.Must(err) + fmt.Println("read:", n) + if !strings.Contains(string(buf[:n]), "Bad Request") { + t.Fail() + } } diff --git a/tunnel/simplesocks/client.go b/tunnel/simplesocks/client.go index 3f23a56..364e681 100644 --- a/tunnel/simplesocks/client.go +++ b/tunnel/simplesocks/client.go @@ -2,6 +2,7 @@ package simplesocks import ( "context" + "github.com/p4gefau1t/trojan-go/log" "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/tunnel" @@ -59,6 +60,7 @@ func (c *Client) Close() error { } func NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) { + log.Debug("simplesocks client created") return &Client{ underlay: underlay, }, nil diff --git a/tunnel/simplesocks/server.go b/tunnel/simplesocks/server.go index 74fe3a7..e0b8812 100644 --- a/tunnel/simplesocks/server.go +++ b/tunnel/simplesocks/server.go @@ -89,5 +89,6 @@ func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) { errChan: make(chan error, 32), } go server.acceptLoop() + log.Debug("simplesocks server created") return server, nil }