"time"
"golang_org/x/net/lex/httplex"
+ "golang_org/x/net/proxy"
)
// DefaultTransport is the default implementation of Transport and is
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)
}
}
+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,
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 != "" {
//
// 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 {
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 = ""
}
}
}
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
"context"
"crypto/rand"
"crypto/tls"
+ "encoding/binary"
"errors"
"fmt"
"internal/nettrace"
}
}
+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)
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)
}
}
{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"},