Skip to content

Commit da5b1ea

Browse files
committed
dial the proxy, ensuring http/1.1 instead of http/2 for TLS-enabled proxy
1 parent 85df601 commit da5b1ea

1 file changed

Lines changed: 45 additions & 3 deletions

File tree

internal/api/proxy.go

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ func (c *connWithBufferedReader) Read(p []byte) (int, error) {
2121
return c.r.Read(p)
2222
}
2323

24+
// proxyDialAddr returns proxyURL.Host with a default port appended if one is
25+
// not already present (443 for https, 80 for http).
26+
func proxyDialAddr(proxyURL *url.URL) string {
27+
addr := proxyURL.Host
28+
if _, _, err := net.SplitHostPort(addr); err != nil {
29+
if proxyURL.Scheme == "https" {
30+
return net.JoinHostPort(addr, "443")
31+
}
32+
return net.JoinHostPort(addr, "80")
33+
}
34+
return addr
35+
}
36+
2437
// withProxyTransport modifies the given transport to handle proxying of unix, socks5 and http connections.
2538
//
2639
// Note: baseTransport is considered to be a clone created with transport.Clone()
@@ -74,9 +87,38 @@ func withProxyTransport(baseTransport *http.Transport, proxyURL *url.URL, proxyP
7487
baseTransport.Proxy = http.ProxyURL(proxyURL)
7588
case "http", "https":
7689
dial := func(ctx context.Context, network, addr string) (net.Conn, error) {
77-
// Dial the proxy
78-
d := net.Dialer{}
79-
conn, err := d.DialContext(ctx, "tcp", proxyURL.Host)
90+
// Dial the proxy. For https:// proxies, we TLS-connect to the
91+
// proxy itself and force ALPN to HTTP/1.1 to prevent Go from
92+
// negotiating HTTP/2 for the CONNECT tunnel. Many proxy servers
93+
// don't support HTTP/2 CONNECT, and Go's default Transport.Proxy
94+
// would negotiate h2 via ALPN when TLS-connecting to an https://
95+
// proxy, causing "bogus greeting" errors. For http:// proxies,
96+
// CONNECT is always HTTP/1.1 over plain TCP so this isn't needed.
97+
// The target connection (e.g. to sourcegraph.com) still negotiates
98+
// HTTP/2 normally through the established tunnel.
99+
proxyAddr := proxyDialAddr(proxyURL)
100+
101+
var conn net.Conn
102+
var err error
103+
if proxyURL.Scheme == "https" {
104+
raw, dialErr := (&net.Dialer{}).DialContext(ctx, "tcp", proxyAddr)
105+
if dialErr != nil {
106+
return nil, dialErr
107+
}
108+
cfg := baseTransport.TLSClientConfig.Clone()
109+
cfg.NextProtos = []string{"http/1.1"}
110+
if cfg.ServerName == "" {
111+
cfg.ServerName = proxyURL.Hostname()
112+
}
113+
tlsConn := tls.Client(raw, cfg)
114+
if err := tlsConn.HandshakeContext(ctx); err != nil {
115+
raw.Close()
116+
return nil, err
117+
}
118+
conn = tlsConn
119+
} else {
120+
conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", proxyAddr)
121+
}
80122
if err != nil {
81123
return nil, err
82124
}

0 commit comments

Comments
 (0)