Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/Servers/Kestrel/Core/src/Http2Limits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class Http2Limits
/// <summary>
/// Limits the number of concurrent request streams per HTTP/2 connection. Excess streams will be refused.
/// <para>
/// Value must be greater than 0, defaults to 100
/// Value must be greater than 0, defaults to 100.
/// </para>
/// </summary>
public int MaxStreamsPerConnection
Expand All @@ -45,7 +45,7 @@ public int MaxStreamsPerConnection
/// <summary>
/// Limits the size of the header compression tables, in octets, the HPACK encoder and decoder on the server can use.
/// <para>
/// Value must be greater than or equal to 0, defaults to 4096
/// Value must be greater than or equal to 0, defaults to 4096.
/// </para>
/// </summary>
public int HeaderTableSize
Expand All @@ -65,7 +65,7 @@ public int HeaderTableSize
/// <summary>
/// Indicates the size of the largest frame payload that is allowed to be received, in octets. The size must be between 2^14 and 2^24-1.
/// <para>
/// Value must be between 2^14 and 2^24, defaults to 2^14 (16,384)
/// Value must be between 2^14 and 2^24, defaults to 2^14 (16,384).
/// </para>
/// </summary>
public int MaxFrameSize
Expand All @@ -85,7 +85,7 @@ public int MaxFrameSize
/// <summary>
/// Indicates the size of the maximum allowed size of a request header field sequence. This limit applies to both name and value sequences in their compressed and uncompressed representations.
/// <para>
/// Value must be greater than 0, defaults to 2^14 (16,384)
/// Value must be greater than 0, defaults to 2^14 (16,384).
/// </para>
/// </summary>
public int MaxRequestHeaderFieldSize
Expand Down
6 changes: 4 additions & 2 deletions src/Servers/Kestrel/Core/src/Http3Limits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// </summary>
public class Http3Limits
{
internal const int DefaultMaxRequestHeaderFieldSize = 16 * 1024;

private int _headerTableSize;
private int _maxRequestHeaderFieldSize = 8192;
private int _maxRequestHeaderFieldSize = DefaultMaxRequestHeaderFieldSize;

/// <summary>
/// Limits the size of the header compression table, in octets, the QPACK decoder on the server can use.
Expand All @@ -37,7 +39,7 @@ internal int HeaderTableSize
/// <summary>
/// Indicates the size of the maximum allowed size of a request header field sequence. This limit applies to both name and value sequences in their compressed and uncompressed representations.
/// <para>
/// Value must be greater than 0, defaults to 8192
/// Value must be greater than 0, defaults to 2^14 (16,384).
/// </para>
/// </summary>
public int MaxRequestHeaderFieldSize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal class Http3Connection : IHttp3StreamLifetimeHandler, IRequestProcessor
private int _activeRequestCount;

private readonly Http3PeerSettings _serverSettings = new Http3PeerSettings();
private readonly Http3PeerSettings _clientSettings = new Http3PeerSettings();
private readonly StreamCloseAwaitable _streamCompletionAwaitable = new StreamCloseAwaitable();
private readonly IProtocolErrorCodeFeature _errorCodeFeature;

Expand All @@ -53,7 +54,7 @@ public Http3Connection(HttpMultiplexedConnectionContext context)
var httpLimits = context.ServiceContext.ServerOptions.Limits;

_serverSettings.HeaderTableSize = (uint)httpLimits.Http3.HeaderTableSize;
_serverSettings.MaxRequestHeaderFieldSize = (uint)httpLimits.Http3.MaxRequestHeaderFieldSize;
_serverSettings.MaxRequestHeaderFieldSectionSize = (uint)httpLimits.MaxRequestHeadersTotalSize;
}

private void UpdateHighestStreamId(long streamId)
Expand Down Expand Up @@ -260,6 +261,7 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> appl
streamContext.Transport,
_streamLifetimeHandler,
streamContext,
_clientSettings,
_serverSettings);
httpConnectionContext.TimeoutControl = _context.TimeoutControl;

Expand Down Expand Up @@ -423,6 +425,7 @@ private async ValueTask<Http3ControlStream> CreateNewUnidirectionalStreamAsync<T
streamContext.Transport,
_streamLifetimeHandler,
streamContext,
_clientSettings,
_serverSettings);
httpConnectionContext.TimeoutControl = _context.TimeoutControl;

Expand Down Expand Up @@ -522,6 +525,7 @@ void IHttp3StreamLifetimeHandler.OnInboundControlStreamSetting(Http3SettingType
case Http3SettingType.QPackMaxTableCapacity:
break;
case Http3SettingType.MaxFieldSectionSize:
_clientSettings.MaxRequestHeaderFieldSectionSize = (uint)value;
break;
case Http3SettingType.QPackBlockedStreams:
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public Http3ControlStream(Http3StreamContext context)
{
var httpLimits = context.ServiceContext.ServerOptions.Limits;
_context = context;
_serverPeerSettings = context.ServerSettings;
_serverPeerSettings = context.ServerPeerSettings;
_streamIdFeature = context.ConnectionFeatures.Get<IStreamIdFeature>()!;
_errorCodeFeature = context.ConnectionFeatures.Get<IProtocolErrorCodeFeature>()!;
_headerType = -1;
Expand All @@ -55,7 +55,9 @@ public Http3ControlStream(Http3StreamContext context)
context.ConnectionId,
context.MemoryPool,
context.ServiceContext.Log,
_streamIdFeature);
_streamIdFeature,
context.ClientPeerSettings,
this);
}

private void OnStreamClosed()
Expand Down Expand Up @@ -295,16 +297,16 @@ private ValueTask ProcessSettingsFrameAsync(ReadOnlySequence<byte> payload)

while (true)
{
var id = VariableLengthIntegerHelper.GetInteger(payload, out var consumed, out var examinded);
var id = VariableLengthIntegerHelper.GetInteger(payload, out var consumed, out _);
if (id == -1)
{
break;
}

payload = payload.Slice(consumed);

var value = VariableLengthIntegerHelper.GetInteger(payload, out consumed, out examinded);
if (id == -1)
var value = VariableLengthIntegerHelper.GetInteger(payload, out consumed, out _);
if (value == -1)
{
break;
}
Expand Down
45 changes: 31 additions & 14 deletions src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal class Http3FrameWriter
{
private readonly object _writeLock = new object();

private IEnumerator<KeyValuePair<string, string>>? _headersEnumerator;
private readonly int _maxTotalHeaderSize;
private readonly PipeWriter _outputWriter;
private readonly ConnectionContext _connectionContext;
private readonly ITimeoutControl _timeoutControl;
Expand All @@ -32,20 +32,22 @@ internal class Http3FrameWriter
private readonly MemoryPool<byte> _memoryPool;
private readonly IKestrelTrace _log;
private readonly IStreamIdFeature _streamIdFeature;
private readonly IHttp3Stream _http3Stream;
private readonly Http3RawFrame _outgoingFrame;
private readonly TimingPipeFlusher _flusher;

private IEnumerator<KeyValuePair<string, string>>? _headersEnumerator;
private int _headersTotalSize;
// TODO update max frame size
private uint _maxFrameSize = 10000; //Http3PeerSettings.MinAllowedMaxFrameSize;
private byte[] _headerEncodingBuffer;

private long _unflushedBytes;
private bool _completed;
private bool _aborted;

//private int _unflushedBytes;

public Http3FrameWriter(PipeWriter output, ConnectionContext connectionContext, ITimeoutControl timeoutControl, MinDataRate? minResponseDataRate, string connectionId, MemoryPool<byte> memoryPool, IKestrelTrace log, IStreamIdFeature streamIdFeature)
public Http3FrameWriter(PipeWriter output, ConnectionContext connectionContext, ITimeoutControl timeoutControl, MinDataRate? minResponseDataRate, string connectionId, MemoryPool<byte> memoryPool, IKestrelTrace log, IStreamIdFeature streamIdFeature, Http3PeerSettings clientPeerSettings, IHttp3Stream http3Stream)
{
_outputWriter = output;
_connectionContext = connectionContext;
Expand All @@ -55,9 +57,18 @@ public Http3FrameWriter(PipeWriter output, ConnectionContext connectionContext,
_memoryPool = memoryPool;
_log = log;
_streamIdFeature = streamIdFeature;
_http3Stream = http3Stream;
_outgoingFrame = new Http3RawFrame();
_flusher = new TimingPipeFlusher(_outputWriter, timeoutControl, log);
_headerEncodingBuffer = new byte[_maxFrameSize];

// Note that max total header size value doesn't react to settings change during a stream.
// Unlikely to be a problem in practice:
// - Settings rarely change after the start of a connection.
// - Response header size limits are a best-effort requirement in the spec.
_maxTotalHeaderSize = clientPeerSettings.MaxRequestHeaderFieldSectionSize > int.MaxValue
? int.MaxValue
: (int)clientPeerSettings.MaxRequestHeaderFieldSectionSize;
}

public void UpdateMaxFrameSize(uint maxFrameSize)
Expand Down Expand Up @@ -257,7 +268,7 @@ internal static int WriteHeader(Http3RawFrame frame, PipeWriter output)
return totalLength;
}

public ValueTask<FlushResult> WriteResponseTrailers(HttpResponseTrailers headers)
public ValueTask<FlushResult> WriteResponseTrailersAsync(long streamId, HttpResponseTrailers headers)
{
lock (_writeLock)
{
Expand All @@ -269,16 +280,17 @@ public ValueTask<FlushResult> WriteResponseTrailers(HttpResponseTrailers headers
try
{
_headersEnumerator = EnumerateHeaders(headers).GetEnumerator();
_headersTotalSize = 0;

_outgoingFrame.PrepareHeaders();
var buffer = _headerEncodingBuffer.AsSpan();
var done = QPackHeaderWriter.BeginEncode(_headersEnumerator, buffer, out var payloadLength);
var done = QPackHeaderWriter.BeginEncode(_headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength);
FinishWritingHeaders(payloadLength, done);
}
catch (QPackEncodingException)
catch (QPackEncodingException ex)
{
//_log.HPackEncodingError(_connectionId, streamId, hex);
//_http3Stream.Abort(new ConnectionAbortedException(hex.Message, hex));
_log.QPackEncodingError(_connectionId, streamId, ex);
_http3Stream.Abort(new ConnectionAbortedException(ex.Message, ex), Http3ErrorCode.InternalError);
}

return TimeFlushUnsynchronizedAsync();
Expand Down Expand Up @@ -324,14 +336,13 @@ internal void WriteResponseHeaders(int statusCode, IHeaderDictionary headers)

_outgoingFrame.PrepareHeaders();
var buffer = _headerEncodingBuffer.AsSpan();
var done = QPackHeaderWriter.BeginEncode(statusCode, _headersEnumerator, buffer, out var payloadLength);
var done = QPackHeaderWriter.BeginEncode(statusCode, _headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength);
FinishWritingHeaders(payloadLength, done);
}
catch (QPackEncodingException hex)
catch (QPackEncodingException ex)
{
// TODO figure out how to abort the stream here.
//_http3Stream.Abort(new ConnectionAbortedException(hex.Message, hex));
throw new InvalidOperationException(hex.Message, hex); // Report the error to the user if this was the first write.
_http3Stream.Abort(new ConnectionAbortedException(ex.Message, ex), Http3ErrorCode.InternalError);
throw new InvalidOperationException(ex.Message, ex); // Report the error to the user if this was the first write.
}
}
}
Expand All @@ -346,12 +357,18 @@ private void FinishWritingHeaders(int payloadLength, bool done)

while (!done)
{
done = QPackHeaderWriter.Encode(_headersEnumerator!, buffer, out payloadLength);
done = QPackHeaderWriter.Encode(_headersEnumerator!, buffer, ref _headersTotalSize, out payloadLength);
_outgoingFrame.Length = payloadLength;

WriteHeaderUnsynchronized();
_outputWriter.Write(buffer.Slice(0, payloadLength));
}

// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.3
if (_headersTotalSize > _maxTotalHeaderSize)
{
throw new QPackEncodingException($"The encoded HTTP headers length exceeds the limit specified by the peer of {_maxTotalHeaderSize} bytes.");
}
}

public ValueTask CompleteAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ private async ValueTask<FlushResult> ProcessDataWrites()
}

_stream.ResponseTrailers.SetReadOnly();
flushResult = await _frameWriter.WriteResponseTrailers(_stream.ResponseTrailers);
flushResult = await _frameWriter.WriteResponseTrailersAsync(_stream.StreamId, _stream.ResponseTrailers);
}
else if (readResult.IsCompleted)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal class Http3PeerSettings
public const uint DefaultMaxRequestHeaderFieldSize = uint.MaxValue;

public uint HeaderTableSize { get; internal set; } = DefaultHeaderTableSize;
public uint MaxRequestHeaderFieldSize { get; internal set; } = DefaultMaxRequestHeaderFieldSize;
public uint MaxRequestHeaderFieldSectionSize { get; internal set; } = DefaultMaxRequestHeaderFieldSize;

// Gets the settings that are different from the protocol defaults (as opposed to the server defaults).
internal List<Http3PeerSetting> GetNonProtocolDefaults()
Expand All @@ -26,9 +26,9 @@ internal List<Http3PeerSetting> GetNonProtocolDefaults()
list.Add(new Http3PeerSetting(Http3SettingType.QPackMaxTableCapacity, HeaderTableSize));
}

if (MaxRequestHeaderFieldSize != DefaultMaxRequestHeaderFieldSize)
if (MaxRequestHeaderFieldSectionSize != DefaultMaxRequestHeaderFieldSize)
{
list.Add(new Http3PeerSetting(Http3SettingType.MaxFieldSectionSize, MaxRequestHeaderFieldSize));
list.Add(new Http3PeerSetting(Http3SettingType.MaxFieldSectionSize, MaxRequestHeaderFieldSectionSize));
}

return list;
Expand Down
16 changes: 13 additions & 3 deletions src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ public Http3Stream(Http3StreamContext context)
context.ConnectionId,
context.MemoryPool,
context.ServiceContext.Log,
_streamIdFeature);
_streamIdFeature,
context.ClientPeerSettings,
this);

// ResponseHeaders aren't set, kind of ugly that we need to reset.
Reset();
Expand Down Expand Up @@ -485,8 +487,16 @@ private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext>
_requestHeaderParsingState = RequestHeaderParsingState.Trailers;
}

QPackDecoder.Decode(payload, handler: this);
QPackDecoder.Reset();
try
{
QPackDecoder.Decode(payload, handler: this);
QPackDecoder.Reset();
}
catch (QPackDecodingException ex)
{
Log.QPackDecodingError(ConnectionId, StreamId, ex);
throw new Http3StreamErrorException(ex.Message, Http3ErrorCode.InternalError);
}

switch (_requestHeaderParsingState)
{
Expand Down
Loading