commit dbafd218e275718951915bd0262c46a3c615448c
Author: David Fifield <[email protected]>
Date:   Thu Jan 31 21:16:42 2019 -0700

    http proxy support for uTLS.
---
 meek-client/proxy_http.go |  76 +++++++++++++++++++++++++++++++++
 meek-client/proxy_test.go | 104 ++++++++++++++++++++++++++++++++++++++++++++++
 meek-client/utls.go       |   2 +
 3 files changed, 182 insertions(+)

diff --git a/meek-client/proxy_http.go b/meek-client/proxy_http.go
new file mode 100644
index 0000000..1136eb2
--- /dev/null
+++ b/meek-client/proxy_http.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+       "bufio"
+       "encoding/base64"
+       "fmt"
+       "net"
+       "net/http"
+       "net/url"
+
+       "golang.org/x/net/proxy"
+)
+
+// https://tools.ietf.org/html/rfc7231#section-4.3.6
+// Conceivably we could also proxy over HTTP/2:
+// https://httpwg.org/specs/rfc7540.html#CONNECT
+// 
https://github.com/caddyserver/forwardproxy/blob/05b2092e07f9d10b3803d8fb9775d2f87dc58590/httpclient/httpclient.go
+
+type httpProxy struct {
+       network, addr string
+       auth          *proxy.Auth
+       forward       proxy.Dialer
+}
+
+func (pr *httpProxy) Dial(network, addr string) (net.Conn, error) {
+       connectReq := &http.Request{
+               Method: "CONNECT",
+               URL:    &url.URL{Opaque: addr},
+               Host:   addr,
+               Header: make(http.Header),
+       }
+       // http.Transport has a ProxyConnectHeader field that we are ignoring
+       // here.
+       if pr.auth != nil {
+               connectReq.Header.Set("Proxy-Authorization", "basic "+
+                       
base64.StdEncoding.EncodeToString([]byte(pr.auth.User+":"+pr.auth.Password)))
+       }
+
+       conn, err := pr.forward.Dial(pr.network, pr.addr)
+       if err != nil {
+               return nil, err
+       }
+
+       err = connectReq.Write(conn)
+       if err != nil {
+               conn.Close()
+               return nil, err
+       }
+
+       // The Go stdlib says: "Okay to use and discard buffered reader here,
+       // because TLS server will not speak until spoken to."
+       br := bufio.NewReader(conn)
+       resp, err := http.ReadResponse(br, connectReq)
+       if br.Buffered() != 0 {
+               panic(br.Buffered())
+       }
+       if err != nil {
+               conn.Close()
+               return nil, err
+       }
+       if resp.StatusCode != 200 {
+               conn.Close()
+               return nil, fmt.Errorf("proxy server returned %q", resp.Status)
+       }
+
+       return conn, nil
+}
+
+func ProxyHTTP(network, addr string, auth *proxy.Auth, forward proxy.Dialer) 
(*httpProxy, error) {
+       return &httpProxy{
+               network: network,
+               addr:    addr,
+               auth:    auth,
+               forward: forward,
+       }, nil
+}
diff --git a/meek-client/proxy_test.go b/meek-client/proxy_test.go
index 5e87e56..783165a 100644
--- a/meek-client/proxy_test.go
+++ b/meek-client/proxy_test.go
@@ -1,10 +1,21 @@
 package main
 
 import (
+       "bufio"
+       "net"
+       "net/http"
        "net/url"
        "testing"
+
+       "golang.org/x/net/proxy"
 )
 
+const testHost = "test.example"
+const testPort = "1234"
+const testAddr = testHost + ":" + testPort
+const testUsername = "username"
+const testPassword = "password"
+
 // Test that addrForDial returns a numeric port number. It needs to be numeric
 // because we pass it as part of the authority-form URL in HTTP proxy requests.
 // https://tools.ietf.org/html/rfc7230#section-5.3.3 authority-form
@@ -52,3 +63,96 @@ func TestAddrForDial(t *testing.T) {
                }
        }
 }
+
+// Dial the given address with the given proxy, and return the http.Request 
that
+// the proxy server would have received.
+func requestResultingFromDial(makeProxy func(addr net.Addr) (*httpProxy, 
error), network, addr string) (*http.Request, error) {
+       ch := make(chan *http.Request, 1)
+
+       ln, err := net.Listen("tcp", "127.0.0.1:0")
+       if err != nil {
+               return nil, err
+       }
+       defer ln.Close()
+
+       go func() {
+               defer func() {
+                       close(ch)
+               }()
+               conn, err := ln.Accept()
+               if err != nil {
+                       panic(err)
+               }
+               defer conn.Close()
+               br := bufio.NewReader(conn)
+               req, err := http.ReadRequest(br)
+               if err != nil {
+                       panic(err)
+               }
+               ch <- req
+       }()
+
+       pr, err := makeProxy(ln.Addr())
+       if err != nil {
+               return nil, err
+       }
+       // The Dial fails because the goroutine "server" hangs up.
+       _, _ = pr.Dial(network, addr)
+
+       return <-ch, nil
+}
+
+// Test that the HTTP proxy client sends a correct request.
+func TestProxyHTTPCONNECT(t *testing.T) {
+       req, err := requestResultingFromDial(func(addr net.Addr) (*httpProxy, 
error) {
+               return ProxyHTTP("tcp", addr.String(), nil, proxy.Direct)
+       }, "tcp", testAddr)
+       if err != nil {
+               panic(err)
+       }
+       if req.Method != "CONNECT" {
+               t.Errorf("expected method %q, got %q", "CONNECT", req.Method)
+       }
+       if req.URL.Hostname() != testHost || req.URL.Port() != testPort {
+               t.Errorf("expected URL %q, got %q", testAddr, req.URL.String())
+       }
+       if req.Host != testAddr {
+               t.Errorf("expected %q, got %q", "Host: "+req.Host, "Host: 
"+testAddr)
+       }
+}
+
+// Test that the HTTP proxy client sends authorization credentials.
+func TestProxyHTTPProxyAuthorization(t *testing.T) {
+       auth := &proxy.Auth{
+               User:     testUsername,
+               Password: testPassword,
+       }
+       req, err := requestResultingFromDial(func(addr net.Addr) (*httpProxy, 
error) {
+               return ProxyHTTP("tcp", addr.String(), auth, proxy.Direct)
+       }, "tcp", testAddr)
+       if err != nil {
+               panic(err)
+       }
+       pa := req.Header.Get("Proxy-Authorization")
+       if pa == "" {
+               t.Fatalf("didn't get a Proxy-Authorization header")
+       }
+       // The standard library Request.BasicAuth does parsing of basic
+       // authentication, but only in the Authorization header, not
+       // Proxy-Authorization.
+       newReq := &http.Request{
+               Header: http.Header{
+                       "Authorization": []string{pa},
+               },
+       }
+       username, password, ok := newReq.BasicAuth()
+       if !ok {
+               panic("shouldn't fail")
+       }
+       if username != testUsername {
+               t.Errorf("expected username %q, got %q", testUsername, username)
+       }
+       if password != testPassword {
+               t.Errorf("expected password %q, got %q", testPassword, password)
+       }
+}
diff --git a/meek-client/utls.go b/meek-client/utls.go
index 1f9d32d..cbb2aaa 100644
--- a/meek-client/utls.go
+++ b/meek-client/utls.go
@@ -170,6 +170,8 @@ func makeProxyDialer(proxyURL *url.URL) (proxy.Dialer, 
error) {
        switch proxyURL.Scheme {
        case "socks5":
                proxyDialer, err = proxy.SOCKS5("tcp", proxyAddr, auth, 
proxyDialer)
+       case "http":
+               proxyDialer, err = ProxyHTTP("tcp", proxyAddr, auth, 
proxyDialer)
        default:
                return nil, fmt.Errorf("cannot use proxy scheme %q with uTLS", 
proxyURL.Scheme)
        }



_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to