-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implements missing HTTPS proxy functionality #978
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,16 +53,20 @@ func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufS | |
type Dialer struct { | ||
// NetDial specifies the dial function for creating TCP connections. If | ||
// NetDial is nil, net.Dialer DialContext is used. | ||
// If "Proxy" field is also set, this function dials the proxy. | ||
NetDial func(network, addr string) (net.Conn, error) | ||
|
||
// NetDialContext specifies the dial function for creating TCP connections. If | ||
// NetDialContext is nil, NetDial is used. | ||
// If "Proxy" field is also set, this function dials the proxy. | ||
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error) | ||
|
||
// NetDialTLSContext specifies the dial function for creating TLS/TCP connections. If | ||
// NetDialTLSContext is nil, NetDialContext is used. | ||
// If NetDialTLSContext is set, Dial assumes the TLS handshake is done there and | ||
// TLSClientConfig is ignored. | ||
// If "Proxy" field is also set, this function dials the proxy (and performs | ||
// the TLS handshake with the proxy, ignoring TLSClientConfig). | ||
NetDialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error) | ||
|
||
// Proxy specifies a function to return a proxy for a given | ||
|
@@ -73,7 +77,7 @@ type Dialer struct { | |
|
||
// TLSClientConfig specifies the TLS configuration to use with tls.Client. | ||
// If nil, the default configuration is used. | ||
// If either NetDialTLS or NetDialTLSContext are set, Dial assumes the TLS handshake | ||
// If NetDialTLSContext is set, Dial assumes the TLS handshake | ||
// is done there and TLSClientConfig is ignored. | ||
TLSClientConfig *tls.Config | ||
|
||
|
@@ -275,18 +279,13 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h | |
} | ||
} | ||
|
||
// If needed, wrap the dial function to connect through a proxy. | ||
// If needed, generate a dial function to connect to the proxy, possibly | ||
// wrapping the previously computed "netDial" function. | ||
if d.Proxy != nil { | ||
proxyURL, err := d.Proxy(req) | ||
netDial, err = d.proxyNetDial(req, netDial) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
if proxyURL != nil { | ||
netDial, err = proxyFromURL(proxyURL, netDial) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
} | ||
} | ||
|
||
hostPort, hostNoPort := hostPortNoPort(u) | ||
|
@@ -415,6 +414,56 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h | |
return conn, resp, nil | ||
} | ||
|
||
// Returns an updated proxy dial function, wrapping the passed | ||
// "netDial" function, or an error if one occurred. If the proxy | ||
// is HTTPS, then TLS handshake will occur in the returned dial function. | ||
func (d *Dialer) proxyNetDial(req *http.Request, netDial netDialerFunc) (netDialerFunc, error) { | ||
if d.Proxy == nil { | ||
return nil, errors.New("proxy url function is nil") | ||
} | ||
// Generate the proxy URL and validate. | ||
proxyURL, err := d.Proxy(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// Request should *not* be proxied; use originial dial function. | ||
if proxyURL == nil { | ||
return netDial, nil | ||
} | ||
if proxyURL.Scheme == "http" && d.NetDialTLSContext != nil { | ||
return nil, errors.New("dial TLS function set for dialing non-HTTPS proxy") | ||
} | ||
// For HTTPS proxy, either use TLS dial function set in Dialer (which performs | ||
// the TLS handshake within the function), or create a new dial function by wrapping | ||
// the passed "netDial" for the connection and using TLSClientConfig for the handshake. | ||
if proxyURL.Scheme == "https" { | ||
if d.NetDialTLSContext != nil { | ||
netDial = d.NetDialTLSContext | ||
Comment on lines
+440
to
+441
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note for self:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I do not think this is correct. There is only one "dial". It is either to the "proxy" or to the "upstream" server. If there is a proxy, the dial is to the "proxy" (and the TLS handshake must occur in the dial). The "proxy" server is the place that dials the "upstream" in this case. The second TLS handshake (not dial) to the "upstream" occurs over the previously established connection, and it MUST use the |
||
// Ensures later TLS handshake occurs to backend over proxied connection. | ||
d.NetDialTLSContext = nil | ||
} else if d.TLSClientConfig == nil { | ||
return nil, errors.New("HTTPS proxy requires TLS dial function or TLS client config") | ||
} else { | ||
proxyDial := netDial | ||
netDial = func(ctx context.Context, unused, addr string) (net.Conn, error) { | ||
// Creates TCP connection to addr using previously computed "netDial" | ||
conn, err := proxyDial(ctx, "tcp", addr) | ||
cfg := cloneTLSConfig(d.TLSClientConfig) | ||
if cfg.ServerName == "" { | ||
_, hostNoPort := hostPortNoPort(proxyURL) | ||
cfg.ServerName = hostNoPort | ||
} | ||
tlsConn := tls.Client(conn, cfg) | ||
// Do the TLS handshake using TLSConfig over the wrapped connection. | ||
err = doHandshake(ctx, tlsConn, cfg) | ||
return tlsConn, err | ||
} | ||
} | ||
} | ||
// Wrap dial function to implement proxy CONNECT method. | ||
return proxyFromURL(proxyURL, netDial) | ||
} | ||
|
||
func cloneTLSConfig(cfg *tls.Config) *tls.Config { | ||
if cfg == nil { | ||
return &tls.Config{} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not match the logic in
that seems to indicate that
NetDialTLSContext
is ONLY used if the scheme is https , but ignored otherwiseThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems we can remove this check or just shortcut and