Skip to content

Commit dbb9451

Browse files
authored
Fixes #10679 - Review HTTP/2 rate control. (#10681)
* Bumped the rate control rate from 50 events/s to 128. * Added rate control for all CONTINUATION frames. * Added rate control for invalid PUSH_PROMISE frames. * Added rate control for RST_STREAM frames. * Added rate control for all SETTINGS frames. * Fixed growth of header block accumulation buffer. Signed-off-by: Simone Bordet <[email protected]>
1 parent 90fdd42 commit dbb9451

File tree

12 files changed

+184
-48
lines changed

12 files changed

+184
-48
lines changed

jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
*/
4040
public abstract class BodyParser
4141
{
42-
protected static final Logger LOG = LoggerFactory.getLogger(BodyParser.class);
42+
private static final Logger LOG = LoggerFactory.getLogger(BodyParser.class);
4343

4444
private final HeaderParser headerParser;
4545
private final Parser.Listener listener;

jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java

+26-10
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,28 @@ public boolean parse(ByteBuffer buffer)
7575
int remaining = buffer.remaining();
7676
if (remaining < length)
7777
{
78-
headerBlockFragments.storeFragment(buffer, remaining, false);
78+
ContinuationFrame frame = new ContinuationFrame(getStreamId(), false);
79+
if (!rateControlOnEvent(frame))
80+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
81+
82+
if (!headerBlockFragments.storeFragment(buffer, remaining, false))
83+
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_continuation_stream");
84+
7985
length -= remaining;
8086
break;
8187
}
8288
else
8389
{
84-
boolean last = hasFlag(Flags.END_HEADERS);
85-
headerBlockFragments.storeFragment(buffer, length, last);
90+
boolean endHeaders = hasFlag(Flags.END_HEADERS);
91+
ContinuationFrame frame = new ContinuationFrame(getStreamId(), endHeaders);
92+
if (!rateControlOnEvent(frame))
93+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
94+
95+
if (!headerBlockFragments.storeFragment(buffer, length, endHeaders))
96+
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_continuation_stream");
97+
8698
reset();
87-
if (last)
99+
if (endHeaders)
88100
return onHeaders(buffer);
89101
return true;
90102
}
@@ -103,17 +115,21 @@ private boolean onHeaders(ByteBuffer buffer)
103115
ByteBuffer headerBlock = headerBlockFragments.complete();
104116
MetaData metaData = headerBlockParser.parse(headerBlock, headerBlock.remaining());
105117
headerBlockFragments.getByteBufferPool().release(headerBlock);
106-
if (metaData == null)
107-
return true;
118+
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, headerBlockFragments.getPriorityFrame(), headerBlockFragments.isEndStream());
119+
headerBlockFragments.reset();
120+
108121
if (metaData == HeaderBlockParser.SESSION_FAILURE)
109122
return false;
110-
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, headerBlockFragments.getPriorityFrame(), headerBlockFragments.isEndStream());
111-
if (metaData == HeaderBlockParser.STREAM_FAILURE)
123+
124+
if (metaData != HeaderBlockParser.STREAM_FAILURE)
125+
{
126+
notifyHeaders(frame);
127+
}
128+
else
112129
{
113130
if (!rateControlOnEvent(frame))
114-
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
131+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
115132
}
116-
notifyHeaders(frame);
117133
return true;
118134
}
119135

jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockFragments.java

+27-8
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,53 @@
2121
public class HeaderBlockFragments
2222
{
2323
private final ByteBufferPool byteBufferPool;
24+
private final int maxCapacity;
2425
private PriorityFrame priorityFrame;
25-
private boolean endStream;
2626
private int streamId;
27+
private boolean endStream;
2728
private ByteBuffer storage;
2829

30+
@Deprecated
2931
public HeaderBlockFragments(ByteBufferPool byteBufferPool)
32+
{
33+
this(byteBufferPool, 8192);
34+
}
35+
36+
public HeaderBlockFragments(ByteBufferPool byteBufferPool, int maxCapacity)
3037
{
3138
this.byteBufferPool = byteBufferPool;
39+
this.maxCapacity = maxCapacity;
3240
}
3341

3442
public ByteBufferPool getByteBufferPool()
3543
{
3644
return byteBufferPool;
3745
}
3846

39-
public void storeFragment(ByteBuffer fragment, int length, boolean last)
47+
void reset()
48+
{
49+
priorityFrame = null;
50+
streamId = 0;
51+
endStream = false;
52+
storage = null;
53+
}
54+
55+
public boolean storeFragment(ByteBuffer fragment, int length, boolean last)
4056
{
4157
if (storage == null)
4258
{
43-
int space = last ? length : length * 2;
44-
storage = byteBufferPool.acquire(space, fragment.isDirect());
59+
if (length > maxCapacity)
60+
return false;
61+
int capacity = last ? length : length * 2;
62+
storage = byteBufferPool.acquire(capacity, fragment.isDirect());
4563
storage.clear();
4664
}
4765

4866
// Grow the storage if necessary.
4967
if (storage.remaining() < length)
5068
{
69+
if (storage.position() + length > maxCapacity)
70+
return false;
5171
int space = last ? length : length * 2;
5272
int capacity = storage.position() + space;
5373
ByteBuffer newStorage = byteBufferPool.acquire(capacity, storage.isDirect());
@@ -63,6 +83,7 @@ public void storeFragment(ByteBuffer fragment, int length, boolean last)
6383
fragment.limit(fragment.position() + length);
6484
storage.put(fragment);
6585
fragment.limit(limit);
86+
return true;
6687
}
6788

6889
public PriorityFrame getPriorityFrame()
@@ -87,10 +108,8 @@ public void setEndStream(boolean endStream)
87108

88109
public ByteBuffer complete()
89110
{
90-
ByteBuffer result = storage;
91-
storage = null;
92-
result.flip();
93-
return result;
111+
storage.flip();
112+
return storage;
94113
}
95114

96115
public int getStreamId()

jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java

+31-10
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@
2222
import org.eclipse.jetty.http2.frames.HeadersFrame;
2323
import org.eclipse.jetty.http2.frames.PriorityFrame;
2424
import org.eclipse.jetty.util.BufferUtil;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
2527

2628
public class HeadersBodyParser extends BodyParser
2729
{
30+
private static final Logger LOG = LoggerFactory.getLogger(HeadersBodyParser.class);
31+
2832
private final HeaderBlockParser headerBlockParser;
2933
private final HeaderBlockFragments headerBlockFragments;
3034
private State state = State.PREPARE;
@@ -71,8 +75,15 @@ else if (hasFlag(Flags.END_HEADERS))
7175
}
7276
else
7377
{
74-
headerBlockFragments.setStreamId(getStreamId());
75-
headerBlockFragments.setEndStream(isEndStream());
78+
if (headerBlockFragments.getStreamId() != 0)
79+
{
80+
connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_frame");
81+
}
82+
else
83+
{
84+
headerBlockFragments.setStreamId(getStreamId());
85+
headerBlockFragments.setEndStream(isEndStream());
86+
}
7687
}
7788
}
7889

@@ -167,6 +178,18 @@ else if (hasFlag(Flags.PRIORITY))
167178
break;
168179
}
169180
case HEADERS:
181+
{
182+
if (!hasFlag(Flags.END_HEADERS))
183+
{
184+
headerBlockFragments.setStreamId(getStreamId());
185+
headerBlockFragments.setEndStream(isEndStream());
186+
if (hasFlag(Flags.PRIORITY))
187+
headerBlockFragments.setPriorityFrame(new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive));
188+
}
189+
state = State.HEADER_BLOCK;
190+
break;
191+
}
192+
case HEADER_BLOCK:
170193
{
171194
if (hasFlag(Flags.END_HEADERS))
172195
{
@@ -191,7 +214,7 @@ else if (hasFlag(Flags.PRIORITY))
191214
{
192215
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
193216
if (!rateControlOnEvent(frame))
194-
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
217+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
195218
}
196219
}
197220
}
@@ -200,16 +223,14 @@ else if (hasFlag(Flags.PRIORITY))
200223
int remaining = buffer.remaining();
201224
if (remaining < length)
202225
{
203-
headerBlockFragments.storeFragment(buffer, remaining, false);
226+
if (!headerBlockFragments.storeFragment(buffer, remaining, false))
227+
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_frame");
204228
length -= remaining;
205229
}
206230
else
207231
{
208-
headerBlockFragments.setStreamId(getStreamId());
209-
headerBlockFragments.setEndStream(isEndStream());
210-
if (hasFlag(Flags.PRIORITY))
211-
headerBlockFragments.setPriorityFrame(new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive));
212-
headerBlockFragments.storeFragment(buffer, length, false);
232+
if (!headerBlockFragments.storeFragment(buffer, length, false))
233+
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_frame");
213234
state = State.PADDING;
214235
loop = paddingLength == 0;
215236
}
@@ -253,6 +274,6 @@ private void onHeaders(HeadersFrame frame)
253274

254275
private enum State
255276
{
256-
PREPARE, PADDING_LENGTH, EXCLUSIVE, PARENT_STREAM_ID, PARENT_STREAM_ID_BYTES, WEIGHT, HEADERS, PADDING
277+
PREPARE, PADDING_LENGTH, EXCLUSIVE, PARENT_STREAM_ID, PARENT_STREAM_ID_BYTES, WEIGHT, HEADERS, HEADER_BLOCK, PADDING
257278
}
258279
}

jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void init(Listener listener)
8585
this.listener = listener;
8686
unknownBodyParser = new UnknownBodyParser(headerParser, listener);
8787
HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, hpackDecoder, unknownBodyParser);
88-
HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments(byteBufferPool);
88+
HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments(byteBufferPool, hpackDecoder.getMaxHeaderListSize());
8989
bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener);
9090
bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments);
9191
bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener);

jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PushPromiseBodyParser.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.eclipse.jetty.http.MetaData;
1919
import org.eclipse.jetty.http2.ErrorCode;
2020
import org.eclipse.jetty.http2.Flags;
21+
import org.eclipse.jetty.http2.frames.HeadersFrame;
2122
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
2223

2324
public class PushPromiseBodyParser extends BodyParser
@@ -65,13 +66,9 @@ public boolean parse(ByteBuffer buffer)
6566
length = getBodyLength();
6667

6768
if (isPadding())
68-
{
6969
state = State.PADDING_LENGTH;
70-
}
7170
else
72-
{
7371
state = State.STREAM_ID;
74-
}
7572
break;
7673
}
7774
case PADDING_LENGTH:
@@ -131,7 +128,15 @@ public boolean parse(ByteBuffer buffer)
131128
state = State.PADDING;
132129
loop = paddingLength == 0;
133130
if (metaData != HeaderBlockParser.STREAM_FAILURE)
131+
{
134132
onPushPromise(streamId, metaData);
133+
}
134+
else
135+
{
136+
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
137+
if (!rateControlOnEvent(frame))
138+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
139+
}
135140
}
136141
break;
137142
}

jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ResetBodyParser.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public boolean parse(ByteBuffer buffer)
5858
{
5959
if (buffer.remaining() >= 4)
6060
{
61-
return onReset(buffer.getInt());
61+
return onReset(buffer, buffer.getInt());
6262
}
6363
else
6464
{
@@ -73,7 +73,7 @@ public boolean parse(ByteBuffer buffer)
7373
--cursor;
7474
error += currByte << (8 * cursor);
7575
if (cursor == 0)
76-
return onReset(error);
76+
return onReset(buffer, error);
7777
break;
7878
}
7979
default:
@@ -85,9 +85,11 @@ public boolean parse(ByteBuffer buffer)
8585
return false;
8686
}
8787

88-
private boolean onReset(int error)
88+
private boolean onReset(ByteBuffer buffer, int error)
8989
{
9090
ResetFrame frame = new ResetFrame(getStreamId(), error);
91+
if (!rateControlOnEvent(frame))
92+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_rst_stream_frame_rate");
9193
reset();
9294
notifyReset(frame);
9395
return true;

jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,7 @@ protected void emptyBody(ByteBuffer buffer)
7272
return;
7373
boolean isReply = hasFlag(Flags.ACK);
7474
SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), isReply);
75-
if (!isReply && !rateControlOnEvent(frame))
76-
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame_rate");
77-
else
78-
onSettings(frame);
75+
onSettings(buffer, frame);
7976
}
8077

8178
private boolean validateFrame(ByteBuffer buffer, int streamId, int bodyLength)
@@ -218,11 +215,13 @@ protected boolean onSettings(ByteBuffer buffer, Map<Integer, Integer> settings)
218215
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_max_frame_size");
219216

220217
SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flags.ACK));
221-
return onSettings(frame);
218+
return onSettings(buffer, frame);
222219
}
223220

224-
private boolean onSettings(SettingsFrame frame)
221+
private boolean onSettings(ByteBuffer buffer, SettingsFrame frame)
225222
{
223+
if (!rateControlOnEvent(frame))
224+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame_rate");
226225
reset();
227226
notifySettings(frame);
228227
return true;

jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ public boolean parse(ByteBuffer buffer)
3535
boolean parsed = cursor == 0;
3636
if (parsed && !rateControlOnEvent(new UnknownFrame(getFrameType())))
3737
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_unknown_frame_rate");
38-
3938
return parsed;
4039
}
4140

0 commit comments

Comments
 (0)