From 5b8bcc70e8bbc4a2999fec757947c7e8915e47c9 Mon Sep 17 00:00:00 2001 From: adonis Date: Sun, 17 Dec 2023 09:46:03 +0200 Subject: [PATCH 1/5] forward proxy support for WS proxy --- client/ws_proxy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ws_proxy.go b/client/ws_proxy.go index 2c7d357..5bedb1e 100644 --- a/client/ws_proxy.go +++ b/client/ws_proxy.go @@ -299,6 +299,7 @@ func createClientWSProxy(endpoint string, tlsClientConf *tls.Config) (*http.Serv httpClient: &http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsClientConf, + Proxy: http.ProxyFromEnvironment, }, }, } From 2de7f0511a6757d21583f6db436b7958c47ee749 Mon Sep 17 00:00:00 2001 From: adonis Date: Sun, 17 Dec 2023 13:29:01 +0200 Subject: [PATCH 2/5] forward proxy support for WS side channel --- .gitignore | 1 + client/side_channel_creds.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 20bd640..26fa58b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.idea/ /deps /.gobin/ +/vendor/ diff --git a/client/side_channel_creds.go b/client/side_channel_creds.go index 689fde1..4094537 100644 --- a/client/side_channel_creds.go +++ b/client/side_channel_creds.go @@ -19,6 +19,7 @@ import ( "net" "sync" + np "golang.org/x/net/proxy" "google.golang.org/grpc/credentials" ) @@ -47,7 +48,7 @@ func (c *sideChannelCreds) ClientHandshake(ctx context.Context, authority string return rawConn, c.authInfo, nil } - sideChannelConn, err := (&net.Dialer{}).DialContext(ctx, "tcp", c.endpoint) + sideChannelConn, err := np.Dial(ctx, "tcp", c.endpoint) if err != nil { return nil, nil, err } From 12e0ec2b5ed046521fd83a51aa36151a79de2634 Mon Sep 17 00:00:00 2001 From: adonis Date: Sun, 17 Dec 2023 16:12:46 +0200 Subject: [PATCH 3/5] http connect side channel dial proxy --- client/side_channel_creds.go | 92 +++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/client/side_channel_creds.go b/client/side_channel_creds.go index 4094537..28a68aa 100644 --- a/client/side_channel_creds.go +++ b/client/side_channel_creds.go @@ -15,11 +15,15 @@ package client import ( + "bufio" "context" + "fmt" "net" + "net/http" + "net/url" "sync" - np "golang.org/x/net/proxy" + "golang.org/x/net/proxy" "google.golang.org/grpc/credentials" ) @@ -48,7 +52,7 @@ func (c *sideChannelCreds) ClientHandshake(ctx context.Context, authority string return rawConn, c.authInfo, nil } - sideChannelConn, err := np.Dial(ctx, "tcp", c.endpoint) + sideChannelConn, err := proxy.Dial(ctx, "tcp", c.endpoint) if err != nil { return nil, nil, err } @@ -62,3 +66,87 @@ func (c *sideChannelCreds) ClientHandshake(ctx context.Context, authority string c.authInfo = authInfo return rawConn, authInfo, nil } + +// httpProxy is a HTTP/HTTPS connect proxy. +type httpProxy struct { + host string + haveAuth bool + username string + password string + forward proxy.Dialer +} + +func newHTTPProxy(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { + s := new(httpProxy) + s.host = uri.Host + s.forward = forward + if uri.User != nil { + s.haveAuth = true + s.username = uri.User.Username() + s.password, _ = uri.User.Password() + } + + return s, nil +} + +func (s *httpProxy) Dial(network, addr string) (net.Conn, error) { + // Dial and create the https client connection. + c, err := s.forward.Dial("tcp", s.host) + if err != nil { + return nil, err + } + + // HACK. http.ReadRequest also does this. + reqURL, err := url.Parse("http://" + addr) + if err != nil { + c.Close() + return nil, err + } + reqURL.Scheme = "" + + req, err := http.NewRequest("CONNECT", reqURL.String(), nil) + if err != nil { + c.Close() + return nil, err + } + req.Close = false + if s.haveAuth { + req.SetBasicAuth(s.username, s.password) + } + // req.Header.Set("User-Agent", "Powerby Gota") + + err = req.Write(c) + if err != nil { + c.Close() + return nil, err + } + + resp, err := http.ReadResponse(bufio.NewReader(c), req) + if err != nil { + // TODO close resp body ? + resp.Body.Close() + c.Close() + return nil, err + } + resp.Body.Close() + if resp.StatusCode != 200 { + c.Close() + err = fmt.Errorf("Connect server using proxy error, StatusCode [%d]", resp.StatusCode) + return nil, err + } + + return c, nil +} + +func FromURL(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { + return proxy.FromURL(u, forward) +} + +func FromEnvironment() proxy.Dialer { + return proxy.FromEnvironment() +} + +func init() { + proxy.RegisterDialerType("http", newHTTPProxy) + proxy.RegisterDialerType("https", newHTTPProxy) +} From cde77958775a05a2141d5a093174f114bf2f4724 Mon Sep 17 00:00:00 2001 From: adonis Date: Sun, 17 Dec 2023 20:18:18 +0200 Subject: [PATCH 4/5] http connect side channel dial proxy --- client/side_channel_creds.go | 109 +++++++++++------------------------ 1 file changed, 34 insertions(+), 75 deletions(-) diff --git a/client/side_channel_creds.go b/client/side_channel_creds.go index 28a68aa..0b022c6 100644 --- a/client/side_channel_creds.go +++ b/client/side_channel_creds.go @@ -23,7 +23,6 @@ import ( "net/url" "sync" - "golang.org/x/net/proxy" "google.golang.org/grpc/credentials" ) @@ -52,7 +51,24 @@ func (c *sideChannelCreds) ClientHandshake(ctx context.Context, authority string return rawConn, c.authInfo, nil } - sideChannelConn, err := proxy.Dial(ctx, "tcp", c.endpoint) + // net dial via HTTP CONNECT if HTTP_PROXY, HTTPS_PROXY, NO_PROXY env + // require that c.endpoint must be go through proxy + destReq, err := http.NewRequest("GET", "http://"+c.endpoint, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to determine proxy URL for %s: %w", c.endpoint, err) + } + proxyURL, err := http.ProxyFromEnvironment(destReq) + if err != nil { + return nil, nil, fmt.Errorf("failed to determine proxy URL for %s: %w", c.endpoint, err) + } + + var sideChannelConn net.Conn + if proxyURL != nil { + sideChannelConn, err = dialViaCONNECT(ctx, c.endpoint, proxyURL) + } else { + sideChannelConn, err = (&net.Dialer{}).DialContext(ctx, "tcp", c.endpoint) + } + if err != nil { return nil, nil, err } @@ -67,86 +83,29 @@ func (c *sideChannelCreds) ClientHandshake(ctx context.Context, authority string return rawConn, authInfo, nil } -// httpProxy is a HTTP/HTTPS connect proxy. -type httpProxy struct { - host string - haveAuth bool - username string - password string - forward proxy.Dialer -} - -func newHTTPProxy(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { - s := new(httpProxy) - s.host = uri.Host - s.forward = forward - if uri.User != nil { - s.haveAuth = true - s.username = uri.User.Username() - s.password, _ = uri.User.Password() - } - - return s, nil -} - -func (s *httpProxy) Dial(network, addr string) (net.Conn, error) { - // Dial and create the https client connection. - c, err := s.forward.Dial("tcp", s.host) - if err != nil { - return nil, err +func dialViaCONNECT(ctx context.Context, addr string, proxy *url.URL) (net.Conn, error) { + proxyAddr := proxy.Host + if proxy.Port() == "" { + proxyAddr = net.JoinHostPort(proxyAddr, "3128") } - - // HACK. http.ReadRequest also does this. - reqURL, err := url.Parse("http://" + addr) + c, err := (&net.Dialer{}).DialContext(ctx, "tcp", proxyAddr) if err != nil { - c.Close() - return nil, err + return nil, fmt.Errorf("failed to dial proxy %q: %w", proxyAddr, err) } - reqURL.Scheme = "" - - req, err := http.NewRequest("CONNECT", reqURL.String(), nil) + fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, proxy.Hostname()) + br := bufio.NewReader(c) + res, err := http.ReadResponse(br, nil) if err != nil { - c.Close() - return nil, err + return nil, fmt.Errorf("reading HTTP response from CONNECT to %s via proxy %s failed: %v", + addr, proxyAddr, err) } - req.Close = false - if s.haveAuth { - req.SetBasicAuth(s.username, s.password) + if res.StatusCode != 200 { + return nil, fmt.Errorf("proxy error from %s while dialing %s: %v", proxyAddr, addr, res.Status) } - // req.Header.Set("User-Agent", "Powerby Gota") - err = req.Write(c) - if err != nil { - c.Close() - return nil, err - } - - resp, err := http.ReadResponse(bufio.NewReader(c), req) - if err != nil { - // TODO close resp body ? - resp.Body.Close() - c.Close() - return nil, err - } - resp.Body.Close() - if resp.StatusCode != 200 { - c.Close() - err = fmt.Errorf("Connect server using proxy error, StatusCode [%d]", resp.StatusCode) - return nil, err + if br.Buffered() > 0 { + return nil, fmt.Errorf("unexpected %d bytes of buffered data from CONNECT proxy %q", + br.Buffered(), proxyAddr) } - return c, nil } - -func FromURL(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { - return proxy.FromURL(u, forward) -} - -func FromEnvironment() proxy.Dialer { - return proxy.FromEnvironment() -} - -func init() { - proxy.RegisterDialerType("http", newHTTPProxy) - proxy.RegisterDialerType("https", newHTTPProxy) -} From f60f159419bdd4dbcff6dccfc79d3d5755994ef3 Mon Sep 17 00:00:00 2001 From: adonis Date: Thu, 28 Dec 2023 16:15:25 +0200 Subject: [PATCH 5/5] cleanup --- client/side_channel_creds.go | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/client/side_channel_creds.go b/client/side_channel_creds.go index 0b022c6..fcefba6 100644 --- a/client/side_channel_creds.go +++ b/client/side_channel_creds.go @@ -51,8 +51,7 @@ func (c *sideChannelCreds) ClientHandshake(ctx context.Context, authority string return rawConn, c.authInfo, nil } - // net dial via HTTP CONNECT if HTTP_PROXY, HTTPS_PROXY, NO_PROXY env - // require that c.endpoint must be go through proxy + // check if c.endpoint is reached via proxy destReq, err := http.NewRequest("GET", "http://"+c.endpoint, nil) if err != nil { return nil, nil, fmt.Errorf("failed to determine proxy URL for %s: %w", c.endpoint, err) @@ -64,9 +63,10 @@ func (c *sideChannelCreds) ClientHandshake(ctx context.Context, authority string var sideChannelConn net.Conn if proxyURL != nil { + // net dial via HTTP CONNECT tunnel if using proxy sideChannelConn, err = dialViaCONNECT(ctx, c.endpoint, proxyURL) } else { - sideChannelConn, err = (&net.Dialer{}).DialContext(ctx, "tcp", c.endpoint) + sideChannelConn, err = new(net.Dialer).DialContext(ctx, "tcp", c.endpoint) } if err != nil { @@ -83,29 +83,27 @@ func (c *sideChannelCreds) ClientHandshake(ctx context.Context, authority string return rawConn, authInfo, nil } +// dialViaCONNECT tunnels a tcp connection to addr through proxy using HTTP CONNECT func dialViaCONNECT(ctx context.Context, addr string, proxy *url.URL) (net.Conn, error) { proxyAddr := proxy.Host if proxy.Port() == "" { - proxyAddr = net.JoinHostPort(proxyAddr, "3128") + proxyAddr = net.JoinHostPort(proxyAddr, "80") } - c, err := (&net.Dialer{}).DialContext(ctx, "tcp", proxyAddr) + conn, err := new(net.Dialer).DialContext(ctx, "tcp", proxyAddr) if err != nil { - return nil, fmt.Errorf("failed to dial proxy %q: %w", proxyAddr, err) + return nil, fmt.Errorf("failed to dial proxy %s: %w", proxyAddr, err) } - fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, proxy.Hostname()) - br := bufio.NewReader(c) - res, err := http.ReadResponse(br, nil) + fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, proxy.Hostname()) + rr := bufio.NewReader(conn) + res, err := http.ReadResponse(rr, nil) if err != nil { - return nil, fmt.Errorf("reading HTTP response from CONNECT to %s via proxy %s failed: %v", - addr, proxyAddr, err) + return nil, fmt.Errorf("failed to read response from HTTP CONNECT to %s via proxy %s: %w", addr, proxyAddr, err) } - if res.StatusCode != 200 { - return nil, fmt.Errorf("proxy error from %s while dialing %s: %v", proxyAddr, addr, res.Status) + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to dial %s via %s. response status: %v", addr, proxyAddr, res.Status) } - - if br.Buffered() > 0 { - return nil, fmt.Errorf("unexpected %d bytes of buffered data from CONNECT proxy %q", - br.Buffered(), proxyAddr) + if rr.Buffered() > 0 { + return nil, fmt.Errorf("CONNECT response from %s resulted in %d bytes of unexpected data", proxyAddr, rr.Buffered()) } - return c, nil + return conn, nil }