From e21d1101df95819408e00210bfa2873c4e0efd11 Mon Sep 17 00:00:00 2001 From: Page Fault Date: Wed, 20 May 2020 04:06:34 +0000 Subject: [PATCH] add server-side router, randomized sleeping --- conf/parse.go | 9 ++++----- docs/content/advance/router.md | 4 ++-- docs/content/basic/config.md | 2 +- docs/content/basic/full-config.md | 4 ++-- main.go | 1 + protocol/trojan/inbound.go | 2 ++ protocol/trojan/websocket.go | 2 +- proxy/option.go | 1 - proxy/server/server.go | 24 ++++++++++++++++++++---- router/mixed/geo.go | 1 + shadow/shadow.go | 5 +++++ 11 files changed, 39 insertions(+), 16 deletions(-) diff --git a/conf/parse.go b/conf/parse.go index 514344bef..058cdeed4 100644 --- a/conf/parse.go +++ b/conf/parse.go @@ -202,11 +202,6 @@ func loadCommonConfig(config *GlobalConfig) error { config.Websocket.ObfuscationKey = pbkdf2.Key(password, salt, 32, aes.BlockSize, sha256.New) } } - return nil -} - -func loadClientConfig(config *GlobalConfig) error { - var err error //router settings config.Router.BlockList = []byte{} @@ -264,6 +259,7 @@ func loadClientConfig(config *GlobalConfig) error { config.Router.ProxyList = append(config.Router.ProxyList, byte('\n')) } + var err error config.Router.GeoIP, err = ioutil.ReadFile(config.Router.GeoIPFilename) if err != nil { config.Router.GeoIP = []byte{} @@ -274,7 +270,10 @@ func loadClientConfig(config *GlobalConfig) error { config.Router.GeoSite = []byte{} log.Warn(err) } + return nil +} +func loadClientConfig(config *GlobalConfig) error { if config.TLS.SNI == "" { log.Warn("SNI is unspecified, using remote_addr as SNI") config.TLS.SNI = config.RemoteHost diff --git a/docs/content/advance/router.md b/docs/content/advance/router.md index e5a317cc6..690114102 100644 --- a/docs/content/advance/router.md +++ b/docs/content/advance/router.md @@ -6,9 +6,9 @@ weight: 3 ### 注意,Trojan-GFW版本不支持这个特性 -Trojan-Go内建的路由模块可以帮助你实现国内直连,即国内网站不经过代理,直接连接。 +Trojan-Go内建的路由模块可以帮助你实现国内直连,即客户端对于国内网站不经过代理,直接连接。 -路由模块只在客户端client生效。 +路由模块在客户端可以配置三种策略(```bypass```, ```proxy```, ```block```),在服务端只可使用```block```策略。 下面是一个例子 diff --git a/docs/content/basic/config.md b/docs/content/basic/config.md index 57dcd977c..6917a8245 100644 --- a/docs/content/basic/config.md +++ b/docs/content/basic/config.md @@ -82,7 +82,7 @@ sudo ./trojan-go -autocert renew 你可以通过使用浏览器访问你的域名```https://your_domain_name```来验证。如果工作正常,你的浏览器会显示一个正常的HTTPS保护的Web页面,页面内容与服务器本机80端口上的页面一致。你还可以使用```http://your_domain_name:443```验证```fallback_port```工作是否正常。 -事实上,你甚至可以将Trojan-Go当作你的HTTPS服务器,用来给你的网站提供HTTPS服务。访客可以正常地通过Trojan-Go浏览你的网站,而和代理流量互不影响。 +事实上,你甚至可以将Trojan-Go当作你的HTTPS服务器,用来给你的网站提供HTTPS服务。访客可以正常地通过Trojan-Go浏览你的网站,而和代理流量互不影响。但是注意,不要在```remote_port```和```fallback_port```搭建有高实时性需求的服务,Trojan-Go识别到非Trojan协议流量时会有意增加少许延迟以抵抗GFW基于时间的检测。 ### 客户端配置 diff --git a/docs/content/basic/full-config.md b/docs/content/basic/full-config.md index cdea402a0..87c74e240 100644 --- a/docs/content/basic/full-config.md +++ b/docs/content/basic/full-config.md @@ -213,11 +213,11 @@ weight: 30 - Block 封锁。不代理请求,直接关闭连接。 -在```proxy```, ```bypass```, ```block```字段中填入对应列表文件名或者geoip/geosite标签名,trojan-go即根据列表中的IP(CIDR)或域名执行相应路由策略。列表文件中每行是一个IP或者域名,trojan-go会自动识别。 +在```proxy```, ```bypass```, ```block```字段中填入对应列表文件名或者geoip/geosite标签名,trojan-go即根据列表中的IP(CIDR)或域名执行相应路由策略。列表文件中每行是一个IP或者域名,trojan-go会自动识别。客户端(client)可以配置三种策略,服务端(server)只可配置block策略。 ```enabled```是否开启路由模块。 -```default_policy```指的是三个列表匹配均失败后,使用的默认策略,默认为"bypass",即进行代理。合法的值有 +```default_policy```指的是三个列表匹配均失败后,使用的默认策略,默认为"proxy",即进行代理。合法的值有 - "proxy" diff --git a/main.go b/main.go index f70bb7c5d..aa1a8c73e 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( ) func main() { + log.Info("Trojan-Go", common.Version) flag.Parse() for { h, err := common.PopOptionHandler() diff --git a/protocol/trojan/inbound.go b/protocol/trojan/inbound.go index 839a35dec..3947cee90 100644 --- a/protocol/trojan/inbound.go +++ b/protocol/trojan/inbound.go @@ -50,10 +50,12 @@ func (i *TrojanInboundConnSession) Close() error { func (i *TrojanInboundConnSession) parseRequest(r *common.RewindReader) error { userHash := [56]byte{} + n, err := r.Read(userHash[:]) if err != nil || n != 56 { return common.NewError("Failed to read hash").Base(err) } + valid, meter := i.auth.AuthUser(string(userHash[:])) if !valid { return common.NewError("Invalid hash:" + string(userHash[:])) diff --git a/protocol/trojan/websocket.go b/protocol/trojan/websocket.go index 53ba2fd88..a31b80926 100644 --- a/protocol/trojan/websocket.go +++ b/protocol/trojan/websocket.go @@ -272,7 +272,7 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global if err != nil { rewindConn.R.Rewind() - //proxy this to our own ws server + //redirect this to our own ws server err = common.NewError("Remote websocket " + conn.RemoteAddr().String() + "didn't send any valid iv").Base(err) goat, err := getWebsocketScapegoat( config, diff --git a/proxy/option.go b/proxy/option.go index 4256f7320..4e207a882 100644 --- a/proxy/option.go +++ b/proxy/option.go @@ -25,7 +25,6 @@ func (*proxyOption) Priority() int { } func (c *proxyOption) Handle() error { - log.Info("Trojan-Go", common.Version, "initializing") log.Info("Loading config file from", *c.args) //exit code 23 stands for initializing error, and systemd will not trying to restart it diff --git a/proxy/server/server.go b/proxy/server/server.go index 28ca4c735..4682c9fbb 100644 --- a/proxy/server/server.go +++ b/proxy/server/server.go @@ -14,6 +14,7 @@ import ( "github.com/p4gefau1t/trojan-go/protocol/simplesocks" "github.com/p4gefau1t/trojan-go/protocol/trojan" "github.com/p4gefau1t/trojan-go/proxy" + "github.com/p4gefau1t/trojan-go/router" "github.com/p4gefau1t/trojan-go/shadow" "github.com/p4gefau1t/trojan-go/sockopt" "github.com/p4gefau1t/trojan-go/stat" @@ -28,6 +29,7 @@ type Server struct { auth stat.Authenticator config *conf.GlobalConfig shadow *shadow.ShadowManager + router router.Router ctx context.Context cancel context.CancelFunc } @@ -41,6 +43,11 @@ func (s *Server) handleMuxConn(stream *smux.Stream) { } defer stream.Close() + if policy, err := s.router.RouteRequest(req); err != nil || policy == router.Block { + log.Info("[Block] conn to", req.String()) + return + } + switch req.Command { case protocol.Connect: outboundConn, err := direct.NewOutboundConnSession(s.ctx, req, s.config) @@ -67,7 +74,7 @@ func (s *Server) handleConn(conn net.Conn) { protocol.SetRandomizedTimeout(conn) inboundConn, req, err := trojan.NewInboundConnSession(s.ctx, conn, s.config, s.auth, s.shadow) if err != nil { - //once the auth is failed, the conn will be took over by shadow manager. don't close it + //once the auth is failed, the conn will be took over by shadow manager. DO NOT close it. log.Error(common.NewError("Failed to start inbound session, remote:" + conn.RemoteAddr().String()).Base(err)) return } @@ -88,6 +95,11 @@ func (s *Server) handleConn(conn net.Conn) { } } + if policy, err := s.router.RouteRequest(req); err != nil || policy == router.Block { + log.Info("[Block] conn to", req.String()) + return + } + if req.Command == protocol.Associate { inboundPacket, err := trojan.NewPacketSession(inboundConn) common.Must(err) @@ -131,7 +143,7 @@ func (s *Server) ListenTCP(errChan chan error) { err = sockopt.ApplyTCPListenerOption(listener.(*net.TCPListener), &s.config.TCP) if err != nil { - errChan <- common.NewError(fmt.Sprintf("failed to apply tcp option: %v", &s.config.TCP)).Base(err) + errChan <- common.NewError(fmt.Sprintf("Failed to apply tcp option: %v", &s.config.TCP)).Base(err) return } @@ -177,7 +189,7 @@ func (s *Server) ListenTCP(errChan chan error) { if err != nil { rewindConn.R.Rewind() - err = common.NewError("Failed to perform tls handshake with " + conn.RemoteAddr().String()).Base(err) + err = common.NewError("Failed to perform TLS handshake with " + conn.RemoteAddr().String()).Base(err) log.Warn(err) if s.config.TLS.FallbackAddress != nil { s.shadow.SubmitScapegoat(&shadow.Scapegoat{ @@ -233,7 +245,10 @@ func (*Server) Build(config *conf.GlobalConfig) (common.Runnable, error) { } auth, err := stat.NewAuth(ctx, authDriver, config) if err != nil { - cancel() + return nil, err + } + router, err := router.NewRouter(&config.Router) + if err != nil { return nil, err } s := &Server{ @@ -241,6 +256,7 @@ func (*Server) Build(config *conf.GlobalConfig) (common.Runnable, error) { ctx: ctx, cancel: cancel, shadow: shadow.NewShadowManager(ctx, config), + router: router, auth: auth, } return s, nil diff --git a/router/mixed/geo.go b/router/mixed/geo.go index e78ac801a..fdc921ce2 100644 --- a/router/mixed/geo.go +++ b/router/mixed/geo.go @@ -47,6 +47,7 @@ func (r *GeoRouter) matchDomain(fulldomain string) bool { return true } default: + log.Debug("Unknown type" + d.GetType().String()) } } return false diff --git a/shadow/shadow.go b/shadow/shadow.go index 729be7d86..f8a3aa722 100644 --- a/shadow/shadow.go +++ b/shadow/shadow.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "math/rand" "net" "time" @@ -46,6 +47,10 @@ func (m *ShadowManager) handleScapegoat() { if conn, ok := goat.Conn.(net.Conn); ok { conn.SetDeadline(time.Time{}) } + + //sleep for a while to resist time-based detection + time.Sleep(time.Millisecond * time.Duration(rand.Intn(50))) + if goat.ShadowConn == nil { if goat.ShadowAddress == nil { panic("incorrect shadow server")