refactor: move third_party code to separate dir
parent
0d5db0bf08
commit
fc191cb14e
16
go.mod
16
go.mod
|
|
@ -1,9 +1,19 @@
|
||||||
module github.com/zhsj/wghttp
|
module github.com/zhsj/wghttp
|
||||||
|
|
||||||
go 1.16
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/jessevdk/go-flags v1.5.0
|
github.com/jessevdk/go-flags v1.5.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20211025155311-828a885a7140
|
golang.zx2c4.com/wireguard v0.0.0-20211116201604-de7c702ace45
|
||||||
golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20211025155311-828a885a7140
|
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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
18
go.sum
18
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-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-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-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-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU=
|
||||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-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-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-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-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
|
||||||
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
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=
|
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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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-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-20211116201604-de7c702ace45 h1:mEVhdMPTuebD9IUXOUB5Q2sjZpcmzkahHWd6DrGpLHA=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20211025155311-828a885a7140/go.mod h1:RTjaYEQboNk7+2qfPGBotaMEh/5HIvmPZ6DIe10lTqI=
|
golang.zx2c4.com/wireguard v0.0.0-20211116201604-de7c702ace45/go.mod h1:evxZIqfCetExY5piKXGAxJYwvXWkps9zTCkWpkoGFxw=
|
||||||
golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20211025155311-828a885a7140 h1:dCphgMdSHXtrMgj5BmSqDXmc68at6MLgUg4EPbVYVb4=
|
golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20211116201604-de7c702ace45 h1:td2v30AWwaU5sKKbgqC3vh6jywoKOG76jJuZV/JnGzA=
|
||||||
golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20211025155311-828a885a7140/go.mod h1:U6gm2r5g3zEe4OlrGL+W3rptmOo4Sj6mIFSIZHMFo4s=
|
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.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.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
26
main.go
26
main.go
|
|
@ -12,6 +12,10 @@ import (
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
"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 (
|
const (
|
||||||
|
|
@ -47,10 +51,24 @@ func main() {
|
||||||
}
|
}
|
||||||
log.Printf("Listening on %s", ln.Addr())
|
log.Printf("Listening on %s", ln.Addr())
|
||||||
|
|
||||||
s := &http.Server{Handler: httpProxyHandler(tnet.DialContext)}
|
socksListener, httpListener := proxymux.SplitSOCKSAndHTTP(ln)
|
||||||
if err := s.Serve(ln); err != nil {
|
|
||||||
log.Fatal(err)
|
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 {
|
func setupNet(opts options) *netstack.Net {
|
||||||
|
|
|
||||||
102
proxy.go
102
proxy.go
|
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue