Skip to content

Commit 36f55a8

Browse files
committed
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 <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 4be4da6 commit 36f55a8

File tree

3 files changed

+158
-14
lines changed

3 files changed

+158
-14
lines changed

src/go/build/deps_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ var pkgDeps = map[string][]string{
394394
"golang_org/x/net/http2/hpack",
395395
"golang_org/x/net/idna",
396396
"golang_org/x/net/lex/httplex",
397+
"golang_org/x/net/proxy",
397398
"golang_org/x/text/unicode/norm",
398399
"golang_org/x/text/width",
399400
"internal/nettrace",

src/net/http/transport.go

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"time"
3030

3131
"golang_org/x/net/lex/httplex"
32+
"golang_org/x/net/proxy"
3233
)
3334

3435
// DefaultTransport is the default implementation of Transport and is
@@ -275,13 +276,17 @@ func ProxyFromEnvironment(req *Request) (*url.URL, error) {
275276
return nil, nil
276277
}
277278
proxyURL, err := url.Parse(proxy)
278-
if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
279+
if err != nil ||
280+
(proxyURL.Scheme != "http" &&
281+
proxyURL.Scheme != "https" &&
282+
proxyURL.Scheme != "socks5") {
279283
// proxy was bogus. Try prepending "http://" to it and
280284
// see if that parses correctly. If not, we fall
281285
// through and complain about the original one.
282286
if proxyURL, err := url.Parse("http://" + proxy); err == nil {
283287
return proxyURL, nil
284288
}
289+
285290
}
286291
if err != nil {
287292
return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
@@ -964,6 +969,23 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (*persistC
964969
}
965970
}
966971

972+
type oneConnDialer <-chan net.Conn
973+
974+
func newOneConnDialer(c net.Conn) proxy.Dialer {
975+
ch := make(chan net.Conn, 1)
976+
ch <- c
977+
return oneConnDialer(ch)
978+
}
979+
980+
func (d oneConnDialer) Dial(network, addr string) (net.Conn, error) {
981+
select {
982+
case c := <-d:
983+
return c, nil
984+
default:
985+
return nil, io.EOF
986+
}
987+
}
988+
967989
func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistConn, error) {
968990
pconn := &persistConn{
969991
t: t,
@@ -1020,6 +1042,23 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistCon
10201042
switch {
10211043
case cm.proxyURL == nil:
10221044
// Do nothing. Not using a proxy.
1045+
case cm.proxyURL.Scheme == "socks5":
1046+
conn := pconn.conn
1047+
var auth *proxy.Auth
1048+
if u := cm.proxyURL.User; u != nil {
1049+
auth = &proxy.Auth{}
1050+
auth.User = u.Username()
1051+
auth.Password, _ = u.Password()
1052+
}
1053+
p, err := proxy.SOCKS5("", cm.addr(), auth, newOneConnDialer(conn))
1054+
if err != nil {
1055+
conn.Close()
1056+
return nil, err
1057+
}
1058+
if _, err := p.Dial("tcp", cm.targetAddr); err != nil {
1059+
conn.Close()
1060+
return nil, err
1061+
}
10231062
case cm.targetScheme == "http":
10241063
pconn.isProxy = true
10251064
if pa := cm.proxyAuth(); pa != "" {
@@ -1193,27 +1232,29 @@ func useProxy(addr string) bool {
11931232
//
11941233
// A connect method may be of the following types:
11951234
//
1196-
// Cache key form Description
1197-
// ----------------- -------------------------
1198-
// |http|foo.com http directly to server, no proxy
1199-
// |https|foo.com https directly to server, no proxy
1200-
// http://proxy.com|https|foo.com http to proxy, then CONNECT to foo.com
1201-
// http://proxy.com|http http to proxy, http to anywhere after that
1235+
// Cache key form Description
1236+
// ----------------- -------------------------
1237+
// |http|foo.com http directly to server, no proxy
1238+
// |https|foo.com https directly to server, no proxy
1239+
// http://proxy.com|https|foo.com http to proxy, then CONNECT to foo.com
1240+
// http://proxy.com|http http to proxy, http to anywhere after that
1241+
// socks5://proxy.com|http|foo.com socks5 to proxy, then http to foo.com
1242+
// socks5://proxy.com|https|foo.com socks5 to proxy, then https to foo.com
12021243
//
12031244
// Note: no support to https to the proxy yet.
12041245
//
12051246
type connectMethod struct {
12061247
proxyURL *url.URL // nil for no proxy, else full proxy URL
12071248
targetScheme string // "http" or "https"
1208-
targetAddr string // Not used if proxy + http targetScheme (4th example in table)
1249+
targetAddr string // Not used if http proxy + http targetScheme (4th example in table)
12091250
}
12101251

12111252
func (cm *connectMethod) key() connectMethodKey {
12121253
proxyStr := ""
12131254
targetAddr := cm.targetAddr
12141255
if cm.proxyURL != nil {
12151256
proxyStr = cm.proxyURL.String()
1216-
if cm.targetScheme == "http" {
1257+
if strings.HasPrefix(cm.proxyURL.Scheme, "http") && cm.targetScheme == "http" {
12171258
targetAddr = ""
12181259
}
12191260
}
@@ -1982,8 +2023,9 @@ func (pc *persistConn) closeLocked(err error) {
19822023
}
19832024

19842025
var portMap = map[string]string{
1985-
"http": "80",
1986-
"https": "443",
2026+
"http": "80",
2027+
"https": "443",
2028+
"socks5": "1080",
19872029
}
19882030

19892031
// canonicalAddr returns url.Host but always with a ":port" suffix

src/net/http/transport_test.go

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"context"
1717
"crypto/rand"
1818
"crypto/tls"
19+
"encoding/binary"
1920
"errors"
2021
"fmt"
2122
"internal/nettrace"
@@ -943,6 +944,98 @@ func TestTransportExpect100Continue(t *testing.T) {
943944
}
944945
}
945946

947+
func TestSocks5Proxy(t *testing.T) {
948+
defer afterTest(t)
949+
ch := make(chan string, 1)
950+
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
951+
ch <- "real server"
952+
}))
953+
defer ts.Close()
954+
l := newLocalListener(t)
955+
defer l.Close()
956+
go func() {
957+
defer close(ch)
958+
s, err := l.Accept()
959+
if err != nil {
960+
t.Errorf("socks5 proxy Accept(): %v", err)
961+
return
962+
}
963+
defer s.Close()
964+
var buf [22]byte
965+
if _, err := io.ReadFull(s, buf[:3]); err != nil {
966+
t.Errorf("socks5 proxy initial read: %v", err)
967+
return
968+
}
969+
if want := []byte{5, 1, 0}; !bytes.Equal(buf[:3], want) {
970+
t.Errorf("socks5 proxy initial read: got %v, want %v", buf[:3], want)
971+
return
972+
}
973+
if _, err := s.Write([]byte{5, 0}); err != nil {
974+
t.Errorf("socks5 proxy initial write: %v", err)
975+
return
976+
}
977+
if _, err := io.ReadFull(s, buf[:4]); err != nil {
978+
t.Errorf("socks5 proxy second read: %v", err)
979+
return
980+
}
981+
if want := []byte{5, 1, 0}; !bytes.Equal(buf[:3], want) {
982+
t.Errorf("socks5 proxy second read: got %v, want %v", buf[:3], want)
983+
return
984+
}
985+
var ipLen int
986+
switch buf[3] {
987+
case 1:
988+
ipLen = 4
989+
case 4:
990+
ipLen = 16
991+
default:
992+
t.Fatalf("socks5 proxy second read: unexpected address type %v", buf[4])
993+
}
994+
if _, err := io.ReadFull(s, buf[4:ipLen+6]); err != nil {
995+
t.Errorf("socks5 proxy address read: %v", err)
996+
return
997+
}
998+
ip := net.IP(buf[4 : ipLen+4])
999+
port := binary.BigEndian.Uint16(buf[ipLen+4 : ipLen+6])
1000+
copy(buf[:3], []byte{5, 0, 0})
1001+
if _, err := s.Write(buf[:ipLen+6]); err != nil {
1002+
t.Errorf("socks5 proxy connect write: %v", err)
1003+
return
1004+
}
1005+
done := make(chan struct{})
1006+
srv := &Server{Handler: HandlerFunc(func(w ResponseWriter, r *Request) {
1007+
done <- struct{}{}
1008+
})}
1009+
srv.Serve(&oneConnListener{conn: s})
1010+
<-done
1011+
srv.Shutdown(context.Background())
1012+
ch <- fmt.Sprintf("proxy for %s:%d", ip, port)
1013+
}()
1014+
1015+
pu, err := url.Parse("socks5://" + l.Addr().String())
1016+
if err != nil {
1017+
t.Fatal(err)
1018+
}
1019+
c := &Client{Transport: &Transport{Proxy: ProxyURL(pu)}}
1020+
if _, err := c.Head(ts.URL); err != nil {
1021+
t.Error(err)
1022+
}
1023+
var got string
1024+
select {
1025+
case got = <-ch:
1026+
case <-time.After(5 * time.Second):
1027+
t.Fatal("timeout connecting to socks5 proxy")
1028+
}
1029+
tsu, err := url.Parse(ts.URL)
1030+
if err != nil {
1031+
t.Fatal(err)
1032+
}
1033+
want := "proxy for " + tsu.Host
1034+
if got != want {
1035+
t.Errorf("got %q, want %q", got, want)
1036+
}
1037+
}
1038+
9461039
func TestTransportProxy(t *testing.T) {
9471040
defer afterTest(t)
9481041
ch := make(chan string, 1)
@@ -960,11 +1053,18 @@ func TestTransportProxy(t *testing.T) {
9601053
t.Fatal(err)
9611054
}
9621055
c := &Client{Transport: &Transport{Proxy: ProxyURL(pu)}}
963-
c.Head(ts.URL)
964-
got := <-ch
1056+
if _, err := c.Head(ts.URL); err != nil {
1057+
t.Error(err)
1058+
}
1059+
var got string
1060+
select {
1061+
case got = <-ch:
1062+
case <-time.After(5 * time.Second):
1063+
t.Fatal("timeout connecting to http proxy")
1064+
}
9651065
want := "proxy for " + ts.URL + "/"
9661066
if got != want {
967-
t.Errorf("want %q, got %q", want, got)
1067+
t.Errorf("got %q, want %q", got, want)
9681068
}
9691069
}
9701070

@@ -2160,6 +2260,7 @@ var proxyFromEnvTests = []proxyFromEnvTest{
21602260
{env: "https://cache.corp.example.com", want: "https://cache.corp.example.com"},
21612261
{env: "http://127.0.0.1:8080", want: "http://127.0.0.1:8080"},
21622262
{env: "https://127.0.0.1:8080", want: "https://127.0.0.1:8080"},
2263+
{env: "socks5://127.0.0.1", want: "socks5://127.0.0.1"},
21632264

21642265
// Don't use secure for http
21652266
{req: "http://insecure.tld/", env: "http.proxy.tld", httpsenv: "secure.proxy.tld", want: "http://http.proxy.tld"},

0 commit comments

Comments
 (0)