Skip to content

Commit 0aa844c

Browse files
committed
http2: support unencrypted HTTP/2 handoff from net/http
Allow net/http to pass unencrypted net.Conns to Server/Transport. We don't have an existing way to pass a conn other than a *tls.Conn into this package, so (ab)use TLSNextProto to pass unencrypted connections: The http2 package adds an "unencrypted_http2" entry to the TLSNextProto maps. The net/http package calls this function with a *tls.Conn wrapping a net.Conn with an UnencryptedNetConn method returning the underlying, unencrypted net.Conn. For golang/go#67816 Change-Id: I31f9c1ba31a17c82c8ed651382bd94193acf09b9 Reviewed-on: https://go-review.googlesource.com/c/net/+/625175 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: David Chase <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent f35fec9 commit 0aa844c

File tree

4 files changed

+96
-17
lines changed

4 files changed

+96
-17
lines changed

http2/client_conn_pool.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ package http2
88

99
import (
1010
"context"
11-
"crypto/tls"
1211
"errors"
12+
"net"
1313
"net/http"
1414
"sync"
1515
)
@@ -158,7 +158,7 @@ func (c *dialCall) dial(ctx context.Context, addr string) {
158158
// This code decides which ones live or die.
159159
// The return value used is whether c was used.
160160
// c is never closed.
161-
func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
161+
func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c net.Conn) (used bool, err error) {
162162
p.mu.Lock()
163163
for _, cc := range p.conns[key] {
164164
if cc.CanTakeNewRequest() {
@@ -194,8 +194,8 @@ type addConnCall struct {
194194
err error
195195
}
196196

197-
func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
198-
cc, err := t.NewClientConn(tc)
197+
func (c *addConnCall) run(t *Transport, key string, nc net.Conn) {
198+
cc, err := t.NewClientConn(nc)
199199

200200
p := c.p
201201
p.mu.Lock()

http2/server.go

+24-5
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ func ConfigureServer(s *http.Server, conf *Server) error {
306306
if s.TLSNextProto == nil {
307307
s.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){}
308308
}
309-
protoHandler := func(hs *http.Server, c *tls.Conn, h http.Handler) {
309+
protoHandler := func(hs *http.Server, c net.Conn, h http.Handler, sawClientPreface bool) {
310310
if testHookOnConn != nil {
311311
testHookOnConn()
312312
}
@@ -323,12 +323,31 @@ func ConfigureServer(s *http.Server, conf *Server) error {
323323
ctx = bc.BaseContext()
324324
}
325325
conf.ServeConn(c, &ServeConnOpts{
326-
Context: ctx,
327-
Handler: h,
328-
BaseConfig: hs,
326+
Context: ctx,
327+
Handler: h,
328+
BaseConfig: hs,
329+
SawClientPreface: sawClientPreface,
329330
})
330331
}
331-
s.TLSNextProto[NextProtoTLS] = protoHandler
332+
s.TLSNextProto[NextProtoTLS] = func(hs *http.Server, c *tls.Conn, h http.Handler) {
333+
protoHandler(hs, c, h, false)
334+
}
335+
// The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns.
336+
//
337+
// A connection passed in this method has already had the HTTP/2 preface read from it.
338+
s.TLSNextProto[nextProtoUnencryptedHTTP2] = func(hs *http.Server, c *tls.Conn, h http.Handler) {
339+
nc, err := unencryptedNetConnFromTLSConn(c)
340+
if err != nil {
341+
if lg := hs.ErrorLog; lg != nil {
342+
lg.Print(err)
343+
} else {
344+
log.Print(err)
345+
}
346+
go c.Close()
347+
return
348+
}
349+
protoHandler(hs, nc, h, true)
350+
}
332351
return nil
333352
}
334353

http2/transport.go

+36-8
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,8 @@ func configureTransports(t1 *http.Transport) (*Transport, error) {
281281
if !strSliceContains(t1.TLSClientConfig.NextProtos, "http/1.1") {
282282
t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1")
283283
}
284-
upgradeFn := func(authority string, c *tls.Conn) http.RoundTripper {
285-
addr := authorityAddr("https", authority)
284+
upgradeFn := func(scheme, authority string, c net.Conn) http.RoundTripper {
285+
addr := authorityAddr(scheme, authority)
286286
if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil {
287287
go c.Close()
288288
return erringRoundTripper{err}
@@ -293,18 +293,37 @@ func configureTransports(t1 *http.Transport) (*Transport, error) {
293293
// was unknown)
294294
go c.Close()
295295
}
296+
if scheme == "http" {
297+
return (*unencryptedTransport)(t2)
298+
}
296299
return t2
297300
}
298-
if m := t1.TLSNextProto; len(m) == 0 {
299-
t1.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{
300-
"h2": upgradeFn,
301+
if t1.TLSNextProto == nil {
302+
t1.TLSNextProto = make(map[string]func(string, *tls.Conn) http.RoundTripper)
303+
}
304+
t1.TLSNextProto[NextProtoTLS] = func(authority string, c *tls.Conn) http.RoundTripper {
305+
return upgradeFn("https", authority, c)
306+
}
307+
// The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns.
308+
t1.TLSNextProto[nextProtoUnencryptedHTTP2] = func(authority string, c *tls.Conn) http.RoundTripper {
309+
nc, err := unencryptedNetConnFromTLSConn(c)
310+
if err != nil {
311+
go c.Close()
312+
return erringRoundTripper{err}
301313
}
302-
} else {
303-
m["h2"] = upgradeFn
314+
return upgradeFn("http", authority, nc)
304315
}
305316
return t2, nil
306317
}
307318

319+
// unencryptedTransport is a Transport with a RoundTrip method that
320+
// always permits http:// URLs.
321+
type unencryptedTransport Transport
322+
323+
func (t *unencryptedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
324+
return (*Transport)(t).RoundTripOpt(req, RoundTripOpt{allowHTTP: true})
325+
}
326+
308327
func (t *Transport) connPool() ClientConnPool {
309328
t.connPoolOnce.Do(t.initConnPool)
310329
return t.connPoolOrDef
@@ -538,6 +557,8 @@ type RoundTripOpt struct {
538557
// no cached connection is available, RoundTripOpt
539558
// will return ErrNoCachedConn.
540559
OnlyCachedConn bool
560+
561+
allowHTTP bool // allow http:// URLs
541562
}
542563

543564
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -570,7 +591,14 @@ func authorityAddr(scheme string, authority string) (addr string) {
570591

571592
// RoundTripOpt is like RoundTrip, but takes options.
572593
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
573-
if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
594+
switch req.URL.Scheme {
595+
case "https":
596+
// Always okay.
597+
case "http":
598+
if !t.AllowHTTP && !opt.allowHTTP {
599+
return nil, errors.New("http2: unencrypted HTTP/2 not enabled")
600+
}
601+
default:
574602
return nil, errors.New("http2: unsupported scheme")
575603
}
576604

http2/unencrypted.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package http2
6+
7+
import (
8+
"crypto/tls"
9+
"errors"
10+
"net"
11+
)
12+
13+
const nextProtoUnencryptedHTTP2 = "unencrypted_http2"
14+
15+
// unencryptedNetConnFromTLSConn retrieves a net.Conn wrapped in a *tls.Conn.
16+
//
17+
// TLSNextProto functions accept a *tls.Conn.
18+
//
19+
// When passing an unencrypted HTTP/2 connection to a TLSNextProto function,
20+
// we pass a *tls.Conn with an underlying net.Conn containing the unencrypted connection.
21+
// To be extra careful about mistakes (accidentally dropping TLS encryption in a place
22+
// where we want it), the tls.Conn contains a net.Conn with an UnencryptedNetConn method
23+
// that returns the actual connection we want to use.
24+
func unencryptedNetConnFromTLSConn(tc *tls.Conn) (net.Conn, error) {
25+
conner, ok := tc.NetConn().(interface {
26+
UnencryptedNetConn() net.Conn
27+
})
28+
if !ok {
29+
return nil, errors.New("http2: TLS conn unexpectedly found in unencrypted handoff")
30+
}
31+
return conner.UnencryptedNetConn(), nil
32+
}

0 commit comments

Comments
 (0)