Skip to content

Commit 0d344f4

Browse files
committed
Fixes #10679 - Review HTTP/2 rate control.
* 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 d1d2af6 commit 0d344f4

File tree

11 files changed

+179
-49
lines changed

11 files changed

+179
-49
lines changed

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

+26-10
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,28 @@ public boolean parse(ByteBuffer buffer)
7676
int remaining = buffer.remaining();
7777
if (remaining < length)
7878
{
79-
headerBlockFragments.storeFragment(buffer, remaining, false);
79+
ContinuationFrame frame = new ContinuationFrame(getStreamId(), false);
80+
if (!rateControlOnEvent(frame))
81+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
82+
83+
if (!headerBlockFragments.storeFragment(buffer, remaining, false))
84+
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_continuation_stream");
85+
8086
length -= remaining;
8187
break;
8288
}
8389
else
8490
{
85-
boolean last = hasFlag(Flags.END_HEADERS);
86-
headerBlockFragments.storeFragment(buffer, length, last);
91+
boolean endHeaders = hasFlag(Flags.END_HEADERS);
92+
ContinuationFrame frame = new ContinuationFrame(getStreamId(), endHeaders);
93+
if (!rateControlOnEvent(frame))
94+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
95+
96+
if (!headerBlockFragments.storeFragment(buffer, length, endHeaders))
97+
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_continuation_stream");
98+
8799
reset();
88-
if (last)
100+
if (endHeaders)
89101
return onHeaders(buffer);
90102
return true;
91103
}
@@ -104,17 +116,21 @@ private boolean onHeaders(ByteBuffer buffer)
104116
RetainableByteBuffer headerBlock = headerBlockFragments.complete();
105117
MetaData metaData = headerBlockParser.parse(headerBlock.getByteBuffer(), headerBlock.remaining());
106118
headerBlock.release();
107-
if (metaData == null)
108-
return true;
119+
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, headerBlockFragments.getPriorityFrame(), headerBlockFragments.isEndStream());
120+
headerBlockFragments.reset();
121+
109122
if (metaData == HeaderBlockParser.SESSION_FAILURE)
110123
return false;
111-
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, headerBlockFragments.getPriorityFrame(), headerBlockFragments.isEndStream());
112-
if (metaData == HeaderBlockParser.STREAM_FAILURE)
124+
125+
if (metaData != HeaderBlockParser.STREAM_FAILURE)
126+
{
127+
notifyHeaders(frame);
128+
}
129+
else
113130
{
114131
if (!rateControlOnEvent(frame))
115-
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
132+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
116133
}
117-
notifyHeaders(frame);
118134
return true;
119135
}
120136

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

+22-9
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,43 @@
2323
public class HeaderBlockFragments
2424
{
2525
private final ByteBufferPool bufferPool;
26+
private final int maxCapacity;
2627
private PriorityFrame priorityFrame;
27-
private boolean endStream;
2828
private int streamId;
29+
private boolean endStream;
2930
private RetainableByteBuffer storage;
3031

31-
public HeaderBlockFragments(ByteBufferPool bufferPool)
32+
public HeaderBlockFragments(ByteBufferPool bufferPool, int maxCapacity)
3233
{
3334
this.bufferPool = bufferPool;
35+
this.maxCapacity = maxCapacity;
36+
}
37+
38+
void reset()
39+
{
40+
priorityFrame = null;
41+
streamId = 0;
42+
endStream = false;
43+
storage = null;
3444
}
3545

36-
public void storeFragment(ByteBuffer fragment, int length, boolean last)
46+
public boolean storeFragment(ByteBuffer fragment, int length, boolean last)
3747
{
3848
if (storage == null)
3949
{
40-
int space = last ? length : length * 2;
41-
storage = bufferPool.acquire(space, fragment.isDirect());
50+
if (length > maxCapacity)
51+
return false;
52+
int capacity = last ? length : length * 2;
53+
storage = bufferPool.acquire(capacity, fragment.isDirect());
4254
BufferUtil.flipToFill(storage.getByteBuffer());
4355
}
4456

4557
// Grow the storage if necessary.
4658
if (storage.remaining() < length)
4759
{
4860
ByteBuffer byteBuffer = storage.getByteBuffer();
61+
if (byteBuffer.position() + length > maxCapacity)
62+
return false;
4963
int space = last ? length : length * 2;
5064
int capacity = byteBuffer.position() + space;
5165
RetainableByteBuffer newStorage = bufferPool.acquire(capacity, storage.isDirect());
@@ -61,6 +75,7 @@ public void storeFragment(ByteBuffer fragment, int length, boolean last)
6175
fragment.limit(fragment.position() + length);
6276
storage.getByteBuffer().put(fragment);
6377
fragment.limit(limit);
78+
return true;
6479
}
6580

6681
public PriorityFrame getPriorityFrame()
@@ -85,10 +100,8 @@ public void setEndStream(boolean endStream)
85100

86101
public RetainableByteBuffer complete()
87102
{
88-
RetainableByteBuffer result = storage;
89-
storage = null;
90-
result.getByteBuffer().flip();
91-
return result;
103+
storage.getByteBuffer().flip();
104+
return storage;
92105
}
93106

94107
public int getStreamId()

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

+27-10
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,15 @@ else if (hasFlag(Flags.END_HEADERS))
7575
}
7676
else
7777
{
78-
headerBlockFragments.setStreamId(getStreamId());
79-
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+
}
8087
}
8188
}
8289

@@ -171,6 +178,18 @@ else if (hasFlag(Flags.PRIORITY))
171178
break;
172179
}
173180
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:
174193
{
175194
if (hasFlag(Flags.END_HEADERS))
176195
{
@@ -195,7 +214,7 @@ else if (hasFlag(Flags.PRIORITY))
195214
{
196215
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
197216
if (!rateControlOnEvent(frame))
198-
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");
199218
}
200219
}
201220
}
@@ -204,16 +223,14 @@ else if (hasFlag(Flags.PRIORITY))
204223
int remaining = buffer.remaining();
205224
if (remaining < length)
206225
{
207-
headerBlockFragments.storeFragment(buffer, remaining, false);
226+
if (!headerBlockFragments.storeFragment(buffer, remaining, false))
227+
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_frame");
208228
length -= remaining;
209229
}
210230
else
211231
{
212-
headerBlockFragments.setStreamId(getStreamId());
213-
headerBlockFragments.setEndStream(isEndStream());
214-
if (hasFlag(Flags.PRIORITY))
215-
headerBlockFragments.setPriorityFrame(new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive));
216-
headerBlockFragments.storeFragment(buffer, length, false);
232+
if (!headerBlockFragments.storeFragment(buffer, length, false))
233+
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_frame");
217234
state = State.PADDING;
218235
loop = paddingLength == 0;
219236
}
@@ -257,6 +274,6 @@ private void onHeaders(HeadersFrame frame)
257274

258275
private enum State
259276
{
260-
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
261278
}
262279
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public void init(Listener listener)
7474
this.listener = listener;
7575
unknownBodyParser = new UnknownBodyParser(headerParser, listener);
7676
HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, bufferPool, hpackDecoder, unknownBodyParser);
77-
HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments(bufferPool);
77+
HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments(bufferPool, hpackDecoder.getMaxHeaderListSize());
7878
bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener);
7979
bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments);
8080
bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener);

jetty-core/jetty-http2/jetty-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-core/jetty-http2/jetty-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-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java

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

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

221218
SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flags.ACK));
222-
return onSettings(frame);
219+
return onSettings(buffer, frame);
223220
}
224221

225-
private boolean onSettings(SettingsFrame frame)
222+
private boolean onSettings(ByteBuffer buffer, SettingsFrame frame)
226223
{
224+
if (!rateControlOnEvent(frame))
225+
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame_rate");
227226
reset();
228227
notifySettings(frame);
229228
return true;

jetty-core/jetty-http2/jetty-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)