netty5/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java

394 lines
16 KiB
Java
Raw Normal View History

/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.netty.handler.codec.http2;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultChannelPromise;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.util.AsciiString;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.internal.UnstableApi;
import static io.netty.buffer.Unpooled.directBuffer;
import static io.netty.buffer.Unpooled.unreleasableBuffer;
HTTP/2 Ensure default settings are correctly enforced and interfaces clarified Motivation: The responsibility for retaining the settings values and enforcing the settings constraints is spread out in different areas of the code and may be initialized with different values than the default specified in the RFC. This should not be allowed by default and interfaces which are responsible for maintaining/enforcing settings state should clearly indicate the restrictions that they should only be set by the codec upon receipt of a SETTINGS ACK frame. Modifications: - Encoder, Decoder, and the Headers Encoder/Decoder no longer expose public constructors that allow the default settings to be changed. - Http2HeadersDecoder#maxHeaderSize() exists to provide some bound when headers/continuation frames are being aggregated. However this is roughly the same as SETTINGS_MAX_HEADER_LIST_SIZE (besides the 32 byte octet for each header field) and can be used instead of attempting to keep the two independent values in sync. - Encoding headers now enforces SETTINGS_MAX_HEADER_LIST_SIZE at the octect level. Previously the header encoder compared the number of header key/value pairs against SETTINGS_MAX_HEADER_LIST_SIZE instead of the number of octets (plus 32 bytes overhead). - DefaultHttp2ConnectionDecoder#onData calls shouldIgnoreHeadersOrDataFrame but may swallow exceptions from this method. This means a STREAM_RST frame may not be sent when it should for an unknown stream and thus violate the RFC. The exception is no longer swallowed. Result: Default settings state is enforced and interfaces related to settings state are clarified.
2016-09-16 15:57:33 +02:00
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
HTTP/2 Max Header List Size Bug Motivation: If the HPACK Decoder detects that SETTINGS_MAX_HEADER_LIST_SIZE has been violated it aborts immediately and sends a RST_STREAM frame for what ever stream caused the issue. Because HPACK is stateful this means that the HPACK state may become out of sync between peers, and the issue won't be detected until the next headers frame. We should make a best effort to keep processing to keep the HPACK state in sync with our peer, or completely close the connection. If the HPACK Encoder is configured to verify SETTINGS_MAX_HEADER_LIST_SIZE it checks the limit and encodes at the same time. This may result in modifying the HPACK local state but not sending the headers to the peer if SETTINGS_MAX_HEADER_LIST_SIZE is violated. This will also lead to an inconsistency in HPACK state that will be flagged at some later time. Modifications: - HPACK Decoder now has 2 levels of limits related to SETTINGS_MAX_HEADER_LIST_SIZE. The first will attempt to keep processing data and send a RST_STREAM after all data is processed. The second will send a GO_AWAY and close the entire connection. - When the HPACK Encoder enforces SETTINGS_MAX_HEADER_LIST_SIZE it should not modify the HPACK state until the size has been checked. - https://tools.ietf.org/html/rfc7540#section-6.5.2 states that the initial value of SETTINGS_MAX_HEADER_LIST_SIZE is "unlimited". We currently use 8k as a limit. We should honor the specifications default value so we don't unintentionally close a connection before the remote peer is aware of the local settings. - Remove unnecessary object allocation in DefaultHttp2HeadersDecoder and DefaultHttp2HeadersEncoder. Result: Fixes https://github.com/netty/netty/issues/6209.
2017-01-14 02:09:44 +01:00
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2Exception.headerListSizeError;
import static io.netty.util.CharsetUtil.UTF_8;
import static java.lang.Math.max;
import static java.lang.Math.min;
HTTP/2 Child Channel and FrameCodec Feature Parity. Motivation: This PR (unfortunately) does 4 things: 1) Add outbound flow control to the Http2MultiplexCodec: The HTTP/2 child channel API should interact with HTTP/2 outbound/remote flow control. That is, if a H2 stream used up all its flow control window, the corresponding child channel should be marked unwritable and a writability-changed event should be fired. Similarly, a unwritable child channel should be marked writable and a writability-event should be fired, once a WINDOW_UPDATE frame has been received. The changes are (mostly) contained in ChannelOutboundBuffer, AbstractHttp2StreamChannel and Http2MultiplexCodec. 2) Introduce a Http2Stream2 object, that is used instead of stream identifiers on stream frames. A Http2Stream2 object allows an application to attach state to it, and so a application handler no longer needs to maintain stream state (i.e. in a map(id -> state)) himself. 3) Remove stream state events, which are no longer necessary due to the introduction of Http2Stream2. Also those stream state events have been found hard and complex to work with, when porting gRPC to the Http2FrameCodec. 4) Add support for HTTP/2 frames that have not yet been implemented, like PING and SETTINGS. Also add a Http2FrameCodecBuilder that exposes options from the Http2ConnectionHandler API that couldn't else be used with the frame codec, like buffering outbound streams, window update ratio, frame logger, etc. Modifications: 1) A child channel's writability and a H2 stream's outbound flow control window interact, as described in the motivation. A channel handler is free to ignore the channel's writability, in which case the parent channel is reponsible for buffering writes until a WINDOW_UPDATE is received. The connection-level flow control window is ignored for now. That is, a child channel's writability is only affected by the stream-level flow control window. So a child channel could be marked writable, even though the connection-level flow control window is zero. 2) Modify Http2StreamFrame and the Http2FrameCodec to take a Http2Stream2 object intstead of a primitive integer. Introduce a special Http2ChannelDuplexHandler that has newStream() and forEachActiveStream() methods. It's recommended for a user to extend from this handler, to use those advanced features. 3) As explained in the documentation, a new inbound stream active can be detected by checking if the Http2Stream2.managedState() of a Http2HeadersFrame is null. An outbound stream active can be detected by adding a listener to the ChannelPromise of the write of the first Http2HeadersFrame. A stream closed event can be listened to by adding a listener to the Http2Stream2.closeFuture(). 4) Add a simple Http2FrameCodecBuilder and implement the missing frame types. Result: 1) The Http2MultiplexCodec supports outbound flow control. 2) The Http2FrameCodec API makes it easy for a user to manage custom stream specific state and to create new outbound streams. 3) The Http2FrameCodec API is much cleaner and easier to work with. Hacks like the ChannelCarryingHeadersFrame are no longer necessary. 4) The Http2FrameCodec now also supports PING and SETTINGS frames. The Http2FrameCodecBuilder allows the Http2FrameCodec to use some of the rich features of the Http2ConnectionHandler API.
2016-08-23 13:03:39 +02:00
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* Constants and utility method used for encoding/decoding HTTP2 frames.
*/
@UnstableApi
public final class Http2CodecUtil {
public static final int CONNECTION_STREAM_ID = 0;
public static final int HTTP_UPGRADE_STREAM_ID = 1;
public static final CharSequence HTTP_UPGRADE_SETTINGS_HEADER = AsciiString.cached("HTTP2-Settings");
public static final CharSequence HTTP_UPGRADE_PROTOCOL_NAME = "h2c";
public static final CharSequence TLS_UPGRADE_PROTOCOL_NAME = ApplicationProtocolNames.HTTP_2;
public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
public static final short MAX_UNSIGNED_BYTE = 0xff;
/**
* The maximum number of padding bytes. That is the 255 padding bytes appended to the end of a frame and the 1 byte
* pad length field.
*/
public static final int MAX_PADDING = 256;
public static final long MAX_UNSIGNED_INT = 0xffffffffL;
public static final int FRAME_HEADER_LENGTH = 9;
public static final int SETTING_ENTRY_LENGTH = 6;
public static final int PRIORITY_ENTRY_LENGTH = 5;
public static final int INT_FIELD_LENGTH = 4;
public static final short MAX_WEIGHT = 256;
public static final short MIN_WEIGHT = 1;
private static final ByteBuf CONNECTION_PREFACE =
unreleasableBuffer(directBuffer(24).writeBytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(UTF_8)))
.asReadOnly();
private static final int MAX_PADDING_LENGTH_LENGTH = 1;
public static final int DATA_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
public static final int HEADERS_FRAME_HEADER_LENGTH =
FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH + 1;
public static final int PRIORITY_FRAME_LENGTH = FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH;
public static final int RST_STREAM_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
public static final int PUSH_PROMISE_FRAME_HEADER_LENGTH =
FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH;
public static final int GO_AWAY_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + 2 * INT_FIELD_LENGTH;
public static final int WINDOW_UPDATE_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
public static final int CONTINUATION_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
public static final char SETTINGS_HEADER_TABLE_SIZE = 1;
public static final char SETTINGS_ENABLE_PUSH = 2;
public static final char SETTINGS_MAX_CONCURRENT_STREAMS = 3;
public static final char SETTINGS_INITIAL_WINDOW_SIZE = 4;
public static final char SETTINGS_MAX_FRAME_SIZE = 5;
public static final char SETTINGS_MAX_HEADER_LIST_SIZE = 6;
public static final int NUM_STANDARD_SETTINGS = 6;
public static final long MAX_HEADER_TABLE_SIZE = MAX_UNSIGNED_INT;
public static final long MAX_CONCURRENT_STREAMS = MAX_UNSIGNED_INT;
public static final int MAX_INITIAL_WINDOW_SIZE = Integer.MAX_VALUE;
public static final int MAX_FRAME_SIZE_LOWER_BOUND = 0x4000;
public static final int MAX_FRAME_SIZE_UPPER_BOUND = 0xffffff;
HTTP/2 Ensure default settings are correctly enforced and interfaces clarified Motivation: The responsibility for retaining the settings values and enforcing the settings constraints is spread out in different areas of the code and may be initialized with different values than the default specified in the RFC. This should not be allowed by default and interfaces which are responsible for maintaining/enforcing settings state should clearly indicate the restrictions that they should only be set by the codec upon receipt of a SETTINGS ACK frame. Modifications: - Encoder, Decoder, and the Headers Encoder/Decoder no longer expose public constructors that allow the default settings to be changed. - Http2HeadersDecoder#maxHeaderSize() exists to provide some bound when headers/continuation frames are being aggregated. However this is roughly the same as SETTINGS_MAX_HEADER_LIST_SIZE (besides the 32 byte octet for each header field) and can be used instead of attempting to keep the two independent values in sync. - Encoding headers now enforces SETTINGS_MAX_HEADER_LIST_SIZE at the octect level. Previously the header encoder compared the number of header key/value pairs against SETTINGS_MAX_HEADER_LIST_SIZE instead of the number of octets (plus 32 bytes overhead). - DefaultHttp2ConnectionDecoder#onData calls shouldIgnoreHeadersOrDataFrame but may swallow exceptions from this method. This means a STREAM_RST frame may not be sent when it should for an unknown stream and thus violate the RFC. The exception is no longer swallowed. Result: Default settings state is enforced and interfaces related to settings state are clarified.
2016-09-16 15:57:33 +02:00
public static final long MAX_HEADER_LIST_SIZE = MAX_UNSIGNED_INT;
public static final long MIN_HEADER_TABLE_SIZE = 0;
public static final long MIN_CONCURRENT_STREAMS = 0;
public static final int MIN_INITIAL_WINDOW_SIZE = 0;
public static final long MIN_HEADER_LIST_SIZE = 0;
public static final int DEFAULT_WINDOW_SIZE = 65535;
public static final short DEFAULT_PRIORITY_WEIGHT = 16;
public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
HTTP/2 Max Header List Size Bug Motivation: If the HPACK Decoder detects that SETTINGS_MAX_HEADER_LIST_SIZE has been violated it aborts immediately and sends a RST_STREAM frame for what ever stream caused the issue. Because HPACK is stateful this means that the HPACK state may become out of sync between peers, and the issue won't be detected until the next headers frame. We should make a best effort to keep processing to keep the HPACK state in sync with our peer, or completely close the connection. If the HPACK Encoder is configured to verify SETTINGS_MAX_HEADER_LIST_SIZE it checks the limit and encodes at the same time. This may result in modifying the HPACK local state but not sending the headers to the peer if SETTINGS_MAX_HEADER_LIST_SIZE is violated. This will also lead to an inconsistency in HPACK state that will be flagged at some later time. Modifications: - HPACK Decoder now has 2 levels of limits related to SETTINGS_MAX_HEADER_LIST_SIZE. The first will attempt to keep processing data and send a RST_STREAM after all data is processed. The second will send a GO_AWAY and close the entire connection. - When the HPACK Encoder enforces SETTINGS_MAX_HEADER_LIST_SIZE it should not modify the HPACK state until the size has been checked. - https://tools.ietf.org/html/rfc7540#section-6.5.2 states that the initial value of SETTINGS_MAX_HEADER_LIST_SIZE is "unlimited". We currently use 8k as a limit. We should honor the specifications default value so we don't unintentionally close a connection before the remote peer is aware of the local settings. - Remove unnecessary object allocation in DefaultHttp2HeadersDecoder and DefaultHttp2HeadersEncoder. Result: Fixes https://github.com/netty/netty/issues/6209.
2017-01-14 02:09:44 +01:00
/**
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">The initial value of this setting is unlimited</a>.
* However in practice we don't want to allow our peers to use unlimited memory by default. So we take advantage
* of the <q>For any given request, a lower limit than what is advertised MAY be enforced.</q> loophole.
*/
public static final long DEFAULT_HEADER_LIST_SIZE = 8192;
public static final int DEFAULT_MAX_FRAME_SIZE = MAX_FRAME_SIZE_LOWER_BOUND;
HTTP/2 Non Active Stream RFC Corrections Motivation: codec-http2 couples the dependency tree state with the remainder of the stream state (Http2Stream). This makes implementing constraints where stream state and dependency tree state diverge in the RFC challenging. For example the RFC recommends retaining dependency tree state after a stream transitions to closed [1]. Dependency tree state can be exchanged on streams in IDLE. In practice clients may use stream IDs for the purpose of establishing QoS classes and therefore retaining this dependency tree state can be important to client perceived performance. It is difficult to limit the total amount of state we retain when stream state and dependency tree state is combined. Modifications: - Remove dependency tree, priority, and weight related items from public facing Http2Connection and Http2Stream APIs. This information is optional to track and depends on the flow controller implementation. - Move all dependency tree, priority, and weight related code from DefaultHttp2Connection to WeightedFairQueueByteDistributor. This is currently the only place which cares about priority. We can pull out the dependency tree related code in the future if it is generally useful to expose for other implementations. - DefaultHttp2Connection should explicitly limit the number of reserved streams now that IDLE streams are no longer created. Result: More compliant with the HTTP/2 RFC. Fixes https://github.com/netty/netty/issues/6206. [1] https://tools.ietf.org/html/rfc7540#section-5.3.4
2017-01-24 21:50:39 +01:00
/**
* The assumed minimum value for {@code SETTINGS_MAX_CONCURRENT_STREAMS} as
* recommended by the <a herf="https://tools.ietf.org/html/rfc7540#section-6.5.2">HTTP/2 spec</a>.
*/
public static final int SMALLEST_MAX_CONCURRENT_STREAMS = 100;
static final int DEFAULT_MAX_RESERVED_STREAMS = SMALLEST_MAX_CONCURRENT_STREAMS;
static final int DEFAULT_MIN_ALLOCATION_CHUNK = 1024;
static final int DEFAULT_INITIAL_HUFFMAN_DECODE_CAPACITY = 32;
/**
* Calculate the threshold in bytes which should trigger a {@code GO_AWAY} if a set of headers exceeds this amount.
* @param maxHeaderListSize
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a> for the local
* endpoint.
* @return the threshold in bytes which should trigger a {@code GO_AWAY} if a set of headers exceeds this amount.
*/
public static long calculateMaxHeaderListSizeGoAway(long maxHeaderListSize) {
// This is equivalent to `maxHeaderListSize * 1.25` but we avoid floating point multiplication.
return maxHeaderListSize + (maxHeaderListSize >>> 2);
}
HTTP/2 Child Channel and FrameCodec Feature Parity. Motivation: This PR (unfortunately) does 4 things: 1) Add outbound flow control to the Http2MultiplexCodec: The HTTP/2 child channel API should interact with HTTP/2 outbound/remote flow control. That is, if a H2 stream used up all its flow control window, the corresponding child channel should be marked unwritable and a writability-changed event should be fired. Similarly, a unwritable child channel should be marked writable and a writability-event should be fired, once a WINDOW_UPDATE frame has been received. The changes are (mostly) contained in ChannelOutboundBuffer, AbstractHttp2StreamChannel and Http2MultiplexCodec. 2) Introduce a Http2Stream2 object, that is used instead of stream identifiers on stream frames. A Http2Stream2 object allows an application to attach state to it, and so a application handler no longer needs to maintain stream state (i.e. in a map(id -> state)) himself. 3) Remove stream state events, which are no longer necessary due to the introduction of Http2Stream2. Also those stream state events have been found hard and complex to work with, when porting gRPC to the Http2FrameCodec. 4) Add support for HTTP/2 frames that have not yet been implemented, like PING and SETTINGS. Also add a Http2FrameCodecBuilder that exposes options from the Http2ConnectionHandler API that couldn't else be used with the frame codec, like buffering outbound streams, window update ratio, frame logger, etc. Modifications: 1) A child channel's writability and a H2 stream's outbound flow control window interact, as described in the motivation. A channel handler is free to ignore the channel's writability, in which case the parent channel is reponsible for buffering writes until a WINDOW_UPDATE is received. The connection-level flow control window is ignored for now. That is, a child channel's writability is only affected by the stream-level flow control window. So a child channel could be marked writable, even though the connection-level flow control window is zero. 2) Modify Http2StreamFrame and the Http2FrameCodec to take a Http2Stream2 object intstead of a primitive integer. Introduce a special Http2ChannelDuplexHandler that has newStream() and forEachActiveStream() methods. It's recommended for a user to extend from this handler, to use those advanced features. 3) As explained in the documentation, a new inbound stream active can be detected by checking if the Http2Stream2.managedState() of a Http2HeadersFrame is null. An outbound stream active can be detected by adding a listener to the ChannelPromise of the write of the first Http2HeadersFrame. A stream closed event can be listened to by adding a listener to the Http2Stream2.closeFuture(). 4) Add a simple Http2FrameCodecBuilder and implement the missing frame types. Result: 1) The Http2MultiplexCodec supports outbound flow control. 2) The Http2FrameCodec API makes it easy for a user to manage custom stream specific state and to create new outbound streams. 3) The Http2FrameCodec API is much cleaner and easier to work with. Hacks like the ChannelCarryingHeadersFrame are no longer necessary. 4) The Http2FrameCodec now also supports PING and SETTINGS frames. The Http2FrameCodecBuilder allows the Http2FrameCodec to use some of the rich features of the Http2ConnectionHandler API.
2016-08-23 13:03:39 +02:00
public static final long DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_MILLIS = MILLISECONDS.convert(30, SECONDS);
/**
* Returns {@code true} if the stream is an outbound stream.
*
* @param server {@code true} if the endpoint is a server, {@code false} otherwise.
* @param streamId the stream identifier
*/
public static boolean isOutboundStream(boolean server, int streamId) {
boolean even = (streamId & 1) == 0;
return streamId > 0 && server == even;
}
/**
* Returns true if the {@code streamId} is a valid HTTP/2 stream identifier.
*/
public static boolean isStreamIdValid(int streamId) {
return streamId >= 0;
}
/**
* Indicates whether or not the given value for max frame size falls within the valid range.
*/
public static boolean isMaxFrameSizeValid(int maxFrameSize) {
return maxFrameSize >= MAX_FRAME_SIZE_LOWER_BOUND && maxFrameSize <= MAX_FRAME_SIZE_UPPER_BOUND;
}
/**
* Returns a buffer containing the {@link #CONNECTION_PREFACE}.
*/
public static ByteBuf connectionPrefaceBuf() {
// Return a duplicate so that modifications to the reader index will not affect the original buffer.
return CONNECTION_PREFACE.retainedDuplicate();
}
/**
2017-04-19 22:37:03 +02:00
* Iteratively looks through the causality chain for the given exception and returns the first
* {@link Http2Exception} or {@code null} if none.
*/
public static Http2Exception getEmbeddedHttp2Exception(Throwable cause) {
while (cause != null) {
if (cause instanceof Http2Exception) {
return (Http2Exception) cause;
}
cause = cause.getCause();
}
return null;
}
/**
* Creates a buffer containing the error message from the given exception. If the cause is
* {@code null} returns an empty buffer.
*/
public static ByteBuf toByteBuf(ChannelHandlerContext ctx, Throwable cause) {
if (cause == null || cause.getMessage() == null) {
return Unpooled.EMPTY_BUFFER;
}
return ByteBufUtil.writeUtf8(ctx.alloc(), cause.getMessage());
}
/**
* Reads a big-endian (31-bit) integer from the buffer.
*/
public static int readUnsignedInt(ByteBuf buf) {
return buf.readInt() & 0x7fffffff;
}
/**
* Writes an HTTP/2 frame header to the output buffer.
*/
public static void writeFrameHeader(ByteBuf out, int payloadLength, byte type,
Http2Flags flags, int streamId) {
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
writeFrameHeaderInternal(out, payloadLength, type, flags, streamId);
}
/**
* Calculate the amount of bytes that can be sent by {@code state}. The lower bound is {@code 0}.
*/
public static int streamableBytes(StreamByteDistributor.StreamState state) {
return max(0, (int) min(state.pendingBytes(), state.windowSize()));
}
HTTP/2 Max Header List Size Bug Motivation: If the HPACK Decoder detects that SETTINGS_MAX_HEADER_LIST_SIZE has been violated it aborts immediately and sends a RST_STREAM frame for what ever stream caused the issue. Because HPACK is stateful this means that the HPACK state may become out of sync between peers, and the issue won't be detected until the next headers frame. We should make a best effort to keep processing to keep the HPACK state in sync with our peer, or completely close the connection. If the HPACK Encoder is configured to verify SETTINGS_MAX_HEADER_LIST_SIZE it checks the limit and encodes at the same time. This may result in modifying the HPACK local state but not sending the headers to the peer if SETTINGS_MAX_HEADER_LIST_SIZE is violated. This will also lead to an inconsistency in HPACK state that will be flagged at some later time. Modifications: - HPACK Decoder now has 2 levels of limits related to SETTINGS_MAX_HEADER_LIST_SIZE. The first will attempt to keep processing data and send a RST_STREAM after all data is processed. The second will send a GO_AWAY and close the entire connection. - When the HPACK Encoder enforces SETTINGS_MAX_HEADER_LIST_SIZE it should not modify the HPACK state until the size has been checked. - https://tools.ietf.org/html/rfc7540#section-6.5.2 states that the initial value of SETTINGS_MAX_HEADER_LIST_SIZE is "unlimited". We currently use 8k as a limit. We should honor the specifications default value so we don't unintentionally close a connection before the remote peer is aware of the local settings. - Remove unnecessary object allocation in DefaultHttp2HeadersDecoder and DefaultHttp2HeadersEncoder. Result: Fixes https://github.com/netty/netty/issues/6209.
2017-01-14 02:09:44 +01:00
/**
* Results in a RST_STREAM being sent for {@code streamId} due to violating
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>.
* @param streamId The stream ID that was being processed when the exceptional condition occurred.
* @param maxHeaderListSize The max allowed size for a list of headers in bytes which was exceeded.
* @param onDecode {@code true} if the exception was encountered during decoder. {@code false} for encode.
* @throws Http2Exception a stream error.
*/
public static void headerListSizeExceeded(int streamId, long maxHeaderListSize,
boolean onDecode) throws Http2Exception {
throw headerListSizeError(streamId, PROTOCOL_ERROR, onDecode, "Header size exceeded max " +
"allowed size (%d)", maxHeaderListSize);
HTTP/2 Ensure default settings are correctly enforced and interfaces clarified Motivation: The responsibility for retaining the settings values and enforcing the settings constraints is spread out in different areas of the code and may be initialized with different values than the default specified in the RFC. This should not be allowed by default and interfaces which are responsible for maintaining/enforcing settings state should clearly indicate the restrictions that they should only be set by the codec upon receipt of a SETTINGS ACK frame. Modifications: - Encoder, Decoder, and the Headers Encoder/Decoder no longer expose public constructors that allow the default settings to be changed. - Http2HeadersDecoder#maxHeaderSize() exists to provide some bound when headers/continuation frames are being aggregated. However this is roughly the same as SETTINGS_MAX_HEADER_LIST_SIZE (besides the 32 byte octet for each header field) and can be used instead of attempting to keep the two independent values in sync. - Encoding headers now enforces SETTINGS_MAX_HEADER_LIST_SIZE at the octect level. Previously the header encoder compared the number of header key/value pairs against SETTINGS_MAX_HEADER_LIST_SIZE instead of the number of octets (plus 32 bytes overhead). - DefaultHttp2ConnectionDecoder#onData calls shouldIgnoreHeadersOrDataFrame but may swallow exceptions from this method. This means a STREAM_RST frame may not be sent when it should for an unknown stream and thus violate the RFC. The exception is no longer swallowed. Result: Default settings state is enforced and interfaces related to settings state are clarified.
2016-09-16 15:57:33 +02:00
}
HTTP/2 Max Header List Size Bug Motivation: If the HPACK Decoder detects that SETTINGS_MAX_HEADER_LIST_SIZE has been violated it aborts immediately and sends a RST_STREAM frame for what ever stream caused the issue. Because HPACK is stateful this means that the HPACK state may become out of sync between peers, and the issue won't be detected until the next headers frame. We should make a best effort to keep processing to keep the HPACK state in sync with our peer, or completely close the connection. If the HPACK Encoder is configured to verify SETTINGS_MAX_HEADER_LIST_SIZE it checks the limit and encodes at the same time. This may result in modifying the HPACK local state but not sending the headers to the peer if SETTINGS_MAX_HEADER_LIST_SIZE is violated. This will also lead to an inconsistency in HPACK state that will be flagged at some later time. Modifications: - HPACK Decoder now has 2 levels of limits related to SETTINGS_MAX_HEADER_LIST_SIZE. The first will attempt to keep processing data and send a RST_STREAM after all data is processed. The second will send a GO_AWAY and close the entire connection. - When the HPACK Encoder enforces SETTINGS_MAX_HEADER_LIST_SIZE it should not modify the HPACK state until the size has been checked. - https://tools.ietf.org/html/rfc7540#section-6.5.2 states that the initial value of SETTINGS_MAX_HEADER_LIST_SIZE is "unlimited". We currently use 8k as a limit. We should honor the specifications default value so we don't unintentionally close a connection before the remote peer is aware of the local settings. - Remove unnecessary object allocation in DefaultHttp2HeadersDecoder and DefaultHttp2HeadersEncoder. Result: Fixes https://github.com/netty/netty/issues/6209.
2017-01-14 02:09:44 +01:00
/**
* Results in a GO_AWAY being sent due to violating
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a> in an unrecoverable
* manner.
* @param maxHeaderListSize The max allowed size for a list of headers in bytes which was exceeded.
* @throws Http2Exception a connection error.
*/
public static void headerListSizeExceeded(long maxHeaderListSize) throws Http2Exception {
throw connectionError(PROTOCOL_ERROR, "Header size exceeded max " +
"allowed size (%d)", maxHeaderListSize);
}
static void writeFrameHeaderInternal(ByteBuf out, int payloadLength, byte type,
Http2Flags flags, int streamId) {
out.writeMedium(payloadLength);
out.writeByte(type);
out.writeByte(flags.value());
out.writeInt(streamId);
}
/**
* Provides the ability to associate the outcome of multiple {@link ChannelPromise}
* objects into a single {@link ChannelPromise} object.
*/
static final class SimpleChannelPromiseAggregator extends DefaultChannelPromise {
private final ChannelPromise promise;
private int expectedCount;
private int doneCount;
private Throwable lastFailure;
private boolean doneAllocating;
SimpleChannelPromiseAggregator(ChannelPromise promise, Channel c, EventExecutor e) {
super(c, e);
assert promise != null && !promise.isDone();
this.promise = promise;
}
/**
* Allocate a new promise which will be used to aggregate the overall success of this promise aggregator.
* @return A new promise which will be aggregated.
* {@code null} if {@link #doneAllocatingPromises()} was previously called.
*/
public ChannelPromise newPromise() {
assert !doneAllocating : "Done allocating. No more promises can be allocated.";
++expectedCount;
return this;
}
/**
* Signify that no more {@link #newPromise()} allocations will be made.
* The aggregation can not be successful until this method is called.
* @return The promise that is the aggregation of all promises allocated with {@link #newPromise()}.
*/
public ChannelPromise doneAllocatingPromises() {
if (!doneAllocating) {
doneAllocating = true;
if (doneCount == expectedCount || expectedCount == 0) {
return setPromise();
}
}
return this;
}
@Override
public boolean tryFailure(Throwable cause) {
if (allowFailure()) {
++doneCount;
lastFailure = cause;
if (allPromisesDone()) {
return tryPromise();
}
// TODO: We break the interface a bit here.
// Multiple failure events can be processed without issue because this is an aggregation.
return true;
}
return false;
}
/**
* Fail this object if it has not already been failed.
* <p>
* This method will NOT throw an {@link IllegalStateException} if called multiple times
* because that may be expected.
*/
@Override
public ChannelPromise setFailure(Throwable cause) {
if (allowFailure()) {
++doneCount;
lastFailure = cause;
if (allPromisesDone()) {
return setPromise();
}
}
return this;
}
@Override
public ChannelPromise setSuccess(Void result) {
if (awaitingPromises()) {
++doneCount;
if (allPromisesDone()) {
setPromise();
}
}
return this;
}
@Override
public boolean trySuccess(Void result) {
if (awaitingPromises()) {
++doneCount;
if (allPromisesDone()) {
return tryPromise();
}
// TODO: We break the interface a bit here.
// Multiple success events can be processed without issue because this is an aggregation.
return true;
}
return false;
}
private boolean allowFailure() {
return awaitingPromises() || expectedCount == 0;
}
private boolean awaitingPromises() {
return doneCount < expectedCount;
}
private boolean allPromisesDone() {
return doneCount == expectedCount && doneAllocating;
}
private ChannelPromise setPromise() {
if (lastFailure == null) {
promise.setSuccess();
return super.setSuccess(null);
} else {
promise.setFailure(lastFailure);
return super.setFailure(lastFailure);
}
}
private boolean tryPromise() {
if (lastFailure == null) {
promise.trySuccess();
return super.trySuccess(null);
} else {
promise.tryFailure(lastFailure);
return super.tryFailure(lastFailure);
}
}
}
public static void verifyPadding(int padding) {
if (padding < 0 || padding > MAX_PADDING) {
throw new IllegalArgumentException(String.format("Invalid padding '%d'. Padding must be between 0 and " +
"%d (inclusive).", padding, MAX_PADDING));
}
}
private Http2CodecUtil() { }
}