Skip to content

Commit 9a51899

Browse files
WeidiDenggopherbot
authored andcommitted
http2: add SETTINGS_ENABLE_CONNECT_PROTOCOL support
For golang/go#49918 Change-Id: Ibcd8fb189200c0976cf1bd03a796abae4afa4cfd GitHub-Last-Rev: cba5ecd GitHub-Pull-Request: #221 Reviewed-on: https://go-review.googlesource.com/c/net/+/610977 Reviewed-by: Damien Neil <[email protected]> Auto-Submit: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]>
1 parent 334afa0 commit 9a51899

File tree

5 files changed

+185
-49
lines changed

5 files changed

+185
-49
lines changed

http2/frame.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1490,15 +1490,15 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
14901490
pf := mh.PseudoFields()
14911491
for i, hf := range pf {
14921492
switch hf.Name {
1493-
case ":method", ":path", ":scheme", ":authority":
1493+
case ":method", ":path", ":scheme", ":authority", ":protocol":
14941494
isRequest = true
14951495
case ":status":
14961496
isResponse = true
14971497
default:
14981498
return pseudoHeaderError(hf.Name)
14991499
}
15001500
// Check for duplicates.
1501-
// This would be a bad algorithm, but N is 4.
1501+
// This would be a bad algorithm, but N is 5.
15021502
// And this doesn't allocate.
15031503
for _, hf2 := range pf[:i] {
15041504
if hf.Name == hf2.Name {

http2/http2.go

+26-16
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ import (
3434
)
3535

3636
var (
37-
VerboseLogs bool
38-
logFrameWrites bool
39-
logFrameReads bool
40-
inTests bool
37+
VerboseLogs bool
38+
logFrameWrites bool
39+
logFrameReads bool
40+
inTests bool
41+
disableExtendedConnectProtocol bool
4142
)
4243

4344
func init() {
@@ -50,6 +51,9 @@ func init() {
5051
logFrameWrites = true
5152
logFrameReads = true
5253
}
54+
if strings.Contains(e, "http2xconnect=0") {
55+
disableExtendedConnectProtocol = true
56+
}
5357
}
5458

5559
const (
@@ -141,6 +145,10 @@ func (s Setting) Valid() error {
141145
if s.Val < 16384 || s.Val > 1<<24-1 {
142146
return ConnectionError(ErrCodeProtocol)
143147
}
148+
case SettingEnableConnectProtocol:
149+
if s.Val != 1 && s.Val != 0 {
150+
return ConnectionError(ErrCodeProtocol)
151+
}
144152
}
145153
return nil
146154
}
@@ -150,21 +158,23 @@ func (s Setting) Valid() error {
150158
type SettingID uint16
151159

152160
const (
153-
SettingHeaderTableSize SettingID = 0x1
154-
SettingEnablePush SettingID = 0x2
155-
SettingMaxConcurrentStreams SettingID = 0x3
156-
SettingInitialWindowSize SettingID = 0x4
157-
SettingMaxFrameSize SettingID = 0x5
158-
SettingMaxHeaderListSize SettingID = 0x6
161+
SettingHeaderTableSize SettingID = 0x1
162+
SettingEnablePush SettingID = 0x2
163+
SettingMaxConcurrentStreams SettingID = 0x3
164+
SettingInitialWindowSize SettingID = 0x4
165+
SettingMaxFrameSize SettingID = 0x5
166+
SettingMaxHeaderListSize SettingID = 0x6
167+
SettingEnableConnectProtocol SettingID = 0x8
159168
)
160169

161170
var settingName = map[SettingID]string{
162-
SettingHeaderTableSize: "HEADER_TABLE_SIZE",
163-
SettingEnablePush: "ENABLE_PUSH",
164-
SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
165-
SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
166-
SettingMaxFrameSize: "MAX_FRAME_SIZE",
167-
SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
171+
SettingHeaderTableSize: "HEADER_TABLE_SIZE",
172+
SettingEnablePush: "ENABLE_PUSH",
173+
SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
174+
SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
175+
SettingMaxFrameSize: "MAX_FRAME_SIZE",
176+
SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
177+
SettingEnableConnectProtocol: "ENABLE_CONNECT_PROTOCOL",
168178
}
169179

170180
func (s SettingID) String() string {

http2/server.go

+26-9
Original file line numberDiff line numberDiff line change
@@ -932,14 +932,18 @@ func (sc *serverConn) serve(conf http2Config) {
932932
sc.vlogf("http2: server connection from %v on %p", sc.conn.RemoteAddr(), sc.hs)
933933
}
934934

935+
settings := writeSettings{
936+
{SettingMaxFrameSize, conf.MaxReadFrameSize},
937+
{SettingMaxConcurrentStreams, sc.advMaxStreams},
938+
{SettingMaxHeaderListSize, sc.maxHeaderListSize()},
939+
{SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize},
940+
{SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)},
941+
}
942+
if !disableExtendedConnectProtocol {
943+
settings = append(settings, Setting{SettingEnableConnectProtocol, 1})
944+
}
935945
sc.writeFrame(FrameWriteRequest{
936-
write: writeSettings{
937-
{SettingMaxFrameSize, conf.MaxReadFrameSize},
938-
{SettingMaxConcurrentStreams, sc.advMaxStreams},
939-
{SettingMaxHeaderListSize, sc.maxHeaderListSize()},
940-
{SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize},
941-
{SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)},
942-
},
946+
write: settings,
943947
})
944948
sc.unackedSettings++
945949

@@ -1801,6 +1805,9 @@ func (sc *serverConn) processSetting(s Setting) error {
18011805
sc.maxFrameSize = int32(s.Val) // the maximum valid s.Val is < 2^31
18021806
case SettingMaxHeaderListSize:
18031807
sc.peerMaxHeaderListSize = s.Val
1808+
case SettingEnableConnectProtocol:
1809+
// Receipt of this parameter by a server does not
1810+
// have any impact
18041811
default:
18051812
// Unknown setting: "An endpoint that receives a SETTINGS
18061813
// frame with any unknown or unsupported identifier MUST
@@ -2231,11 +2238,17 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
22312238
scheme: f.PseudoValue("scheme"),
22322239
authority: f.PseudoValue("authority"),
22332240
path: f.PseudoValue("path"),
2241+
protocol: f.PseudoValue("protocol"),
2242+
}
2243+
2244+
// extended connect is disabled, so we should not see :protocol
2245+
if disableExtendedConnectProtocol && rp.protocol != "" {
2246+
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
22342247
}
22352248

22362249
isConnect := rp.method == "CONNECT"
22372250
if isConnect {
2238-
if rp.path != "" || rp.scheme != "" || rp.authority == "" {
2251+
if rp.protocol == "" && (rp.path != "" || rp.scheme != "" || rp.authority == "") {
22392252
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
22402253
}
22412254
} else if rp.method == "" || rp.path == "" || (rp.scheme != "https" && rp.scheme != "http") {
@@ -2259,6 +2272,9 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
22592272
if rp.authority == "" {
22602273
rp.authority = rp.header.Get("Host")
22612274
}
2275+
if rp.protocol != "" {
2276+
rp.header.Set(":protocol", rp.protocol)
2277+
}
22622278

22632279
rw, req, err := sc.newWriterAndRequestNoBody(st, rp)
22642280
if err != nil {
@@ -2285,6 +2301,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
22852301
type requestParam struct {
22862302
method string
22872303
scheme, authority, path string
2304+
protocol string
22882305
header http.Header
22892306
}
22902307

@@ -2326,7 +2343,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*r
23262343

23272344
var url_ *url.URL
23282345
var requestURI string
2329-
if rp.method == "CONNECT" {
2346+
if rp.method == "CONNECT" && rp.protocol == "" {
23302347
url_ = &url.URL{Host: rp.authority}
23312348
requestURI = rp.authority // mimic HTTP/1 server behavior
23322349
} else {

http2/transport.go

+69-22
Original file line numberDiff line numberDiff line change
@@ -368,25 +368,26 @@ type ClientConn struct {
368368
idleTimeout time.Duration // or 0 for never
369369
idleTimer timer
370370

371-
mu sync.Mutex // guards following
372-
cond *sync.Cond // hold mu; broadcast on flow/closed changes
373-
flow outflow // our conn-level flow control quota (cs.outflow is per stream)
374-
inflow inflow // peer's conn-level flow control
375-
doNotReuse bool // whether conn is marked to not be reused for any future requests
376-
closing bool
377-
closed bool
378-
seenSettings bool // true if we've seen a settings frame, false otherwise
379-
wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
380-
goAway *GoAwayFrame // if non-nil, the GoAwayFrame we received
381-
goAwayDebug string // goAway frame's debug data, retained as a string
382-
streams map[uint32]*clientStream // client-initiated
383-
streamsReserved int // incr by ReserveNewRequest; decr on RoundTrip
384-
nextStreamID uint32
385-
pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams
386-
pings map[[8]byte]chan struct{} // in flight ping data to notification channel
387-
br *bufio.Reader
388-
lastActive time.Time
389-
lastIdle time.Time // time last idle
371+
mu sync.Mutex // guards following
372+
cond *sync.Cond // hold mu; broadcast on flow/closed changes
373+
flow outflow // our conn-level flow control quota (cs.outflow is per stream)
374+
inflow inflow // peer's conn-level flow control
375+
doNotReuse bool // whether conn is marked to not be reused for any future requests
376+
closing bool
377+
closed bool
378+
seenSettings bool // true if we've seen a settings frame, false otherwise
379+
seenSettingsChan chan struct{} // closed when seenSettings is true or frame reading fails
380+
wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
381+
goAway *GoAwayFrame // if non-nil, the GoAwayFrame we received
382+
goAwayDebug string // goAway frame's debug data, retained as a string
383+
streams map[uint32]*clientStream // client-initiated
384+
streamsReserved int // incr by ReserveNewRequest; decr on RoundTrip
385+
nextStreamID uint32
386+
pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams
387+
pings map[[8]byte]chan struct{} // in flight ping data to notification channel
388+
br *bufio.Reader
389+
lastActive time.Time
390+
lastIdle time.Time // time last idle
390391
// Settings from peer: (also guarded by wmu)
391392
maxFrameSize uint32
392393
maxConcurrentStreams uint32
@@ -396,6 +397,7 @@ type ClientConn struct {
396397
initialStreamRecvWindowSize int32
397398
readIdleTimeout time.Duration
398399
pingTimeout time.Duration
400+
extendedConnectAllowed bool
399401

400402
// pendingResets is the number of RST_STREAM frames we have sent to the peer,
401403
// without confirming that the peer has received them. When we send a RST_STREAM,
@@ -819,6 +821,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
819821
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
820822
streams: make(map[uint32]*clientStream),
821823
singleUse: singleUse,
824+
seenSettingsChan: make(chan struct{}),
822825
wantSettingsAck: true,
823826
readIdleTimeout: conf.SendPingTimeout,
824827
pingTimeout: conf.PingTimeout,
@@ -1466,6 +1469,8 @@ func (cs *clientStream) doRequest(req *http.Request, streamf func(*clientStream)
14661469
cs.cleanupWriteRequest(err)
14671470
}
14681471

1472+
var errExtendedConnectNotSupported = errors.New("net/http: extended connect not supported by peer")
1473+
14691474
// writeRequest sends a request.
14701475
//
14711476
// It returns nil after the request is written, the response read,
@@ -1481,12 +1486,31 @@ func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStre
14811486
return err
14821487
}
14831488

1489+
// wait for setting frames to be received, a server can change this value later,
1490+
// but we just wait for the first settings frame
1491+
var isExtendedConnect bool
1492+
if req.Method == "CONNECT" && req.Header.Get(":protocol") != "" {
1493+
isExtendedConnect = true
1494+
}
1495+
14841496
// Acquire the new-request lock by writing to reqHeaderMu.
14851497
// This lock guards the critical section covering allocating a new stream ID
14861498
// (requires mu) and creating the stream (requires wmu).
14871499
if cc.reqHeaderMu == nil {
14881500
panic("RoundTrip on uninitialized ClientConn") // for tests
14891501
}
1502+
if isExtendedConnect {
1503+
select {
1504+
case <-cs.reqCancel:
1505+
return errRequestCanceled
1506+
case <-ctx.Done():
1507+
return ctx.Err()
1508+
case <-cc.seenSettingsChan:
1509+
if !cc.extendedConnectAllowed {
1510+
return errExtendedConnectNotSupported
1511+
}
1512+
}
1513+
}
14901514
select {
14911515
case cc.reqHeaderMu <- struct{}{}:
14921516
case <-cs.reqCancel:
@@ -2030,7 +2054,7 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
20302054

20312055
func validateHeaders(hdrs http.Header) string {
20322056
for k, vv := range hdrs {
2033-
if !httpguts.ValidHeaderFieldName(k) {
2057+
if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" {
20342058
return fmt.Sprintf("name %q", k)
20352059
}
20362060
for _, v := range vv {
@@ -2046,6 +2070,10 @@ func validateHeaders(hdrs http.Header) string {
20462070

20472071
var errNilRequestURL = errors.New("http2: Request.URI is nil")
20482072

2073+
func isNormalConnect(req *http.Request) bool {
2074+
return req.Method == "CONNECT" && req.Header.Get(":protocol") == ""
2075+
}
2076+
20492077
// requires cc.wmu be held.
20502078
func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) {
20512079
cc.hbuf.Reset()
@@ -2066,7 +2094,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
20662094
}
20672095

20682096
var path string
2069-
if req.Method != "CONNECT" {
2097+
if !isNormalConnect(req) {
20702098
path = req.URL.RequestURI()
20712099
if !validPseudoPath(path) {
20722100
orig := path
@@ -2103,7 +2131,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
21032131
m = http.MethodGet
21042132
}
21052133
f(":method", m)
2106-
if req.Method != "CONNECT" {
2134+
if !isNormalConnect(req) {
21072135
f(":path", path)
21082136
f(":scheme", req.URL.Scheme)
21092137
}
@@ -2507,6 +2535,9 @@ func (rl *clientConnReadLoop) run() error {
25072535
if VerboseLogs {
25082536
cc.vlogf("http2: Transport conn %p received error from processing frame %v: %v", cc, summarizeFrame(f), err)
25092537
}
2538+
if !cc.seenSettings {
2539+
close(cc.seenSettingsChan)
2540+
}
25102541
return err
25112542
}
25122543
}
@@ -3073,6 +3104,21 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
30733104
case SettingHeaderTableSize:
30743105
cc.henc.SetMaxDynamicTableSize(s.Val)
30753106
cc.peerMaxHeaderTableSize = s.Val
3107+
case SettingEnableConnectProtocol:
3108+
if err := s.Valid(); err != nil {
3109+
return err
3110+
}
3111+
// If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL,
3112+
// we require that it do so in the first SETTINGS frame.
3113+
//
3114+
// When we attempt to use extended CONNECT, we wait for the first
3115+
// SETTINGS frame to see if the server supports it. If we let the
3116+
// server enable the feature with a later SETTINGS frame, then
3117+
// users will see inconsistent results depending on whether we've
3118+
// seen that frame or not.
3119+
if !cc.seenSettings {
3120+
cc.extendedConnectAllowed = s.Val == 1
3121+
}
30763122
default:
30773123
cc.vlogf("Unhandled Setting: %v", s)
30783124
}
@@ -3090,6 +3136,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
30903136
// connection can establish to our default.
30913137
cc.maxConcurrentStreams = defaultMaxConcurrentStreams
30923138
}
3139+
close(cc.seenSettingsChan)
30933140
cc.seenSettings = true
30943141
}
30953142

0 commit comments

Comments
 (0)