diff --git a/README.md b/README.md index 1f780f1..65ad2fd 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ glider -config CONFIGPATH -listen :8080 -verbose ## Usage ```bash -glider 0.8.0 usage: +glider 0.9.0 usage: -checkinterval int proxy check interval(seconds) (default 30) -checktimeout int @@ -191,6 +191,7 @@ Available methods for ss: AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5 Alias: chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305 + Plain: DUMMY SSR scheme: ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz @@ -282,17 +283,14 @@ Examples: glider -config glider.conf -run glider with specified config file. - glider -config glider.conf -rulefile office.rule -rulefile home.rule - -run glider with specified global config file and rule config files. + glider -listen :8443 -verbose + -listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode. - glider -listen :8443 - -listen on :8443, serve as http/socks5 proxy on the same port. - - glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 + glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose -listen on 0.0.0.0:8443 as a ss server. - glider -listen socks5://:1080 -verbose - -listen on :1080 as a socks5 proxy server, in verbose mode. + glider -listen socks5://user1:pass1@:1080 -verbose + -listen on :1080 as a socks5 proxy server, enable authentication. glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose -listen on :443 as a https(http over tls) proxy server. diff --git a/conf.go b/conf.go index 16424f0..4736578 100644 --- a/conf.go +++ b/conf.go @@ -255,17 +255,14 @@ func usage() { fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf\n") fmt.Fprintf(os.Stderr, " -run glider with specified config file.\n") fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf -rulefile office.rule -rulefile home.rule\n") - fmt.Fprintf(os.Stderr, " -run glider with specified global config file and rule config files.\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen :8443 -verbose\n") + fmt.Fprintf(os.Stderr, " -listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.\n") fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen :8443\n") - fmt.Fprintf(os.Stderr, " -listen on :8443, serve as http/socks5 proxy on the same port.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose\n") fmt.Fprintf(os.Stderr, " -listen on 0.0.0.0:8443 as a ss server.\n") fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -verbose\n") - fmt.Fprintf(os.Stderr, " -listen on :1080 as a socks5 proxy server, in verbose mode.\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://user1:pass1@:1080 -verbose\n") + fmt.Fprintf(os.Stderr, " -listen on :1080 as a socks5 proxy server, enable authentication.\n") fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, " "+app+" -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose\n") fmt.Fprintf(os.Stderr, " -listen on :443 as a https(http over tls) proxy server.\n") diff --git a/proxy/http/client.go b/proxy/http/client.go new file mode 100644 index 0000000..db7ba95 --- /dev/null +++ b/proxy/http/client.go @@ -0,0 +1,81 @@ +package http + +import ( + "bytes" + "encoding/base64" + "errors" + "net" + "net/textproto" + + "github.com/nadoo/glider/common/conn" + "github.com/nadoo/glider/common/log" + "github.com/nadoo/glider/proxy" +) + +// NewHTTPDialer returns a http proxy dialer. +func NewHTTPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { + return NewHTTP(s, d, nil) +} + +// Addr returns forwarder's address. +func (s *HTTP) Addr() string { + if s.addr == "" { + return s.dialer.Addr() + } + return s.addr +} + +// Dial connects to the address addr on the network net via the proxy. +func (s *HTTP) Dial(network, addr string) (net.Conn, error) { + rc, err := s.dialer.Dial(network, s.addr) + if err != nil { + log.F("[http] dial to %s error: %s", s.addr, err) + return nil, err + } + + var buf bytes.Buffer + buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") + buf.WriteString("Host: " + addr + "\r\n") + buf.WriteString("Proxy-Connection: Keep-Alive\r\n") + + if s.user != "" && s.password != "" { + auth := s.user + ":" + s.password + buf.Write([]byte("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n")) + } + + // header ended + buf.WriteString("\r\n") + _, err = rc.Write(buf.Bytes()) + if err != nil { + return nil, err + } + + c := conn.NewConn(rc) + tpr := textproto.NewReader(c.Reader()) + line, err := tpr.ReadLine() + if err != nil { + return c, err + } + + _, code, _, ok := parseStartLine(line) + if ok && code == "200" { + tpr.ReadMIMEHeader() + return c, err + } + + switch code { + case "403": + log.F("[http] 'CONNECT' to ports other than 443 are not allowed by proxy %s", s.addr) + case "405": + log.F("[http] 'CONNECT' method not allowed by proxy %s", s.addr) + case "407": + log.F("[http] authencation needed by proxy %s", s.addr) + } + + return nil, errors.New("[http] can not connect remote address: " + addr + ". error code: " + code) +} + +// DialUDP connects to the given address via the proxy. +func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) { + return nil, nil, errors.New("http client does not support udp") +} diff --git a/proxy/http/http.go b/proxy/http/http.go index 5211e60..e1a6bd3 100644 --- a/proxy/http/http.go +++ b/proxy/http/http.go @@ -5,31 +5,24 @@ package http import ( - "bufio" "bytes" "encoding/base64" - "errors" - "fmt" - "io" - "net" "net/textproto" "net/url" "strings" - "time" - "github.com/nadoo/glider/common/conn" "github.com/nadoo/glider/common/log" "github.com/nadoo/glider/proxy" ) // HTTP struct. type HTTP struct { - dialer proxy.Dialer - proxy proxy.Proxy - addr string - user string - password string - pretendAsWebServer bool + dialer proxy.Dialer + proxy proxy.Proxy + addr string + user string + password string + pretend bool } func init() { @@ -50,240 +43,23 @@ func NewHTTP(s string, d proxy.Dialer, p proxy.Proxy) (*HTTP, error) { pass, _ := u.User.Password() h := &HTTP{ - dialer: d, - proxy: p, - addr: addr, - user: user, - password: pass, - pretendAsWebServer: false, + dialer: d, + proxy: p, + addr: addr, + user: user, + password: pass, + pretend: false, } - pretend := u.Query().Get("pretend") - if pretend == "true" { - h.pretendAsWebServer = true + if u.Query().Get("pretend") == "true" { + h.pretend = true } return h, nil } -// NewHTTPDialer returns a http proxy dialer. -func NewHTTPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { - return NewHTTP(s, d, nil) -} - -// NewHTTPServer returns a http proxy server. -func NewHTTPServer(s string, p proxy.Proxy) (proxy.Server, error) { - return NewHTTP(s, nil, p) -} - -// ListenAndServe listens on server's addr and serves connections. -func (s *HTTP) ListenAndServe() { - l, err := net.Listen("tcp", s.addr) - if err != nil { - log.F("[http] failed to listen on %s: %v", s.addr, err) - return - } - defer l.Close() - - log.F("[http] listening TCP on %s", s.addr) - - for { - c, err := l.Accept() - if err != nil { - log.F("[http] failed to accept: %v", err) - continue - } - - go s.Serve(c) - } -} - -// Serve serves a connection. -func (s *HTTP) Serve(c net.Conn) { - defer c.Close() - - if c, ok := c.(*net.TCPConn); ok { - c.SetKeepAlive(true) - } - - reqR := bufio.NewReader(c) - reqTP := textproto.NewReader(reqR) - method, requestURI, proto, ok := parseStartLine(reqTP) - if !ok { - return - } - - if s.pretendAsWebServer { - fmt.Fprintf(c, "%s 404 Not Found\r\nServer: nginx\r\n\r\n404 Not Found\r\n", proto) - log.F("[http pretender] being accessed as web server from %s", c.RemoteAddr().String()) - return - } - - if method == "CONNECT" { - s.servHTTPS(method, requestURI, proto, c) - return - } - - reqHeader, err := reqTP.ReadMIMEHeader() - if err != nil { - log.F("[http] read header error:%s", err) - return - } - cleanHeaders(reqHeader) - - // tell the remote server not to keep alive - reqHeader.Set("Connection", "close") - - u, err := url.ParseRequestURI(requestURI) - if err != nil { - log.F("[http] parse request url error: %s", err) - return - } - - tgt := u.Host - if !strings.Contains(u.Host, ":") { - tgt += ":80" - } - - rc, p, err := s.proxy.Dial("tcp", tgt) - if err != nil { - fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", proto) - log.F("[http] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, p, err) - return - } - defer rc.Close() - - // GET http://example.com/a/index.htm HTTP/1.1 --> - // GET /a/index.htm HTTP/1.1 - u.Scheme, u.Host = "", "" - uri := u.String() - - var buf bytes.Buffer - writeStartLine(&buf, method, uri, proto) - writeHeaders(&buf, reqHeader) - - // send request to remote server - rc.Write(buf.Bytes()) - - // copy the left request bytes to remote server. eg. length specificed or chunked body - go func() { - if _, err := reqR.Peek(1); err == nil { - io.Copy(rc, reqR) - rc.SetDeadline(time.Now()) - c.SetDeadline(time.Now()) - } - }() - - respR := bufio.NewReader(rc) - respTP := textproto.NewReader(respR) - proto, code, status, ok := parseStartLine(respTP) - if !ok { - return - } - - respHeader, err := respTP.ReadMIMEHeader() - if err != nil { - log.F("[http] %s <-> %s via %s, read header error: %v", c.RemoteAddr(), tgt, p, err) - return - } - - respHeader.Set("Proxy-Connection", "close") - respHeader.Set("Connection", "close") - - buf.Reset() - writeStartLine(&buf, proto, code, status) - writeHeaders(&buf, respHeader) - - log.F("[http] %s <-> %s via %s", c.RemoteAddr(), tgt, p) - c.Write(buf.Bytes()) - - io.Copy(c, respR) -} - -func (s *HTTP) servHTTPS(method, requestURI, proto string, c net.Conn) { - rc, p, err := s.proxy.Dial("tcp", requestURI) - if err != nil { - c.Write([]byte(proto)) - c.Write([]byte(" 502 ERROR\r\n\r\n")) - log.F("[http] %s <-> %s [c] via %s, error in dial: %v", c.RemoteAddr(), requestURI, p, err) - return - } - - c.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) - - log.F("[http] %s <-> %s [c] via %s", c.RemoteAddr(), requestURI, p) - - _, _, err = conn.Relay(c, rc) - if err != nil { - if err, ok := err.(net.Error); ok && err.Timeout() { - return // ignore i/o timeout - } - log.F("[http] relay error: %v", err) - } -} - -// Addr returns forwarder's address. -func (s *HTTP) Addr() string { - if s.addr == "" { - return s.dialer.Addr() - } - return s.addr -} - -// Dial connects to the address addr on the network net via the proxy. -func (s *HTTP) Dial(network, addr string) (net.Conn, error) { - rc, err := s.dialer.Dial(network, s.addr) - if err != nil { - log.F("[http] dial to %s error: %s", s.addr, err) - return nil, err - } - - var buf bytes.Buffer - buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") - buf.WriteString("Host: " + addr + "\r\n") - buf.WriteString("Proxy-Connection: Keep-Alive\r\n") - - if s.user != "" && s.password != "" { - auth := s.user + ":" + s.password - buf.Write([]byte("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n")) - } - - // header ended - buf.WriteString("\r\n") - _, err = rc.Write(buf.Bytes()) - if err != nil { - return nil, err - } - - c := conn.NewConn(rc) - tpr := textproto.NewReader(c.Reader()) - _, code, _, ok := parseStartLine(tpr) - if ok && code == "200" { - tpr.ReadMIMEHeader() - return c, err - } - - if code == "407" { - log.F("[http] authencation needed by proxy %s", s.addr) - } else if code == "405" { - log.F("[http] 'CONNECT' method not allowed by proxy %s", s.addr) - } - - return nil, errors.New("[http] can not connect remote address: " + addr + ". error code: " + code) -} - -// DialUDP connects to the given address via the proxy. -func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) { - return nil, nil, errors.New("http client does not support udp") -} - // parseStartLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts. -func parseStartLine(tp *textproto.Reader) (r1, r2, r3 string, ok bool) { - line, err := tp.ReadLine() - if err != nil { - return - } - +func parseStartLine(line string) (r1, r2, r3 string, ok bool) { s1 := strings.Index(line, " ") s2 := strings.Index(line[s1+1:], " ") if s1 < 0 || s2 < 0 { @@ -317,3 +93,22 @@ func writeHeaders(buf *bytes.Buffer, header textproto.MIMEHeader) { } buf.WriteString("\r\n") } + +func extractUserPass(auth string) (username, password string, ok bool) { + if !strings.HasPrefix(auth, "Basic ") { + return + } + + b, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic ")) + if err != nil { + return + } + + s := string(b) + idx := strings.IndexByte(s, ':') + if idx < 0 { + return + } + + return s[:idx], s[idx+1:], true +} diff --git a/proxy/http/request.go b/proxy/http/request.go new file mode 100644 index 0000000..1104b3b --- /dev/null +++ b/proxy/http/request.go @@ -0,0 +1,113 @@ +package http + +import ( + "bufio" + "bytes" + "net/textproto" + "net/url" + "strings" + + "github.com/nadoo/glider/common/log" +) + +// Methods are http methods from rfc. +// https://www.ietf.org/rfc/rfc2616.txt, http methods must be uppercase +var Methods = [...][]byte{ + []byte("GET"), + []byte("POST"), + []byte("PUT"), + []byte("DELETE"), + []byte("CONNECT"), + []byte("HEAD"), + []byte("OPTIONS"), + []byte("TRACE"), + []byte("PATCH"), +} + +type request struct { + method string + uri string + proto string + auth string + header textproto.MIMEHeader + + target string // target host with port + ruri string // relative uri + absuri string // absolute uri +} + +func parseRequest(r *bufio.Reader) (*request, error) { + tpr := textproto.NewReader(r) + line, err := tpr.ReadLine() + if err != nil { + return nil, err + } + + method, uri, proto, ok := parseStartLine(line) + if !ok { + return nil, err + } + + header, err := tpr.ReadMIMEHeader() + if err != nil { + log.F("[http] read header error:%s", err) + return nil, err + } + + auth := header.Get("Proxy-Authorization") + + cleanHeaders(header) + header.Set("Connection", "close") + + u, err := url.ParseRequestURI(uri) + if err != nil { + log.F("[http] parse request url error: %s, uri: %s", err, uri) + return nil, err + } + + var tgt = u.Host + if !strings.Contains(u.Host, ":") { + tgt += ":80" + } + + req := &request{ + method: method, + uri: uri, + proto: proto, + auth: auth, + header: header, + target: tgt, + } + + if u.IsAbs() { + req.absuri = u.String() + u.Scheme = "" + u.Host = "" + req.ruri = u.String() + } else { + req.ruri = u.String() + + base, err := url.Parse("http://" + header.Get("Host")) + if err != nil { + return nil, err + } + u = base.ResolveReference(u) + req.absuri = u.String() + } + + return req, nil +} + +func (r *request) Marshal() []byte { + var buf bytes.Buffer + writeStartLine(&buf, r.method, r.ruri, r.proto) + writeHeaders(&buf, r.header) + return buf.Bytes() +} + +func (r *request) MarshalAbs() []byte { + var buf bytes.Buffer + writeStartLine(&buf, r.method, r.absuri, r.proto) + writeHeaders(&buf, r.header) + return buf.Bytes() +} diff --git a/proxy/http/server.go b/proxy/http/server.go new file mode 100644 index 0000000..0b9c807 --- /dev/null +++ b/proxy/http/server.go @@ -0,0 +1,163 @@ +package http + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net" + "net/textproto" + "time" + + "github.com/nadoo/glider/common/conn" + "github.com/nadoo/glider/common/log" + "github.com/nadoo/glider/proxy" +) + +// NewHTTPServer returns a http proxy server. +func NewHTTPServer(s string, p proxy.Proxy) (proxy.Server, error) { + return NewHTTP(s, nil, p) +} + +// ListenAndServe listens on server's addr and serves connections. +func (s *HTTP) ListenAndServe() { + l, err := net.Listen("tcp", s.addr) + if err != nil { + log.F("[http] failed to listen on %s: %v", s.addr, err) + return + } + defer l.Close() + + log.F("[http] listening TCP on %s", s.addr) + + for { + c, err := l.Accept() + if err != nil { + log.F("[http] failed to accept: %v", err) + continue + } + + go s.Serve(c) + } +} + +// Serve serves a connection. +func (s *HTTP) Serve(cc net.Conn) { + defer cc.Close() + + var c *conn.Conn + switch ccc := cc.(type) { + case *net.TCPConn: + ccc.SetKeepAlive(true) + c = conn.NewConn(ccc) + case *conn.Conn: + c = ccc + } + + req, err := parseRequest(c.Reader()) + if err != nil { + return + } + + if s.pretend { + fmt.Fprintf(c, "%s 404 Not Found\r\nServer: nginx\r\n\r\n404 Not Found\r\n", req.proto) + log.F("[http] accessed by %s as web server", c.RemoteAddr().String()) + return + } + + s.servRequest(req, c) +} + +func (s *HTTP) servRequest(req *request, c *conn.Conn) { + // Auth + if s.user != "" && s.password != "" { + if user, pass, ok := extractUserPass(req.auth); !ok || user != s.user || pass != s.password { + c.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n")) + log.F("[http] auth failed from %s, auth info: %s:%s", c.RemoteAddr(), user, pass) + return + } + } + + if req.method == "CONNECT" { + s.servHTTPS(req, c) + return + } + + s.servHTTP(req, c) +} + +func (s *HTTP) servHTTPS(r *request, c net.Conn) { + rc, p, err := s.proxy.Dial("tcp", r.uri) + if err != nil { + c.Write([]byte(r.proto + " 502 ERROR\r\n\r\n")) + log.F("[http] %s <-> %s [c] via %s, error in dial: %v", c.RemoteAddr(), r.uri, p, err) + return + } + + c.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) + + log.F("[http] %s <-> %s [c] via %s", c.RemoteAddr(), r.uri, p) + + _, _, err = conn.Relay(c, rc) + if err != nil { + if err, ok := err.(net.Error); ok && err.Timeout() { + return // ignore i/o timeout + } + log.F("[http] relay error: %v", err) + } +} + +func (s *HTTP) servHTTP(req *request, c *conn.Conn) { + rc, p, err := s.proxy.Dial("tcp", req.target) + if err != nil { + fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", req.proto) + log.F("[http] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), req.target, p, err) + return + } + defer rc.Close() + + // send request to remote server + _, err = rc.Write(req.Marshal()) + if err != nil { + return + } + + // copy the left request bytes to remote server. eg. length specificed or chunked body. + go func() { + if _, err := c.Reader().Peek(1); err == nil { + io.Copy(rc, c) + rc.SetDeadline(time.Now()) + c.SetDeadline(time.Now()) + } + }() + + r := bufio.NewReader(rc) + tpr := textproto.NewReader(r) + line, err := tpr.ReadLine() + if err != nil { + return + } + + proto, code, status, ok := parseStartLine(line) + if !ok { + return + } + + header, err := tpr.ReadMIMEHeader() + if err != nil { + log.F("[http] read header error:%s", err) + return + } + + header.Set("Proxy-Connection", "close") + header.Set("Connection", "close") + + var buf bytes.Buffer + writeStartLine(&buf, proto, code, status) + writeHeaders(&buf, header) + + log.F("[http] %s <-> %s", c.RemoteAddr(), req.target) + c.Write(buf.Bytes()) + + io.Copy(c, r) +} diff --git a/proxy/mixed/mixed.go b/proxy/mixed/mixed.go index f07236d..cbac5f6 100644 --- a/proxy/mixed/mixed.go +++ b/proxy/mixed/mixed.go @@ -12,26 +12,13 @@ import ( "github.com/nadoo/glider/proxy/socks5" ) -// https://www.ietf.org/rfc/rfc2616.txt, http methods must be uppercase -var httpMethods = [...][]byte{ - []byte("GET"), - []byte("POST"), - []byte("PUT"), - []byte("DELETE"), - []byte("CONNECT"), - []byte("HEAD"), - []byte("OPTIONS"), - []byte("TRACE"), - []byte("PATCH"), -} - // Mixed struct. type Mixed struct { proxy proxy.Proxy addr string - http *http.HTTP - socks5 *socks5.Socks5 + httpServer *http.HTTP + socks5Server *socks5.Socks5 } func init() { @@ -51,8 +38,15 @@ func NewMixed(s string, p proxy.Proxy) (*Mixed, error) { addr: u.Host, } - m.http, _ = http.NewHTTP(s, nil, p) - m.socks5, _ = socks5.NewSocks5(s, nil, p) + m.httpServer, err = http.NewHTTP(s, nil, p) + if err != nil { + return nil, err + } + + m.socks5Server, err = socks5.NewSocks5(s, nil, p) + if err != nil { + return nil, err + } return m, nil } @@ -64,7 +58,7 @@ func NewMixedServer(s string, p proxy.Proxy) (proxy.Server, error) { // ListenAndServe listens on server's addr and serves connections. func (m *Mixed) ListenAndServe() { - go m.socks5.ListenAndServeUDP() + go m.socks5Server.ListenAndServeUDP() l, err := net.Listen("tcp", m.addr) if err != nil { @@ -95,32 +89,28 @@ func (m *Mixed) Serve(c net.Conn) { cc := conn.NewConn(c) - if m.socks5 != nil { - head, err := cc.Peek(1) - if err != nil { - // log.F("[mixed] socks5 peek error: %s", err) - return - } - - // check socks5, client send socksversion: 5 as the first byte - if head[0] == socks5.Version { - m.socks5.Serve(cc) - return - } + head, err := cc.Peek(1) + if err != nil { + // log.F("[mixed] socks5 peek error: %s", err) + return } - if m.http != nil { - head, err := cc.Peek(8) - if err != nil { - log.F("[mixed] http peek error: %s", err) - return - } + // check socks5, client send socksversion: 5 as the first byte + if head[0] == socks5.Version { + m.socks5Server.Serve(cc) + return + } - for _, method := range httpMethods { - if bytes.HasPrefix(head, method) { - m.http.Serve(cc) - return - } + head, err = cc.Peek(8) + if err != nil { + log.F("[mixed] http peek error: %s", err) + return + } + + for _, method := range http.Methods { + if bytes.HasPrefix(head, method) { + m.httpServer.Serve(cc) + return } } diff --git a/proxy/socks5/socks5.go b/proxy/socks5/socks5.go index 39d3c02..c6f0165 100644 --- a/proxy/socks5/socks5.go +++ b/proxy/socks5/socks5.go @@ -103,11 +103,16 @@ func (s *Socks5) ListenAndServeTCP() { } // Serve serves a connection. -func (s *Socks5) Serve(c net.Conn) { - defer c.Close() +func (s *Socks5) Serve(cc net.Conn) { + defer cc.Close() - if c, ok := c.(*net.TCPConn); ok { - c.SetKeepAlive(true) + var c *conn.Conn + switch ccc := cc.(type) { + case *net.TCPConn: + ccc.SetKeepAlive(true) + c = conn.NewConn(ccc) + case *conn.Conn: + c = ccc } tgt, err := s.handshake(c) @@ -126,7 +131,7 @@ func (s *Socks5) Serve(c net.Conn) { } } - log.F("[socks5] failed in handshake: %v", err) + log.F("[socks5] failed in handshake with %s: %v", c.RemoteAddr(), err) return } @@ -483,7 +488,7 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) { if err != nil { return nil, err } - return nil, errors.New("auth failed") + return nil, errors.New("auth failed, authinfo: " + user + ":" + pass) } // Response auth state