mirror of
https://github.com/golang/go
synced 2024-11-18 12:04:57 -07:00
net/http: add support for socks5 proxy
See #18508 This commit adds http Client support for socks5 proxies. Change-Id: Ib015f3819801da13781d5acdd780149ae1f5857b Reviewed-on: https://go-review.googlesource.com/35488 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
4be4da6331
commit
36f55a8b61
@ -394,6 +394,7 @@ var pkgDeps = map[string][]string{
|
||||
"golang_org/x/net/http2/hpack",
|
||||
"golang_org/x/net/idna",
|
||||
"golang_org/x/net/lex/httplex",
|
||||
"golang_org/x/net/proxy",
|
||||
"golang_org/x/text/unicode/norm",
|
||||
"golang_org/x/text/width",
|
||||
"internal/nettrace",
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
"time"
|
||||
|
||||
"golang_org/x/net/lex/httplex"
|
||||
"golang_org/x/net/proxy"
|
||||
)
|
||||
|
||||
// DefaultTransport is the default implementation of Transport and is
|
||||
@ -275,13 +276,17 @@ func ProxyFromEnvironment(req *Request) (*url.URL, error) {
|
||||
return nil, nil
|
||||
}
|
||||
proxyURL, err := url.Parse(proxy)
|
||||
if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
|
||||
if err != nil ||
|
||||
(proxyURL.Scheme != "http" &&
|
||||
proxyURL.Scheme != "https" &&
|
||||
proxyURL.Scheme != "socks5") {
|
||||
// proxy was bogus. Try prepending "http://" to it and
|
||||
// see if that parses correctly. If not, we fall
|
||||
// through and complain about the original one.
|
||||
if proxyURL, err := url.Parse("http://" + proxy); err == nil {
|
||||
return proxyURL, nil
|
||||
}
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
|
||||
@ -964,6 +969,23 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (*persistC
|
||||
}
|
||||
}
|
||||
|
||||
type oneConnDialer <-chan net.Conn
|
||||
|
||||
func newOneConnDialer(c net.Conn) proxy.Dialer {
|
||||
ch := make(chan net.Conn, 1)
|
||||
ch <- c
|
||||
return oneConnDialer(ch)
|
||||
}
|
||||
|
||||
func (d oneConnDialer) Dial(network, addr string) (net.Conn, error) {
|
||||
select {
|
||||
case c := <-d:
|
||||
return c, nil
|
||||
default:
|
||||
return nil, io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistConn, error) {
|
||||
pconn := &persistConn{
|
||||
t: t,
|
||||
@ -1020,6 +1042,23 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistCon
|
||||
switch {
|
||||
case cm.proxyURL == nil:
|
||||
// Do nothing. Not using a proxy.
|
||||
case cm.proxyURL.Scheme == "socks5":
|
||||
conn := pconn.conn
|
||||
var auth *proxy.Auth
|
||||
if u := cm.proxyURL.User; u != nil {
|
||||
auth = &proxy.Auth{}
|
||||
auth.User = u.Username()
|
||||
auth.Password, _ = u.Password()
|
||||
}
|
||||
p, err := proxy.SOCKS5("", cm.addr(), auth, newOneConnDialer(conn))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
if _, err := p.Dial("tcp", cm.targetAddr); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
case cm.targetScheme == "http":
|
||||
pconn.isProxy = true
|
||||
if pa := cm.proxyAuth(); pa != "" {
|
||||
@ -1193,19 +1232,21 @@ func useProxy(addr string) bool {
|
||||
//
|
||||
// A connect method may be of the following types:
|
||||
//
|
||||
// Cache key form Description
|
||||
// ----------------- -------------------------
|
||||
// |http|foo.com http directly to server, no proxy
|
||||
// |https|foo.com https directly to server, no proxy
|
||||
// http://proxy.com|https|foo.com http to proxy, then CONNECT to foo.com
|
||||
// http://proxy.com|http http to proxy, http to anywhere after that
|
||||
// Cache key form Description
|
||||
// ----------------- -------------------------
|
||||
// |http|foo.com http directly to server, no proxy
|
||||
// |https|foo.com https directly to server, no proxy
|
||||
// http://proxy.com|https|foo.com http to proxy, then CONNECT to foo.com
|
||||
// http://proxy.com|http http to proxy, http to anywhere after that
|
||||
// socks5://proxy.com|http|foo.com socks5 to proxy, then http to foo.com
|
||||
// socks5://proxy.com|https|foo.com socks5 to proxy, then https to foo.com
|
||||
//
|
||||
// Note: no support to https to the proxy yet.
|
||||
//
|
||||
type connectMethod struct {
|
||||
proxyURL *url.URL // nil for no proxy, else full proxy URL
|
||||
targetScheme string // "http" or "https"
|
||||
targetAddr string // Not used if proxy + http targetScheme (4th example in table)
|
||||
targetAddr string // Not used if http proxy + http targetScheme (4th example in table)
|
||||
}
|
||||
|
||||
func (cm *connectMethod) key() connectMethodKey {
|
||||
@ -1213,7 +1254,7 @@ func (cm *connectMethod) key() connectMethodKey {
|
||||
targetAddr := cm.targetAddr
|
||||
if cm.proxyURL != nil {
|
||||
proxyStr = cm.proxyURL.String()
|
||||
if cm.targetScheme == "http" {
|
||||
if strings.HasPrefix(cm.proxyURL.Scheme, "http") && cm.targetScheme == "http" {
|
||||
targetAddr = ""
|
||||
}
|
||||
}
|
||||
@ -1982,8 +2023,9 @@ func (pc *persistConn) closeLocked(err error) {
|
||||
}
|
||||
|
||||
var portMap = map[string]string{
|
||||
"http": "80",
|
||||
"https": "443",
|
||||
"http": "80",
|
||||
"https": "443",
|
||||
"socks5": "1080",
|
||||
}
|
||||
|
||||
// canonicalAddr returns url.Host but always with a ":port" suffix
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/nettrace"
|
||||
@ -943,6 +944,98 @@ func TestTransportExpect100Continue(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSocks5Proxy(t *testing.T) {
|
||||
defer afterTest(t)
|
||||
ch := make(chan string, 1)
|
||||
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
||||
ch <- "real server"
|
||||
}))
|
||||
defer ts.Close()
|
||||
l := newLocalListener(t)
|
||||
defer l.Close()
|
||||
go func() {
|
||||
defer close(ch)
|
||||
s, err := l.Accept()
|
||||
if err != nil {
|
||||
t.Errorf("socks5 proxy Accept(): %v", err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
var buf [22]byte
|
||||
if _, err := io.ReadFull(s, buf[:3]); err != nil {
|
||||
t.Errorf("socks5 proxy initial read: %v", err)
|
||||
return
|
||||
}
|
||||
if want := []byte{5, 1, 0}; !bytes.Equal(buf[:3], want) {
|
||||
t.Errorf("socks5 proxy initial read: got %v, want %v", buf[:3], want)
|
||||
return
|
||||
}
|
||||
if _, err := s.Write([]byte{5, 0}); err != nil {
|
||||
t.Errorf("socks5 proxy initial write: %v", err)
|
||||
return
|
||||
}
|
||||
if _, err := io.ReadFull(s, buf[:4]); err != nil {
|
||||
t.Errorf("socks5 proxy second read: %v", err)
|
||||
return
|
||||
}
|
||||
if want := []byte{5, 1, 0}; !bytes.Equal(buf[:3], want) {
|
||||
t.Errorf("socks5 proxy second read: got %v, want %v", buf[:3], want)
|
||||
return
|
||||
}
|
||||
var ipLen int
|
||||
switch buf[3] {
|
||||
case 1:
|
||||
ipLen = 4
|
||||
case 4:
|
||||
ipLen = 16
|
||||
default:
|
||||
t.Fatalf("socks5 proxy second read: unexpected address type %v", buf[4])
|
||||
}
|
||||
if _, err := io.ReadFull(s, buf[4:ipLen+6]); err != nil {
|
||||
t.Errorf("socks5 proxy address read: %v", err)
|
||||
return
|
||||
}
|
||||
ip := net.IP(buf[4 : ipLen+4])
|
||||
port := binary.BigEndian.Uint16(buf[ipLen+4 : ipLen+6])
|
||||
copy(buf[:3], []byte{5, 0, 0})
|
||||
if _, err := s.Write(buf[:ipLen+6]); err != nil {
|
||||
t.Errorf("socks5 proxy connect write: %v", err)
|
||||
return
|
||||
}
|
||||
done := make(chan struct{})
|
||||
srv := &Server{Handler: HandlerFunc(func(w ResponseWriter, r *Request) {
|
||||
done <- struct{}{}
|
||||
})}
|
||||
srv.Serve(&oneConnListener{conn: s})
|
||||
<-done
|
||||
srv.Shutdown(context.Background())
|
||||
ch <- fmt.Sprintf("proxy for %s:%d", ip, port)
|
||||
}()
|
||||
|
||||
pu, err := url.Parse("socks5://" + l.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := &Client{Transport: &Transport{Proxy: ProxyURL(pu)}}
|
||||
if _, err := c.Head(ts.URL); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var got string
|
||||
select {
|
||||
case got = <-ch:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timeout connecting to socks5 proxy")
|
||||
}
|
||||
tsu, err := url.Parse(ts.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "proxy for " + tsu.Host
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransportProxy(t *testing.T) {
|
||||
defer afterTest(t)
|
||||
ch := make(chan string, 1)
|
||||
@ -960,11 +1053,18 @@ func TestTransportProxy(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := &Client{Transport: &Transport{Proxy: ProxyURL(pu)}}
|
||||
c.Head(ts.URL)
|
||||
got := <-ch
|
||||
if _, err := c.Head(ts.URL); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var got string
|
||||
select {
|
||||
case got = <-ch:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timeout connecting to http proxy")
|
||||
}
|
||||
want := "proxy for " + ts.URL + "/"
|
||||
if got != want {
|
||||
t.Errorf("want %q, got %q", want, got)
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2160,6 +2260,7 @@ var proxyFromEnvTests = []proxyFromEnvTest{
|
||||
{env: "https://cache.corp.example.com", want: "https://cache.corp.example.com"},
|
||||
{env: "http://127.0.0.1:8080", want: "http://127.0.0.1:8080"},
|
||||
{env: "https://127.0.0.1:8080", want: "https://127.0.0.1:8080"},
|
||||
{env: "socks5://127.0.0.1", want: "socks5://127.0.0.1"},
|
||||
|
||||
// Don't use secure for http
|
||||
{req: "http://insecure.tld/", env: "http.proxy.tld", httpsenv: "secure.proxy.tld", want: "http://http.proxy.tld"},
|
||||
|
Loading…
Reference in New Issue
Block a user