[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [httpsproxy/master] Integrate utls, forwardproxy/httpclient, cosmetics (#2)
commit f39e2c48e9a13a286622b7fdd73ca098ab9705f3
Author: sergeyfrolov <sergey.frolov@xxxxxxxxxxxx>
Date: Mon Aug 6 11:08:28 2018 -0400
Integrate utls, forwardproxy/httpclient, cosmetics (#2)
Integrate utls, forwardproxy/httpclient, cosmetics
---
client/client.go | 252 ++++++++++++++-----------------------------------------
1 file changed, 62 insertions(+), 190 deletions(-)
diff --git a/client/client.go b/client/client.go
index eea871d..c9907c5 100644
--- a/client/client.go
+++ b/client/client.go
@@ -16,9 +16,6 @@
package main
import (
- "bufio"
- "crypto/tls"
- "encoding/base64"
"errors"
"flag"
"fmt"
@@ -35,10 +32,12 @@ import (
"syscall"
pt "git.torproject.org/pluggable-transports/goptlib.git"
- "golang.org/x/net/http2"
+ "github.com/caddyserver/forwardproxy/httpclient"
+ tls "github.com/refraction-networking/utls"
)
var ptInfo pt.ClientInfo
+var utlsRoller *tls.Roller
// When a connection handler starts, +1 is written to this channel; when it
// ends, -1 is written.
@@ -53,14 +52,13 @@ var handlerChan = make(chan int)
// Reasoning: http2ResponseWriter will not flush on its own, so we have to do it manually.
func flushingIoCopy(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) {
flusher, ok := dst.(http.Flusher)
- if !ok {
- return io.CopyBuffer(dst, src, buf)
- }
for {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
- flusher.Flush()
+ if ok {
+ flusher.Flush()
+ }
if nw > 0 {
written += int64(nw)
}
@@ -84,36 +82,18 @@ func flushingIoCopy(dst io.Writer, src io.Reader, buf []byte) (written int64, er
}
// simple copy loop without padding, works with http/1.1
-// TODO: we can't pad, but we probably can split
func copyLoop(local, remote net.Conn) {
var wg sync.WaitGroup
wg.Add(2)
+ buf1 := make([]byte, 65536)
+ buf2 := make([]byte, 65536)
go func() {
- io.Copy(remote, local)
+ flushingIoCopy(local, remote, buf1)
wg.Done()
}()
go func() {
- io.Copy(local, remote)
- wg.Done()
- }()
- // TODO: try not to spawn extra goroutine
-
- wg.Wait()
-}
-
-func h2copyLoop(w1 io.Writer, r1 io.Reader, w2 io.Writer, r2 io.Reader) {
- var wg sync.WaitGroup
- wg.Add(2)
-
- buf1 := make([]byte, 16384)
- buf2 := make([]byte, 16384)
- go func() {
- flushingIoCopy(w1, r1, buf1)
- wg.Done()
- }()
- go func() {
- flushingIoCopy(w2, r2, buf2)
+ flushingIoCopy(remote, local, buf2)
wg.Done()
}()
// TODO: try not to spawn extra goroutine
@@ -124,20 +104,20 @@ func h2copyLoop(w1 io.Writer, r1 io.Reader, w2 io.Writer, r2 io.Reader) {
func parseTCPAddr(s string) (*net.TCPAddr, error) {
hostStr, portStr, err := net.SplitHostPort(s)
if err != nil {
- fmt.Printf("net.SplitHostPort(%s) failed: %+v", s, err)
+ log.Printf("net.SplitHostPort(%s) failed: %+v", s, err)
return nil, err
}
port, err := strconv.Atoi(portStr)
if err != nil {
- fmt.Printf("strconv.Atoi(%s) failed: %+v", portStr, err)
+ log.Printf("strconv.Atoi(%s) failed: %+v", portStr, err)
return nil, err
}
ip := net.ParseIP(hostStr)
if ip == nil {
err = errors.New("net.ParseIP(" + s + ") returned nil")
- fmt.Printf("%+v\n", err)
+ log.Printf("%+v\n", err)
return nil, err
}
@@ -155,6 +135,7 @@ func handler(conn *pt.SocksConn) error {
guardTCPAddr, err := parseTCPAddr(conn.Req.Target)
if err != nil {
+ log.Println(err)
conn.Reject()
return err
}
@@ -162,23 +143,51 @@ func handler(conn *pt.SocksConn) error {
webproxyUrlArg, ok := conn.Req.Args.Get("url")
if !ok {
err := errors.New("address of webproxy in form of `url=https://username:password@xxxxxxxxxxx` is required")
+ log.Println(err)
conn.Reject()
return err
}
- httpsClient, err := NewHTTPSClient(webproxyUrlArg)
+ proxyUrl, err := url.Parse(webproxyUrlArg)
if err != nil {
- log.Printf("NewHTTPSClient(%s, nil) failed: %s\n", webproxyUrlArg, err)
+ log.Println(err)
+ conn.Reject()
+ return err
+ }
+
+ if proxyUrl.Scheme != "https" {
+ err = errors.New("Scheme " + proxyUrl.Scheme + " is not supported")
+ log.Println(err)
conn.Reject()
return err
}
- err = httpsClient.Connect(conn.Req.Target)
+ if proxyUrl.Host == "" {
+ conn.Reject()
+ return errors.New("misparsed `url=`, make sure to specify full url like https://username:password@xxxxxxxxxxxx:443/")
+ }
+
+ if proxyUrl.Port() == "" {
+ proxyUrl.Host = net.JoinHostPort(proxyUrl.Host, "443")
+ }
+
+ dialer, err := httpclient.NewHTTPConnectDialer(webproxyUrlArg)
if err != nil {
- log.Printf("httpsClient.Connect(%s, nil) failed: %s\n", conn.Req.Target, err)
+ log.Printf("httpclient.NewHTTPConnectDialer(%s) failed: %s\n", webproxyUrlArg, err)
conn.Reject()
return err
}
+ dialer.DialTLS = func(network string, address string) (net.Conn, string, error) {
+ host, _, err := net.SplitHostPort(address)
+ if err != nil {
+ return nil, "", err
+ }
+ conn, err := utlsRoller.Dial(network, address, host)
+ if err != nil {
+ return nil, "", err
+ }
+ return conn, conn.ConnectionState().NegotiatedProtocol, nil
+ }
err = conn.Grant(guardTCPAddr)
if err != nil {
@@ -187,7 +196,15 @@ func handler(conn *pt.SocksConn) error {
return err
}
- return httpsClient.CopyLoop(conn)
+ remoteConn, err := dialer.Dial("tcp", conn.Req.Target)
+ if err != nil {
+ log.Printf("dialer.Dial(%s) failed: %s\n", conn.Req.Target, err)
+ conn.Reject()
+ return err
+ }
+
+ copyLoop(conn, remoteConn)
+ return nil
}
func acceptLoop(ln *pt.SocksListener) error {
@@ -231,6 +248,13 @@ func main() {
log.SetOutput(f)
}
+ utlsRoller, err = tls.NewRoller()
+ if err != nil {
+ pt.CmethodError("httpsproxy",
+ fmt.Sprintf("could not creat utls.Roller: %v", err))
+ os.Exit(3)
+ }
+
listeners := make([]net.Listener, 0)
for _, methodName := range ptInfo.MethodNames {
switch methodName {
@@ -282,155 +306,3 @@ func main() {
numHandlers += <-handlerChan
}
}
-
-type HTTPConnectClient struct {
- Header http.Header
- ProxyHost string
- TlsConf tls.Config
-
- Conn *tls.Conn
-
- In io.Writer
- Out io.Reader
-}
-
-// NewHTTPSClient creates one-time use client to tunnel traffic via HTTPS proxy.
-// If spkiFp is set, HTTPSClient will use it as SPKI fingerprint to confirm identity of the
-// proxy, instead of relying on standard PKI CA roots
-func NewHTTPSClient(proxyUrlStr string) (*HTTPConnectClient, error) {
- proxyUrl, err := url.Parse(proxyUrlStr)
- if err != nil {
- return nil, err
- }
-
- switch proxyUrl.Scheme {
- case "http", "":
- fallthrough
- default:
- return nil, errors.New("Scheme " + proxyUrl.Scheme + " is not supported")
- case "https":
- }
-
- if proxyUrl.Host == "" {
- return nil, errors.New("misparsed `url=`, make sure to specify full url like https://username:password@xxxxxxxxxxxx:443/")
- }
-
- if proxyUrl.Port() == "" {
- proxyUrl.Host = net.JoinHostPort(proxyUrl.Host, "443")
- }
-
- tlsConf := tls.Config{
- NextProtos: []string{"h2", "http/1.1"},
- ServerName: proxyUrl.Hostname(),
- }
-
- client := &HTTPConnectClient{
- Header: make(http.Header),
- ProxyHost: proxyUrl.Host,
- TlsConf: tlsConf,
- }
-
- if proxyUrl.User.Username() != "" {
- password, _ := proxyUrl.User.Password()
- client.Header.Set("Proxy-Authorization", "Basic "+
- base64.StdEncoding.EncodeToString([]byte(proxyUrl.User.Username()+":"+password)))
- }
- return client, nil
-}
-
-func (c *HTTPConnectClient) Connect(target string) error {
- req := &http.Request{
- Method: "CONNECT",
- URL: &url.URL{Host: target},
- Header: c.Header,
- Host: target,
- }
-
- tcpConn, err := net.Dial("tcp", c.ProxyHost)
- if err != nil {
- return err
- }
-
- c.Conn = tls.Client(tcpConn, &c.TlsConf)
-
- err = c.Conn.Handshake()
- if err != nil {
- return err
- }
-
- var resp *http.Response
- switch c.Conn.ConnectionState().NegotiatedProtocol {
- case "":
- fallthrough
- case "http/1.1":
- req.Proto = "HTTP/1.1"
- req.ProtoMajor = 1
- req.ProtoMinor = 1
-
- err = req.Write(c.Conn)
- if err != nil {
- c.Conn.Close()
- return err
- }
-
- resp, err = http.ReadResponse(bufio.NewReader(c.Conn), req)
- if err != nil {
- c.Conn.Close()
- return err
- }
-
- c.In = c.Conn
- c.Out = c.Conn
- case "h2":
- req.Proto = "HTTP/2.0"
- req.ProtoMajor = 2
- req.ProtoMinor = 0
- pr, pw := io.Pipe()
- req.Body = ioutil.NopCloser(pr)
-
- t := http2.Transport{}
- h2client, err := t.NewClientConn(c.Conn)
- if err != nil {
- c.Conn.Close()
- return err
- }
-
- resp, err = h2client.RoundTrip(req)
- if err != nil {
- c.Conn.Close()
- return err
- }
-
- c.In = pw
- c.Out = resp.Body
- default:
- c.Conn.Close()
- return errors.New("negotiated unsupported application layer protocol: " +
- c.Conn.ConnectionState().NegotiatedProtocol)
- }
-
- if resp.StatusCode != http.StatusOK {
- c.Conn.Close()
- return errors.New("Proxy responded with non 200 code: " + resp.Status)
- }
-
- return nil
-}
-
-func (c *HTTPConnectClient) CopyLoop(conn net.Conn) error {
- defer c.Conn.Close()
- defer conn.Close()
-
- switch c.Conn.ConnectionState().NegotiatedProtocol {
- case "":
- fallthrough
- case "http/1.1":
- copyLoop(conn, c.Conn)
- case "h2":
- h2copyLoop(c.In, conn, conn, c.Out)
- default:
- return errors.New("negotiated unsupported application layer protocol: " +
- c.Conn.ConnectionState().NegotiatedProtocol)
- }
- return nil
-}
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits