From fc191cb14e4b1dc9dee06f2f0da0e6d0b32b6dfe Mon Sep 17 00:00:00 2001 From: Shengjing Zhu Date: Wed, 1 Dec 2021 02:02:15 +0800 Subject: [PATCH] refactor: move third_party code to separate dir --- go.mod | 16 +- go.sum | 18 +- internal/third_party/tailscale/LICENSE | 29 ++ .../third_party/tailscale/httpproxy/proxy.go | 79 ++++ .../third_party/tailscale/proxymux/mux.go | 145 +++++++ .../third_party/tailscale/socks5/socks5.go | 369 ++++++++++++++++++ main.go | 26 +- proxy.go | 102 ----- 8 files changed, 667 insertions(+), 117 deletions(-) create mode 100644 internal/third_party/tailscale/LICENSE create mode 100644 internal/third_party/tailscale/httpproxy/proxy.go create mode 100644 internal/third_party/tailscale/proxymux/mux.go create mode 100644 internal/third_party/tailscale/socks5/socks5.go delete mode 100644 proxy.go diff --git a/go.mod b/go.mod index 1af14d0..ce9ff01 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,19 @@ module github.com/zhsj/wghttp -go 1.16 +go 1.17 require ( github.com/jessevdk/go-flags v1.5.0 - golang.zx2c4.com/wireguard v0.0.0-20211025155311-828a885a7140 - golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20211025155311-828a885a7140 + golang.zx2c4.com/wireguard v0.0.0-20211116201604-de7c702ace45 + golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20211116201604-de7c702ace45 +) + +require ( + github.com/google/btree v1.0.1 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 // indirect + golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect + golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect + golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect + gvisor.dev/gvisor v0.0.0-20211020211948-f76a604701b6 // indirect ) diff --git a/go.sum b/go.sum index bf548b8..41afc9a 100644 --- a/go.sum +++ b/go.sum @@ -642,8 +642,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI= -golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU= +golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -736,8 +736,8 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211020174200-9d6173849985 h1:LOlKVhfDyahgmqa97awczplwkjzNaELFg3zRIJ13RYo= -golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -812,11 +812,13 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20210424170727-c9db4b7aaa22/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg= -golang.zx2c4.com/wireguard v0.0.0-20211025155311-828a885a7140 h1:2b5DXA/r6bViC2AwUO66SKfdGIus8ffcwR4+BLF0KTk= -golang.zx2c4.com/wireguard v0.0.0-20211025155311-828a885a7140/go.mod h1:RTjaYEQboNk7+2qfPGBotaMEh/5HIvmPZ6DIe10lTqI= -golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20211025155311-828a885a7140 h1:dCphgMdSHXtrMgj5BmSqDXmc68at6MLgUg4EPbVYVb4= -golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20211025155311-828a885a7140/go.mod h1:U6gm2r5g3zEe4OlrGL+W3rptmOo4Sj6mIFSIZHMFo4s= +golang.zx2c4.com/wireguard v0.0.0-20211116201604-de7c702ace45 h1:mEVhdMPTuebD9IUXOUB5Q2sjZpcmzkahHWd6DrGpLHA= +golang.zx2c4.com/wireguard v0.0.0-20211116201604-de7c702ace45/go.mod h1:evxZIqfCetExY5piKXGAxJYwvXWkps9zTCkWpkoGFxw= +golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20211116201604-de7c702ace45 h1:td2v30AWwaU5sKKbgqC3vh6jywoKOG76jJuZV/JnGzA= +golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20211116201604-de7c702ace45/go.mod h1:U6gm2r5g3zEe4OlrGL+W3rptmOo4Sj6mIFSIZHMFo4s= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= diff --git a/internal/third_party/tailscale/LICENSE b/internal/third_party/tailscale/LICENSE new file mode 100644 index 0000000..66284d3 --- /dev/null +++ b/internal/third_party/tailscale/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020 Tailscale & AUTHORS. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/internal/third_party/tailscale/httpproxy/proxy.go b/internal/third_party/tailscale/httpproxy/proxy.go new file mode 100644 index 0000000..2bd6a6f --- /dev/null +++ b/internal/third_party/tailscale/httpproxy/proxy.go @@ -0,0 +1,79 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// HTTP proxy code + +package httpproxy + +import ( + "context" + "io" + "net" + "net/http" + "net/http/httputil" + "strings" +) + +// Handler returns an HTTP proxy http.Handler using the +// provided backend dialer. +func Handler(dialer func(ctx context.Context, netw, addr string) (net.Conn, error)) http.Handler { + rp := &httputil.ReverseProxy{ + Director: func(r *http.Request) {}, // no change + Transport: &http.Transport{ + DialContext: dialer, + }, + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "CONNECT" { + backURL := r.RequestURI + if strings.HasPrefix(backURL, "/") || backURL == "*" { + http.Error(w, "bogus RequestURI; must be absolute URL or CONNECT", 400) + return + } + rp.ServeHTTP(w, r) + return + } + + // CONNECT support: + + dst := r.RequestURI + c, err := dialer(r.Context(), "tcp", dst) + if err != nil { + w.Header().Set("Connect-Error", err.Error()) + http.Error(w, err.Error(), 500) + return + } + defer c.Close() + + cc, ccbuf, err := w.(http.Hijacker).Hijack() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + defer cc.Close() + + io.WriteString(cc, "HTTP/1.1 200 OK\r\n\r\n") + + var clientSrc io.Reader = ccbuf + if ccbuf.Reader.Buffered() == 0 { + // In the common case (with no + // buffered data), read directly from + // the underlying client connection to + // save some memory, letting the + // bufio.Reader/Writer get GC'ed. + clientSrc = cc + } + + errc := make(chan error, 1) + go func() { + _, err := io.Copy(cc, c) + errc <- err + }() + go func() { + _, err := io.Copy(c, clientSrc) + errc <- err + }() + <-errc + }) +} diff --git a/internal/third_party/tailscale/proxymux/mux.go b/internal/third_party/tailscale/proxymux/mux.go new file mode 100644 index 0000000..f16759c --- /dev/null +++ b/internal/third_party/tailscale/proxymux/mux.go @@ -0,0 +1,145 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package proxymux splits a net.Listener in two, routing SOCKS5 +// connections to one and HTTP requests to the other. +// +// It allows for hosting both a SOCKS5 proxy and an HTTP proxy on the +// same listener. +package proxymux + +import ( + "io" + "net" + "sync" + "time" +) + +// SplitSOCKSAndHTTP accepts connections on ln and passes connections +// through to either socksListener or httpListener, depending the +// first byte sent by the client. +func SplitSOCKSAndHTTP(ln net.Listener) (socksListener, httpListener net.Listener) { + sl := &listener{ + addr: ln.Addr(), + c: make(chan net.Conn), + closed: make(chan struct{}), + } + hl := &listener{ + addr: ln.Addr(), + c: make(chan net.Conn), + closed: make(chan struct{}), + } + + go splitSOCKSAndHTTPListener(ln, sl, hl) + + return sl, hl +} + +func splitSOCKSAndHTTPListener(ln net.Listener, sl, hl *listener) { + for { + conn, err := ln.Accept() + if err != nil { + sl.Close() + hl.Close() + return + } + go routeConn(conn, sl, hl) + } +} + +func routeConn(c net.Conn, socksListener, httpListener *listener) { + if err := c.SetReadDeadline(time.Now().Add(15 * time.Second)); err != nil { + c.Close() + return + } + + var b [1]byte + if _, err := io.ReadFull(c, b[:]); err != nil { + c.Close() + return + } + + if err := c.SetReadDeadline(time.Time{}); err != nil { + c.Close() + return + } + + conn := &connWithOneByte{ + Conn: c, + b: b[0], + } + + // First byte of a SOCKS5 session is a version byte set to 5. + var ln *listener + if b[0] == 5 { + ln = socksListener + } else { + ln = httpListener + } + select { + case ln.c <- conn: + case <-ln.closed: + c.Close() + } +} + +type listener struct { + addr net.Addr + c chan net.Conn + mu sync.Mutex // serializes close() on closed. It's okay to receive on closed without locking. + closed chan struct{} +} + +func (ln *listener) Accept() (net.Conn, error) { + // Once closed, reliably stay closed, don't race with attempts at + // further connections. + select { + case <-ln.closed: + return nil, net.ErrClosed + default: + } + select { + case ret := <-ln.c: + return ret, nil + case <-ln.closed: + return nil, net.ErrClosed + } +} + +func (ln *listener) Close() error { + ln.mu.Lock() + defer ln.mu.Unlock() + select { + case <-ln.closed: + // Already closed + default: + close(ln.closed) + } + return nil +} + +func (ln *listener) Addr() net.Addr { + return ln.addr +} + +// connWithOneByte is a net.Conn that returns b for the first read +// request, then forwards everything else to Conn. +type connWithOneByte struct { + net.Conn + + b byte + bRead bool +} + +func (c *connWithOneByte) Read(bs []byte) (int, error) { + if c.bRead { + return c.Conn.Read(bs) + } + if len(bs) == 0 { + return 0, nil + } + c.bRead = true + bs[0] = c.b + return 1, nil +} diff --git a/internal/third_party/tailscale/socks5/socks5.go b/internal/third_party/tailscale/socks5/socks5.go new file mode 100644 index 0000000..a7d5873 --- /dev/null +++ b/internal/third_party/tailscale/socks5/socks5.go @@ -0,0 +1,369 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package socks5 is a SOCKS5 server implementation. +// +// This is used for userspace networking in Tailscale. Specifically, +// this is used for dialing out of the machine to other nodes, without +// the host kernel's involvement, so it doesn't proper routing tables, +// TUN, IPv6, etc. This package is meant to only handle the SOCKS5 protocol +// details and not any integration with Tailscale internals itself. +// +// The glue between this package and Tailscale is in net/socks5/tssocks. +package socks5 + +import ( + "context" + "encoding/binary" + "fmt" + "io" + "log" + "net" + "strconv" + "time" +) + +const ( + noAuthRequired byte = 0 + noAcceptableAuth byte = 255 + + // socks5Version is the byte that represents the SOCKS version + // in requests. + socks5Version byte = 5 +) + +// commandType are the bytes sent in SOCKS5 packets +// that represent the kind of connection the client needs. +type commandType byte + +// The set of valid SOCKS5 commands as described in RFC 1928. +const ( + connect commandType = 1 + bind commandType = 2 + udpAssociate commandType = 3 +) + +// addrType are the bytes sent in SOCKS5 packets +// that represent particular address types. +type addrType byte + +// The set of valid SOCKS5 address types as defined in RFC 1928. +const ( + ipv4 addrType = 1 + domainName addrType = 3 + ipv6 addrType = 4 +) + +// replyCode are the bytes sent in SOCKS5 packets +// that represent replies from the server to a client +// request. +type replyCode byte + +// The set of valid SOCKS5 reply types as per the RFC 1928. +const ( + success replyCode = 0 + generalFailure replyCode = 1 + connectionNotAllowed replyCode = 2 + networkUnreachable replyCode = 3 + hostUnreachable replyCode = 4 + connectionRefused replyCode = 5 + ttlExpired replyCode = 6 + commandNotSupported replyCode = 7 + addrTypeNotSupported replyCode = 8 +) + +// Server is a SOCKS5 proxy server. +type Server struct { + // Logf optionally specifies the logger to use. + // If nil, the standard logger is used. + Logf func(format string, args ...interface{}) + + // Dialer optionally specifies the dialer to use for outgoing connections. + // If nil, the net package's standard dialer is used. + Dialer func(ctx context.Context, network, addr string) (net.Conn, error) +} + +func (s *Server) dial(ctx context.Context, network, addr string) (net.Conn, error) { + dial := s.Dialer + if dial == nil { + dialer := &net.Dialer{} + dial = dialer.DialContext + } + return dial(ctx, network, addr) +} + +func (s *Server) logf(format string, args ...interface{}) { + logf := s.Logf + if logf == nil { + logf = log.Printf + } + logf(format, args...) +} + +// Serve accepts and handles incoming connections on the given listener. +func (s *Server) Serve(l net.Listener) error { + defer l.Close() + for { + c, err := l.Accept() + if err != nil { + return err + } + go func() { + conn := &Conn{clientConn: c, srv: s} + err := conn.Run() + if err != nil { + s.logf("client connection failed: %v", err) + conn.clientConn.Close() + } + }() + } +} + +// Conn is a SOCKS5 connection for client to reach +// server. +type Conn struct { + // The struct is filled by each of the internal + // methods in turn as the transaction progresses. + + srv *Server + clientConn net.Conn + request *request +} + +// Run starts the new connection. +func (c *Conn) Run() error { + err := parseClientGreeting(c.clientConn) + if err != nil { + c.clientConn.Write([]byte{socks5Version, noAcceptableAuth}) + return err + } + c.clientConn.Write([]byte{socks5Version, noAuthRequired}) + return c.handleRequest() +} + +func (c *Conn) handleRequest() error { + req, err := parseClientRequest(c.clientConn) + if err != nil { + res := &response{reply: generalFailure} + buf, _ := res.marshal() + c.clientConn.Write(buf) + return err + } + if req.command != connect { + res := &response{reply: commandNotSupported} + buf, _ := res.marshal() + c.clientConn.Write(buf) + return fmt.Errorf("unsupported command %v", req.command) + } + c.request = req + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + srv, err := c.srv.dial( + ctx, + "tcp", + net.JoinHostPort(c.request.destination, strconv.Itoa(int(c.request.port))), + ) + if err != nil { + res := &response{reply: generalFailure} + buf, _ := res.marshal() + c.clientConn.Write(buf) + return err + } + defer srv.Close() + serverAddr, serverPortStr, err := net.SplitHostPort(srv.LocalAddr().String()) + if err != nil { + return err + } + serverPort, _ := strconv.Atoi(serverPortStr) + + var bindAddrType addrType + if ip := net.ParseIP(serverAddr); ip != nil { + if ip.To4() != nil { + bindAddrType = ipv4 + } else { + bindAddrType = ipv6 + } + } else { + bindAddrType = domainName + } + res := &response{ + reply: success, + bindAddrType: bindAddrType, + bindAddr: serverAddr, + bindPort: uint16(serverPort), + } + buf, err := res.marshal() + if err != nil { + res = &response{reply: generalFailure} + buf, _ = res.marshal() + } + c.clientConn.Write(buf) + + errc := make(chan error, 2) + go func() { + _, err := io.Copy(c.clientConn, srv) + if err != nil { + err = fmt.Errorf("from backend to client: %w", err) + } + errc <- err + }() + go func() { + _, err := io.Copy(srv, c.clientConn) + if err != nil { + err = fmt.Errorf("from client to backend: %w", err) + } + errc <- err + }() + return <-errc +} + +// parseClientGreeting parses a request initiation packet +// and returns a slice that contains the acceptable auth methods +// for the client. +func parseClientGreeting(r io.Reader) error { + var hdr [2]byte + _, err := io.ReadFull(r, hdr[:]) + if err != nil { + return fmt.Errorf("could not read packet header") + } + if hdr[0] != socks5Version { + return fmt.Errorf("incompatible SOCKS version") + } + count := int(hdr[1]) + methods := make([]byte, count) + _, err = io.ReadFull(r, methods) + if err != nil { + return fmt.Errorf("could not read methods") + } + for _, m := range methods { + if m == noAuthRequired { + return nil + } + } + return fmt.Errorf("no acceptable auth methods") +} + +// request represents data contained within a SOCKS5 +// connection request packet. +type request struct { + command commandType + destination string + port uint16 + destAddrType addrType +} + +// parseClientRequest converts raw packet bytes into a +// SOCKS5Request struct. +func parseClientRequest(r io.Reader) (*request, error) { + var hdr [4]byte + _, err := io.ReadFull(r, hdr[:]) + if err != nil { + return nil, fmt.Errorf("could not read packet header") + } + cmd := hdr[1] + destAddrType := addrType(hdr[3]) + + var destination string + var port uint16 + + if destAddrType == ipv4 { + var ip [4]byte + _, err = io.ReadFull(r, ip[:]) + if err != nil { + return nil, fmt.Errorf("could not read IPv4 address") + } + destination = net.IP(ip[:]).String() + } else if destAddrType == domainName { + var dstSizeByte [1]byte + _, err = io.ReadFull(r, dstSizeByte[:]) + if err != nil { + return nil, fmt.Errorf("could not read domain name size") + } + dstSize := int(dstSizeByte[0]) + domainName := make([]byte, dstSize) + _, err = io.ReadFull(r, domainName) + if err != nil { + return nil, fmt.Errorf("could not read domain name") + } + destination = string(domainName) + } else if destAddrType == ipv6 { + var ip [16]byte + _, err = io.ReadFull(r, ip[:]) + if err != nil { + return nil, fmt.Errorf("could not read IPv6 address") + } + destination = net.IP(ip[:]).String() + } else { + return nil, fmt.Errorf("unsupported address type") + } + var portBytes [2]byte + _, err = io.ReadFull(r, portBytes[:]) + if err != nil { + return nil, fmt.Errorf("could not read port") + } + port = binary.BigEndian.Uint16(portBytes[:]) + + return &request{ + command: commandType(cmd), + destination: destination, + port: port, + destAddrType: destAddrType, + }, nil +} + +// response contains the contents of +// a response packet sent from the proxy +// to the client. +type response struct { + reply replyCode + bindAddrType addrType + bindAddr string + bindPort uint16 +} + +// marshal converts a SOCKS5Response struct into +// a packet. If res.reply == Success, it may throw an error on +// receiving an invalid bind address. Otherwise, it will not throw. +func (res *response) marshal() ([]byte, error) { + pkt := make([]byte, 4) + pkt[0] = socks5Version + pkt[1] = byte(res.reply) + pkt[2] = 0 // null reserved byte + pkt[3] = byte(res.bindAddrType) + + if res.reply != success { + return pkt, nil + } + + var addr []byte + switch res.bindAddrType { + case ipv4: + addr = net.ParseIP(res.bindAddr).To4() + if addr == nil { + return nil, fmt.Errorf("invalid IPv4 address for binding") + } + case domainName: + if len(res.bindAddr) > 255 { + return nil, fmt.Errorf("invalid domain name for binding") + } + addr = make([]byte, 0, len(res.bindAddr)+1) + addr = append(addr, byte(len(res.bindAddr))) + addr = append(addr, []byte(res.bindAddr)...) + case ipv6: + addr = net.ParseIP(res.bindAddr).To16() + if addr == nil { + return nil, fmt.Errorf("invalid IPv6 address for binding") + } + default: + return nil, fmt.Errorf("unsupported address type") + } + + pkt = append(pkt, addr...) + port := make([]byte, 2) + binary.BigEndian.PutUint16(port, uint16(res.bindPort)) + pkt = append(pkt, port...) + + return pkt, nil +} diff --git a/main.go b/main.go index 674dd15..eeb1a3b 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,10 @@ import ( "golang.zx2c4.com/wireguard/conn" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/tun/netstack" + + "github.com/zhsj/wghttp/internal/third_party/tailscale/httpproxy" + "github.com/zhsj/wghttp/internal/third_party/tailscale/proxymux" + "github.com/zhsj/wghttp/internal/third_party/tailscale/socks5" ) const ( @@ -47,10 +51,24 @@ func main() { } log.Printf("Listening on %s", ln.Addr()) - s := &http.Server{Handler: httpProxyHandler(tnet.DialContext)} - if err := s.Serve(ln); err != nil { - log.Fatal(err) - } + socksListener, httpListener := proxymux.SplitSOCKSAndHTTP(ln) + + httpProxy := &http.Server{Handler: httpproxy.Handler(tnet.DialContext)} + socksProxy := &socks5.Server{Dialer: tnet.DialContext} + + errc := make(chan error, 2) + go func() { + if err := httpProxy.Serve(httpListener); err != nil { + errc <- err + } + }() + go func() { + if err := socksProxy.Serve(socksListener); err != nil { + errc <- err + } + }() + + log.Fatal(<-errc) } func setupNet(opts options) *netstack.Net { diff --git a/proxy.go b/proxy.go deleted file mode 100644 index ad8132d..0000000 --- a/proxy.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// HTTP proxy code - -package main - -import ( - "context" - "io" - "net" - "net/http" - "net/http/httputil" - "strings" -) - -// httpProxyHandler returns an HTTP proxy http.Handler using the -// provided backend dialer. -func httpProxyHandler(dialer func(ctx context.Context, netw, addr string) (net.Conn, error)) http.Handler { - rp := &httputil.ReverseProxy{ - Director: func(r *http.Request) {}, // no change - Transport: &http.Transport{ - DialContext: dialer, - }, - } - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != "CONNECT" { - backURL := r.RequestURI - if strings.HasPrefix(backURL, "/") || backURL == "*" { - http.Error(w, "bogus RequestURI; must be absolute URL or CONNECT", 400) - return - } - rp.ServeHTTP(w, r) - return - } - - // CONNECT support: - - dst := r.RequestURI - c, err := dialer(r.Context(), "tcp", dst) - if err != nil { - w.Header().Set("Connect-Error", err.Error()) - http.Error(w, err.Error(), 500) - return - } - defer c.Close() - - cc, ccbuf, err := w.(http.Hijacker).Hijack() - if err != nil { - http.Error(w, err.Error(), 500) - return - } - defer cc.Close() - - io.WriteString(cc, "HTTP/1.1 200 OK\r\n\r\n") - - var clientSrc io.Reader = ccbuf - if ccbuf.Reader.Buffered() == 0 { - // In the common case (with no - // buffered data), read directly from - // the underlying client connection to - // save some memory, letting the - // bufio.Reader/Writer get GC'ed. - clientSrc = cc - } - - errc := make(chan error, 1) - go func() { - _, err := io.Copy(cc, c) - errc <- err - }() - go func() { - _, err := io.Copy(c, clientSrc) - errc <- err - }() - <-errc - }) -}