From 748637382168b01af01803fc7944e082b4492cc2 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Wed, 4 Aug 2021 19:13:22 +0800 Subject: [PATCH] proxy: support server mode of PROXY protocol v1 --- README.md | 9 +-- config.go | 4 +- dns/message.go | 14 ++-- feature.go | 1 + go.mod | 4 +- go.sum | 10 +-- proxy/conn.go | 12 ++-- proxy/http/request.go | 6 +- proxy/http/server.go | 2 +- proxy/obfs/tls.go | 2 +- proxy/pxyproto/server.go | 134 +++++++++++++++++++++++++++++++++++++++ proxy/socks5/packet.go | 2 +- proxy/socks5/socks5.go | 2 +- proxy/ws/frame.go | 2 +- rule/check.go | 6 +- 15 files changed, 173 insertions(+), 37 deletions(-) create mode 100644 proxy/pxyproto/server.go diff --git a/README.md b/README.md index 13b74bc..d171e3b 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ we can set up local listeners as proxy servers, and forward requests to internet |Unix |√|√|√|√|transport client & server |Smux |√| |√| |transport client & server |Websocket(WS)|√| |√| |transport client & server -|WS Secure |√| |√| |transport client & server +|WS Secure |√| |√| |websocket secure (wss) +|Proxy Proto |√| |√| |transport client & server |Simple-Obfs | | |√| |transport client only |Redir |√| | | |linux redirect proxy |Redir6 |√| | | |linux redirect proxy(ipv6) @@ -182,8 +183,8 @@ glider -verbose -listen :8443 -forward SCHEME://HOST:PORT ```bash Available schemes: - listen: mixed ss socks5 http vless trojan trojanc redir redir6 tproxy tcp udp tls ws wss unix smux kcp - forward: direct reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws wss unix smux kcp simple-obfs + listen: mixed ss socks5 http vless trojan trojanc redir redir6 tproxy tcp udp tls ws wss unix smux kcp pxyproto + forward: direct reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws wss unix smux kcp simple-obfs pxyproto Socks5 scheme: socks://[user:pass@]host:port @@ -397,7 +398,7 @@ Examples: // _ "github.com/nadoo/glider/proxy/kcp" ``` -3. Build it(requires **Go 1.16+** ) +3. Build it(requires **Go 1.17+** ) ```bash go build -v -ldflags "-s -w" ``` diff --git a/config.go b/config.go index 0931cbb..e8efb47 100644 --- a/config.go +++ b/config.go @@ -150,8 +150,8 @@ func usage() { fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Available schemes:\n") - fmt.Fprintf(w, " listen: mixed ss socks5 http vless trojan trojanc redir redir6 tproxy tcp udp tls ws wss unix smux kcp\n") - fmt.Fprintf(w, " forward: direct reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws wss unix smux kcp simple-obfs\n") + fmt.Fprintf(w, " listen: mixed ss socks5 http vless trojan trojanc redir redir6 tproxy tcp udp tls ws wss unix smux kcp pxyproto\n") + fmt.Fprintf(w, " forward: direct reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws wss unix smux kcp simple-obfs pxyproto\n") fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Socks5 scheme:\n") diff --git a/dns/message.go b/dns/message.go index 36806c3..4b24e19 100644 --- a/dns/message.go +++ b/dns/message.go @@ -11,7 +11,7 @@ import ( ) // UDPMaxLen is the max size of udp dns request. -// https://tools.ietf.org/html/rfc1035#section-4.2.1 +// https://www.rfc-editor.org/rfc/rfc1035#section-4.2.1 // Messages carried by UDP are restricted to 512 bytes (not counting the IP // or UDP headers). Longer messages are truncated and the TC bit is set in // the header. @@ -36,7 +36,7 @@ const ( const ClassINET uint16 = 1 // Message format: -// https://tools.ietf.org/html/rfc1035#section-4.1 +// https://www.rfc-editor.org/rfc/rfc1035#section-4.1 // All communications inside of the domain protocol are carried in a single // format called a message. The top level format of message is divided // into 5 sections (some of which are empty in certain cases) shown below: @@ -165,7 +165,7 @@ func UnmarshalMessage(b []byte) (*Message, error) { } // Header format: -// https://tools.ietf.org/html/rfc1035#section-4.1.1 +// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.1 // The header contains the following fields: // // 1 1 1 1 1 1 @@ -244,7 +244,7 @@ func UnmarshalHeader(b []byte, h *Header) error { } // Question format: -// https://tools.ietf.org/html/rfc1035#section-4.1.2 +// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.2 // The question section is used to carry the "question" in most queries, // i.e., the parameters that define what is being asked. The section // contains QDCOUNT (usually 1) entries, each of the following format: @@ -322,8 +322,8 @@ func (m *Message) UnmarshalQuestion(b []byte, q *Question) (n int, err error) { } // RR format: -// https://tools.ietf.org/html/rfc1035#section-3.2.1 -// https://tools.ietf.org/html/rfc1035#section-4.1.3 +// https://www.rfc-editor.org/rfc/rfc1035#section-3.2.1 +// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.3 // The answer, authority, and additional sections all share the same // format: a variable number of resource records, where the number of // records is specified in the corresponding count field in the header. @@ -479,7 +479,7 @@ func (m *Message) UnmarshalDomainTo(sb *strings.Builder, b []byte) (int, error) var idx, size int for len(b[idx:]) != 0 { - // https://tools.ietf.org/html/rfc1035#section-4.1.4 + // https://www.rfc-editor.org/rfc/rfc1035#section-4.1.4 // "Message compression", // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // | 1 1| OFFSET | diff --git a/feature.go b/feature.go index c4c5b07..21b49fc 100644 --- a/feature.go +++ b/feature.go @@ -9,6 +9,7 @@ import ( _ "github.com/nadoo/glider/proxy/kcp" _ "github.com/nadoo/glider/proxy/mixed" _ "github.com/nadoo/glider/proxy/obfs" + _ "github.com/nadoo/glider/proxy/pxyproto" _ "github.com/nadoo/glider/proxy/reject" _ "github.com/nadoo/glider/proxy/smux" _ "github.com/nadoo/glider/proxy/socks4" diff --git a/go.mod b/go.mod index b0faf1a..30c70ad 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e - github.com/klauspost/cpuid/v2 v2.0.8 // indirect - github.com/klauspost/reedsolomon v1.9.12 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/klauspost/reedsolomon v1.9.13 // indirect github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf // indirect github.com/nadoo/conflag v0.2.3 github.com/nadoo/ipset v0.3.0 diff --git a/go.sum b/go.sum index 01cba5e..b3121ae 100644 --- a/go.sum +++ b/go.sum @@ -49,12 +49,12 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= -github.com/klauspost/cpuid/v2 v2.0.2/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.8 h1:bhR2mgIlno/Sfk4oUbH4sPlc83z1yGrN9bvqiq3C33I= -github.com/klauspost/cpuid/v2 v2.0.8/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo= -github.com/klauspost/reedsolomon v1.9.12 h1:EyOucRmcrLH+2hqKGdoA5SM8pwPKR6BJsf3r6zpYOA0= -github.com/klauspost/reedsolomon v1.9.12/go.mod h1:nLvuzNvy1ZDNQW30IuMc2ZWCbiqrJgdLoUS2X8HAUVg= +github.com/klauspost/reedsolomon v1.9.13 h1:Xr0COKf7F0ACTXUNnz2ZFCWlUKlUTAUX3y7BODdUxqU= +github.com/klauspost/reedsolomon v1.9.13/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= diff --git a/proxy/conn.go b/proxy/conn.go index cddea85..36afbc7 100644 --- a/proxy/conn.go +++ b/proxy/conn.go @@ -45,6 +45,12 @@ func (c *Conn) Peek(n int) ([]byte, error) { return c.r.Peek(n) } // WriteTo implements io.WriterTo. func (c *Conn) WriteTo(w io.Writer) (n int64, err error) { return c.r.WriteTo(w) } +// Close closes the Conn. +func (c *Conn) Close() error { + pool.PutBufReader(c.r) + return c.Conn.Close() +} + // Relay relays between left and right. func Relay(left, right net.Conn) error { var err, err1 error @@ -73,12 +79,6 @@ func Relay(left, right net.Conn) error { return nil } -// Close closes the Conn. -func (c *Conn) Close() error { - pool.PutBufReader(c.r) - return c.Conn.Close() -} - // Copy copies from src to dst. func Copy(dst io.Writer, src io.Reader) (written int64, err error) { dst = underlyingWriter(dst) diff --git a/proxy/http/request.go b/proxy/http/request.go index b7f3331..e5576b8 100644 --- a/proxy/http/request.go +++ b/proxy/http/request.go @@ -3,7 +3,7 @@ package http import ( "bufio" "bytes" - "errors" + "fmt" "net/textproto" "net/url" "strings" @@ -12,7 +12,7 @@ import ( ) // Methods are http methods from rfc. -// https://www.ietf.org/rfc/rfc2616.txt, http methods must be uppercase +// https://www.rfc-editor.org/rfc/rfc2616, http methods must be uppercase var Methods = [...][]byte{ []byte("GET"), []byte("POST"), @@ -46,7 +46,7 @@ func parseRequest(r *bufio.Reader) (*request, error) { method, uri, proto, ok := parseStartLine(line) if !ok { - return nil, errors.New("error in parseStartLine") + return nil, fmt.Errorf("error in parseStartLine: %s", line) } header, err := tpr.ReadMIMEHeader() diff --git a/proxy/http/server.go b/proxy/http/server.go index 6478d05..28737c8 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -51,7 +51,7 @@ func (s *HTTP) Serve(cc net.Conn) { c := proxy.NewConn(cc) req, err := parseRequest(c.Reader()) if err != nil { - log.F("[http] can not parse request from %s", c.RemoteAddr()) + log.F("[http] can not parse request from %s, error: %v", c.RemoteAddr(), err) return } diff --git a/proxy/obfs/tls.go b/proxy/obfs/tls.go index e436518..ff516ff 100644 --- a/proxy/obfs/tls.go +++ b/proxy/obfs/tls.go @@ -1,4 +1,4 @@ -// https://www.ietf.org/rfc/rfc5246.txt +// https://www.rfc-editor.org/rfc/rfc5246 // https://golang.org/src/crypto/tls/handshake_messages.go // NOTE: diff --git a/proxy/pxyproto/server.go b/proxy/pxyproto/server.go new file mode 100644 index 0000000..fd2fae2 --- /dev/null +++ b/proxy/pxyproto/server.go @@ -0,0 +1,134 @@ +package pxyproto + +import ( + "errors" + "fmt" + "net" + "net/url" + "strings" + + "github.com/nadoo/glider/log" + "github.com/nadoo/glider/proxy" +) + +func init() { + proxy.RegisterServer("pxyproto", NewPxyProtoServer) +} + +// PxyProtoServer struct. +type PxyProtoServer struct { + addr string + proxy proxy.Proxy + server proxy.Server +} + +// NewPxyProtoServer returns a PxyProtoServer struct. +func NewPxyProtoServer(s string, p proxy.Proxy) (proxy.Server, error) { + schemes := strings.SplitN(s, ",", 2) + u, err := url.Parse(schemes[0]) + if err != nil { + log.F("[pxyproto] parse url err: %s", err) + return nil, err + } + + t := &PxyProtoServer{proxy: p, addr: u.Host} + if len(schemes) < 2 { + return nil, errors.New("[pxyproto] you must use pxyproto with a proxy server, e.g: pxyproto://:1234,http://") + } + + t.server, err = proxy.ServerFromURL(schemes[1], p) + if err != nil { + return nil, err + } + + return t, nil +} + +// ListenAndServe listens on server's addr and serves connections. +func (s *PxyProtoServer) ListenAndServe() { + l, err := net.Listen("tcp", s.addr) + if err != nil { + log.F("[pxyproto] failed to listen on %s: %v", s.addr, err) + return + } + defer l.Close() + + log.F("[pxyproto] listening TCP on %s", s.addr) + + for { + c, err := l.Accept() + if err != nil { + log.F("[pxyproto] failed to accept: %v", err) + continue + } + + go s.Serve(c) + } +} + +// Serve serves a connection. +func (s *PxyProtoServer) Serve(cc net.Conn) { + c, err := newServerConn(cc) + if err != nil { + log.F("[pxyproto] parse header failed, error: %v", err) + cc.Close() + return + } + + // log.F("[pxyproto] %s <-> %s <-> %s <-> %s", + // c.RemoteAddr(), c.LocalAddr(), cc.RemoteAddr(), cc.LocalAddr()) + + if s.server != nil { + s.server.Serve(c) + return + } +} + +type serverConn struct { + *proxy.Conn + src, dst net.Addr +} + +func newServerConn(c net.Conn) (*serverConn, error) { + sc := &serverConn{ + Conn: proxy.NewConn(c), + src: c.RemoteAddr(), + dst: c.LocalAddr(), + } + return sc, sc.parseHeader() +} + +// "PROXY TCPx SRC_IP DST_IP SRC_PORT DST_PORT" +func (c *serverConn) parseHeader() error { + line, err := c.Conn.Reader().ReadString('\n') + if err != nil { + return err + } + + line = strings.ReplaceAll(line, "\r\n", "") + // log.F("[pxyproto] req header: %s", line) + + header := strings.Split(line, " ") + if len(header) != 6 { + return fmt.Errorf("invalid header: %s", line) + } + + if header[0] != "PROXY" { + return fmt.Errorf("invalid header: %s", line) + } + + c.src, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(header[2], header[4])) + if err != nil { + return fmt.Errorf("parse header: %s, error: %v", line, err) + } + + c.dst, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(header[3], header[5])) + if err != nil { + return fmt.Errorf("parse header: %s, error: %v", line, err) + } + + return nil +} + +func (c *serverConn) LocalAddr() net.Addr { return c.dst } +func (c *serverConn) RemoteAddr() net.Addr { return c.src } diff --git a/proxy/socks5/packet.go b/proxy/socks5/packet.go index 0378af3..bd7bd9a 100644 --- a/proxy/socks5/packet.go +++ b/proxy/socks5/packet.go @@ -66,7 +66,7 @@ func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) { return n, raddr, errors.New("not enough size to get addr") } - // https://tools.ietf.org/html/rfc1928#section-7 + // https://www.rfc-editor.org/rfc/rfc1928#section-7 // +----+------+------+----------+----------+----------+ // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | // +----+------+------+----------+----------+----------+ diff --git a/proxy/socks5/socks5.go b/proxy/socks5/socks5.go index 2400672..39ca63a 100644 --- a/proxy/socks5/socks5.go +++ b/proxy/socks5/socks5.go @@ -1,4 +1,4 @@ -// https://tools.ietf.org/html/rfc1928 +// https://www.rfc-editor.org/rfc/rfc1928 // socks5 client: // https://github.com/golang/net/tree/master/proxy diff --git a/proxy/ws/frame.go b/proxy/ws/frame.go index 58038a2..b38a66e 100644 --- a/proxy/ws/frame.go +++ b/proxy/ws/frame.go @@ -1,4 +1,4 @@ -// https://tools.ietf.org/html/rfc6455#section-5.2 +// https://www.rfc-editor.org/rfc/rfc6455#section-5.2 // // Frame Format // 0 1 2 3 diff --git a/rule/check.go b/rule/check.go index bea5211..c4ba392 100644 --- a/rule/check.go +++ b/rule/check.go @@ -1,12 +1,12 @@ package rule import ( - "bytes" "errors" "fmt" "io" "os" "os/exec" + "strings" "time" "github.com/nadoo/glider/pool" @@ -71,12 +71,12 @@ func (c *httpChecker) Check(dialer proxy.Dialer) (time.Duration, error) { r := pool.GetBufReader(rc) defer pool.PutBufReader(r) - line, _, err := r.ReadLine() + line, err := r.ReadString('\n') if err != nil { return 0, err } - if !bytes.Contains(line, []byte(c.expect)) { + if !strings.Contains(line, c.expect) { return 0, fmt.Errorf("expect: %s, got: %s", c.expect, line) }