From d5640efd7ee273d81da6d40f78137ead6f59be23 Mon Sep 17 00:00:00 2001 From: Toby Date: Wed, 22 Apr 2020 13:45:25 -0700 Subject: [PATCH] WIP SOCKS5 proxy --- LICENSE.md | 21 ++ README.md | 8 + cmd/client.json | 8 + cmd/config.go | 84 +++++ cmd/main.go | 40 +++ cmd/proxy_client.go | 88 +++++ cmd/proxy_config.go | 69 ++++ cmd/proxy_server.go | 101 ++++++ cmd/relay/main.go | 26 -- cmd/{relay/client.go => relay_client.go} | 32 +- cmd/{relay/config.go => relay_config.go} | 93 +---- cmd/{relay/server.go => relay_server.go} | 32 +- cmd/server.json | 9 + cmd/{relay/flags.go => utils.go} | 0 docs/logos/readme.png | Bin 0 -> 11979 bytes go.mod | 8 +- go.sum | 8 + internal/core/client.go | 10 +- internal/core/control.pb.go | 69 ++-- internal/core/control.proto | 4 +- internal/core/server.go | 21 +- internal/utils/pipe.go | 12 + pkg/core/interface.go | 10 +- pkg/socks5/handler.go | 56 +++ pkg/socks5/server.go | 429 +++++++++++++++++++++++ 25 files changed, 1024 insertions(+), 214 deletions(-) create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 cmd/client.json create mode 100644 cmd/config.go create mode 100644 cmd/main.go create mode 100644 cmd/proxy_client.go create mode 100644 cmd/proxy_config.go create mode 100644 cmd/proxy_server.go delete mode 100644 cmd/relay/main.go rename cmd/{relay/client.go => relay_client.go} (77%) rename cmd/{relay/config.go => relay_config.go} (54%) rename cmd/{relay/server.go => relay_server.go} (67%) create mode 100644 cmd/server.json rename cmd/{relay/flags.go => utils.go} (100%) create mode 100644 docs/logos/readme.png create mode 100644 pkg/socks5/handler.go create mode 100644 pkg/socks5/server.go diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..55c410b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Toby + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..679f3f0 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +![Logo](docs/logos/readme.png) + +[![License][1]][2] [![Telegram][3]][4] + +[1]: https://img.shields.io/github/license/tobyxdd/hysteria?style=flat-square& +[2]: LICENSE.md +[3]: https://patrolavia.github.io/telegram-badge/chat.png +[4]: https://t.me/hysteria_github \ No newline at end of file diff --git a/cmd/client.json b/cmd/client.json new file mode 100644 index 0000000..0ddb13d --- /dev/null +++ b/cmd/client.json @@ -0,0 +1,8 @@ +{ + "listen": "localhost:1080", + "server": "toby.moe:36712", + "name": "", + "insecure": false, + "up_mbps": 50, + "down_mbps": 80 +} \ No newline at end of file diff --git a/cmd/config.go b/cmd/config.go new file mode 100644 index 0000000..3fb6069 --- /dev/null +++ b/cmd/config.go @@ -0,0 +1,84 @@ +package main + +import ( + "encoding/json" + "flag" + "io/ioutil" + "os" + "reflect" + "strings" +) + +const ( + mbpsToBps = 125000 + + DefaultMaxReceiveStreamFlowControlWindow = 33554432 + DefaultMaxReceiveConnectionFlowControlWindow = 67108864 + DefaultMaxIncomingStreams = 200 +) + +func loadConfig(cfg interface{}, args []string) error { + cfgVal := reflect.ValueOf(cfg).Elem() + fs := flag.NewFlagSet("", flag.ContinueOnError) + fsValMap := make(map[reflect.Value]interface{}, cfgVal.NumField()) + for i := 0; i < cfgVal.NumField(); i++ { + structField := cfgVal.Type().Field(i) + tag := structField.Tag + switch structField.Type.Kind() { + case reflect.String: + fsValMap[cfgVal.Field(i)] = + fs.String(jsonTagToFlagName(tag.Get("json")), "", tag.Get("desc")) + case reflect.Int: + fsValMap[cfgVal.Field(i)] = + fs.Int(jsonTagToFlagName(tag.Get("json")), 0, tag.Get("desc")) + case reflect.Uint64: + fsValMap[cfgVal.Field(i)] = + fs.Uint64(jsonTagToFlagName(tag.Get("json")), 0, tag.Get("desc")) + case reflect.Bool: + var bf optionalBoolFlag + fs.Var(&bf, jsonTagToFlagName(tag.Get("json")), tag.Get("desc")) + fsValMap[cfgVal.Field(i)] = &bf + } + } + configFile := fs.String("config", "", "Configuration file") + // Parse + if err := fs.Parse(args); err != nil { + os.Exit(1) + } + // Put together the config + if len(*configFile) > 0 { + cb, err := ioutil.ReadFile(*configFile) + if err != nil { + return err + } + if err := json.Unmarshal(cb, cfg); err != nil { + return err + } + } + // Flags override config from file + for field, val := range fsValMap { + switch v := val.(type) { + case *string: + if len(*v) > 0 { + field.SetString(*v) + } + case *int: + if *v != 0 { + field.SetInt(int64(*v)) + } + case *uint64: + if *v != 0 { + field.SetUint(*v) + } + case *optionalBoolFlag: + if v.Exists { + field.SetBool(v.Value) + } + } + } + return nil +} + +func jsonTagToFlagName(tag string) string { + return strings.ReplaceAll(tag, "_", "-") +} diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..acbb192 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" +) + +var modeMap = map[string]func(args []string){ + "relay server": relayServer, + "relay client": relayClient, + "proxy server": proxyServer, + "proxy client": proxyClient, +} + +func main() { + if len(os.Args) < 3 { + fmt.Println() + fmt.Printf("Usage: %s MODE SUBMODE [OPTIONS]\n\n"+ + "Available mode/submode combinations: "+getModes()+"\n"+ + "Use -h to see the available options for a mode.\n\n", os.Args[0]) + return + } + modeStr := fmt.Sprintf("%s %s", strings.ToLower(strings.TrimSpace(os.Args[1])), + strings.ToLower(strings.TrimSpace(os.Args[2]))) + f := modeMap[modeStr] + if f != nil { + f(os.Args[3:]) + } else { + fmt.Println("Invalid mode:", modeStr) + } +} + +func getModes() string { + modes := make([]string, 0, len(modeMap)) + for mode := range modeMap { + modes = append(modes, mode) + } + return strings.Join(modes, ", ") +} diff --git a/cmd/proxy_client.go b/cmd/proxy_client.go new file mode 100644 index 0000000..a49327e --- /dev/null +++ b/cmd/proxy_client.go @@ -0,0 +1,88 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/congestion" + hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion" + "github.com/tobyxdd/hysteria/pkg/core" + "github.com/tobyxdd/hysteria/pkg/socks5" + "io/ioutil" + "log" + "os/user" +) + +func proxyClient(args []string) { + var config proxyClientConfig + err := loadConfig(&config, args) + if err != nil { + log.Fatalln("Unable to load configuration:", err) + } + if err := config.Check(); err != nil { + log.Fatalln("Configuration error:", err) + } + if len(config.Name) == 0 { + usr, err := user.Current() + if err == nil { + config.Name = usr.Name + } + } + log.Printf("Configuration loaded: %+v\n", config) + + tlsConfig := &tls.Config{ + NextProtos: []string{proxyTLSProtocol}, + MinVersion: tls.VersionTLS13, + } + // Load CA + if len(config.CustomCAFile) > 0 { + bs, err := ioutil.ReadFile(config.CustomCAFile) + if err != nil { + log.Fatalln("Unable to load CA file:", err) + } + cp := x509.NewCertPool() + if !cp.AppendCertsFromPEM(bs) { + log.Fatalln("Unable to parse CA file", config.CustomCAFile) + } + tlsConfig.RootCAs = cp + } + + quicConfig := &quic.Config{ + MaxReceiveStreamFlowControlWindow: config.ReceiveWindowConn, + MaxReceiveConnectionFlowControlWindow: config.ReceiveWindow, + KeepAlive: true, + } + if quicConfig.MaxReceiveStreamFlowControlWindow == 0 { + quicConfig.MaxReceiveStreamFlowControlWindow = DefaultMaxReceiveStreamFlowControlWindow + } + if quicConfig.MaxReceiveConnectionFlowControlWindow == 0 { + quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow + } + + client, err := core.NewClient(config.ServerAddr, config.Name, "", tlsConfig, quicConfig, + uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps, + func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos { + return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) + }) + if err != nil { + log.Fatalln("Client initialization failed:", err) + } + defer client.Close() + log.Println("Connected to", config.ServerAddr) + + socks5server, err := socks5.NewServer(config.SOCKS5Addr, "", nil, 0, 0, 0) + if err != nil { + log.Fatalln("SOCKS5 server initialization failed:", err) + } + log.Println("SOCKS5 server up and running on", config.SOCKS5Addr) + + log.Fatalln(socks5server.ListenAndServe(&socks5.HyHandler{ + Client: client, + NewTCPRequestFunc: func(addr, reqAddr string) { + log.Printf("[TCP] %s <-> %s\n", addr, reqAddr) + }, + TCPRequestClosedFunc: func(addr, reqAddr string, err error) { + log.Printf("Closed [TCP] %s <-> %s: %s\n", addr, reqAddr, err.Error()) + }, + })) +} diff --git a/cmd/proxy_config.go b/cmd/proxy_config.go new file mode 100644 index 0000000..eb750fd --- /dev/null +++ b/cmd/proxy_config.go @@ -0,0 +1,69 @@ +package main + +import "errors" + +const proxyTLSProtocol = "hysteria-proxy" + +type proxyClientConfig struct { + SOCKS5Addr string `json:"socks5_addr" desc:"SOCKS5 listen address"` + SOCKS5Timeout int `json:"socks5_timeout" desc:"SOCKS5 connection timeout in seconds"` + ServerAddr string `json:"server" desc:"Server address"` + Name string `json:"name" desc:"Client name presented to the server"` + Insecure bool `json:"insecure" desc:"Ignore TLS certificate errors"` + CustomCAFile string `json:"ca" desc:"Specify a trusted CA file"` + UpMbps int `json:"up_mbps" desc:"Upload speed in Mbps"` + DownMbps int `json:"down_mbps" desc:"Download speed in Mbps"` + ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"` + ReceiveWindow uint64 `json:"recv_window" desc:"Max receive window size"` +} + +func (c *proxyClientConfig) Check() error { + if len(c.SOCKS5Addr) == 0 { + return errors.New("no SOCKS5 listen address") + } + if c.SOCKS5Timeout != 0 && c.SOCKS5Timeout <= 4 { + return errors.New("invalid SOCKS5 timeout") + } + if len(c.ServerAddr) == 0 { + return errors.New("no server address") + } + if c.UpMbps <= 0 || c.DownMbps <= 0 { + return errors.New("invalid speed") + } + if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) || + (c.ReceiveWindow != 0 && c.ReceiveWindow < 65536) { + return errors.New("invalid receive window size") + } + return nil +} + +type proxyServerConfig struct { + ListenAddr string `json:"listen" desc:"Server listen address"` + CertFile string `json:"cert" desc:"TLS certificate file"` + KeyFile string `json:"key" desc:"TLS key file"` + UpMbps int `json:"up_mbps" desc:"Max upload speed per client in Mbps"` + DownMbps int `json:"down_mbps" desc:"Max download speed per client in Mbps"` + ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"` + ReceiveWindowClient uint64 `json:"recv_window_client" desc:"Max receive window size per client"` + MaxConnClient int `json:"max_conn_client" desc:"Max simultaneous connections allowed per client"` +} + +func (c *proxyServerConfig) Check() error { + if len(c.ListenAddr) == 0 { + return errors.New("no listen address") + } + if len(c.CertFile) == 0 || len(c.KeyFile) == 0 { + return errors.New("TLS cert or key not provided") + } + if c.UpMbps < 0 || c.DownMbps < 0 { + return errors.New("invalid speed") + } + if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) || + (c.ReceiveWindowClient != 0 && c.ReceiveWindowClient < 65536) { + return errors.New("invalid receive window size") + } + if c.MaxConnClient < 0 { + return errors.New("invalid max connections per client") + } + return nil +} diff --git a/cmd/proxy_server.go b/cmd/proxy_server.go new file mode 100644 index 0000000..c987f57 --- /dev/null +++ b/cmd/proxy_server.go @@ -0,0 +1,101 @@ +package main + +import ( + "crypto/tls" + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/congestion" + hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion" + "github.com/tobyxdd/hysteria/pkg/core" + "io" + "log" + "net" +) + +func proxyServer(args []string) { + var config proxyServerConfig + err := loadConfig(&config, args) + if err != nil { + log.Fatalln("Unable to load configuration:", err) + } + if err := config.Check(); err != nil { + log.Fatalln("Configuration error:", err.Error()) + } + log.Printf("Configuration loaded: %+v\n", config) + // Load cert + cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile) + if err != nil { + log.Fatalln("Unable to load the certificate:", err) + } + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + NextProtos: []string{proxyTLSProtocol}, + MinVersion: tls.VersionTLS13, + } + + quicConfig := &quic.Config{ + MaxReceiveStreamFlowControlWindow: config.ReceiveWindowConn, + MaxReceiveConnectionFlowControlWindow: config.ReceiveWindowClient, + MaxIncomingStreams: config.MaxConnClient, + KeepAlive: true, + } + if quicConfig.MaxReceiveStreamFlowControlWindow == 0 { + quicConfig.MaxReceiveStreamFlowControlWindow = DefaultMaxReceiveStreamFlowControlWindow + } + if quicConfig.MaxReceiveConnectionFlowControlWindow == 0 { + quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow + } + if quicConfig.MaxIncomingStreams == 0 { + quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams + } + + server, err := core.NewServer(config.ListenAddr, tlsConfig, quicConfig, + uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps, + func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos { + return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) + }, + func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (core.AuthResult, string) { + // No authentication logic in relay, just log username and speed + log.Printf("%s (%s) connected, negotiated speed (Mbps): Up %d / Down %d\n", + addr.String(), username, sSend/mbpsToBps, sRecv/mbpsToBps) + return core.AuthSuccess, "" + }, + func(addr net.Addr, username string, err error) { + log.Printf("%s (%s) disconnected: %s\n", addr.String(), username, err.Error()) + }, + func(addr net.Addr, username string, id int, packet bool, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) { + if !packet { + // TCP + log.Printf("%s (%s): [TCP] %s\n", addr.String(), username, reqAddr) + conn, err := net.Dial("tcp", reqAddr) + if err != nil { + log.Printf("TCP error %s: %s\n", reqAddr, err.Error()) + return core.ConnFailed, err.Error(), nil + } + return core.ConnSuccess, "", conn + } else { + // UDP + log.Printf("%s (%s): [UDP] %s\n", addr.String(), username, reqAddr) + conn, err := net.Dial("udp", reqAddr) + if err != nil { + log.Printf("UDP error %s: %s\n", reqAddr, err.Error()) + return core.ConnFailed, err.Error(), nil + } + return core.ConnSuccess, "", conn + } + }, + func(addr net.Addr, username string, id int, packet bool, reqAddr string, err error) { + if !packet { + log.Printf("%s (%s): closed [TCP] %s: %s\n", addr.String(), username, reqAddr, err.Error()) + } else { + log.Printf("%s (%s): closed [UDP] %s: %s\n", addr.String(), username, reqAddr, err.Error()) + } + }, + ) + if err != nil { + log.Fatalln("Server initialization failed:", err) + } + defer server.Close() + log.Println("Up and running on", config.ListenAddr) + + log.Fatalln(server.Serve()) +} diff --git a/cmd/relay/main.go b/cmd/relay/main.go deleted file mode 100644 index 65515c7..0000000 --- a/cmd/relay/main.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" -) - -func main() { - if len(os.Args) < 2 { - fmt.Println() - fmt.Printf("Usage: %s MODE [OPTIONS]\n\n"+ - "Modes: server / client\n"+ - "Use -h to see the available options for a mode.\n\n", os.Args[0]) - return - } - mode := strings.ToLower(strings.TrimSpace(os.Args[1])) - switch mode { - case "server", "s": - server(os.Args[2:]) - case "client", "c": - client(os.Args[2:]) - default: - fmt.Println("Invalid mode:", mode) - } -} diff --git a/cmd/relay/client.go b/cmd/relay_client.go similarity index 77% rename from cmd/relay/client.go rename to cmd/relay_client.go index 8253261..9eac229 100644 --- a/cmd/relay/client.go +++ b/cmd/relay_client.go @@ -8,15 +8,14 @@ import ( "github.com/tobyxdd/hysteria/internal/utils" hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion" "github.com/tobyxdd/hysteria/pkg/core" - "io" "io/ioutil" "log" "net" "os/user" ) -func client(args []string) { - var config cmdClientConfig +func relayClient(args []string) { + var config relayClientConfig err := loadConfig(&config, args) if err != nil { log.Fatalln("Unable to load configuration:", err) @@ -33,7 +32,7 @@ func client(args []string) { log.Printf("Configuration loaded: %+v\n", config) tlsConfig := &tls.Config{ - NextProtos: []string{TLSAppProtocol}, + NextProtos: []string{relayTLSProtocol}, MinVersion: tls.VersionTLS13, } // Load CA @@ -70,7 +69,7 @@ func client(args []string) { log.Fatalln("Client initialization failed:", err) } defer client.Close() - log.Println("Client initialization complete, connected to", config.ServerAddr) + log.Println("Connected to", config.ServerAddr) listener, err := net.Listen("tcp", config.ListenAddr) if err != nil { @@ -84,16 +83,16 @@ func client(args []string) { if err != nil { log.Fatalln("TCP accept failed:", err) } - go clientHandleConn(conn, client) + go relayClientHandleConn(conn, client) } } -func clientHandleConn(conn net.Conn, client core.Client) { - log.Println("New TCP connection from", conn.RemoteAddr().String()) +func relayClientHandleConn(conn net.Conn, client core.Client) { + log.Println("New connection", conn.RemoteAddr().String()) var closeErr error defer func() { _ = conn.Close() - log.Println("TCP connection from", conn.RemoteAddr().String(), "closed", closeErr) + log.Println("Connection", conn.RemoteAddr().String(), "closed", closeErr) }() rwc, err := client.Dial(false, "") if err != nil { @@ -101,18 +100,5 @@ func clientHandleConn(conn net.Conn, client core.Client) { return } defer rwc.Close() - closeErr = pipePair(conn, rwc) -} - -func pipePair(rw1, rw2 io.ReadWriter) error { - // Pipes - errChan := make(chan error, 2) - go func() { - errChan <- utils.Pipe(rw2, rw1, nil) - }() - go func() { - errChan <- utils.Pipe(rw1, rw2, nil) - }() - // We only need the first error - return <-errChan + closeErr = utils.PipePair(conn, rwc, nil, nil) } diff --git a/cmd/relay/config.go b/cmd/relay_config.go similarity index 54% rename from cmd/relay/config.go rename to cmd/relay_config.go index 1ec7fc4..bb74af6 100644 --- a/cmd/relay/config.go +++ b/cmd/relay_config.go @@ -1,25 +1,10 @@ package main -import ( - "encoding/json" - "errors" - "flag" - "io/ioutil" - "os" - "reflect" - "strings" -) +import "errors" -const ( - mbpsToBps = 125000 +const relayTLSProtocol = "hysteria-relay" - TLSAppProtocol = "hysteria-relay" - - DefaultMaxReceiveStreamFlowControlWindow = 33554432 - DefaultMaxReceiveConnectionFlowControlWindow = 67108864 -) - -type cmdClientConfig struct { +type relayClientConfig struct { ListenAddr string `json:"listen" desc:"TCP listen address"` ServerAddr string `json:"server" desc:"Server address"` Name string `json:"name" desc:"Client name presented to the server"` @@ -31,7 +16,7 @@ type cmdClientConfig struct { ReceiveWindow uint64 `json:"recv_window" desc:"Max receive window size"` } -func (c *cmdClientConfig) Check() error { +func (c *relayClientConfig) Check() error { if len(c.ListenAddr) == 0 { return errors.New("no listen address") } @@ -48,7 +33,7 @@ func (c *cmdClientConfig) Check() error { return nil } -type cmdServerConfig struct { +type relayServerConfig struct { ListenAddr string `json:"listen" desc:"Server listen address"` RemoteAddr string `json:"remote" desc:"Remote relay address"` CertFile string `json:"cert" desc:"TLS certificate file"` @@ -60,7 +45,7 @@ type cmdServerConfig struct { MaxConnClient int `json:"max_conn_client" desc:"Max simultaneous connections allowed per client"` } -func (c *cmdServerConfig) Check() error { +func (c *relayServerConfig) Check() error { if len(c.ListenAddr) == 0 { return errors.New("no listen address") } @@ -82,69 +67,3 @@ func (c *cmdServerConfig) Check() error { } return nil } - -func loadConfig(cfg interface{}, args []string) error { - cfgVal := reflect.ValueOf(cfg).Elem() - fs := flag.NewFlagSet("", flag.ContinueOnError) - fsValMap := make(map[reflect.Value]interface{}, cfgVal.NumField()) - for i := 0; i < cfgVal.NumField(); i++ { - structField := cfgVal.Type().Field(i) - tag := structField.Tag - switch structField.Type.Kind() { - case reflect.String: - fsValMap[cfgVal.Field(i)] = - fs.String(jsonTagToFlagName(tag.Get("json")), "", tag.Get("desc")) - case reflect.Int: - fsValMap[cfgVal.Field(i)] = - fs.Int(jsonTagToFlagName(tag.Get("json")), 0, tag.Get("desc")) - case reflect.Uint64: - fsValMap[cfgVal.Field(i)] = - fs.Uint64(jsonTagToFlagName(tag.Get("json")), 0, tag.Get("desc")) - case reflect.Bool: - var bf optionalBoolFlag - fs.Var(&bf, jsonTagToFlagName(tag.Get("json")), tag.Get("desc")) - fsValMap[cfgVal.Field(i)] = &bf - } - } - configFile := fs.String("config", "", "Configuration file") - // Parse - if err := fs.Parse(args); err != nil { - os.Exit(1) - } - // Put together the config - if len(*configFile) > 0 { - cb, err := ioutil.ReadFile(*configFile) - if err != nil { - return err - } - if err := json.Unmarshal(cb, cfg); err != nil { - return err - } - } - // Flags override config from file - for field, val := range fsValMap { - switch v := val.(type) { - case *string: - if len(*v) > 0 { - field.SetString(*v) - } - case *int: - if *v != 0 { - field.SetInt(int64(*v)) - } - case *uint64: - if *v != 0 { - field.SetUint(*v) - } - case *optionalBoolFlag: - if v.Exists { - field.SetBool(v.Value) - } - } - } - return nil -} - -func jsonTagToFlagName(tag string) string { - return strings.ReplaceAll(tag, "_", "-") -} diff --git a/cmd/relay/server.go b/cmd/relay_server.go similarity index 67% rename from cmd/relay/server.go rename to cmd/relay_server.go index 1dd787b..65bf7b0 100644 --- a/cmd/relay/server.go +++ b/cmd/relay_server.go @@ -11,8 +11,8 @@ import ( "net" ) -func server(args []string) { - var config cmdServerConfig +func relayServer(args []string) { + var config relayServerConfig err := loadConfig(&config, args) if err != nil { log.Fatalln("Unable to load configuration:", err) @@ -28,13 +28,14 @@ func server(args []string) { } tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, - NextProtos: []string{TLSAppProtocol}, + NextProtos: []string{relayTLSProtocol}, MinVersion: tls.VersionTLS13, } quicConfig := &quic.Config{ MaxReceiveStreamFlowControlWindow: config.ReceiveWindowConn, MaxReceiveConnectionFlowControlWindow: config.ReceiveWindowClient, + MaxIncomingStreams: config.MaxConnClient, KeepAlive: true, } if quicConfig.MaxReceiveStreamFlowControlWindow == 0 { @@ -43,6 +44,9 @@ func server(args []string) { if quicConfig.MaxReceiveConnectionFlowControlWindow == 0 { quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow } + if quicConfig.MaxIncomingStreams == 0 { + quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams + } server, err := core.NewServer(config.ListenAddr, tlsConfig, quicConfig, uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps, @@ -51,34 +55,34 @@ func server(args []string) { }, func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (core.AuthResult, string) { // No authentication logic in relay, just log username and speed - log.Printf("Client %s connected, negotiated speed in Mbps: Up %d / Down %d\n", - addr.String(), sSend/mbpsToBps, sRecv/mbpsToBps) + log.Printf("%s (%s) connected, negotiated speed (Mbps): Up %d / Down %d\n", + addr.String(), username, sSend/mbpsToBps, sRecv/mbpsToBps) return core.AuthSuccess, "" }, func(addr net.Addr, username string, err error) { - log.Printf("Client %s (%s) disconnected: %s\n", addr.String(), username, err.Error()) + log.Printf("%s (%s) disconnected: %s\n", addr.String(), username, err.Error()) }, - func(addr net.Addr, username string, id int, isUDP bool, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) { - log.Printf("Client %s (%s) opened stream ID %d\n", addr.String(), username, id) - if isUDP { + func(addr net.Addr, username string, id int, packet bool, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) { + log.Printf("%s (%s): new stream ID %d\n", addr.String(), username, id) + if packet { return core.ConnBlocked, "unsupported", nil } conn, err := net.Dial("tcp", config.RemoteAddr) if err != nil { - log.Printf("TCP error when connecting to %s: %s", config.RemoteAddr, err.Error()) + log.Printf("TCP error %s: %s\n", config.RemoteAddr, err.Error()) return core.ConnFailed, err.Error(), nil } return core.ConnSuccess, "", conn }, - func(addr net.Addr, username string, id int, isUDP bool, reqAddr string, err error) { - log.Printf("Client %s (%s) closed stream ID %d: %s", addr.String(), username, id, err.Error()) + func(addr net.Addr, username string, id int, packet bool, reqAddr string, err error) { + log.Printf("%s (%s): closed stream ID %d: %s\n", addr.String(), username, id, err.Error()) }, ) if err != nil { log.Fatalln("Server initialization failed:", err) } defer server.Close() - log.Println("The server is now up and running :)") + log.Println("Up and running on", config.ListenAddr) - log.Fatalln("Server error:", server.Serve()) + log.Fatalln(server.Serve()) } diff --git a/cmd/server.json b/cmd/server.json new file mode 100644 index 0000000..a1d7542 --- /dev/null +++ b/cmd/server.json @@ -0,0 +1,9 @@ +{ + "listen": ":36712", + "remote": "localhost:1080", + "cert": "/home/ubuntu/.caddy/acme/acme-v02.api.letsencrypt.org/sites/toby.moe/toby.moe.crt", + "key": "/home/ubuntu/.caddy/acme/acme-v02.api.letsencrypt.org/sites/toby.moe/toby.moe.key", + "up_mbps": 100, + "down_mbps": 100, + "max_conn_client": 200 +} \ No newline at end of file diff --git a/cmd/relay/flags.go b/cmd/utils.go similarity index 100% rename from cmd/relay/flags.go rename to cmd/utils.go diff --git a/docs/logos/readme.png b/docs/logos/readme.png new file mode 100644 index 0000000000000000000000000000000000000000..4c74c68f1178e1eb1af07cac9f441d5cf80b9a16 GIT binary patch literal 11979 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⁣ho@yV~wNzSs-V#t2d$lQpdqqaZY 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 github.com/tobyxdd/quic-go v0.1.3-tquic-1 diff --git a/go.sum b/go.sum index 6571e36..538985b 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,8 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -117,6 +119,12 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tobyxdd/quic-go v0.1.3-tquic-1 h1:LOD8EsuNTYaDInkkgy3swL8d3y2SIliKaGlGSomvlik= github.com/tobyxdd/quic-go v0.1.3-tquic-1/go.mod h1:oj40DjNLuNugvtXWg4PwaYgv7tAbzAabrT57CC69EhI= +github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997 h1:vlDgnShahmE2XLslpr0hnzxfAmSj3JLX2CYi8Xct7G4= +github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM= +github.com/txthinking/socks5 v0.0.0-20200327133705-caf148ab5e9d h1:V+Wyj2AqtLwLG7KnniV8QG+gEkENPsudZbivvLyX4kw= +github.com/txthinking/socks5 v0.0.0-20200327133705-caf148ab5e9d/go.mod h1:d3n8NJ6QMRb6I/WAlp4z5ZPAoaeqDmX5NgVZA0mhe+I= +github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9 h1:ngJOce33YJJT1PFTfC9ao7S27AfrUh11Dr3Bc+ooBdM= +github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= diff --git a/internal/core/client.go b/internal/core/client.go index 1d17eba..2e4587d 100644 --- a/internal/core/client.go +++ b/internal/core/client.go @@ -49,17 +49,17 @@ func NewClient(serverAddr string, username string, password string, tlsConfig *t return c, nil } -func (c *Client) Dial(udp bool, addr string) (io.ReadWriteCloser, error) { +func (c *Client) Dial(packet bool, addr string) (io.ReadWriteCloser, error) { stream, err := c.openStreamWithReconnect() if err != nil { return nil, err } // Send request req := &ClientConnectRequest{Address: addr} - if udp { - req.Type = ConnectionType_UDP + if packet { + req.Type = ConnectionType_Packet } else { - req.Type = ConnectionType_TCP + req.Type = ConnectionType_Stream } err = writeClientConnectRequest(stream, req) if err != nil { @@ -77,7 +77,7 @@ func (c *Client) Dial(udp bool, addr string) (io.ReadWriteCloser, error) { return nil, fmt.Errorf("server rejected the connection %s (msg: %s)", resp.Result.String(), resp.Message) } - if udp { + if packet { return &utils.PacketReadWriteCloser{Orig: stream}, nil } else { return stream, nil diff --git a/internal/core/control.pb.go b/internal/core/control.pb.go index b9d5da5..b07dfc0 100644 --- a/internal/core/control.pb.go +++ b/internal/core/control.pb.go @@ -51,18 +51,18 @@ func (AuthResult) EnumDescriptor() ([]byte, []int) { type ConnectionType int32 const ( - ConnectionType_TCP ConnectionType = 0 - ConnectionType_UDP ConnectionType = 1 + ConnectionType_Stream ConnectionType = 0 + ConnectionType_Packet ConnectionType = 1 ) var ConnectionType_name = map[int32]string{ - 0: "TCP", - 1: "UDP", + 0: "Stream", + 1: "Packet", } var ConnectionType_value = map[string]int32{ - "TCP": 0, - "UDP": 1, + "Stream": 0, + "Packet": 1, } func (x ConnectionType) String() string { @@ -334,7 +334,7 @@ func (m *ClientConnectRequest) GetType() ConnectionType { if m != nil { return m.Type } - return ConnectionType_TCP + return ConnectionType_Stream } func (m *ClientConnectRequest) GetAddress() string { @@ -408,32 +408,33 @@ func init() { } var fileDescriptor_0c5120591600887d = []byte{ - // 431 bytes of a gzipped FileDescriptorProto + // 434 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xd1, 0x6e, 0xd3, 0x30, - 0x14, 0x86, 0xd7, 0xae, 0x5b, 0xb7, 0x13, 0x36, 0x32, 0x6f, 0x13, 0x83, 0x1b, 0x20, 0x57, 0x55, - 0x91, 0x2a, 0x34, 0x9e, 0x20, 0x75, 0x82, 0xa8, 0xa8, 0xd2, 0xc9, 0x69, 0xb9, 0xe0, 0x82, 0x2a, - 0x4b, 0x8e, 0x58, 0xa5, 0xcc, 0x36, 0xb6, 0x33, 0x34, 0xf1, 0xf2, 0x28, 0x8e, 0x93, 0xae, 0x48, - 0x48, 0xbb, 0xeb, 0x39, 0xe7, 0xd7, 0xff, 0xf9, 0xab, 0x02, 0x27, 0xb9, 0xe0, 0x46, 0x89, 0x72, - 0x22, 0x95, 0x30, 0x82, 0x0c, 0x72, 0xa1, 0x30, 0xa0, 0x70, 0x90, 0x4a, 0xc4, 0x82, 0xbc, 0x86, - 0x23, 0x8d, 0xbc, 0x58, 0xdf, 0x4a, 0x7d, 0xd5, 0x7b, 0xd7, 0x1b, 0x0d, 0xd8, 0xb0, 0x9e, 0xa7, - 0x52, 0x93, 0xb7, 0xe0, 0x29, 0xcc, 0x71, 0xf3, 0x80, 0xf6, 0xda, 0xb7, 0x57, 0x70, 0xab, 0xa9, - 0xd4, 0x41, 0x04, 0x40, 0x15, 0x16, 0xc8, 0xcd, 0x26, 0x2b, 0xc9, 0x1b, 0x38, 0xaa, 0x34, 0x2a, - 0x9e, 0xdd, 0xa3, 0x6d, 0x3a, 0x66, 0xdd, 0x5c, 0xdf, 0x64, 0xa6, 0xf5, 0x6f, 0xa1, 0x0a, 0xdb, - 0x73, 0xcc, 0xba, 0x39, 0xb8, 0x83, 0x33, 0x5a, 0x6e, 0x90, 0x9b, 0xb0, 0x32, 0x77, 0x0c, 0x7f, - 0x55, 0xa8, 0x0d, 0xf9, 0x08, 0x90, 0x77, 0xd5, 0xb6, 0xce, 0xbb, 0xf6, 0x27, 0xf5, 0xd3, 0x27, - 0x5b, 0x24, 0x7b, 0x92, 0x21, 0xef, 0xe1, 0x40, 0xd7, 0x46, 0xb6, 0xdf, 0xbb, 0xf6, 0x9a, 0xb0, - 0x95, 0x64, 0xcd, 0x25, 0xf8, 0x03, 0x24, 0x45, 0xf5, 0x80, 0xaa, 0x21, 0x69, 0x29, 0xb8, 0x46, - 0x32, 0x82, 0x43, 0x85, 0xba, 0x2a, 0x8d, 0xc5, 0x9c, 0xb6, 0x18, 0x97, 0xa9, 0x4a, 0xc3, 0xdc, - 0x9d, 0x5c, 0xc1, 0xf0, 0x1e, 0xb5, 0xce, 0x7e, 0xa2, 0x93, 0x68, 0xc7, 0x2d, 0x7c, 0xff, 0xbf, - 0xf0, 0xef, 0x70, 0xd1, 0x68, 0x52, 0xc1, 0x39, 0xe6, 0xa6, 0x35, 0x1d, 0xc1, 0xc0, 0x3c, 0x4a, - 0x74, 0xf0, 0x0b, 0xe7, 0xd8, 0x64, 0x36, 0x82, 0x2f, 0x1f, 0x25, 0x32, 0x9b, 0xa8, 0xf1, 0x59, - 0x51, 0x28, 0xd4, 0xba, 0xc5, 0xbb, 0x31, 0xf8, 0x01, 0x97, 0x8d, 0x58, 0xd7, 0xed, 0xdc, 0x3e, - 0xfc, 0xe3, 0x76, 0xbe, 0x53, 0xff, 0x5c, 0xbd, 0x71, 0x02, 0xb0, 0xfd, 0x3b, 0x88, 0x0f, 0x2f, - 0xc2, 0xd5, 0xf2, 0xcb, 0x3a, 0x5d, 0x51, 0x1a, 0xa7, 0xa9, 0xbf, 0x47, 0x2e, 0xe1, 0xcc, 0x6e, - 0x66, 0xc9, 0xb7, 0x70, 0x3e, 0x8b, 0xd6, 0x94, 0xc5, 0x91, 0xdf, 0x23, 0xaf, 0xe0, 0xdc, 0xad, - 0x97, 0x31, 0x4b, 0xc2, 0xf9, 0x3a, 0x66, 0x6c, 0xc1, 0xfc, 0xfe, 0x38, 0x80, 0xd3, 0x5d, 0x43, - 0x32, 0x84, 0xfd, 0x25, 0xbd, 0xf1, 0xf7, 0xea, 0x1f, 0xab, 0xe8, 0xc6, 0xef, 0x8d, 0x23, 0x38, - 0xd9, 0x79, 0x66, 0x8d, 0xa5, 0x8b, 0x24, 0x79, 0x82, 0x7d, 0x09, 0x9e, 0xdd, 0x7c, 0x0e, 0x67, - 0x73, 0x0b, 0x6c, 0x23, 0xd3, 0xf9, 0x82, 0x7e, 0x8d, 0x23, 0xbf, 0x7f, 0x7b, 0x68, 0x3f, 0xfa, - 0x4f, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x65, 0xfc, 0xeb, 0x5c, 0x05, 0x03, 0x00, 0x00, + 0x14, 0x86, 0xd7, 0xd2, 0x75, 0xdb, 0x09, 0x1b, 0x99, 0xb7, 0x89, 0xc1, 0x0d, 0x90, 0xab, 0xaa, + 0x48, 0x15, 0x1a, 0x4f, 0x90, 0x3a, 0x41, 0x54, 0x54, 0x29, 0x72, 0x3a, 0x2e, 0xb8, 0xa0, 0xca, + 0x92, 0x23, 0x56, 0x91, 0xda, 0xc6, 0x76, 0x86, 0x26, 0x5e, 0x1e, 0xc5, 0x71, 0xd2, 0x16, 0x09, + 0x89, 0xbb, 0x9e, 0x73, 0x7e, 0xfd, 0x9f, 0xbf, 0x2a, 0x70, 0x9a, 0x0b, 0x6e, 0x94, 0x28, 0x27, + 0x52, 0x09, 0x23, 0xc8, 0x20, 0x17, 0x0a, 0x03, 0x0a, 0x87, 0xa9, 0x44, 0x2c, 0xc8, 0x0b, 0x38, + 0xd6, 0xc8, 0x8b, 0xd5, 0x9d, 0xd4, 0xd7, 0xbd, 0xd7, 0xbd, 0xd1, 0x80, 0x1d, 0xd5, 0xf3, 0x54, + 0x6a, 0xf2, 0x0a, 0x3c, 0x85, 0x39, 0xae, 0x1f, 0xd0, 0x5e, 0xfb, 0xf6, 0x0a, 0x6e, 0x35, 0x95, + 0x3a, 0x88, 0x00, 0xa8, 0xc2, 0x02, 0xb9, 0x59, 0x67, 0x25, 0x79, 0x09, 0xc7, 0x95, 0x46, 0xc5, + 0xb3, 0x0d, 0xda, 0xa6, 0x13, 0xd6, 0xcd, 0xf5, 0x4d, 0x66, 0x5a, 0xff, 0x12, 0xaa, 0xb0, 0x3d, + 0x27, 0xac, 0x9b, 0x83, 0x7b, 0x38, 0xa7, 0xe5, 0x1a, 0xb9, 0x09, 0x2b, 0x73, 0xcf, 0xf0, 0x67, + 0x85, 0xda, 0x90, 0x77, 0x00, 0x79, 0x57, 0x6d, 0xeb, 0xbc, 0x1b, 0x7f, 0x52, 0x3f, 0x7d, 0xb2, + 0x45, 0xb2, 0x9d, 0x0c, 0x79, 0x03, 0x87, 0xba, 0x36, 0xb2, 0xfd, 0xde, 0x8d, 0xd7, 0x84, 0xad, + 0x24, 0x6b, 0x2e, 0xc1, 0x6f, 0x20, 0x29, 0xaa, 0x07, 0x54, 0x0d, 0x49, 0x4b, 0xc1, 0x35, 0x92, + 0x11, 0x0c, 0x15, 0xea, 0xaa, 0x34, 0x16, 0x73, 0xd6, 0x62, 0x5c, 0xa6, 0x2a, 0x0d, 0x73, 0x77, + 0x72, 0x0d, 0x47, 0x1b, 0xd4, 0x3a, 0xfb, 0x8e, 0x4e, 0xa2, 0x1d, 0xb7, 0xf0, 0x27, 0xff, 0x84, + 0x7f, 0x85, 0xcb, 0x46, 0x93, 0x0a, 0xce, 0x31, 0x37, 0xad, 0xe9, 0x08, 0x06, 0xe6, 0x51, 0xa2, + 0x83, 0x5f, 0x3a, 0xc7, 0x26, 0xb3, 0x16, 0x7c, 0xf9, 0x28, 0x91, 0xd9, 0x44, 0x8d, 0xcf, 0x8a, + 0x42, 0xa1, 0xd6, 0x2d, 0xde, 0x8d, 0xc1, 0x37, 0xb8, 0x6a, 0xc4, 0xba, 0x6e, 0xe7, 0xf6, 0xf6, + 0x2f, 0xb7, 0x8b, 0xbd, 0xfa, 0xff, 0xd5, 0x1b, 0x27, 0x00, 0xdb, 0xbf, 0x83, 0xf8, 0xf0, 0x34, + 0xbc, 0x5d, 0x7e, 0x5c, 0xa5, 0xb7, 0x94, 0xc6, 0x69, 0xea, 0x1f, 0x90, 0x2b, 0x38, 0xb7, 0x9b, + 0x59, 0xf2, 0x25, 0x9c, 0xcf, 0xa2, 0x15, 0x65, 0x71, 0xe4, 0xf7, 0xc8, 0x73, 0xb8, 0x70, 0xeb, + 0x65, 0xcc, 0x92, 0x70, 0xbe, 0x8a, 0x19, 0x5b, 0x30, 0xbf, 0x3f, 0x1e, 0xc1, 0xd9, 0xbe, 0x21, + 0x01, 0x18, 0xa6, 0x46, 0x61, 0xb6, 0xf1, 0x0f, 0xea, 0xdf, 0x9f, 0xb3, 0xfc, 0x07, 0x1a, 0xbf, + 0x37, 0x8e, 0xe0, 0x74, 0xef, 0xb1, 0x35, 0x9c, 0x2e, 0x92, 0x64, 0x07, 0xfe, 0x0c, 0x3c, 0xbb, + 0xf9, 0x10, 0xce, 0xe6, 0x16, 0xdb, 0x46, 0xa6, 0xf3, 0x05, 0xfd, 0x14, 0x47, 0x7e, 0xff, 0x6e, + 0x68, 0x3f, 0xfd, 0xf7, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x2f, 0x8d, 0x6f, 0x0b, 0x03, + 0x00, 0x00, } diff --git a/internal/core/control.proto b/internal/core/control.proto index 53d8279..a72eda3 100644 --- a/internal/core/control.proto +++ b/internal/core/control.proto @@ -29,8 +29,8 @@ message ServerAuthResponse { } enum ConnectionType { - TCP = 0; - UDP = 1; + Stream = 0; + Packet = 1; } enum ConnectResult { diff --git a/internal/core/server.go b/internal/core/server.go index 411da71..479cb6b 100644 --- a/internal/core/server.go +++ b/internal/core/server.go @@ -175,25 +175,12 @@ func (s *Server) handleStream(addr net.Addr, username string, stream quic.Stream return } switch req.Type { - case ConnectionType_TCP: - err = s.pipePair(stream, conn) - case ConnectionType_UDP: - err = s.pipePair(&utils.PacketReadWriteCloser{Orig: stream}, conn) + case ConnectionType_Stream: + err = utils.PipePair(stream, conn, &s.outboundBytes, &s.inboundBytes) + case ConnectionType_Packet: + err = utils.PipePair(&utils.PacketReadWriteCloser{Orig: stream}, conn, &s.outboundBytes, &s.inboundBytes) default: err = fmt.Errorf("unsupported connection type %s", req.Type.String()) } s.requestClosedFunc(addr, username, int(stream.StreamID()), req.Type, req.Address, err) } - -func (s *Server) pipePair(rw1, rw2 io.ReadWriter) error { - // Pipes - errChan := make(chan error, 2) - go func() { - errChan <- utils.Pipe(rw2, rw1, &s.outboundBytes) - }() - go func() { - errChan <- utils.Pipe(rw1, rw2, &s.inboundBytes) - }() - // We only need the first error - return <-errChan -} diff --git a/internal/utils/pipe.go b/internal/utils/pipe.go index fd35b45..a92dde9 100644 --- a/internal/utils/pipe.go +++ b/internal/utils/pipe.go @@ -25,3 +25,15 @@ func Pipe(src, dst io.ReadWriter, atomicCounter *uint64) error { } } } + +func PipePair(rw1, rw2 io.ReadWriter, rw1WriteCounter, rw2WriteCounter *uint64) error { + errChan := make(chan error, 2) + go func() { + errChan <- Pipe(rw2, rw1, rw1WriteCounter) + }() + go func() { + errChan <- Pipe(rw1, rw2, rw2WriteCounter) + }() + // We only need the first error + return <-errChan +} diff --git a/pkg/core/interface.go b/pkg/core/interface.go index 04bdec1..dea0b23 100644 --- a/pkg/core/interface.go +++ b/pkg/core/interface.go @@ -27,8 +27,8 @@ const ( type CongestionFactory core.CongestionFactory type ClientAuthFunc func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (AuthResult, string) type ClientDisconnectedFunc core.ClientDisconnectedFunc -type HandleRequestFunc func(addr net.Addr, username string, id int, isUDP bool, reqAddr string) (ConnectResult, string, io.ReadWriteCloser) -type RequestClosedFunc func(addr net.Addr, username string, id int, isUDP bool, reqAddr string, err error) +type HandleRequestFunc func(addr net.Addr, username string, id int, packet bool, reqAddr string) (ConnectResult, string, io.ReadWriteCloser) +type RequestClosedFunc func(addr net.Addr, username string, id int, packet bool, reqAddr string, err error) type Server interface { Serve() error @@ -49,16 +49,16 @@ func NewServer(addr string, tlsConfig *tls.Config, quicConfig *quic.Config, }, core.ClientDisconnectedFunc(clientDisconnectedFunc), func(addr net.Addr, username string, id int, reqType core.ConnectionType, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) { - r, msg, conn := handleRequestFunc(addr, username, id, reqType == core.ConnectionType_UDP, reqAddr) + r, msg, conn := handleRequestFunc(addr, username, id, reqType == core.ConnectionType_Packet, reqAddr) return core.ConnectResult(r), msg, conn }, func(addr net.Addr, username string, id int, reqType core.ConnectionType, reqAddr string, err error) { - requestClosedFunc(addr, username, id, reqType == core.ConnectionType_UDP, reqAddr, err) + requestClosedFunc(addr, username, id, reqType == core.ConnectionType_Packet, reqAddr, err) }) } type Client interface { - Dial(udp bool, addr string) (io.ReadWriteCloser, error) + Dial(packet bool, addr string) (io.ReadWriteCloser, error) Stats() (inbound uint64, outbound uint64) Close() error } diff --git a/pkg/socks5/handler.go b/pkg/socks5/handler.go new file mode 100644 index 0000000..2b01a83 --- /dev/null +++ b/pkg/socks5/handler.go @@ -0,0 +1,56 @@ +package socks5 + +import ( + "github.com/tobyxdd/hysteria/internal/utils" + "github.com/tobyxdd/hysteria/pkg/core" + "github.com/txthinking/socks5" + "net" +) + +type HyHandler struct { + Client core.Client + NewTCPRequestFunc func(addr, reqAddr string) + TCPRequestClosedFunc func(addr, reqAddr string, err error) +} + +func (h *HyHandler) TCPHandle(server *Server, conn *net.TCPConn, request *socks5.Request) error { + if request.Cmd == socks5.CmdConnect { + h.NewTCPRequestFunc(conn.RemoteAddr().String(), request.Address()) + var closeErr error + defer func() { + h.TCPRequestClosedFunc(conn.RemoteAddr().String(), request.Address(), closeErr) + }() + rc, err := h.Client.Dial(false, request.Address()) + if err != nil { + _ = sendFailed(request, conn, socks5.RepHostUnreachable) + closeErr = err + return err + } + // All good + p := socks5.NewReply(socks5.RepSuccess, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) + _, _ = p.WriteTo(conn) + defer rc.Close() + closeErr = utils.PipePair(conn, rc, nil, nil) + return nil + } else { + p := socks5.NewReply(socks5.RepCommandNotSupported, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) + _, _ = p.WriteTo(conn) + return ErrUnsupportedCmd + } +} + +func (h *HyHandler) UDPHandle(server *Server, addr *net.UDPAddr, datagram *socks5.Datagram) error { + // Not supported for now + return nil +} + +func sendFailed(request *socks5.Request, conn *net.TCPConn, rep byte) error { + var p *socks5.Reply + if request.Atyp == socks5.ATYPIPv4 || request.Atyp == socks5.ATYPDomain { + p = socks5.NewReply(rep, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) + } else { + p = socks5.NewReply(rep, socks5.ATYPIPv6, net.IPv6zero, []byte{0x00, 0x00}) + } + _, err := p.WriteTo(conn) + return err +} diff --git a/pkg/socks5/server.go b/pkg/socks5/server.go new file mode 100644 index 0000000..c2ddff0 --- /dev/null +++ b/pkg/socks5/server.go @@ -0,0 +1,429 @@ +package socks5 + +import "errors" + +// Modified based on https://github.com/txthinking/socks5/blob/master/server.go + +import ( + "github.com/txthinking/socks5" + "log" + "net" + "time" + + "github.com/patrickmn/go-cache" + "github.com/txthinking/runnergroup" +) + +var ( + ErrUnsupportedCmd = errors.New("unsupported command") + ErrUserPassAuth = errors.New("invalid username or password") +) + +// Server is socks5 server wrapper +type Server struct { + AuthFunc func(username, password string) bool + Method byte + SupportedCommands []byte + TCPAddr *net.TCPAddr + UDPAddr *net.UDPAddr + ServerAddr *net.UDPAddr + TCPListen *net.TCPListener + UDPConn *net.UDPConn + UDPExchanges *cache.Cache + TCPDeadline int + UDPDeadline int + UDPSessionTime int // If client does't send address, use this fixed time + Handle Handler + TCPUDPAssociate *cache.Cache + RunnerGroup *runnergroup.RunnerGroup +} + +// UDPExchange used to store client address and remote connection +type UDPExchange struct { + ClientAddr *net.UDPAddr + RemoteConn *net.UDPConn +} + +func NewServer(addr, ip string, authFunc func(username, password string) bool, tcpDeadline, udpDeadline, udpSessionTime int) (*Server, error) { + _, p, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + taddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return nil, err + } + uaddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } + saddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(ip, p)) + if err != nil { + return nil, err + } + m := socks5.MethodNone + if authFunc != nil { + m = socks5.MethodUsernamePassword + } + cs := cache.New(cache.NoExpiration, cache.NoExpiration) + cs1 := cache.New(cache.NoExpiration, cache.NoExpiration) + s := &Server{ + Method: m, + AuthFunc: authFunc, + SupportedCommands: []byte{socks5.CmdConnect, socks5.CmdUDP}, + TCPAddr: taddr, + UDPAddr: uaddr, + ServerAddr: saddr, + UDPExchanges: cs, + TCPDeadline: tcpDeadline, + UDPDeadline: udpDeadline, + UDPSessionTime: udpSessionTime, + TCPUDPAssociate: cs1, + RunnerGroup: runnergroup.New(), + } + return s, nil +} + +// Negotiate handle negotiate packet. +// This method do not handle gssapi(0x01) method now. +// Error or OK both replied. +func (s *Server) Negotiate(c *net.TCPConn) error { + rq, err := socks5.NewNegotiationRequestFrom(c) + if err != nil { + return err + } + var got bool + var m byte + for _, m = range rq.Methods { + if m == s.Method { + got = true + } + } + if !got { + rp := socks5.NewNegotiationReply(socks5.MethodUnsupportAll) + if _, err := rp.WriteTo(c); err != nil { + return err + } + } + rp := socks5.NewNegotiationReply(s.Method) + if _, err := rp.WriteTo(c); err != nil { + return err + } + + if s.Method == socks5.MethodUsernamePassword { + urq, err := socks5.NewUserPassNegotiationRequestFrom(c) + if err != nil { + return err + } + if !s.AuthFunc(string(urq.Uname), string(urq.Passwd)) { + urp := socks5.NewUserPassNegotiationReply(socks5.UserPassStatusFailure) + if _, err := urp.WriteTo(c); err != nil { + return err + } + return ErrUserPassAuth + } + urp := socks5.NewUserPassNegotiationReply(socks5.UserPassStatusSuccess) + if _, err := urp.WriteTo(c); err != nil { + return err + } + } + return nil +} + +// GetRequest get request packet from client, and check command according to SupportedCommands +// Error replied. +func (s *Server) GetRequest(c *net.TCPConn) (*socks5.Request, error) { + r, err := socks5.NewRequestFrom(c) + if err != nil { + return nil, err + } + var supported bool + for _, c := range s.SupportedCommands { + if r.Cmd == c { + supported = true + break + } + } + if !supported { + var p *socks5.Reply + if r.Atyp == socks5.ATYPIPv4 || r.Atyp == socks5.ATYPDomain { + p = socks5.NewReply(socks5.RepCommandNotSupported, socks5.ATYPIPv4, net.IPv4zero, []byte{0x00, 0x00}) + } else { + p = socks5.NewReply(socks5.RepCommandNotSupported, socks5.ATYPIPv6, net.IPv6zero, []byte{0x00, 0x00}) + } + if _, err := p.WriteTo(c); err != nil { + return nil, err + } + return nil, ErrUnsupportedCmd + } + return r, nil +} + +// Run server +func (s *Server) ListenAndServe(h Handler) error { + if h == nil { + s.Handle = &DefaultHandle{} + } else { + s.Handle = h + } + s.RunnerGroup.Add(&runnergroup.Runner{ + Start: func() error { + return s.RunTCPServer() + }, + Stop: func() error { + if s.TCPListen != nil { + return s.TCPListen.Close() + } + return nil + }, + }) + s.RunnerGroup.Add(&runnergroup.Runner{ + Start: func() error { + return s.RunUDPServer() + }, + Stop: func() error { + if s.UDPConn != nil { + return s.UDPConn.Close() + } + return nil + }, + }) + return s.RunnerGroup.Wait() +} + +// RunTCPServer starts tcp server +func (s *Server) RunTCPServer() error { + var err error + s.TCPListen, err = net.ListenTCP("tcp", s.TCPAddr) + if err != nil { + return err + } + defer s.TCPListen.Close() + for { + c, err := s.TCPListen.AcceptTCP() + if err != nil { + return err + } + go func(c *net.TCPConn) { + defer c.Close() + if s.TCPDeadline != 0 { + if err := c.SetDeadline(time.Now().Add(time.Duration(s.TCPDeadline) * time.Second)); err != nil { + return + } + } + if err := s.Negotiate(c); err != nil { + return + } + r, err := s.GetRequest(c) + if err != nil { + return + } + _ = s.Handle.TCPHandle(s, c, r) + }(c) + } +} + +// RunUDPServer starts udp server +func (s *Server) RunUDPServer() error { + var err error + s.UDPConn, err = net.ListenUDP("udp", s.UDPAddr) + if err != nil { + return err + } + defer s.UDPConn.Close() + for { + b := make([]byte, 65536) + n, addr, err := s.UDPConn.ReadFromUDP(b) + if err != nil { + return err + } + go func(addr *net.UDPAddr, b []byte) { + d, err := socks5.NewDatagramFromBytes(b) + if err != nil { + return + } + if d.Frag != 0x00 { + return + } + _ = s.Handle.UDPHandle(s, addr, d) + }(addr, b[0:n]) + } +} + +// Stop server +func (s *Server) Shutdown() error { + return s.RunnerGroup.Done() +} + +// TCP connection waits for associated UDP to close +func (s *Server) TCPWaitsForUDP(addr *net.UDPAddr) error { + _, p, err := net.SplitHostPort(addr.String()) + if err != nil { + return err + } + if p == "0" { + time.Sleep(time.Duration(s.UDPSessionTime) * time.Second) + return nil + } + ch := make(chan byte) + s.TCPUDPAssociate.Set(addr.String(), ch, cache.DefaultExpiration) + <-ch + return nil +} + +// UDP releases associated TCP +func (s *Server) UDPReleasesTCP(addr *net.UDPAddr) { + v, ok := s.TCPUDPAssociate.Get(addr.String()) + if ok { + ch := v.(chan byte) + ch <- 0x00 + s.TCPUDPAssociate.Delete(addr.String()) + } +} + +// Handler handle tcp, udp request +type Handler interface { + // Request has not been replied yet + TCPHandle(*Server, *net.TCPConn, *socks5.Request) error + UDPHandle(*Server, *net.UDPAddr, *socks5.Datagram) error +} + +// DefaultHandle implements Handler interface +type DefaultHandle struct { +} + +// TCPHandle auto handle request. You may prefer to do yourself. +func (h *DefaultHandle) TCPHandle(s *Server, c *net.TCPConn, r *socks5.Request) error { + if r.Cmd == socks5.CmdConnect { + rc, err := r.Connect(c) + if err != nil { + return err + } + defer rc.Close() + go func() { + var bf [1024 * 2]byte + for { + if s.TCPDeadline != 0 { + if err := rc.SetDeadline(time.Now().Add(time.Duration(s.TCPDeadline) * time.Second)); err != nil { + return + } + } + i, err := rc.Read(bf[:]) + if err != nil { + return + } + if _, err := c.Write(bf[0:i]); err != nil { + return + } + } + }() + var bf [1024 * 2]byte + for { + if s.TCPDeadline != 0 { + if err := c.SetDeadline(time.Now().Add(time.Duration(s.TCPDeadline) * time.Second)); err != nil { + return nil + } + } + i, err := c.Read(bf[:]) + if err != nil { + return nil + } + if _, err := rc.Write(bf[0:i]); err != nil { + return nil + } + } + } + if r.Cmd == socks5.CmdUDP { + caddr, err := r.UDP(c, s.ServerAddr) + if err != nil { + return err + } + if err := s.TCPWaitsForUDP(caddr); err != nil { + return err + } + return nil + } + return ErrUnsupportedCmd +} + +// UDPHandle auto handle packet. You may prefer to do yourself. +func (h *DefaultHandle) UDPHandle(s *Server, addr *net.UDPAddr, d *socks5.Datagram) error { + send := func(ue *UDPExchange, data []byte) error { + _, err := ue.RemoteConn.Write(data) + if err != nil { + return err + } + if socks5.Debug { + log.Printf("Sent UDP data to remote. client: %#v server: %#v remote: %#v data: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), data) + } + return nil + } + + var ue *UDPExchange + iue, ok := s.UDPExchanges.Get(addr.String()) + if ok { + ue = iue.(*UDPExchange) + return send(ue, d.Data) + } + + if socks5.Debug { + log.Printf("Call udp: %#v\n", d.Address()) + } + c, err := socks5.Dial.Dial("udp", d.Address()) + if err != nil { + s.UDPReleasesTCP(addr) + return err + } + // A UDP association terminates when the TCP connection that the UDP + // ASSOCIATE request arrived on terminates. + rc := c.(*net.UDPConn) + ue = &UDPExchange{ + ClientAddr: addr, + RemoteConn: rc, + } + if socks5.Debug { + log.Printf("Created remote UDP conn for client. client: %#v server: %#v remote: %#v\n", addr.String(), ue.RemoteConn.LocalAddr().String(), d.Address()) + } + if err := send(ue, d.Data); err != nil { + s.UDPReleasesTCP(ue.ClientAddr) + ue.RemoteConn.Close() + return err + } + s.UDPExchanges.Set(ue.ClientAddr.String(), ue, cache.DefaultExpiration) + go func(ue *UDPExchange) { + defer func() { + s.UDPReleasesTCP(ue.ClientAddr) + s.UDPExchanges.Delete(ue.ClientAddr.String()) + ue.RemoteConn.Close() + }() + var b [65536]byte + for { + if s.UDPDeadline != 0 { + if err := ue.RemoteConn.SetDeadline(time.Now().Add(time.Duration(s.UDPDeadline) * time.Second)); err != nil { + log.Println(err) + break + } + } + n, err := ue.RemoteConn.Read(b[:]) + if err != nil { + break + } + if socks5.Debug { + log.Printf("Got UDP data from remote. client: %#v server: %#v remote: %#v data: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), b[0:n]) + } + a, addr, port, err := socks5.ParseAddress(ue.ClientAddr.String()) + if err != nil { + log.Println(err) + break + } + d1 := socks5.NewDatagram(a, addr, port, b[0:n]) + if _, err := s.UDPConn.WriteToUDP(d1.Bytes(), ue.ClientAddr); err != nil { + break + } + if socks5.Debug { + log.Printf("Sent Datagram. client: %#v server: %#v remote: %#v data: %#v %#v %#v %#v %#v %#v datagram address: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), d1.Rsv, d1.Frag, d1.Atyp, d1.DstAddr, d1.DstPort, d1.Data, d1.Address()) + } + } + }(ue) + return nil +}