From de6b1e33af49e894d5518029e091c1672247307d Mon Sep 17 00:00:00 2001 From: Page Fault Date: Fri, 12 Jun 2020 16:24:39 +0000 Subject: [PATCH] add forward proxy --- README.md | 4 ++- docs/content/basic/full-config.md | 21 +++++++----- docs/content/developer/overview.md | 4 +-- tunnel/raw/client.go | 51 ++++++++++++++++++++++++------ tunnel/raw/config.go | 17 +++++++--- tunnel/raw/conn.go | 2 +- tunnel/raw/server.go | 2 +- tunnel/tproxy/server.go | 3 +- 8 files changed, 76 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 99e2f791a..6e0e0f4a5 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,8 @@ Trojan-Go服务端可以兼容所有Trojan-GFW的客户端,如Igniter,Shadow ## 特性 +一般情况下,Trojan-Go和Trojan-GFW版本是互相兼容的。但是一旦使用下面介绍的扩展特性(如多路复用,Websocket等),则无法与之兼容。 + ### 移植性 运行Trojan-Go的可执行文件不依赖其他组件。 @@ -220,7 +222,7 @@ Trojan-Go支持多路复用(基于[smux](https://github.com/xtaci/smux))。通 启用多路复用并不会增加你测速得到的链路速度,但会降低延迟,提升大量并发请求时的网络体验,例如浏览含有大量图片的网页等。 -注意,这个特性和Trojan-GFW**不兼容**,出于兼容性考虑,这个特性是默认关闭的。你可以通过设置客户端的mux选项"enabled"字段启用它。如下 +你可以通过设置客户端的mux选项"enabled"字段启用它。如下 ```json ... diff --git a/docs/content/basic/full-config.md b/docs/content/basic/full-config.md index dbda9ff8a..bb7babf5d 100644 --- a/docs/content/basic/full-config.md +++ b/docs/content/basic/full-config.md @@ -16,12 +16,14 @@ weight: 30 - ```remote_port``` -对于服务器server,```key```和```cert```为必填。 +对于服务器```server```,```key```和```cert```为必填。 -对于客户端client,反向代理隧道forward,以及透明代理nat,```password```必填 +对于客户端```client```,反向代理隧道```forward```,以及透明代理```nat```,```password```必填 其余未填的选项,用下面给出的值进行填充。 +*Trojan-Go支持对人类更友好的YAML语法,配置文件的基本结构与JSON相同,效果等价。不过为了遵守YAML的命名习惯,你需要把下划线("_")转换为横杠("-"),如```remote_addr```在YAML文件中为```remote-addr```* + ```json { "run_type": *required*, @@ -52,6 +54,7 @@ weight: 30 "session_ticket": true, "reuse_session": true, "plain_http_response": "", + "fallback_addr": "", "fallback_port": 0, "fingerprint": "firefox", }, @@ -72,8 +75,8 @@ weight: 30 "block": [], "default_policy": "proxy", "domain_strategy": "as_is", - "geoip": "geoip.dat", - "geosite": "geosite.dat" + "geoip": "$PROGRAM_DIR$/geoip.dat", + "geosite": "$PROGRAM_DIR$/geosite.dat" }, "websocket": { "enabled": false, @@ -172,7 +175,7 @@ weight: 30 ```sni```指的是TLS客户端请求中的服务器名字段,一般和证书的Common Name相同。如果你使用let'sencrypt等机构签发的证书,这里填入你的域名。如果这一项未填,将使用```remote_addr```填充。你应当指定一个有效的SNI(和远端证书CN一致),否则客户端可能无法验证远端证书有效性从而无法连接。 -```alpn```为TLS的应用层协议协商指定协议。在TLS Client/Server Hello中传输,协商应用层使用的协议,仅用作指纹伪造,并无实际作用。**如果使用了CDN,错误的alpn字段可能导致与CDN协商错误的应用层协议**。 +```alpn```为TLS的应用层协议协商指定协议。在TLS Client/Server Hello中传输,协商应用层使用的协议,仅用作指纹伪造,并无实际作用。**如果使用了CDN,错误的alpn字段可能导致与CDN协商得到错误的应用层协议**。 ```prefer_server_cipher```客户端是否偏好选择服务端在协商中提供的密码学套件。 @@ -194,7 +197,7 @@ weight: 30 ```plain_http_response```指服务端TLS握手失败时,明文发送的原始数据(原始TCP数据)。这个字段填入该文件路径。推荐使用```fallback_port```而不是该字段。 -```fallback_port```指服务端TLS握手失败时,trojan-go将该连接代理的端口。这是trojan-go的特性,以便更好地隐蔽Trojan服务器,抵抗GFW的主动检测,使得服务器的443端口在遭遇非TLS协议的探测时,行为与正常服务器完全一致。当服务器接受了一个连接但无法进行TLS握手时,如果```fallback_port```不为空,则流量将会被代理至remote_addr:fallback_port。例如,你可以在本地使用nginx开启一个https服务,当你的服务器443端口被非TLS协议请求时(比如http请求),trojan-go将代理至本地https服务器,nginx将使用http协议明文返回一个400 Bad Request页面。你可以通过使用浏览器访问```http://your-domain-name.com:443```进行验证。 +```fallback_addr```和```fallback_port```指服务端TLS握手失败时,trojan-go将该连接重定向到该地址。这是trojan-go的特性,以便更好地隐蔽服务器,抵抗GFW的主动检测,使得服务器的443端口在遭遇非TLS协议的探测时,行为与正常服务器完全一致。当服务器接受了一个连接但无法进行TLS握手时,如果```fallback_port```不为空,则流量将会被代理至fallback_addr:fallback_port。如果```fallback_addr```为空,则用```remote_addr```填充。例如,你可以在本地使用nginx开启一个https服务,当你的服务器443端口被非TLS协议请求时(比如http请求),trojan-go将代理至本地https服务器,nginx将使用http协议明文返回一个400 Bad Request页面。你可以通过使用浏览器访问```http://your-domain-name.com:443```进行验证。 ```key_log```TLS密钥日志的文件路径。如果填写则开启密钥日志。**记录密钥将破坏TLS的安全性,此项不应该用于除调试以外的其他任何用途。** @@ -266,9 +269,11 @@ Websocket传输是trojan-go的特性。在**正常的直接连接代理节点** - 你的证书失效,无法验证证书有效性 +- 你使用了无法保证密码学安全的可插拔传输层 + 等等。 -由于使用的是AEAD,trojan-go启用了AEAD加密后依然可以正确判断是否遭到主动探测,并作出相应的响应。 +由于使用的是AEAD,trojan-go可以正确判断请求是否有效,是否遭到主动探测,并作出相应的响应。 ```enabled```是否启用Shadowsocks AEAD加密Trojan协议层。 @@ -280,7 +285,7 @@ Websocket传输是trojan-go的特性。在**正常的直接连接代理节点** - "AES-256-GCM" -```password```用于生成主密钥的密码。必须确保客户端和服务端一致。 +```password```用于生成主密钥的密码。如果启用AEAD加密,必须确保客户端和服务端一致。 ### ```transport_plugin```传输层插件选项 diff --git a/docs/content/developer/overview.md b/docs/content/developer/overview.md index 07a7701ff..fd4025271 100644 --- a/docs/content/developer/overview.md +++ b/docs/content/developer/overview.md @@ -68,9 +68,9 @@ Trojan-Go将所有协议(包括路由功能等)抽象为隧道(tunnel.Tunnel ## proxy.Proxy代理核心 -代理核心的作用,监听上述隧道进行组合堆叠并形成的协议栈,将所有的入站协议栈(多个的隧道Server)中抽取的流和包,以及对应元信息,转送给出站协议栈(一个隧道Client)。 +代理核心的作用,是监听上述隧道进行组合堆叠并形成的协议栈,将所有的入站协议栈(多个隧道Server)中抽取的流和包,以及对应元信息,转送给出站协议栈(一个隧道Client)。 -注意,这里的入站协议栈可以有多个,如客户端可以同时从Socks5和HTTP协议栈中抽取流和包,服务端可以同时从Websocket承载的Trojan协议,和TLS承载的Trojan协议中抽取流和包等。但是出站协议栈只能有一个。 +注意,这里的入站协议栈可以有多个,如客户端可以同时从Socks5和HTTP协议栈中抽取流和包,服务端可以同时从Websocket承载的Trojan协议,和TLS承载的Trojan协议中抽取流和包等。但是出站协议栈只能有一个,如只使用TLS承载的Trojan协议出站。 为了描述入站协议栈(隧道服务端)的组合和堆叠方式,使用一棵多叉树对所有协议栈进行描述。你可以在client/forward/nat/server中看到构建树的过程。 diff --git a/tunnel/raw/client.go b/tunnel/raw/client.go index e67bcfd36..bf0e3a838 100644 --- a/tunnel/raw/client.go +++ b/tunnel/raw/client.go @@ -6,6 +6,7 @@ import ( "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/config" "github.com/p4gefau1t/trojan-go/log" + "golang.org/x/net/proxy" "net" "net/url" "strconv" @@ -15,11 +16,15 @@ import ( ) type Client struct { - preferIPv4 bool - noDelay bool - keepAlive bool - dns []string - ctx context.Context + preferIPv4 bool + noDelay bool + keepAlive bool + dns []string + ctx context.Context + forwardProxy bool + proxyAddr *tunnel.Address + username string + password string } func (c *Client) resolveIP(addr *tunnel.Address) ([]net.IPAddr, error) { @@ -90,6 +95,29 @@ func (c *Client) DialConn(addr *tunnel.Address, t tunnel.Tunnel) (tunnel.Conn, e if c.preferIPv4 { network = "tcp4" } + + // forward proxy + if c.forwardProxy { + var auth *proxy.Auth + if c.username != "" { + auth = &proxy.Auth{ + User: c.username, + Password: c.password, + } + } + dialer, err := proxy.SOCKS5(network, c.proxyAddr.String(), auth, proxy.Direct) + if err != nil { + return nil, err + } + socksConn, err := dialer.Dial(network, addr.String()) + if err != nil { + return nil, err + } + return &Conn{ + Conn: socksConn, + }, nil + } + tcpConn, err := net.Dial(network, addr.String()) if err != nil { return nil, err @@ -98,7 +126,7 @@ func (c *Client) DialConn(addr *tunnel.Address, t tunnel.Tunnel) (tunnel.Conn, e tcpConn.(*net.TCPConn).SetKeepAlive(c.keepAlive) tcpConn.(*net.TCPConn).SetNoDelay(c.noDelay) return &Conn{ - TCPConn: tcpConn.(*net.TCPConn), + Conn: tcpConn, }, nil } @@ -123,11 +151,14 @@ func (c *Client) Close() error { func NewClient(ctx context.Context, client tunnel.Client) (*Client, error) { // TODO implement dns cfg := config.FromContext(ctx, Name).(*Config) + addr := tunnel.NewAddressFromHostPort("tcp", cfg.ForwardProxy.ProxyHost, cfg.ForwardProxy.ProxyPort) return &Client{ - dns: cfg.DNS, - noDelay: cfg.TCP.NoDelay, - keepAlive: cfg.TCP.KeepAlive, - preferIPv4: cfg.TCP.PreferIPV4, + dns: cfg.DNS, + noDelay: cfg.TCP.NoDelay, + keepAlive: cfg.TCP.KeepAlive, + preferIPv4: cfg.TCP.PreferIPV4, + forwardProxy: cfg.ForwardProxy.Enabled, + proxyAddr: addr, }, nil } diff --git a/tunnel/raw/config.go b/tunnel/raw/config.go index dc0962904..d28c0435b 100644 --- a/tunnel/raw/config.go +++ b/tunnel/raw/config.go @@ -3,10 +3,11 @@ package raw import "github.com/p4gefau1t/trojan-go/config" type Config struct { - LocalHost string `json:"local_addr" yaml:"local-addr"` - LocalPort int `json:"local_port" yaml:"local-port"` - DNS []string `json:"dns" yaml:"dns"` - TCP TCPConfig `json:"tcp" yaml:"tcp"` + LocalHost string `json:"local_addr" yaml:"local-addr"` + LocalPort int `json:"local_port" yaml:"local-port"` + DNS []string `json:"dns" yaml:"dns"` + TCP TCPConfig `json:"tcp" yaml:"tcp"` + ForwardProxy ForwardProxyConfig `json:"forward_proxy" yaml:"forward-proxy"` } type TCPConfig struct { @@ -15,6 +16,14 @@ type TCPConfig struct { NoDelay bool `json:"no_delay" yaml:"no-delay"` } +type ForwardProxyConfig struct { + Enabled bool `json,yaml:"enabled"` + ProxyHost string `json:"proxy_addr" yaml:"proxy-addr"` + ProxyPort int `json:"proxy_port" yaml:"proxy-port"` + Username string `json,yaml:"username"` + Password string `json,yaml:"password"` +} + func init() { config.RegisterConfigCreator(Name, func() interface{} { return &Config{ diff --git a/tunnel/raw/conn.go b/tunnel/raw/conn.go index 7f5b2571f..920e61a82 100644 --- a/tunnel/raw/conn.go +++ b/tunnel/raw/conn.go @@ -8,7 +8,7 @@ import ( ) type Conn struct { - *net.TCPConn + net.Conn } func (c *Conn) Metadata() *tunnel.Metadata { diff --git a/tunnel/raw/server.go b/tunnel/raw/server.go index d032889ff..be017b3d4 100644 --- a/tunnel/raw/server.go +++ b/tunnel/raw/server.go @@ -17,7 +17,7 @@ func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { return nil, err } return &Conn{ - TCPConn: conn.(*net.TCPConn), + Conn: conn, }, nil } diff --git a/tunnel/tproxy/server.go b/tunnel/tproxy/server.go index e208b66a3..2822efb9a 100644 --- a/tunnel/tproxy/server.go +++ b/tunnel/tproxy/server.go @@ -73,7 +73,7 @@ func (s *Server) packetDispatchLoop() { s.Close() return } - log.Debug("udp packet from", src, "to", dst, "size", n) + log.Debug("udp packet from", src, "metadata", dst, "size", n) s.mappingLock.Lock() if conn, found := s.mapping[src.String()]; found { conn.Input <- buf[:n] @@ -131,6 +131,7 @@ func (s *Server) packetDispatchLoop() { func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { select { case conn := <-s.packetChan: + log.Info("tproxy packet conn accpeted") return conn, nil case <-s.ctx.Done(): return nil, io.EOF