diff --git a/api/service/server.go b/api/service/server.go index 38cb2bf..203afb8 100644 --- a/api/service/server.go +++ b/api/service/server.go @@ -30,7 +30,7 @@ func (s *ServerAPI) GetUsers(stream TrojanServerService_GetUsersServer) error { return err } if req.User == nil { - return common.NewError("User is unspecified") + return common.NewError("user is unspecified") } if req.User.Hash == "" { req.User.Hash = common.SHA224String(req.User.Password) @@ -96,7 +96,7 @@ func (s *ServerAPI) SetUsers(stream TrojanServerService_SetUsersServer) error { if req.SpeedLimit != nil { valid, user := s.auth.AuthUser(req.User.Hash) if !valid { - return common.NewError("Failed to add new user") + return common.NewError("failed to add new user") } user.SetSpeedLimit(int(req.SpeedLimit.DownloadSpeed), int(req.SpeedLimit.UploadSpeed)) } @@ -105,7 +105,7 @@ func (s *ServerAPI) SetUsers(stream TrojanServerService_SetUsersServer) error { case SetUsersRequest_Modify: valid, user := s.auth.AuthUser(req.User.Hash) if !valid { - err = common.NewError("Invalid user " + req.User.Hash) + err = common.NewError("invalid user " + req.User.Hash) } else { if req.SpeedLimit.DownloadSpeed > 0 || req.SpeedLimit.UploadSpeed > 0 { user.SetSpeedLimit(int(req.SpeedLimit.DownloadSpeed), int(req.SpeedLimit.UploadSpeed)) diff --git a/easy/easy.go b/easy/easy.go index f7b1d47..496682b 100644 --- a/easy/easy.go +++ b/easy/easy.go @@ -81,6 +81,7 @@ func (o *easy) Handle() error { "%s" ], "ssl": { + "verify_hostname": false, "cert": "%s", "key": "%s" } diff --git a/proxy/stack.go b/proxy/stack.go index 3e8db0c..19c2403 100644 --- a/proxy/stack.go +++ b/proxy/stack.go @@ -14,6 +14,16 @@ type Node struct { tunnel.Server } +func NewNode(name string, isEndpoint bool, context context.Context, server tunnel.Server) *Node { + return &Node{ + Name: name, + IsEndpoint: isEndpoint, + Context: context, + Server: server, + Next: make(map[string]*Node), + } +} + func (n *Node) BuildNext(name string) *Node { if next, found := n.Next[name]; found { return next diff --git a/statistic/memory/memory.go b/statistic/memory/memory.go index 362c35f..4a0bb7d 100644 --- a/statistic/memory/memory.go +++ b/statistic/memory/memory.go @@ -205,7 +205,7 @@ func (a *Authenticator) DelUser(hash string) error { defer a.Unlock() meter, found := a.users[hash] if !found { - return common.NewError("Hash " + hash + "is not exist") + return common.NewError("hash " + hash + " not found") } meter.Close() delete(a.users, hash) diff --git a/test/scenario/proxy_test.go b/test/scenario/proxy_test.go index 733e6b1..64445bd 100644 --- a/test/scenario/proxy_test.go +++ b/test/scenario/proxy_test.go @@ -6,6 +6,7 @@ import ( "github.com/p4gefau1t/trojan-go/test/util" "io/ioutil" "net" + "net/http" "sync" "testing" "time" @@ -19,6 +20,7 @@ import ( _ "github.com/p4gefau1t/trojan-go/proxy/server" _ "github.com/p4gefau1t/trojan-go/statistic/memory" netproxy "golang.org/x/net/proxy" + _ "net/http/pprof" ) var cert = ` @@ -326,7 +328,6 @@ websocket: if !CheckClientServer(clientData, serverData, socksPort) { t.Fail() } - } func TestForward(t *testing.T) { @@ -420,3 +421,79 @@ shadowsocks: t.Fail() } } + +func SingleThreadBenchmark(clientData, serverData string, socksPort int) { + server, err := proxy.NewProxyFromConfigData([]byte(clientData), false) + common.Must(err) + go server.Run() + + client, err := proxy.NewProxyFromConfigData([]byte(serverData), false) + common.Must(err) + go client.Run() + + time.Sleep(time.Second * 2) + dialer, err := netproxy.SOCKS5("tcp", fmt.Sprintf("127.0.0.1:%d", socksPort), nil, netproxy.Direct) + + const num = 100 + wg := sync.WaitGroup{} + wg.Add(num) + const payloadSize = 1024 * 1024 * 1024 + payload := util.GeneratePayload(payloadSize) + + for i := 0; i < 100; i++ { + conn, err := dialer.Dial("tcp", util.BlackHoleAddr) + common.Must(err) + + t1 := time.Now() + common.Must2(conn.Write(payload)) + t2 := time.Now() + + speed := float64(payloadSize) / (float64(t2.Sub(t1).Nanoseconds()) / float64(time.Second)) + fmt.Printf("speed: %f Gbps\n", speed/1024/1024/1024) + + conn.Close() + } + client.Close() + server.Close() + return +} + +func BenchmarkClientServer(b *testing.B) { + go func() { + fmt.Println(http.ListenAndServe("localhost:6060", nil)) + }() + 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 +log-level: 0 +password: + - password +ssl: + verify: false + fingerprint: firefox + sni: localhost +`, socksPort, serverPort) + serverData := fmt.Sprintf(` +run-type: server +local-addr: 127.0.0.1 +local-port: %d +remote-addr: 127.0.0.1 +remote-port: %s +log-level: 0 +disable-http-check: true +password: + - password +ssl: + verify-hostname: false + key: server.key + cert: server.crt + sni: localhost +`, serverPort, util.HTTPPort) + + SingleThreadBenchmark(clientData, serverData, socksPort) +} diff --git a/test/util/target.go b/test/util/target.go index df9f860..c21810d 100644 --- a/test/util/target.go +++ b/test/util/target.go @@ -7,6 +7,7 @@ import ( "github.com/p4gefau1t/trojan-go/log" "golang.org/x/net/websocket" "io" + "io/ioutil" "net" "net/http" "sync" @@ -102,14 +103,59 @@ func GeneratePayload(length int) []byte { return buf } +var BlackHoleAddr string +var BlackHolePort int + +func runTCPBlackHoleServer() { + listener, err := net.Listen("tcp", BlackHoleAddr) + common.Must(err) + wg.Done() + go func() { + for { + conn, err := listener.Accept() + if err != nil { + return + } + go func(conn net.Conn) { + io.Copy(ioutil.Discard, conn) + conn.Close() + }(conn) + } + }() +} + +func runUDPBlackHoleServer() { + conn, err := net.ListenPacket("udp", BlackHoleAddr) + common.Must(err) + wg.Done() + go func() { + buf := make([]byte, 1024*8) + for { + _, _, err := conn.ReadFrom(buf[:]) + if err != nil { + return + } + } + }() +} + var wg = sync.WaitGroup{} func init() { - wg.Add(3) + wg.Add(5) runHelloHTTPServer() + EchoPort = common.PickPort("tcp", "127.0.0.1") EchoAddr = fmt.Sprintf("127.0.0.1:%d", EchoPort) + + BlackHolePort = common.PickPort("tcp", "127.0.0.1") + BlackHoleAddr = fmt.Sprintf("127.0.0.1:%d", BlackHolePort) + runTCPEchoServer() runUDPEchoServer() + + runTCPBlackHoleServer() + runUDPBlackHoleServer() + wg.Wait() } diff --git a/tunnel/freedom/client.go b/tunnel/freedom/client.go index 0f0c40d..4bc08b7 100644 --- a/tunnel/freedom/client.go +++ b/tunnel/freedom/client.go @@ -6,13 +6,12 @@ import ( "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/config" "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/tunnel" "golang.org/x/net/proxy" "net" "net/url" "strconv" "time" - - "github.com/p4gefau1t/trojan-go/tunnel" ) type Client struct { @@ -137,7 +136,7 @@ func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { } udpConn, err := net.ListenPacket(network, "") if err != nil { - return nil, err + return nil, common.NewError("freedom failed to listen udp socket").Base(err) } return &PacketConn{ UDPConn: udpConn.(*net.UDPConn), @@ -150,6 +149,7 @@ func (c *Client) Close() error { func NewClient(ctx context.Context, client tunnel.Client) (*Client, error) { // TODO implement dns + // TODO socks5 udp cfg := config.FromContext(ctx, Name).(*Config) addr := tunnel.NewAddressFromHostPort("tcp", cfg.ForwardProxy.ProxyHost, cfg.ForwardProxy.ProxyPort) return &Client{ diff --git a/tunnel/tls/server.go b/tunnel/tls/server.go index eb6a50c..3208fd7 100644 --- a/tunnel/tls/server.go +++ b/tunnel/tls/server.go @@ -43,6 +43,7 @@ type Server struct { ctx context.Context cancel context.CancelFunc underlay tunnel.Server + nextHTTP bool } func (s *Server) Close() error { @@ -125,7 +126,7 @@ func (s *Server) acceptLoop() { // we use real http header parser to mimic a real http server rewindConn := common.NewRewindConn(tlsConn) - rewindConn.SetBufferSize(512) + rewindConn.SetBufferSize(1024) r := bufio.NewReader(rewindConn) httpReq, err := http.ReadRequest(r) rewindConn.Rewind() @@ -136,6 +137,14 @@ func (s *Server) acceptLoop() { Conn: rewindConn, } } else { + if !s.nextHTTP { + // there is no websocket layer waiting for connections, redirect it + s.redir.Redirect(&redirector.Redirection{ + InboundConn: rewindConn, + RedirectTo: s.fallbackAddress, + }) + return + } // this is a http request, pass it to websocket protocol layer log.Debug("http req: ", httpReq) s.wsChan <- &transport.Conn{ @@ -148,6 +157,8 @@ func (s *Server) acceptLoop() { func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) { if _, ok := overlay.(*websocket.Tunnel); ok { + s.nextHTTP = true + log.Debug("next proto http") // websocket overlay select { case conn := <-s.wsChan: @@ -173,22 +184,23 @@ func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) { cfg := config.FromContext(ctx, Name).(*Config) ctx, cancel := context.WithCancel(ctx) - fallbackAddress := tunnel.NewAddressFromHostPort("tcp", cfg.TLS.FallbackHost, cfg.TLS.FallbackPort) - 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") - } else { + var fallbackAddress *tunnel.Address + if cfg.TLS.FallbackPort != 0 { + if cfg.TLS.FallbackHost == "" { + cfg.TLS.FallbackHost = cfg.RemoteHost + log.Warn("empty tls fallback address") + } + fallbackAddress = tunnel.NewAddressFromHostPort("tcp", cfg.TLS.FallbackHost, cfg.TLS.FallbackPort) fallbackConn, err := net.Dial("tcp", fallbackAddress.String()) if err != nil { return nil, common.NewError("invalid fallback address").Base(err) } fallbackConn.Close() + } else { + log.Warn("empty tls fallback port") } + if cfg.TLS.SNI == "" && cfg.TLS.VerifyHostName { return nil, common.NewError("cannot verify hostname without sni") } diff --git a/tunnel/transport/server.go b/tunnel/transport/server.go index f501c01..9d0f73d 100644 --- a/tunnel/transport/server.go +++ b/tunnel/transport/server.go @@ -20,6 +20,7 @@ type Server struct { cmd *exec.Cmd connChan chan tunnel.Conn wsChan chan tunnel.Conn + nextHTTP bool ctx context.Context cancel context.CancelFunc } @@ -46,23 +47,31 @@ func (s *Server) acceptLoop() { go func(tcpConn net.Conn) { log.Info("tcp connection from", tcpConn.RemoteAddr()) - // we use real http header parser to mimic a real http server - rewindConn := common.NewRewindConn(tcpConn) - rewindConn.SetBufferSize(512) - r := bufio.NewReader(rewindConn) - httpReq, err := http.ReadRequest(r) - rewindConn.Rewind() - rewindConn.StopBuffering() - if err != nil { - // this is not a http request, pass it to trojan protocol layer for further inspection - s.connChan <- &Conn{ - Conn: rewindConn, + if s.nextHTTP { // plaintext mode enabled + // we use real http header parser to mimic a real http server + rewindConn := common.NewRewindConn(tcpConn) + rewindConn.SetBufferSize(512) + defer rewindConn.StopBuffering() + + r := bufio.NewReader(rewindConn) + httpReq, err := http.ReadRequest(r) + rewindConn.Rewind() + rewindConn.StopBuffering() + if err != nil { + // this is not a http request, pass it to trojan protocol layer for further inspection + s.connChan <- &Conn{ + Conn: rewindConn, + } + } else { + // this is a http request, pass it to websocket protocol layer + log.Debug("plaintext http request: ", httpReq) + s.wsChan <- &Conn{ + Conn: rewindConn, + } } } else { - // this is a http request, pass it to websocket protocol layer - log.Debug("plaintext http request: ", httpReq) - s.wsChan <- &Conn{ - Conn: rewindConn, + s.connChan <- &Conn{ + Conn: tcpConn, } } }(tcpConn) @@ -72,6 +81,7 @@ func (s *Server) acceptLoop() { func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) { // TODO fix import cycle if overlay != nil && overlay.Name() == "WEBSOCKET" { + s.nextHTTP = true select { case conn := <-s.wsChan: return conn, nil diff --git a/tunnel/trojan/client.go b/tunnel/trojan/client.go index e56507c..9191e56 100644 --- a/tunnel/trojan/client.go +++ b/tunnel/trojan/client.go @@ -55,14 +55,14 @@ func (c *OutboundConn) WriteHeader(payload []byte) error { c.headerWritten = true return err } - return common.NewError("header is already written") + return common.NewError("trojan header has been written") } func (c *OutboundConn) Write(p []byte) (int, error) { if !c.headerWritten { err := c.WriteHeader(p) if err != nil { - return 0, err + return 0, common.NewError("trojan failed to flush header with payload").Base(err) } return len(p), nil } diff --git a/tunnel/trojan/packet.go b/tunnel/trojan/packet.go index 39c1dab..26c4232 100644 --- a/tunnel/trojan/packet.go +++ b/tunnel/trojan/packet.go @@ -3,6 +3,7 @@ package trojan import ( "bytes" "encoding/binary" + "github.com/p4gefau1t/trojan-go/log" "io" "io/ioutil" "net" @@ -45,6 +46,8 @@ func (c *PacketConn) WriteWithMetadata(payload []byte, metadata *tunnel.Metadata w.Write(payload) _, err := c.Conn.Write(w.Bytes()) + + log.Debug("udp packet back to", c.RemoteAddr(), "metadata", metadata, "size", length) return len(payload), err } @@ -68,8 +71,8 @@ func (c *PacketConn) ReadWithMetadata(payload []byte) (int, *tunnel.Metadata, er return 0, nil, common.NewError("failed to read crlf") } - if len(payload) < int(length) || length > MaxPacketSize { - io.CopyN(ioutil.Discard, c.Conn, int64(length)) + if len(payload) < length || length > MaxPacketSize { + io.CopyN(ioutil.Discard, c.Conn, int64(length)) //drain the rest of the packet return 0, nil, common.NewError("incoming packet size is too large") } _, err = io.ReadFull(c.Conn, payload[:length]) @@ -77,6 +80,7 @@ func (c *PacketConn) ReadWithMetadata(payload []byte) (int, *tunnel.Metadata, er return 0, nil, common.NewError("failed to read payload") } + log.Debug("udp packet from", c.RemoteAddr(), "metadata", addr.String(), "size", length) return length, &tunnel.Metadata{ Address: addr, }, err diff --git a/tunnel/trojan/server.go b/tunnel/trojan/server.go index 295a1b9..3f71ec9 100644 --- a/tunnel/trojan/server.go +++ b/tunnel/trojan/server.go @@ -124,6 +124,7 @@ func (s *Server) acceptLoop() { go func(conn tunnel.Conn) { rewindConn := common.NewRewindConn(conn) rewindConn.SetBufferSize(128) + defer rewindConn.StopBuffering() inboundConn := &InboundConn{ Conn: rewindConn,