From a3ee9af30b33e549ab09957eac44e79d3f9dc048 Mon Sep 17 00:00:00 2001 From: Page Fault Date: Wed, 10 Jun 2020 03:40:03 +0000 Subject: [PATCH] utls support --- go.mod | 1 + go.sum | 2 + test/senario/proxy_test.go | 136 +++++++++++++-- test/util/target.go | 104 ++++++++++++ test/util/util.go | 2 +- tunnel/mux/client.go | 4 +- tunnel/router/client.go | 1 + tunnel/router/config.go | 1 + tunnel/router/tunnel.go | 23 +++ tunnel/shadowsocks/tunnel.go | 1 + tunnel/tproxy/server.go | 2 +- tunnel/transport/client.go | 85 ++++++++-- tunnel/transport/config.go | 27 +-- tunnel/transport/fingerprint/tls.go | 250 ++++++++++++++++++++++++++++ tunnel/transport/server.go | 34 ++-- tunnel/trojan/config.go | 14 +- tunnel/trojan/server.go | 30 ++-- 17 files changed, 646 insertions(+), 71 deletions(-) create mode 100644 test/util/target.go create mode 100644 tunnel/router/client.go create mode 100644 tunnel/router/config.go create mode 100644 tunnel/router/tunnel.go create mode 100644 tunnel/shadowsocks/tunnel.go create mode 100644 tunnel/transport/fingerprint/tls.go diff --git a/go.mod b/go.mod index 6e55876..db063b8 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed github.com/go-sql-driver/mysql v1.5.0 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0 github.com/smartystreets/goconvey v1.6.4 github.com/xtaci/smux v1.5.15-0.20200523091831-637399ad4398 golang.org/x/crypto v0.0.0-20200602180216-279210d13fed diff --git a/go.sum b/go.sum index 046a3ef..4c52cb3 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0 h1:vIkvetWOJZSADSKCF9MLTsQNW2httdBmYz47dQQteP8= +github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= diff --git a/test/senario/proxy_test.go b/test/senario/proxy_test.go index c31e80c..26f5213 100644 --- a/test/senario/proxy_test.go +++ b/test/senario/proxy_test.go @@ -1,15 +1,23 @@ package senario_test import ( + "bytes" + "fmt" + "github.com/p4gefau1t/trojan-go/test/util" "io/ioutil" + "net" "testing" + "time" "github.com/p4gefau1t/trojan-go/common" _ "github.com/p4gefau1t/trojan-go/log/golog" "github.com/p4gefau1t/trojan-go/proxy" _ "github.com/p4gefau1t/trojan-go/proxy/client" + _ "github.com/p4gefau1t/trojan-go/proxy/forward" + _ "github.com/p4gefau1t/trojan-go/proxy/nat" _ "github.com/p4gefau1t/trojan-go/proxy/server" _ "github.com/p4gefau1t/trojan-go/statistic/memory" + netproxy "golang.org/x/net/proxy" ) var cert string = ` @@ -71,46 +79,150 @@ func init() { ioutil.WriteFile("server.key", []byte(key), 0777) } -func TestProxy(t *testing.T) { - clientData := ` +func TestClientServer(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: 4444 -local-addr: 127.0.0.1 -remote-port: 4443 +local-port: %d +remote-addr: 127.0.0.1 +remote-port: %d password: - password ssl: verify: false + fingerprint: firefox + sni: localhost websocket: enabled: true path: /ws hostname: 127.0.0.1 mux: enabled: true -` +`, socksPort, serverPort) go func() { err := proxy.RunProxy([]byte(clientData), false) common.Must(err) }() - serverData := ` + serverData := fmt.Sprintf(` run-type: server local-addr: 127.0.0.1 -local-port: 4443 +local-port: %d remote-addr: 127.0.0.1 -remote-port: 80 +remote-port: %s password: - password ssl: - verify: false + verify-hostname: false key: server.key cert: server.crt + sni: "localhost" websocket: enabled: true path: /ws hostname: 127.0.0.1 -` - err := proxy.RunProxy([]byte(serverData), false) +`, serverPort, util.HTTPPort) + go func() { + err := proxy.RunProxy([]byte(serverData), false) + common.Must(err) + }() + + 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") + _, targetPort, _ := net.SplitHostPort(util.EchoAddr) + clientData := fmt.Sprintf(` +run-type: forward +local-addr: 127.0.0.1 +local-port: %d +remote-addr: 127.0.0.1 +remote-port: %d +target-addr: 127.0.0.1 +target-port: %s +password: + - password +ssl: + verify: false + fingerprint: firefox + sni: localhost +websocket: + enabled: true + path: /ws + hostname: 127.0.0.1 +mux: + enabled: true +`, clientPort, serverPort, targetPort) + go func() { + err := proxy.RunProxy([]byte(clientData), false) + common.Must(err) + }() + + serverData := fmt.Sprintf(` +run-type: server +local-addr: 127.0.0.1 +local-port: %d +remote-addr: 127.0.0.1 +remote-port: %s +password: + - password +ssl: + verify-hostname: false + key: server.key + cert: server.crt + sni: "localhost" +websocket: + enabled: true + path: /ws + hostname: 127.0.0.1 +`, serverPort, util.HTTPPort) + go func() { + err := proxy.RunProxy([]byte(serverData), false) + common.Must(err) + }() + + time.Sleep(time.Second * 2) + + payload := util.GeneratePayload(1024) + buf := [1024]byte{} + + conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", clientPort)) + common.Must(err) + + common.Must2(conn.Write(payload)) + common.Must2(conn.Read(buf[:])) + + if !bytes.Equal(payload, buf[:]) { + t.Fail() + } + + packet, err := net.ListenPacket("udp", "") + common.Must(err) + common.Must2(packet.WriteTo(payload, &net.UDPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: clientPort, + })) + _, _, err = packet.ReadFrom(buf[:]) + common.Must(err) + if !bytes.Equal(payload, buf[:]) { + t.Fail() + } } diff --git a/test/util/target.go b/test/util/target.go new file mode 100644 index 0000000..f5d6431 --- /dev/null +++ b/test/util/target.go @@ -0,0 +1,104 @@ +package util + +import ( + "crypto/rand" + "fmt" + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/log" + "golang.org/x/net/websocket" + "io" + "net" + "net/http" + "time" +) + +var HTTPAddr string +var HTTPPort string + +func runHelloHTTPServer() { + httpHello := func(w http.ResponseWriter, req *http.Request) { + w.Write([]byte("HelloWorld")) + } + + wsConfig, err := websocket.NewConfig("wss://127.0.0.1/websocket", "https://127.0.0.1") + common.Must(err) + wsServer := websocket.Server{ + Config: *wsConfig, + Handler: func(conn *websocket.Conn) { + conn.Write([]byte("HelloWorld")) + }, + Handshake: func(wsConfig *websocket.Config, httpRequest *http.Request) error { + log.Debug("websocket url", httpRequest.URL, "origin", httpRequest.Header.Get("Origin")) + return nil + }, + } + mux := &http.ServeMux{} + mux.HandleFunc("/", httpHello) + mux.HandleFunc("/websocket", wsServer.ServeHTTP) + HTTPAddr = GetTestAddr() + _, HTTPPort, _ = net.SplitHostPort(HTTPAddr) + server := http.Server{ + Addr: HTTPAddr, + Handler: mux, + } + go server.ListenAndServe() + fmt.Println("http test server listening on", HTTPAddr) +} + +var EchoAddr string + +func runTCPEchoServer() { + listener, err := net.Listen("tcp", EchoAddr) + common.Must(err) + go func() { + for { + conn, err := listener.Accept() + if err != nil { + return + } + go func(conn net.Conn) { + for { + conn.SetDeadline(time.Now().Add(time.Second)) + buf := make([]byte, 2048) + n, err := conn.Read(buf) + if err != nil { + return + } + _, err = conn.Write(buf[0:n]) + if err != nil { + return + } + } + }(conn) + } + }() +} + +func runUDPEchoServer() { + conn, err := net.ListenPacket("udp", EchoAddr) + common.Must(err) + go func() { + for { + buf := make([]byte, 1024*8) + n, addr, err := conn.ReadFrom(buf[:]) + if err != nil { + return + } + log.Info("Echo from", addr) + conn.WriteTo(buf[0:n], addr) + } + }() +} + +func GeneratePayload(length int) []byte { + buf := make([]byte, length) + io.ReadFull(rand.Reader, buf) + return buf +} + +func init() { + runHelloHTTPServer() + EchoAddr = GetTestAddr() + runTCPEchoServer() + runUDPEchoServer() +} diff --git a/test/util/util.go b/test/util/util.go index fac6fa7..57329b4 100644 --- a/test/util/util.go +++ b/test/util/util.go @@ -96,7 +96,7 @@ func CheckPacket(a, b net.PacketConn) bool { return true } -func FindAvaliableAddr() string { +func GetTestAddr() string { port := common.PickPort("tcp", "127.0.0.1") return fmt.Sprintf("127.0.0.1:%d", port) } diff --git a/tunnel/mux/client.go b/tunnel/mux/client.go index 89da719..4b1c4aa 100644 --- a/tunnel/mux/client.go +++ b/tunnel/mux/client.go @@ -103,7 +103,7 @@ func (c *Client) newMuxClient() (*smuxClientInfo, error) { } conn, err := c.underlay.DialConn(fakeAddr, &Tunnel{}) if err != nil { - return nil, err + return nil, common.NewError("mux failed to dial").Base(err) } conn = newStickyConn(conn) @@ -150,7 +150,7 @@ func (c *Client) DialConn(addr *tunnel.Address, _ tunnel.Tunnel) (tunnel.Conn, e info, err := c.newMuxClient() if err != nil { - return nil, common.NewError("no avaliable mux client found") + return nil, common.NewError("no available mux client found").Base(err) } return createNewConn(info) } diff --git a/tunnel/router/client.go b/tunnel/router/client.go new file mode 100644 index 0000000..7ef135b --- /dev/null +++ b/tunnel/router/client.go @@ -0,0 +1 @@ +package router diff --git a/tunnel/router/config.go b/tunnel/router/config.go new file mode 100644 index 0000000..7ef135b --- /dev/null +++ b/tunnel/router/config.go @@ -0,0 +1 @@ +package router diff --git a/tunnel/router/tunnel.go b/tunnel/router/tunnel.go new file mode 100644 index 0000000..eb404e3 --- /dev/null +++ b/tunnel/router/tunnel.go @@ -0,0 +1,23 @@ +package router + +import ( + "context" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const Name = "ROUTER" + +type Tunnel struct { +} + +func (t *Tunnel) Name() string { + return Name +} + +func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { + panic("implement me") +} + +func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { + panic("not supported") +} diff --git a/tunnel/shadowsocks/tunnel.go b/tunnel/shadowsocks/tunnel.go new file mode 100644 index 0000000..4b35346 --- /dev/null +++ b/tunnel/shadowsocks/tunnel.go @@ -0,0 +1 @@ +package shadowsocks diff --git a/tunnel/tproxy/server.go b/tunnel/tproxy/server.go index e642991..25b0fd3 100644 --- a/tunnel/tproxy/server.go +++ b/tunnel/tproxy/server.go @@ -68,7 +68,7 @@ func (s *Server) packetDispatchLoop() { s.mappingLock.Unlock() continue } - log.Info("tproxy udp session from", src, "to", dst) + log.Info("tproxy udp session, from", src, "to", dst) ctx, cancel := context.WithCancel(s.ctx) conn := &PacketConn{ dokodemo.PacketConn{ diff --git a/tunnel/transport/client.go b/tunnel/transport/client.go index a0a9889..0c07fae 100644 --- a/tunnel/transport/client.go +++ b/tunnel/transport/client.go @@ -5,8 +5,12 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" + "github.com/p4gefau1t/trojan-go/tunnel/transport/fingerprint" + utls "github.com/refraction-networking/utls" "io" "io/ioutil" + "net" + "strings" "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/config" @@ -22,8 +26,11 @@ type Client struct { ca *x509.CertPool cipher []uint16 sessionTicket bool + reuseSession bool curve []tls.CurveID + fingerprint string keyLogger io.WriteCloser + websocket bool } func (c *Client) Close() error { @@ -31,16 +38,43 @@ func (c *Client) Close() error { } func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { - return nil, common.NewError("Not supported") + panic("not supported") } // DialConn implements tunnel.Client. It will ignore the params and directly dial to remote server func (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) { + if c.fingerprint != "" { + tcpConn, err := net.Dial("tcp", c.serverAddress.String()) + if err != nil { + return nil, err + } + tlsConn := utls.UClient(tcpConn, &utls.Config{ + RootCAs: c.ca, + ServerName: c.sni, + InsecureSkipVerify: !c.verify, + KeyLogWriter: c.keyLogger, + }, utls.HelloCustom) + spec, err := fingerprint.GetClientHelloSpec(c.fingerprint, c.websocket) + if err != nil { + return nil, common.NewError("invalid hello spec").Base(err) + } + if err := tlsConn.ApplyPreset(spec); err != nil { + return nil, common.NewError("transport failed to apply preset fingerprint").Base(err) + } + if err := tlsConn.Handshake(); err != nil { + return nil, common.NewError("transport failed to handshake with remote server").Base(err) + } + return &Conn{ + Conn: tlsConn, + }, nil + } tlsConn, err := tls.Dial("tcp", c.serverAddress.String(), &tls.Config{ - InsecureSkipVerify: !c.verify, - ServerName: c.sni, - RootCAs: c.ca, - KeyLogWriter: c.keyLogger, + InsecureSkipVerify: !c.verify, + ServerName: c.sni, + RootCAs: c.ca, + KeyLogWriter: c.keyLogger, + CipherSuites: c.cipher, + SessionTicketsDisabled: !c.sessionTicket, }) if err != nil { return nil, err @@ -52,15 +86,34 @@ func (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) { // NewClient creates a transport layer client func NewClient(ctx context.Context, c tunnel.Client) (*Client, error) { - clientConfig := config.FromContext(ctx, Name).(*Config) - serverAddress := tunnel.NewAddressFromHostPort("tcp", clientConfig.RemoteHost, clientConfig.RemotePort) - client := &Client{ - verify: clientConfig.TLS.Verify, - sni: clientConfig.TLS.SNI, - serverAddress: serverAddress, + cfg := config.FromContext(ctx, Name).(*Config) + serverAddress := tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort) + + if cfg.TLS.Fingerprint != "" { + _, err := fingerprint.GetClientHelloSpec(cfg.TLS.Fingerprint, cfg.Websocket.Enabled) + if err != nil { + return nil, err + } + log.Info("tls fingerprint", cfg.TLS.Fingerprint, "applied") } - if clientConfig.TLS.CertPath != "" { - caCertByte, err := ioutil.ReadFile(clientConfig.TLS.CertPath) + + if cfg.TLS.SNI == "" { + cfg.TLS.SNI = cfg.RemoteHost + log.Warn("tls sni is unspecified. using remote-address") + } + + client := &Client{ + verify: cfg.TLS.Verify, + sni: cfg.TLS.SNI, + serverAddress: serverAddress, + cipher: fingerprint.ParseCipher(strings.Split(cfg.TLS.Cipher, ":")), + sessionTicket: cfg.TLS.ReuseSession, + fingerprint: cfg.TLS.Fingerprint, + websocket: cfg.Websocket.Enabled, + } + + if cfg.TLS.CertPath != "" { + caCertByte, err := ioutil.ReadFile(cfg.TLS.CertPath) if err != nil { return nil, common.NewError("failed to load cert file").Base(err) } @@ -88,16 +141,16 @@ func NewClient(ctx context.Context, c tunnel.Client) (*Client, error) { } log.Trace("issuer:", cert.Issuer, "subject:", cert.Subject) } - } else if len(clientConfig.TLS.CertBytes) != 0 { + } else if len(cfg.TLS.CertBytes) != 0 { client.ca = x509.NewCertPool() - ok := client.ca.AppendCertsFromPEM(clientConfig.TLS.CertBytes) + ok := client.ca.AppendCertsFromPEM(cfg.TLS.CertBytes) if !ok { log.Warn("invalid cert list") } log.Info("using custom cert (data)") } - if clientConfig.TLS.CertPath == "" && len(clientConfig.TLS.CertBytes) == 0 { + if cfg.TLS.CertPath == "" && len(cfg.TLS.CertBytes) == 0 { log.Info("cert is unspecified, using default ca list") } diff --git a/tunnel/transport/config.go b/tunnel/transport/config.go index d779461..f0a891e 100644 --- a/tunnel/transport/config.go +++ b/tunnel/transport/config.go @@ -11,6 +11,11 @@ type Config struct { RemotePort int `json:"remote_port" yaml:"remote-port"` TLS TLSConfig `json:"ssl" yaml:"ssl"` TransportPlugin TransportPluginConfig `json:"transport_plugin" yaml:"transport-plugin"` + Websocket WebsocketConfig `json:"websocket" yaml:"websocket"` +} + +type WebsocketConfig struct { + Enabled bool `json:"enabled" yaml:"enabled"'` } type TLSConfig struct { @@ -43,17 +48,15 @@ type TransportPluginConfig struct { Env []string `json:"env" yaml:"env"` } -func newDefaultConfig() interface{} { - return &Config{ - TLS: TLSConfig{ - Verify: true, - VerifyHostName: true, - Fingerprint: "firefox", - ALPN: []string{"http/1.1"}, - }, - } -} - func init() { - config.RegisterConfigCreator(Name, newDefaultConfig) + config.RegisterConfigCreator(Name, func() interface{} { + return &Config{ + TLS: TLSConfig{ + Verify: true, + VerifyHostName: true, + Fingerprint: "firefox", + ALPN: []string{"http/1.1"}, + }, + } + }) } diff --git a/tunnel/transport/fingerprint/tls.go b/tunnel/transport/fingerprint/tls.go new file mode 100644 index 0000000..cb6beb3 --- /dev/null +++ b/tunnel/transport/fingerprint/tls.go @@ -0,0 +1,250 @@ +package fingerprint + +import ( + "crypto/tls" + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/log" + . "github.com/refraction-networking/utls" +) + +func GetClientHelloSpec(name string, websocket bool) (*ClientHelloSpec, error) { + var spec *ClientHelloSpec + switch name { + case "chrome": + spec = &ClientHelloSpec{ + CipherSuites: []uint16{ + GREASE_PLACEHOLDER, + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + TLS_CHACHA20_POLY1305_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_GCM_SHA256, + TLS_RSA_WITH_AES_256_GCM_SHA384, + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + }, + CompressionMethods: []byte{ + 0x00, // compressionNone + }, + Extensions: []TLSExtension{ + &UtlsGREASEExtension{}, + &SNIExtension{}, + &UtlsExtendedMasterSecretExtension{}, + &RenegotiationInfoExtension{Renegotiation: RenegotiateOnceAsClient}, + &SupportedCurvesExtension{[]CurveID{ + CurveID(GREASE_PLACEHOLDER), + X25519, + CurveP256, + CurveP384, + }}, + &SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &SessionTicketExtension{}, + &ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &StatusRequestExtension{}, + &SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{ + ECDSAWithP256AndSHA256, + PSSWithSHA256, + PKCS1WithSHA256, + ECDSAWithP384AndSHA384, + PSSWithSHA384, + PKCS1WithSHA384, + PSSWithSHA512, + PKCS1WithSHA512, + }}, + &SCTExtension{}, + &KeyShareExtension{[]KeyShare{ + {Group: CurveID(GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: X25519}, + }}, + &PSKKeyExchangeModesExtension{[]uint8{ + PskModeDHE, + }}, + &SupportedVersionsExtension{[]uint16{ + GREASE_PLACEHOLDER, + VersionTLS13, + VersionTLS12, + VersionTLS11, + VersionTLS10, + }}, + &FakeCertCompressionAlgsExtension{[]CertCompressionAlgo{ + CertCompressionBrotli, + }}, + &UtlsGREASEExtension{}, + &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}, + }, + } + case "firefox": + spec = &ClientHelloSpec{ + TLSVersMin: VersionTLS10, + TLSVersMax: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_CHACHA20_POLY1305_SHA256, + TLS_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_3DES_EDE_CBC_SHA, + }, + CompressionMethods: []byte{ + 0, //compressionNone, + }, + Extensions: []TLSExtension{ + &SNIExtension{}, + &UtlsExtendedMasterSecretExtension{}, + &RenegotiationInfoExtension{Renegotiation: RenegotiateOnceAsClient}, + &SupportedCurvesExtension{[]CurveID{ + X25519, + CurveP256, + CurveP384, + CurveP521, + CurveID(FakeFFDHE2048), + CurveID(FakeFFDHE3072), + }}, + &SupportedPointsExtension{SupportedPoints: []byte{ + 0, //pointFormatUncompressed, + }}, + &SessionTicketExtension{}, + &ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &StatusRequestExtension{}, + &KeyShareExtension{[]KeyShare{ + {Group: X25519}, + {Group: CurveP256}, + }}, + &SupportedVersionsExtension{[]uint16{ + VersionTLS13, + VersionTLS12, + VersionTLS11, + VersionTLS10}}, + &SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{ + ECDSAWithP256AndSHA256, + ECDSAWithP384AndSHA384, + ECDSAWithP521AndSHA512, + PSSWithSHA256, + PSSWithSHA384, + PSSWithSHA512, + PKCS1WithSHA256, + PKCS1WithSHA384, + PKCS1WithSHA512, + ECDSAWithSHA1, + PKCS1WithSHA1, + }}, + &PSKKeyExchangeModesExtension{[]uint8{1 /*pskModeDHE*/}}, + &FakeRecordSizeLimitExtension{0x4001}, + &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}, + }} + case "ios": + spec = &ClientHelloSpec{ + CipherSuites: []uint16{ + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + DISABLED_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + DISABLED_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_RSA_WITH_AES_256_GCM_SHA384, + TLS_RSA_WITH_AES_128_GCM_SHA256, + DISABLED_TLS_RSA_WITH_AES_256_CBC_SHA256, + TLS_RSA_WITH_AES_128_CBC_SHA256, + TLS_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_CBC_SHA, + 0xc008, + TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_RSA_WITH_3DES_EDE_CBC_SHA, + }, + CompressionMethods: []byte{ + 0, //compressionNone, + }, + Extensions: []TLSExtension{ + &RenegotiationInfoExtension{Renegotiation: RenegotiateOnceAsClient}, + &SNIExtension{}, + &UtlsExtendedMasterSecretExtension{}, + &SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{ + ECDSAWithP256AndSHA256, + PSSWithSHA256, + PKCS1WithSHA256, + ECDSAWithP384AndSHA384, + ECDSAWithSHA1, + PSSWithSHA384, + PSSWithSHA384, + PKCS1WithSHA384, + PSSWithSHA512, + PKCS1WithSHA512, + PKCS1WithSHA1, + }}, + &StatusRequestExtension{}, + &NPNExtension{}, + &SCTExtension{}, + &ALPNExtension{AlpnProtocols: []string{"h2", "h2-16", "h2-15", "h2-14", "spdy/3.1", "spdy/3", "http/1.1"}}, + &SupportedPointsExtension{SupportedPoints: []byte{ + 0, //pointFormatUncompressed, + }}, + &SupportedCurvesExtension{[]CurveID{ + X25519, + CurveP256, + CurveP384, + CurveP521, + }}, + }, + } + } + if spec == nil { + return nil, common.NewError("Invalid fingerprint:" + name) + } + if websocket { + for i := range spec.Extensions { + if alpn, ok := spec.Extensions[i].(*ALPNExtension); ok { + alpn.AlpnProtocols = []string{"http/1.1"} + spec.Extensions[i] = alpn + log.Debug("websocket http/1.1") + } + } + } + return spec, nil +} + +func ParseCipher(s []string) []uint16 { + all := tls.CipherSuites() + result := []uint16{} + for _, p := range s { + found := true + for _, q := range all { + if q.Name == p { + result = append(result, q.ID) + break + } + if !found { + log.Warn("invalid cipher suite", p, "skipped") + } + } + } + return result +} diff --git a/tunnel/transport/server.go b/tunnel/transport/server.go index ba87284..8902af9 100644 --- a/tunnel/transport/server.go +++ b/tunnel/transport/server.go @@ -40,7 +40,6 @@ type Server struct { redir *redirector.Redirector connChan chan tunnel.Conn wsChan chan tunnel.Conn - errChan chan error ctx context.Context cancel context.CancelFunc } @@ -56,7 +55,7 @@ func (s *Server) acceptLoop() { tcpConn, err := s.tcpListener.Accept() if err != nil { s.cancel() - s.errChan <- common.NewError("transport accept error") + log.Error(common.NewError("transport accept error")) return } go func(tcpConn net.Conn) { @@ -83,7 +82,6 @@ func (s *Server) acceptLoop() { rewindConn.SetBufferSize(2048) tlsConn := tls.Server(rewindConn, tlsConfig) - tlsConn.Handshake() err = tlsConn.Handshake() rewindConn.StopBuffering() @@ -91,12 +89,11 @@ func (s *Server) acceptLoop() { if !sniVerified { // close tls conn immediately if the sni is invalid tlsConn.Close() - s.errChan <- common.NewError("tls client hello with wrong sni").Base(err) + log.Error(common.NewError("tls client hello with wrong sni").Base(err)) } else if strings.Contains(err.Error(), "first record does not look like a TLS handshake") { // not a valid tls client hello rewindConn.Rewind() - err = common.NewError("failed to perform tls handshake with " + tlsConn.RemoteAddr().String() + ", redirecting").Base(err) - s.errChan <- err + log.Error(common.NewError("failed to perform tls handshake with " + tlsConn.RemoteAddr().String() + ", redirecting").Base(err)) if s.fallbackAddress != nil { s.redir.Redirect(&redirector.Redirection{ InboundConn: rewindConn, @@ -111,7 +108,7 @@ func (s *Server) acceptLoop() { } else { // other cases, simply close it tlsConn.Close() - s.errChan <- common.NewError("tls handshake failed").Base(err) + log.Error(common.NewError("tls handshake failed").Base(err)) } return } @@ -150,8 +147,6 @@ func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) { return conn, nil case <-s.ctx.Done(): return nil, io.EOF - case err := <-s.errChan: - return nil, err } } // trojan overlay @@ -160,21 +155,35 @@ func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) { return conn, nil case <-s.ctx.Done(): return nil, io.EOF - case err := <-s.errChan: - return nil, err } } func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { - return nil, common.NewError("not supported") + panic("not supported") } // NewServer creates a transport layer server func NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) { cfg := config.FromContext(ctx, Name).(*Config) + if cfg.TLS.FallbackHost == "" { + cfg.TLS.FallbackHost = cfg.RemoteHost + log.Warn("empty fallback address") + } + if cfg.TLS.FallbackPort == 0 { + cfg.TLS.FallbackPort = cfg.RemotePort + log.Warn("empty fallback port") + } + listenAddress := tunnel.NewAddressFromHostPort("tcp", cfg.LocalHost, cfg.LocalPort) fallbackAddress := tunnel.NewAddressFromHostPort("tcp", cfg.TLS.FallbackHost, cfg.TLS.FallbackPort) + if cfg.TLS.FallbackPort != 0 { + fallbackConn, err := net.Dial("tcp", fallbackAddress.String()) + if err != nil { + return nil, common.NewError("invalid fallback address").Base(err) + } + fallbackConn.Close() + } tcpListener, err := net.Listen("tcp", listenAddress.String()) if err != nil { @@ -191,6 +200,7 @@ func NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) { wsChan: make(chan tunnel.Conn, 32), sni: cfg.TLS.SNI, alpn: cfg.TLS.ALPN, + verifySNI: cfg.TLS.VerifyHostName, } if cfg.TLS.KeyLogPath != "" { diff --git a/tunnel/trojan/config.go b/tunnel/trojan/config.go index a2aa521..b9f806c 100644 --- a/tunnel/trojan/config.go +++ b/tunnel/trojan/config.go @@ -3,10 +3,16 @@ package trojan 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"` - RemoteHost string `json:"remote_addr" yaml:"remote-addr"` - RemotePort int `json:"remote_port" yaml:"remote-port"` + LocalHost string `json:"local_addr" yaml:"local-addr"` + LocalPort int `json:"local_port" yaml:"local-port"` + RemoteHost string `json:"remote_addr" yaml:"remote-addr"` + RemotePort int `json:"remote_port" yaml:"remote-port"` + DisableHTTPCheck bool `json:"disable_http_check" yaml:"disable-http-check"` + MySQL MySQLConfig `json:"mysql" yaml:"mysql"` +} + +type MySQLConfig struct { + Enabled bool `json:"enabled" yaml:"enabled"` } func init() { diff --git a/tunnel/trojan/server.go b/tunnel/trojan/server.go index 5c39517..27a92aa 100644 --- a/tunnel/trojan/server.go +++ b/tunnel/trojan/server.go @@ -3,6 +3,8 @@ package trojan import ( "context" "fmt" + "github.com/p4gefau1t/trojan-go/statistic/memory" + "github.com/p4gefau1t/trojan-go/statistic/mysql" "io" "net" @@ -96,7 +98,6 @@ type Server struct { connChan chan tunnel.Conn muxChan chan tunnel.Conn packetChan chan tunnel.PacketConn - errChan chan error ctx context.Context } @@ -151,7 +152,7 @@ func (s *Server) acceptLoop() { s.muxChan <- inboundConn log.Debug("mux connection") default: - s.errChan <- common.NewError(fmt.Sprintf("unknown trojan command %d", inboundConn.metadata.Command)) + log.Error(common.NewError(fmt.Sprintf("unknown trojan command %d", inboundConn.metadata.Command))) } }(conn) } @@ -163,8 +164,6 @@ func (s *Server) AcceptConn(nextTunnel tunnel.Tunnel) (tunnel.Conn, error) { select { case t := <-s.muxChan: return t, nil - case err := <-s.errChan: - return nil, err case <-s.ctx.Done(): return nil, common.NewError("trojan client closed") } @@ -172,8 +171,6 @@ func (s *Server) AcceptConn(nextTunnel tunnel.Tunnel) (tunnel.Conn, error) { select { case t := <-s.connChan: return t, nil - case err := <-s.errChan: - return nil, err case <-s.ctx.Done(): return nil, common.NewError("trojan client closed") } @@ -190,11 +187,16 @@ func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { } func NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) { - auth, err := statistic.NewAuthenticator(ctx, "memory") - if err != nil { - return nil, common.NewError("failed to create server authenticator").Base(err) - } cfg := config.FromContext(ctx, Name).(*Config) + + // TODO replace this dirty code + auth, err := statistic.NewAuthenticator(ctx, memory.Name) + if cfg.MySQL.Enabled { + auth, err = statistic.NewAuthenticator(ctx, mysql.Name) + } + if err != nil { + return nil, common.NewError("failed to create authenticator").Base(err) + } redirAddr := tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort) s := &Server{ underlay: underlay, @@ -204,8 +206,14 @@ func NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, erro connChan: make(chan tunnel.Conn, 32), muxChan: make(chan tunnel.Conn, 32), packetChan: make(chan tunnel.PacketConn, 32), - errChan: make(chan error, 32), } + + redirConn, err := net.Dial("tcp", redirAddr.String()) + if err != nil { + return nil, common.NewError("invalid redirect address").Base(err) + } + redirConn.Close() + go s.acceptLoop() log.Debug("trojan server created") return s, nil