Skip to content

Commit 35bb599

Browse files
Ben Schwartztombergan
Ben Schwartz
authored andcommitted
net/http: HTTPS proxies support
net/http already supports http proxies. This CL allows it to establish a connection to the http proxy over https. See more at: https://www.chromium.org/developers/design-documents/secure-web-proxy Fixes golang/go#11332 Change-Id: If0e017df0e8f8c2c499a2ddcbbeb625c8fa2bb6b Reviewed-on: https://go-review.googlesource.com/68550 Run-TryBot: Tom Bergan <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Tom Bergan <[email protected]>
1 parent 4ac1703 commit 35bb599

File tree

2 files changed

+283
-120
lines changed

2 files changed

+283
-120
lines changed

transport.go

Lines changed: 100 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -618,11 +618,6 @@ func (t *Transport) connectMethodForRequest(treq *transportRequest) (cm connectM
618618
if port := cm.proxyURL.Port(); !validPort(port) {
619619
return cm, fmt.Errorf("invalid proxy URL port %q", port)
620620
}
621-
switch cm.proxyURL.Scheme {
622-
case "http", "socks5":
623-
default:
624-
return cm, fmt.Errorf("invalid proxy URL scheme %q", cm.proxyURL.Scheme)
625-
}
626621
}
627622
}
628623
return cm, err
@@ -1021,6 +1016,69 @@ func (d oneConnDialer) Dial(network, addr string) (net.Conn, error) {
10211016
}
10221017
}
10231018

1019+
// The connect method and the transport can both specify a TLS
1020+
// Host name. The transport's name takes precedence if present.
1021+
func chooseTLSHost(cm connectMethod, t *Transport) string {
1022+
tlsHost := ""
1023+
if t.TLSClientConfig != nil {
1024+
tlsHost = t.TLSClientConfig.ServerName
1025+
}
1026+
if tlsHost == "" {
1027+
tlsHost = cm.tlsHost()
1028+
}
1029+
return tlsHost
1030+
}
1031+
1032+
// Add TLS to a persistent connection, i.e. negotiate a TLS session. If pconn is already a TLS
1033+
// tunnel, this function establishes a nested TLS session inside the encrypted channel.
1034+
// The remote endpoint's name may be overridden by TLSClientConfig.ServerName.
1035+
func (pconn *persistConn) addTLS(name string, trace *httptrace.ClientTrace) error {
1036+
// Initiate TLS and check remote host name against certificate.
1037+
cfg := cloneTLSConfig(pconn.t.TLSClientConfig)
1038+
if cfg.ServerName == "" {
1039+
cfg.ServerName = name
1040+
}
1041+
plainConn := pconn.conn
1042+
tlsConn := tls.Client(plainConn, cfg)
1043+
errc := make(chan error, 2)
1044+
var timer *time.Timer // for canceling TLS handshake
1045+
if d := pconn.t.TLSHandshakeTimeout; d != 0 {
1046+
timer = time.AfterFunc(d, func() {
1047+
errc <- tlsHandshakeTimeoutError{}
1048+
})
1049+
}
1050+
go func() {
1051+
if trace != nil && trace.TLSHandshakeStart != nil {
1052+
trace.TLSHandshakeStart()
1053+
}
1054+
err := tlsConn.Handshake()
1055+
if timer != nil {
1056+
timer.Stop()
1057+
}
1058+
errc <- err
1059+
}()
1060+
if err := <-errc; err != nil {
1061+
plainConn.Close()
1062+
if trace != nil && trace.TLSHandshakeDone != nil {
1063+
trace.TLSHandshakeDone(tls.ConnectionState{}, err)
1064+
}
1065+
return err
1066+
}
1067+
if !cfg.InsecureSkipVerify {
1068+
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
1069+
plainConn.Close()
1070+
return err
1071+
}
1072+
}
1073+
cs := tlsConn.ConnectionState()
1074+
if trace != nil && trace.TLSHandshakeDone != nil {
1075+
trace.TLSHandshakeDone(cs, nil)
1076+
}
1077+
pconn.tlsState = &cs
1078+
pconn.conn = tlsConn
1079+
return nil
1080+
}
1081+
10241082
func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistConn, error) {
10251083
pconn := &persistConn{
10261084
t: t,
@@ -1032,15 +1090,21 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistCon
10321090
writeLoopDone: make(chan struct{}),
10331091
}
10341092
trace := httptrace.ContextClientTrace(ctx)
1035-
tlsDial := t.DialTLS != nil && cm.targetScheme == "https" && cm.proxyURL == nil
1036-
if tlsDial {
1093+
wrapErr := func(err error) error {
1094+
if cm.proxyURL != nil {
1095+
// Return a typed error, per Issue 16997
1096+
return &net.OpError{Op: "proxyconnect", Net: "tcp", Err: err}
1097+
}
1098+
return err
1099+
}
1100+
if cm.scheme() == "https" && t.DialTLS != nil {
10371101
var err error
10381102
pconn.conn, err = t.DialTLS("tcp", cm.addr())
10391103
if err != nil {
1040-
return nil, err
1104+
return nil, wrapErr(err)
10411105
}
10421106
if pconn.conn == nil {
1043-
return nil, errors.New("net/http: Transport.DialTLS returned (nil, nil)")
1107+
return nil, wrapErr(errors.New("net/http: Transport.DialTLS returned (nil, nil)"))
10441108
}
10451109
if tc, ok := pconn.conn.(*tls.Conn); ok {
10461110
// Handshake here, in case DialTLS didn't. TLSNextProto below
@@ -1064,13 +1128,18 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistCon
10641128
} else {
10651129
conn, err := t.dial(ctx, "tcp", cm.addr())
10661130
if err != nil {
1067-
if cm.proxyURL != nil {
1068-
// Return a typed error, per Issue 16997:
1069-
err = &net.OpError{Op: "proxyconnect", Net: "tcp", Err: err}
1070-
}
1071-
return nil, err
1131+
return nil, wrapErr(err)
10721132
}
10731133
pconn.conn = conn
1134+
if cm.scheme() == "https" {
1135+
var firstTLSHost string
1136+
if firstTLSHost, _, err = net.SplitHostPort(cm.addr()); err != nil {
1137+
return nil, wrapErr(err)
1138+
}
1139+
if err = pconn.addTLS(firstTLSHost, trace); err != nil {
1140+
return nil, wrapErr(err)
1141+
}
1142+
}
10741143
}
10751144

10761145
// Proxy setup.
@@ -1134,50 +1203,10 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistCon
11341203
}
11351204
}
11361205

1137-
if cm.targetScheme == "https" && !tlsDial {
1138-
// Initiate TLS and check remote host name against certificate.
1139-
cfg := cloneTLSConfig(t.TLSClientConfig)
1140-
if cfg.ServerName == "" {
1141-
cfg.ServerName = cm.tlsHost()
1142-
}
1143-
plainConn := pconn.conn
1144-
tlsConn := tls.Client(plainConn, cfg)
1145-
errc := make(chan error, 2)
1146-
var timer *time.Timer // for canceling TLS handshake
1147-
if d := t.TLSHandshakeTimeout; d != 0 {
1148-
timer = time.AfterFunc(d, func() {
1149-
errc <- tlsHandshakeTimeoutError{}
1150-
})
1151-
}
1152-
go func() {
1153-
if trace != nil && trace.TLSHandshakeStart != nil {
1154-
trace.TLSHandshakeStart()
1155-
}
1156-
err := tlsConn.Handshake()
1157-
if timer != nil {
1158-
timer.Stop()
1159-
}
1160-
errc <- err
1161-
}()
1162-
if err := <-errc; err != nil {
1163-
plainConn.Close()
1164-
if trace != nil && trace.TLSHandshakeDone != nil {
1165-
trace.TLSHandshakeDone(tls.ConnectionState{}, err)
1166-
}
1206+
if cm.proxyURL != nil && cm.targetScheme == "https" {
1207+
if err := pconn.addTLS(cm.tlsHost(), trace); err != nil {
11671208
return nil, err
11681209
}
1169-
if !cfg.InsecureSkipVerify {
1170-
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
1171-
plainConn.Close()
1172-
return nil, err
1173-
}
1174-
}
1175-
cs := tlsConn.ConnectionState()
1176-
if trace != nil && trace.TLSHandshakeDone != nil {
1177-
trace.TLSHandshakeDone(cs, nil)
1178-
}
1179-
pconn.tlsState = &cs
1180-
pconn.conn = tlsConn
11811210
}
11821211

11831212
if s := pconn.tlsState; s != nil && s.NegotiatedProtocolIsMutual && s.NegotiatedProtocol != "" {
@@ -1279,21 +1308,24 @@ func useProxy(addr string) bool {
12791308
// http://proxy.com|http http to proxy, http to anywhere after that
12801309
// socks5://proxy.com|http|foo.com socks5 to proxy, then http to foo.com
12811310
// socks5://proxy.com|https|foo.com socks5 to proxy, then https to foo.com
1282-
//
1283-
// Note: no support to https to the proxy yet.
1311+
// https://proxy.com|https|foo.com https to proxy, then CONNECT to foo.com
1312+
// https://proxy.com|http https to proxy, http to anywhere after that
12841313
//
12851314
type connectMethod struct {
12861315
proxyURL *url.URL // nil for no proxy, else full proxy URL
12871316
targetScheme string // "http" or "https"
1288-
targetAddr string // Not used if http proxy + http targetScheme (4th example in table)
1317+
// If proxyURL specifies an http or https proxy, and targetScheme is http (not https),
1318+
// then targetAddr is not included in the connect method key, because the socket can
1319+
// be reused for different targetAddr values.
1320+
targetAddr string
12891321
}
12901322

12911323
func (cm *connectMethod) key() connectMethodKey {
12921324
proxyStr := ""
12931325
targetAddr := cm.targetAddr
12941326
if cm.proxyURL != nil {
12951327
proxyStr = cm.proxyURL.String()
1296-
if strings.HasPrefix(cm.proxyURL.Scheme, "http") && cm.targetScheme == "http" {
1328+
if (cm.proxyURL.Scheme == "http" || cm.proxyURL.Scheme == "https") && cm.targetScheme == "http" {
12971329
targetAddr = ""
12981330
}
12991331
}
@@ -1304,6 +1336,14 @@ func (cm *connectMethod) key() connectMethodKey {
13041336
}
13051337
}
13061338

1339+
// scheme returns the first hop scheme: http, https, or socks5
1340+
func (cm *connectMethod) scheme() string {
1341+
if cm.proxyURL != nil {
1342+
return cm.proxyURL.Scheme
1343+
}
1344+
return cm.targetScheme
1345+
}
1346+
13071347
// addr returns the first hop "host:port" to which we need to TCP connect.
13081348
func (cm *connectMethod) addr() string {
13091349
if cm.proxyURL != nil {

0 commit comments

Comments
 (0)