diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index c26ad06aeb..f8ba53288e 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -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", diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 571943d6e5..2aa00de50a 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -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 diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index 085bb3cd4b..ce98157ed5 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -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"},