diff --git a/api/client.go b/api/client.go index 95c8e64..6c34a6e 100644 --- a/api/client.go +++ b/api/client.go @@ -41,6 +41,8 @@ func (s *ClientAPIService) calcSpeed() { sent, recv := s.meter.Query("") s.uploadSpeed = sent - s.lastSent s.downloadSpeed = recv - s.lastRecv + s.lastSent = sent + s.lastRecv = recv case <-s.ctx.Done(): return } diff --git a/common/io.go b/common/io.go index e648073..4b9ea46 100644 --- a/common/io.go +++ b/common/io.go @@ -62,8 +62,8 @@ func (r *RewindReader) Discard(n int) (int, error) { } func (r *RewindReader) Rewind() { - if !r.buffered { - panic("not buffered yet") + if r.bufferSize == 0 { + panic("has no buffer") } r.rewinded = true r.bufReadIdx = 0 diff --git a/conf/conf.go b/conf/conf.go index 748f5e8..ede8a00 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -100,12 +100,14 @@ type RouterConfig struct { } type WebsocketConfig struct { - Enabled bool `json:"enabled"` - HostName string `json:"hostname"` - Path string `json:"path"` - Obfuscation bool `json:"obfuscation"` - DoubleTLS bool `json:"double_tls"` - DoubleTLSVerify bool `json:"double_tls_verify"` + Enabled bool `json:"enabled"` + HostName string `json:"hostname"` + Path string `json:"path"` + ObfuscationPassword string `json:"obfuscation_password"` + DoubleTLS bool `json:"double_tls"` + DoubleTLSVerify bool `json:"double_tls_verify"` + + ObfuscationKey []byte } type APIConfig struct { diff --git a/conf/parse.go b/conf/parse.go index ba2947e..7e71981 100644 --- a/conf/parse.go +++ b/conf/parse.go @@ -1,6 +1,8 @@ package conf import ( + "crypto/aes" + "crypto/sha256" "crypto/tls" "crypto/x509" "encoding/json" @@ -12,6 +14,7 @@ import ( "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/log" + "golang.org/x/crypto/pbkdf2" ) func loadCommonConfig(config *GlobalConfig) error { @@ -104,6 +107,12 @@ func loadCommonConfig(config *GlobalConfig) error { config.Websocket.HostName = "[" + config.RemoteHost + "]" } } + if config.Websocket.ObfuscationPassword != "" { + log.Info("websocket obfs enabled") + password := []byte(config.Websocket.ObfuscationPassword) + salt := []byte{48, 149, 6, 18, 13, 193, 247, 116, 197, 135, 236, 175, 190, 209, 146, 48} + config.Websocket.ObfuscationKey = pbkdf2.Key(password, salt, 32, aes.BlockSize, sha256.New) + } } return nil } @@ -292,7 +301,7 @@ func ParseJSON(data []byte) (*GlobalConfig, error) { config.Router.GeoSiteFilename = common.GetProgramDir() + "/geosite.dat" config.Websocket.DoubleTLS = true config.Websocket.DoubleTLSVerify = true - config.Websocket.Obfuscation = true + config.Websocket.ObfuscationPassword = "" err := json.Unmarshal(data, config) if err != nil { diff --git a/protocol/trojan/inbound.go b/protocol/trojan/inbound.go index 6aa3914..3d26867 100644 --- a/protocol/trojan/inbound.go +++ b/protocol/trojan/inbound.go @@ -92,9 +92,8 @@ func (i *TrojanInboundConnSession) SetMeter(meter stat.TrafficMeter) { func NewInboundConnSession(ctx context.Context, conn net.Conn, config *conf.GlobalConfig, auth stat.Authenticator, shadowMan *shadow.ShadowManager) (protocol.ConnSession, *protocol.Request, error) { ctx, cancel := context.WithCancel(context.Background()) - - //rwc := common.NewRewindReadWriteCloser(conn) rewindConn := common.NewRewindConn(conn) + i := &TrojanInboundConnSession{ config: config, auth: auth, @@ -103,6 +102,7 @@ func NewInboundConnSession(ctx context.Context, conn net.Conn, config *conf.Glob cancel: cancel, rwc: rewindConn, } + //start buffering rewindConn.R.SetBufferSize(512) defer rewindConn.R.StopBuffering() diff --git a/protocol/trojan/websocket.go b/protocol/trojan/websocket.go index eae71c5..4f8159b 100644 --- a/protocol/trojan/websocket.go +++ b/protocol/trojan/websocket.go @@ -6,7 +6,6 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "crypto/sha1" "crypto/tls" "io" "net" @@ -18,7 +17,6 @@ import ( "github.com/p4gefau1t/trojan-go/log" "github.com/p4gefau1t/trojan-go/protocol" "github.com/p4gefau1t/trojan-go/shadow" - "golang.org/x/crypto/pbkdf2" "golang.org/x/net/websocket" ) @@ -44,16 +42,14 @@ func (rwc *obfReadWriteCloser) Close() error { return rwc.Conn.Close() } -func NewOutboundObfReadWriteCloser(password string, conn *websocket.Conn) *obfReadWriteCloser { +func NewOutboundObfReadWriteCloser(key []byte, conn *websocket.Conn) *obfReadWriteCloser { //use bufio to avoid fixed ws packet length bufrw := common.NewBufioReadWriter(conn) - randomBytes := [aes.BlockSize + 8]byte{} - common.Must2(io.ReadFull(rand.Reader, randomBytes[:])) - bufrw.Write(randomBytes[:]) + iv := [aes.BlockSize]byte{} + common.Must2(io.ReadFull(rand.Reader, iv[:])) + bufrw.Write(iv[:]) + log.Debug("obfs sent iv", iv) - iv := randomBytes[:aes.BlockSize] - salt := randomBytes[aes.BlockSize:] - key := pbkdf2.Key([]byte(password), salt, 32, aes.BlockSize, sha1.New) block, err := aes.NewCipher(key) common.Must(err) @@ -71,17 +67,15 @@ func NewOutboundObfReadWriteCloser(password string, conn *websocket.Conn) *obfRe } } -func NewInboundObfReadWriteCloser(password string, conn net.Conn) (*obfReadWriteCloser, error) { +func NewInboundObfReadWriteCloser(key []byte, conn net.Conn) (*obfReadWriteCloser, error) { bufrw := common.NewBufioReadWriter(conn) - randomBytes := [aes.BlockSize + 8]byte{} - _, err := bufrw.Read(randomBytes[:]) + iv := [aes.BlockSize]byte{} + _, err := bufrw.Read(iv[:]) if err != nil { return nil, err } + log.Debug("obfs recv iv", iv) - iv := randomBytes[:aes.BlockSize] - salt := randomBytes[aes.BlockSize:] - key := pbkdf2.Key([]byte(password), salt, 32, aes.BlockSize, sha1.New) block, err := aes.NewCipher(key) common.Must(err) @@ -128,9 +122,9 @@ func NewOutboundWebosocket(conn net.Conn, config *conf.GlobalConfig) (io.ReadWri return nil, err } var transport net.Conn = wsConn - if config.Websocket.Obfuscation { + if config.Websocket.ObfuscationPassword != "" { log.Debug("ws obfs enabled") - transport = NewOutboundObfReadWriteCloser(config.Passwords[0], wsConn) + transport = NewOutboundObfReadWriteCloser(config.Websocket.ObfuscationKey, wsConn) } if !config.Websocket.DoubleTLS { return transport, nil @@ -194,7 +188,7 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global rewindConn.R.SetBufferSize(512) defer rewindConn.R.StopBuffering() - bufrw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) + bufrw := bufio.NewReadWriter(bufio.NewReader(rewindConn), bufio.NewWriter(rewindConn)) httpRequest, obfErr := http.ReadRequest(bufrw.Reader) if obfErr != nil { log.Debug(common.NewError("not a http request:").Base(obfErr)) @@ -258,19 +252,21 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global return nil, common.NewError("failed to perform websocket handshake") } + //use ws to transport var transport net.Conn - transport = common.NewRewindConn(wsConn) + rewindConn = common.NewRewindConn(wsConn) + transport = rewindConn //start buffering the websocket payload rewindConn.R.SetBufferSize(512) defer rewindConn.R.StopBuffering() - if config.Websocket.Obfuscation { + if config.Websocket.ObfuscationPassword != "" { log.Debug("ws obfs") //deadline for sending the iv and hash rewindConn.SetDeadline(time.Now().Add(protocol.TCPTimeout)) - transport, obfErr = NewInboundObfReadWriteCloser(config.Passwords[0], rewindConn) + transport, obfErr = NewInboundObfReadWriteCloser(config.Websocket.ObfuscationKey, transport) rewindConn.SetDeadline(time.Time{}) if obfErr != nil { diff --git a/proxy/server/server.go b/proxy/server/server.go index 7c7022e..844dfeb 100644 --- a/proxy/server/server.go +++ b/proxy/server/server.go @@ -216,7 +216,6 @@ func (s *Server) Run() error { } return } - defer tlsConn.Close() s.handleConn(tlsConn) }(conn) } diff --git a/test/proxy_test.go b/test/proxy_test.go index d7b3112..34ea954 100644 --- a/test/proxy_test.go +++ b/test/proxy_test.go @@ -3,9 +3,9 @@ package test import ( "bytes" "context" + "crypto/md5" "crypto/tls" "crypto/x509" - "fmt" "io/ioutil" "net" "testing" @@ -97,7 +97,7 @@ func getTLSConfig() conf.TLSConfig { VerifyHostname: true, ReuseSession: true, SessionTicket: true, - FallbackAddress: common.NewAddress("127.0.0.1", 80, "tcp"), + FallbackAddress: common.NewAddress("127.0.0.1", 10080, "tcp"), } return c } @@ -139,12 +139,15 @@ func getBasicClientConfig() *conf.GlobalConfig { func addWsConfig(config *conf.GlobalConfig) *conf.GlobalConfig { config.Websocket = conf.WebsocketConfig{ - Enabled: true, - HostName: "127.0.0.1", - Path: "/websocket", - Obfuscation: false, - DoubleTLS: true, + Enabled: true, + HostName: "127.0.0.1", + Path: "/websocket", + ObfuscationPassword: "123456789", + DoubleTLS: true, } + hash := md5.New() + hash.Write([]byte(config.Websocket.ObfuscationPassword)) + config.Websocket.ObfuscationKey = hash.Sum(nil) return config } @@ -326,13 +329,60 @@ func BenchmarkWebsocket(b *testing.B) { SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig) } -func TestHTTPProxy(t *testing.T) { +func BenchmarkMuxWebsocket(b *testing.B) { + clientConfig := addMuxConfig(addWsConfig(getBasicClientConfig())) + serverConfig := addWsConfig(getBasicServerConfig()) + SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig) +} + +func TestWebsocketShadow(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) go RunHelloHTTPServer(ctx) serverConfig := addWsConfig(getBasicServerConfig()) go RunServer(ctx, serverConfig) time.Sleep(time.Second) + //test http + httpClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + resp, err := httpClient.Get("https://127.0.0.1:4445") + common.Must(err) + body, err := ioutil.ReadAll(resp.Body) + common.Must(err) + if string(body) != "HelloWorld" { + t.Fatal("http shadow") + } + + //test websocket + conn, err := tls.Dial("tcp", "127.0.0.1:4445", &tls.Config{InsecureSkipVerify: true}) + common.Must(err) + wsConfig, err := websocket.NewConfig("wss://127.0.0.1/websocket", "https://127.0.0.1") + common.Must(err) + wsClient, err := websocket.NewClient(wsConfig, conn) + common.Must(err) + buf := [100]byte{} + common.Must2(wsClient.Write([]byte("I'm GFW1231231231231212391273871283719823791237912398721933123"))) + n, err := wsClient.Read(buf[:]) + common.Must(err) + if string(buf[:n]) != "HelloWorld" { + t.Fatal("ws shadow") + } + conn.Close() + cancel() +} + +func TestShadow(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + go RunHelloHTTPServer(ctx) + serverConfig := getBasicServerConfig() + go RunServer(ctx, serverConfig) + time.Sleep(time.Second) + //test http httpClient := &http.Client{ //some config @@ -347,26 +397,16 @@ func TestHTTPProxy(t *testing.T) { body, err := ioutil.ReadAll(resp.Body) common.Must(err) if string(body) != "HelloWorld" { - t.Fatal("server http proxy failed") + t.Fatal("http shadow") } - //test websocket - conn, err := tls.Dial("tcp", "127.0.0.1:4445", &tls.Config{InsecureSkipVerify: true}) - common.Must(err) - wsConfig, err := websocket.NewConfig("wss://127.0.0.1/websocket", "https://127.0.0.1") - common.Must(err) - wsClient, err := websocket.NewClient(wsConfig, conn) - common.Must(err) - buf := [100]byte{} - common.Must2(wsClient.Write([]byte("I'm GFW1231231231231212391273871283719823791237912398721933123"))) - common.Must2(wsClient.Read(buf[:])) - fmt.Println(buf) - common.Must(err) - conn.Close() - + //fallback resp, err = http.Get("http://127.0.0.1:4445") common.Must(err) - resp.Body.Read(buf[:]) - fmt.Println(buf) + body, err = ioutil.ReadAll(resp.Body) + common.Must(err) + if string(body) != "HelloWorld" { + t.Fatal("http shadow") + } cancel() }