Skip to content

Commit 35028a4

Browse files
committed
http2: don't ignore DATA padding in flow control
http://httpwg.org/specs/rfc7540.html#rfc.section.6.1 says: > The entire DATA frame payload is included in flow control, including > the Pad Length and Padding fields if present. But we were just ignoring the padding and pad length, which could lead to clients and servers getting out of sync and deadlock if peers used padding. (Go never does, so it didn't affect Go<->Go) In the process, fix a lingering bug from golang/go#16481 where we didn't account for flow control returned to peers in the stream's inflow, despite sending the peer a WINDOW_UPDATE. Fixes golang/go#16556 Updates golang/go#16481 Change-Id: If7150fa8f0da92a60f34af9c3f754a0346526ece Reviewed-on: https://go-review.googlesource.com/25382 Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Andrew Gerrand <[email protected]>
1 parent 6a513af commit 35028a4

File tree

6 files changed

+256
-23
lines changed

6 files changed

+256
-23
lines changed

http2/frame.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ func parseDataFrame(fh FrameHeader, payload []byte) (Frame, error) {
594594
var (
595595
errStreamID = errors.New("invalid stream ID")
596596
errDepStreamID = errors.New("invalid dependent stream ID")
597+
errPadLength = errors.New("pad length too large")
597598
)
598599

599600
func validStreamIDOrZero(streamID uint32) bool {
@@ -607,18 +608,40 @@ func validStreamID(streamID uint32) bool {
607608
// WriteData writes a DATA frame.
608609
//
609610
// It will perform exactly one Write to the underlying Writer.
610-
// It is the caller's responsibility to not call other Write methods concurrently.
611+
// It is the caller's responsibility not to violate the maximum frame size
612+
// and to not call other Write methods concurrently.
611613
func (f *Framer) WriteData(streamID uint32, endStream bool, data []byte) error {
612-
// TODO: ignoring padding for now. will add when somebody cares.
614+
return f.WriteDataPadded(streamID, endStream, data, nil)
615+
}
616+
617+
// WriteData writes a DATA frame with optional padding.
618+
//
619+
// If pad is nil, the padding bit is not sent.
620+
// The length of pad must not exceed 255 bytes.
621+
//
622+
// It will perform exactly one Write to the underlying Writer.
623+
// It is the caller's responsibility not to violate the maximum frame size
624+
// and to not call other Write methods concurrently.
625+
func (f *Framer) WriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error {
613626
if !validStreamID(streamID) && !f.AllowIllegalWrites {
614627
return errStreamID
615628
}
629+
if len(pad) > 255 {
630+
return errPadLength
631+
}
616632
var flags Flags
617633
if endStream {
618634
flags |= FlagDataEndStream
619635
}
636+
if pad != nil {
637+
flags |= FlagDataPadded
638+
}
620639
f.startWrite(FrameData, flags, streamID)
640+
if pad != nil {
641+
f.wbuf = append(f.wbuf, byte(len(pad)))
642+
}
621643
f.wbuf = append(f.wbuf, data...)
644+
f.wbuf = append(f.wbuf, pad...)
622645
return f.endWrite()
623646
}
624647

http2/frame_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,77 @@ func TestWriteData(t *testing.T) {
100100
}
101101
}
102102

103+
func TestWriteDataPadded(t *testing.T) {
104+
tests := [...]struct {
105+
streamID uint32
106+
endStream bool
107+
data []byte
108+
pad []byte
109+
wantHeader FrameHeader
110+
}{
111+
// Unpadded:
112+
0: {
113+
streamID: 1,
114+
endStream: true,
115+
data: []byte("foo"),
116+
pad: nil,
117+
wantHeader: FrameHeader{
118+
Type: FrameData,
119+
Flags: FlagDataEndStream,
120+
Length: 3,
121+
StreamID: 1,
122+
},
123+
},
124+
125+
// Padded bit set, but no padding:
126+
1: {
127+
streamID: 1,
128+
endStream: true,
129+
data: []byte("foo"),
130+
pad: []byte{},
131+
wantHeader: FrameHeader{
132+
Type: FrameData,
133+
Flags: FlagDataEndStream | FlagDataPadded,
134+
Length: 4,
135+
StreamID: 1,
136+
},
137+
},
138+
139+
// Padded bit set, with padding:
140+
2: {
141+
streamID: 1,
142+
endStream: false,
143+
data: []byte("foo"),
144+
pad: []byte("bar"),
145+
wantHeader: FrameHeader{
146+
Type: FrameData,
147+
Flags: FlagDataPadded,
148+
Length: 7,
149+
StreamID: 1,
150+
},
151+
},
152+
}
153+
for i, tt := range tests {
154+
fr, _ := testFramer()
155+
fr.WriteDataPadded(tt.streamID, tt.endStream, tt.data, tt.pad)
156+
f, err := fr.ReadFrame()
157+
if err != nil {
158+
t.Errorf("%d. ReadFrame: %v", i, err)
159+
continue
160+
}
161+
got := f.Header()
162+
tt.wantHeader.valid = true
163+
if got != tt.wantHeader {
164+
t.Errorf("%d. read %+v; want %+v", i, got, tt.wantHeader)
165+
continue
166+
}
167+
df := f.(*DataFrame)
168+
if !bytes.Equal(df.Data(), tt.data) {
169+
t.Errorf("%d. got %q; want %q", i, df.Data(), tt.data)
170+
}
171+
}
172+
}
173+
103174
func TestWriteHeaders(t *testing.T) {
104175
tests := []struct {
105176
name string

http2/server.go

+22-12
Original file line numberDiff line numberDiff line change
@@ -1298,15 +1298,15 @@ func (sc *serverConn) processData(f *DataFrame) error {
12981298
// But still enforce their connection-level flow control,
12991299
// and return any flow control bytes since we're not going
13001300
// to consume them.
1301-
if int(sc.inflow.available()) < len(data) {
1301+
if sc.inflow.available() < int32(f.Length) {
13021302
return StreamError{id, ErrCodeFlowControl}
13031303
}
13041304
// Deduct the flow control from inflow, since we're
13051305
// going to immediately add it back in
13061306
// sendWindowUpdate, which also schedules sending the
13071307
// frames.
1308-
sc.inflow.take(int32(len(data)))
1309-
sc.sendWindowUpdate(nil, len(data)) // conn-level
1308+
sc.inflow.take(int32(f.Length))
1309+
sc.sendWindowUpdate(nil, int(f.Length)) // conn-level
13101310

13111311
return StreamError{id, ErrCodeStreamClosed}
13121312
}
@@ -1319,20 +1319,30 @@ func (sc *serverConn) processData(f *DataFrame) error {
13191319
st.body.CloseWithError(fmt.Errorf("sender tried to send more than declared Content-Length of %d bytes", st.declBodyBytes))
13201320
return StreamError{id, ErrCodeStreamClosed}
13211321
}
1322-
if len(data) > 0 {
1322+
if f.Length > 0 {
13231323
// Check whether the client has flow control quota.
1324-
if int(st.inflow.available()) < len(data) {
1324+
if st.inflow.available() < int32(f.Length) {
13251325
return StreamError{id, ErrCodeFlowControl}
13261326
}
1327-
st.inflow.take(int32(len(data)))
1328-
wrote, err := st.body.Write(data)
1329-
if err != nil {
1330-
return StreamError{id, ErrCodeStreamClosed}
1327+
st.inflow.take(int32(f.Length))
1328+
1329+
if len(data) > 0 {
1330+
wrote, err := st.body.Write(data)
1331+
if err != nil {
1332+
return StreamError{id, ErrCodeStreamClosed}
1333+
}
1334+
if wrote != len(data) {
1335+
panic("internal error: bad Writer")
1336+
}
1337+
st.bodyBytes += int64(len(data))
13311338
}
1332-
if wrote != len(data) {
1333-
panic("internal error: bad Writer")
1339+
1340+
// Return any padded flow control now, since we won't
1341+
// refund it later on body reads.
1342+
if pad := int32(f.Length) - int32(len(data)); pad > 0 {
1343+
sc.sendWindowUpdate32(nil, pad)
1344+
sc.sendWindowUpdate32(st, pad)
13341345
}
1335-
st.bodyBytes += int64(len(data))
13361346
}
13371347
if f.StreamEnded() {
13381348
st.endStream()

http2/server_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,12 @@ func (st *serverTester) writeData(streamID uint32, endStream bool, data []byte)
359359
}
360360
}
361361

362+
func (st *serverTester) writeDataPadded(streamID uint32, endStream bool, data, pad []byte) {
363+
if err := st.fr.WriteDataPadded(streamID, endStream, data, pad); err != nil {
364+
st.t.Fatalf("Error writing DATA: %v", err)
365+
}
366+
}
367+
362368
func (st *serverTester) readFrame() (Frame, error) {
363369
go func() {
364370
fr, err := st.fr.ReadFrame()
@@ -1083,6 +1089,40 @@ func TestServer_Handler_Sends_WindowUpdate(t *testing.T) {
10831089
st.wantWindowUpdate(0, 3) // no more stream-level, since END_STREAM
10841090
}
10851091

1092+
// the version of the TestServer_Handler_Sends_WindowUpdate with padding.
1093+
// See golang.org/issue/16556
1094+
func TestServer_Handler_Sends_WindowUpdate_Padding(t *testing.T) {
1095+
puppet := newHandlerPuppet()
1096+
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
1097+
puppet.act(w, r)
1098+
})
1099+
defer st.Close()
1100+
defer puppet.done()
1101+
1102+
st.greet()
1103+
1104+
st.writeHeaders(HeadersFrameParam{
1105+
StreamID: 1,
1106+
BlockFragment: st.encodeHeader(":method", "POST"),
1107+
EndStream: false,
1108+
EndHeaders: true,
1109+
})
1110+
st.writeDataPadded(1, false, []byte("abcdef"), []byte("1234"))
1111+
1112+
// Expect to immediately get our 5 bytes of padding back for
1113+
// both the connection and stream (4 bytes of padding + 1 byte of length)
1114+
st.wantWindowUpdate(0, 5)
1115+
st.wantWindowUpdate(1, 5)
1116+
1117+
puppet.do(readBodyHandler(t, "abc"))
1118+
st.wantWindowUpdate(0, 3)
1119+
st.wantWindowUpdate(1, 3)
1120+
1121+
puppet.do(readBodyHandler(t, "def"))
1122+
st.wantWindowUpdate(0, 3)
1123+
st.wantWindowUpdate(1, 3)
1124+
}
1125+
10861126
func TestServer_Send_GoAway_After_Bogus_WindowUpdate(t *testing.T) {
10871127
st := newServerTester(t, nil)
10881128
defer st.Close()

http2/transport.go

+26-9
Original file line numberDiff line numberDiff line change
@@ -1581,34 +1581,51 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
15811581
// by the peer? Tough without accumulating too much state.
15821582

15831583
// But at least return their flow control:
1584-
if len(data) > 0 {
1584+
if f.Length > 0 {
1585+
cc.mu.Lock()
1586+
cc.inflow.add(int32(f.Length))
1587+
cc.mu.Unlock()
1588+
15851589
cc.wmu.Lock()
1586-
cc.fr.WriteWindowUpdate(0, uint32(len(data)))
1590+
cc.fr.WriteWindowUpdate(0, uint32(f.Length))
15871591
cc.bw.Flush()
15881592
cc.wmu.Unlock()
15891593
}
15901594
return nil
15911595
}
1592-
if len(data) > 0 {
1593-
if cs.bufPipe.b == nil {
1596+
if f.Length > 0 {
1597+
if len(data) > 0 && cs.bufPipe.b == nil {
15941598
// Data frame after it's already closed?
15951599
cc.logf("http2: Transport received DATA frame for closed stream; closing connection")
15961600
return ConnectionError(ErrCodeProtocol)
15971601
}
15981602

15991603
// Check connection-level flow control.
16001604
cc.mu.Lock()
1601-
if cs.inflow.available() >= int32(len(data)) {
1602-
cs.inflow.take(int32(len(data)))
1605+
if cs.inflow.available() >= int32(f.Length) {
1606+
cs.inflow.take(int32(f.Length))
16031607
} else {
16041608
cc.mu.Unlock()
16051609
return ConnectionError(ErrCodeFlowControl)
16061610
}
1611+
// Return any padded flow control now, since we won't
1612+
// refund it later on body reads.
1613+
if pad := int32(f.Length) - int32(len(data)); pad > 0 {
1614+
cs.inflow.add(pad)
1615+
cc.inflow.add(pad)
1616+
cc.wmu.Lock()
1617+
cc.fr.WriteWindowUpdate(0, uint32(pad))
1618+
cc.fr.WriteWindowUpdate(cs.ID, uint32(pad))
1619+
cc.bw.Flush()
1620+
cc.wmu.Unlock()
1621+
}
16071622
cc.mu.Unlock()
16081623

1609-
if _, err := cs.bufPipe.Write(data); err != nil {
1610-
rl.endStreamError(cs, err)
1611-
return err
1624+
if len(data) > 0 {
1625+
if _, err := cs.bufPipe.Write(data); err != nil {
1626+
rl.endStreamError(cs, err)
1627+
return err
1628+
}
16121629
}
16131630
}
16141631

http2/transport_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -2177,3 +2177,75 @@ func TestTransportReturnsUnusedFlowControl(t *testing.T) {
21772177
}
21782178
ct.run()
21792179
}
2180+
2181+
// See golang.org/issue/16556
2182+
func TestTransportReturnsDataPaddingFlowControl(t *testing.T) {
2183+
ct := newClientTester(t)
2184+
2185+
unblockClient := make(chan bool, 1)
2186+
2187+
ct.client = func() error {
2188+
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
2189+
res, err := ct.tr.RoundTrip(req)
2190+
if err != nil {
2191+
return err
2192+
}
2193+
defer res.Body.Close()
2194+
<-unblockClient
2195+
return nil
2196+
}
2197+
ct.server = func() error {
2198+
ct.greet()
2199+
2200+
var hf *HeadersFrame
2201+
for {
2202+
f, err := ct.fr.ReadFrame()
2203+
if err != nil {
2204+
return fmt.Errorf("ReadFrame while waiting for Headers: %v", err)
2205+
}
2206+
switch f.(type) {
2207+
case *WindowUpdateFrame, *SettingsFrame:
2208+
continue
2209+
}
2210+
var ok bool
2211+
hf, ok = f.(*HeadersFrame)
2212+
if !ok {
2213+
return fmt.Errorf("Got %T; want HeadersFrame", f)
2214+
}
2215+
break
2216+
}
2217+
2218+
var buf bytes.Buffer
2219+
enc := hpack.NewEncoder(&buf)
2220+
enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
2221+
enc.WriteField(hpack.HeaderField{Name: "content-length", Value: "5000"})
2222+
ct.fr.WriteHeaders(HeadersFrameParam{
2223+
StreamID: hf.StreamID,
2224+
EndHeaders: true,
2225+
EndStream: false,
2226+
BlockFragment: buf.Bytes(),
2227+
})
2228+
pad := []byte("12345")
2229+
ct.fr.WriteDataPadded(hf.StreamID, false, make([]byte, 5000), pad) // without ending stream
2230+
2231+
f, err := ct.fr.ReadFrame()
2232+
if err != nil {
2233+
return fmt.Errorf("ReadFrame while waiting for first WindowUpdateFrame: %v", err)
2234+
}
2235+
wantBack := uint32(len(pad)) + 1 // one byte for the length of the padding
2236+
if wuf, ok := f.(*WindowUpdateFrame); !ok || wuf.Increment != wantBack || wuf.StreamID != 0 {
2237+
return fmt.Errorf("Expected conn WindowUpdateFrame for %d bytes; got %v", wantBack, summarizeFrame(f))
2238+
}
2239+
2240+
f, err = ct.fr.ReadFrame()
2241+
if err != nil {
2242+
return fmt.Errorf("ReadFrame while waiting for second WindowUpdateFrame: %v", err)
2243+
}
2244+
if wuf, ok := f.(*WindowUpdateFrame); !ok || wuf.Increment != wantBack || wuf.StreamID == 0 {
2245+
return fmt.Errorf("Expected stream WindowUpdateFrame for %d bytes; got %v", wantBack, summarizeFrame(f))
2246+
}
2247+
unblockClient <- true
2248+
return nil
2249+
}
2250+
ct.run()
2251+
}

0 commit comments

Comments
 (0)