From e699a5560cdb5d889e24e05b0bff2297f1a68545 Mon Sep 17 00:00:00 2001 From: Toby Date: Wed, 22 Apr 2020 15:41:14 -0700 Subject: [PATCH] Proxy timeout & auth --- cmd/proxy_client.go | 11 ++------- cmd/proxy_config.go | 4 +++- cmd/proxy_server.go | 51 ++++++++++++++++++++++++++++++++++++++---- internal/utils/pipe.go | 4 ++-- pkg/socks5/handler.go | 44 ++++++++++++++++++++++++++++++------ 5 files changed, 91 insertions(+), 23 deletions(-) diff --git a/cmd/proxy_client.go b/cmd/proxy_client.go index a49327e..85ee4b6 100644 --- a/cmd/proxy_client.go +++ b/cmd/proxy_client.go @@ -10,7 +10,6 @@ import ( "github.com/tobyxdd/hysteria/pkg/socks5" "io/ioutil" "log" - "os/user" ) func proxyClient(args []string) { @@ -22,12 +21,6 @@ func proxyClient(args []string) { 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{ @@ -59,7 +52,7 @@ func proxyClient(args []string) { quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow } - client, err := core.NewClient(config.ServerAddr, config.Name, "", tlsConfig, quicConfig, + client, err := core.NewClient(config.ServerAddr, config.Username, config.Password, tlsConfig, quicConfig, uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps, func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos { return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) @@ -70,7 +63,7 @@ func proxyClient(args []string) { defer client.Close() log.Println("Connected to", config.ServerAddr) - socks5server, err := socks5.NewServer(config.SOCKS5Addr, "", nil, 0, 0, 0) + socks5server, err := socks5.NewServer(config.SOCKS5Addr, "", nil, config.SOCKS5Timeout, 0, 0) if err != nil { log.Fatalln("SOCKS5 server initialization failed:", err) } diff --git a/cmd/proxy_config.go b/cmd/proxy_config.go index eb750fd..6820925 100644 --- a/cmd/proxy_config.go +++ b/cmd/proxy_config.go @@ -8,7 +8,8 @@ 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"` + Username string `json:"username" desc:"Authentication username"` + Password string `json:"password" desc:"Authentication password"` 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"` @@ -41,6 +42,7 @@ 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"` + AuthFile string `json:"auth" desc:"Authentication 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"` diff --git a/cmd/proxy_server.go b/cmd/proxy_server.go index c987f57..7a5591a 100644 --- a/cmd/proxy_server.go +++ b/cmd/proxy_server.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "crypto/tls" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/congestion" @@ -9,6 +10,8 @@ import ( "io" "log" "net" + "os" + "strings" ) func proxyServer(args []string) { @@ -48,16 +51,36 @@ func proxyServer(args []string) { quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams } + if len(config.AuthFile) == 0 { + log.Println("WARNING: No authentication configured. This server can be used by anyone!") + } + 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, "" + if len(config.AuthFile) == 0 { + log.Printf("%s (%s) connected, negotiated speed (Mbps): Up %d / Down %d\n", + addr.String(), username, sSend/mbpsToBps, sRecv/mbpsToBps) + return core.AuthSuccess, "" + } else { + // Need auth + ok, err := checkAuth(config.AuthFile, username, password) + if err != nil { + log.Printf("%s (%s) auth error: %s\n", addr.String(), username, err.Error()) + return core.AuthInternalError, "Server auth error" + } + if ok { + log.Printf("%s (%s) authenticated, negotiated speed (Mbps): Up %d / Down %d\n", + addr.String(), username, sSend/mbpsToBps, sRecv/mbpsToBps) + return core.AuthSuccess, "" + } else { + log.Printf("%s (%s) auth failed (invalid credential)\n", addr.String(), username) + return core.AuthInvalidCred, "Invalid credential" + } + } }, func(addr net.Addr, username string, err error) { log.Printf("%s (%s) disconnected: %s\n", addr.String(), username, err.Error()) @@ -99,3 +122,23 @@ func proxyServer(args []string) { log.Fatalln(server.Serve()) } + +func checkAuth(authFile, username, password string) (bool, error) { + f, err := os.Open(authFile) + if err != nil { + return false, err + } + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + pair := strings.Fields(scanner.Text()) + if len(pair) != 2 { + // Invalid format + continue + } + if username == pair[0] && password == pair[1] { + return true, nil + } + } + return false, nil +} diff --git a/internal/utils/pipe.go b/internal/utils/pipe.go index a92dde9..5fa16a0 100644 --- a/internal/utils/pipe.go +++ b/internal/utils/pipe.go @@ -5,10 +5,10 @@ import ( "sync/atomic" ) -const pipeBufferSize = 65536 +const PipeBufferSize = 65536 func Pipe(src, dst io.ReadWriter, atomicCounter *uint64) error { - buf := make([]byte, pipeBufferSize) + buf := make([]byte, PipeBufferSize) for { rn, err := src.Read(buf) if rn > 0 { diff --git a/pkg/socks5/handler.go b/pkg/socks5/handler.go index 2b01a83..8544b54 100644 --- a/pkg/socks5/handler.go +++ b/pkg/socks5/handler.go @@ -4,7 +4,9 @@ import ( "github.com/tobyxdd/hysteria/internal/utils" "github.com/tobyxdd/hysteria/pkg/core" "github.com/txthinking/socks5" + "io" "net" + "time" ) type HyHandler struct { @@ -22,19 +24,17 @@ func (h *HyHandler) TCPHandle(server *Server, conn *net.TCPConn, request *socks5 }() rc, err := h.Client.Dial(false, request.Address()) if err != nil { - _ = sendFailed(request, conn, socks5.RepHostUnreachable) + _ = sendReply(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) + _ = sendReply(request, conn, socks5.RepSuccess) defer rc.Close() - closeErr = utils.PipePair(conn, rc, nil, nil) + closeErr = pipePair(conn, rc, server.TCPDeadline) return nil } else { - p := socks5.NewReply(socks5.RepCommandNotSupported, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) - _, _ = p.WriteTo(conn) + _ = sendReply(request, conn, socks5.RepCommandNotSupported) return ErrUnsupportedCmd } } @@ -44,7 +44,7 @@ func (h *HyHandler) UDPHandle(server *Server, addr *net.UDPAddr, datagram *socks return nil } -func sendFailed(request *socks5.Request, conn *net.TCPConn, rep byte) error { +func sendReply(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}) @@ -54,3 +54,33 @@ func sendFailed(request *socks5.Request, conn *net.TCPConn, rep byte) error { _, err := p.WriteTo(conn) return err } + +func pipePair(conn *net.TCPConn, stream io.ReadWriteCloser, deadline int) error { + errChan := make(chan error, 2) + // TCP to stream + go func() { + buf := make([]byte, utils.PipeBufferSize) + for { + if deadline != 0 { + _ = conn.SetDeadline(time.Now().Add(time.Duration(deadline) * time.Second)) + } + rn, err := conn.Read(buf) + if rn > 0 { + _, err := stream.Write(buf[:rn]) + if err != nil { + errChan <- err + return + } + } + if err != nil { + errChan <- err + return + } + } + }() + // Stream to TCP + go func() { + errChan <- utils.Pipe(stream, conn, nil) + }() + return <-errChan +}