зеркало из https://github.com/dotnet/aspnetcore.git
Adding WebTransport Handshake to Kestrel (#41877)
This commit is contained in:
Родитель
69cec6c7ee
Коммит
d96a100bdd
|
@ -189,6 +189,9 @@ public static class HeaderNames
|
|||
/// <summary>Gets the <c>Pragma</c> HTTP header name.</summary>
|
||||
public static readonly string Pragma = "Pragma";
|
||||
|
||||
/// <summary>Gets the <c>Protocol</c> HTTP header name.</summary>
|
||||
public static readonly string Protocol = ":protocol";
|
||||
|
||||
/// <summary>Gets the <c>Proxy-Authenticate</c> HTTP header name.</summary>
|
||||
public static readonly string ProxyAuthenticate = "Proxy-Authenticate";
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#nullable enable
|
||||
static readonly Microsoft.Net.Http.Headers.HeaderNames.Protocol -> string!
|
||||
|
|
|
@ -668,4 +668,13 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
|||
<data name="Http3ControlStreamErrorInitializingOutbound" xml:space="preserve">
|
||||
<value>Error initializing outbound control stream.</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="Http3DatagramStatusMismatch" xml:space="preserve">
|
||||
<value>HTTP/3 datagrams negotiation mismatch. Currently client has it '{clientStatus}' and server has it '{serverStatus}'</value>
|
||||
</data>
|
||||
<data name="Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader" xml:space="preserve">
|
||||
<value>Method must be CONNECT when using the :protocol pseudo-header.</value>
|
||||
</data>
|
||||
<data name="Http3MissingAuthorityOrPathPseudoHeaders" xml:space="preserve">
|
||||
<value>The :authority and/or :path pseudo-headers are missing.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -56,6 +56,10 @@ internal sealed class Http3Connection : IHttp3StreamLifetimeHandler, IRequestPro
|
|||
|
||||
_serverSettings.HeaderTableSize = (uint)httpLimits.Http3.HeaderTableSize;
|
||||
_serverSettings.MaxRequestHeaderFieldSectionSize = (uint)httpLimits.MaxRequestHeadersTotalSize;
|
||||
_serverSettings.EnableWebTransport = Convert.ToUInt32(context.ServiceContext.ServerOptions.EnableWebTransportAndH3Datagrams);
|
||||
// technically these are 2 different settings so they should have separate values but the Chromium implementation requires
|
||||
// them to both be 1 to useWebTransport.
|
||||
_serverSettings.H3Datagram = Convert.ToUInt32(context.ServiceContext.ServerOptions.EnableWebTransportAndH3Datagrams);
|
||||
}
|
||||
|
||||
private void UpdateHighestOpenedRequestStreamId(long streamId)
|
||||
|
@ -656,6 +660,12 @@ internal sealed class Http3Connection : IHttp3StreamLifetimeHandler, IRequestPro
|
|||
break;
|
||||
case Http3SettingType.QPackBlockedStreams:
|
||||
break;
|
||||
case Http3SettingType.EnableWebTransport:
|
||||
_clientSettings.EnableWebTransport = (uint)value;
|
||||
break;
|
||||
case Http3SettingType.H3Datagram:
|
||||
_clientSettings.H3Datagram = (uint)value;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected setting: " + type);
|
||||
}
|
||||
|
|
|
@ -331,6 +331,8 @@ internal abstract class Http3ControlStream : IHttp3Stream, IThreadPoolWorkItem
|
|||
case (long)Http3SettingType.QPackMaxTableCapacity:
|
||||
case (long)Http3SettingType.MaxFieldSectionSize:
|
||||
case (long)Http3SettingType.QPackBlockedStreams:
|
||||
case (long)Http3SettingType.EnableWebTransport:
|
||||
case (long)Http3SettingType.H3Datagram:
|
||||
_context.StreamLifetimeHandler.OnInboundControlStreamSetting((Http3SettingType)id, value);
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -8,9 +8,13 @@ internal sealed class Http3PeerSettings
|
|||
// Note these are protocol defaults, not Kestrel defaults.
|
||||
public const uint DefaultHeaderTableSize = 0;
|
||||
public const uint DefaultMaxRequestHeaderFieldSize = uint.MaxValue;
|
||||
public const uint DefaultEnableWebTransport = 0;
|
||||
public const uint DefaultH3Datagram = 0;
|
||||
|
||||
public uint HeaderTableSize { get; internal set; } = DefaultHeaderTableSize;
|
||||
public uint MaxRequestHeaderFieldSectionSize { get; internal set; } = DefaultMaxRequestHeaderFieldSize;
|
||||
public uint EnableWebTransport { get; internal set; } = DefaultEnableWebTransport;
|
||||
public uint H3Datagram { get; internal set; } = DefaultH3Datagram;
|
||||
|
||||
// Gets the settings that are different from the protocol defaults (as opposed to the server defaults).
|
||||
internal List<Http3PeerSetting> GetNonProtocolDefaults()
|
||||
|
@ -29,6 +33,16 @@ internal sealed class Http3PeerSettings
|
|||
list.Add(new Http3PeerSetting(Http3SettingType.MaxFieldSectionSize, MaxRequestHeaderFieldSectionSize));
|
||||
}
|
||||
|
||||
if (EnableWebTransport != DefaultEnableWebTransport)
|
||||
{
|
||||
list.Add(new Http3PeerSetting(Http3SettingType.EnableWebTransport, EnableWebTransport));
|
||||
}
|
||||
|
||||
if (H3Datagram != DefaultH3Datagram)
|
||||
{
|
||||
list.Add(new Http3PeerSetting(Http3SettingType.H3Datagram, H3Datagram));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,5 +13,18 @@ internal enum Http3SettingType : long
|
|||
/// </summary>
|
||||
MaxFieldSectionSize = 0x6,
|
||||
// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-5
|
||||
QPackBlockedStreams = 0x7
|
||||
QPackBlockedStreams = 0x7,
|
||||
|
||||
/// <summary>
|
||||
/// SETTINGS_ENABLE_WEBTRANSPORT, default is 0 (off)
|
||||
/// https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-01.html#name-http-3-settings-parameter-r
|
||||
/// </summary>
|
||||
EnableWebTransport = 0x2b603742,
|
||||
|
||||
/// <summary>
|
||||
/// H3_DATAGRAM, default is 0 (off)
|
||||
/// indicates that the server suppprts sending individual datagrams over Http/3
|
||||
/// rather than just streams.
|
||||
/// </summary>
|
||||
H3Datagram = 0xffd277
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
|
|||
private static ReadOnlySpan<byte> AuthorityBytes => ":authority"u8;
|
||||
private static ReadOnlySpan<byte> MethodBytes => ":method"u8;
|
||||
private static ReadOnlySpan<byte> PathBytes => ":path"u8;
|
||||
private static ReadOnlySpan<byte> ProtocolBytes => ":protocol"u8;
|
||||
private static ReadOnlySpan<byte> SchemeBytes => ":scheme"u8;
|
||||
private static ReadOnlySpan<byte> StatusBytes => ":status"u8;
|
||||
private static ReadOnlySpan<byte> ConnectionBytes => "connection"u8;
|
||||
|
@ -507,6 +508,10 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
|
|||
{
|
||||
return PseudoHeaderFields.Authority;
|
||||
}
|
||||
else if (name.SequenceEqual(ProtocolBytes))
|
||||
{
|
||||
return PseudoHeaderFields.Protocol;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PseudoHeaderFields.Unknown;
|
||||
|
@ -821,7 +826,25 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
|
|||
await OnEndStreamReceived();
|
||||
}
|
||||
|
||||
if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.3
|
||||
if (_context.ServiceContext.ServerOptions.EnableWebTransportAndH3Datagrams && HttpRequestHeaders.HeaderProtocol.Count > 0)
|
||||
{
|
||||
if (!_isMethodConnect)
|
||||
{
|
||||
throw new Http3StreamErrorException(CoreStrings.Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader, Http3ErrorCode.ProtocolError);
|
||||
}
|
||||
|
||||
if (!_parsedPseudoHeaderFields.HasFlag(PseudoHeaderFields.Authority) || !_parsedPseudoHeaderFields.HasFlag(PseudoHeaderFields.Path))
|
||||
{
|
||||
throw new Http3StreamErrorException(CoreStrings.Http3MissingAuthorityOrPathPseudoHeaders, Http3ErrorCode.ProtocolError);
|
||||
}
|
||||
|
||||
if (_context.ClientPeerSettings.H3Datagram != _context.ServerPeerSettings.H3Datagram)
|
||||
{
|
||||
throw new Http3StreamErrorException(CoreStrings.FormatHttp3DatagramStatusMismatch(_context.ClientPeerSettings.H3Datagram == 1, _context.ServerPeerSettings.H3Datagram == 1), Http3ErrorCode.SettingsError);
|
||||
}
|
||||
}
|
||||
else if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
|
||||
{
|
||||
// All HTTP/3 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header
|
||||
// fields, unless it is a CONNECT request. An HTTP request that omits mandatory pseudo-header
|
||||
|
@ -928,8 +951,8 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
|
|||
return false;
|
||||
}
|
||||
|
||||
// CONNECT - :scheme and :path must be excluded
|
||||
if (Method == Http.HttpMethod.Connect)
|
||||
// CONNECT - :scheme and :path must be excluded=
|
||||
if (Method == Http.HttpMethod.Connect && HttpRequestHeaders.HeaderProtocol.Count == 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(RequestHeaders[HeaderNames.Scheme]) || !string.IsNullOrEmpty(RequestHeaders[HeaderNames.Path]))
|
||||
{
|
||||
|
@ -1157,6 +1180,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
|
|||
Path = 0x4,
|
||||
Scheme = 0x8,
|
||||
Status = 0x10,
|
||||
Protocol = 0x20,
|
||||
Unknown = 0x40000000
|
||||
}
|
||||
|
||||
|
|
|
@ -157,6 +157,24 @@ public class KestrelServerOptions
|
|||
/// </summary>
|
||||
internal bool IsDevCertLoaded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal AppContext switch to toggle the WebTransport and HTTP/3 datagrams experiemental features.
|
||||
/// </summary>
|
||||
private bool? _enableWebTransportAndH3Datagrams;
|
||||
internal bool EnableWebTransportAndH3Datagrams
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_enableWebTransportAndH3Datagrams.HasValue)
|
||||
{
|
||||
_enableWebTransportAndH3Datagrams = AppContext.TryGetSwitch("Microsoft.AspNetCore.Server.Kestrel.Experimental.WebTransportAndH3Datagrams", out var enabled) && enabled;
|
||||
}
|
||||
|
||||
return _enableWebTransportAndH3Datagrams.Value;
|
||||
}
|
||||
set => _enableWebTransportAndH3Datagrams = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies a configuration Action to run for each newly created endpoint. Calling this again will replace
|
||||
/// the prior action.
|
||||
|
|
|
@ -16,4 +16,9 @@
|
|||
<Reference Include="Microsoft.Extensions.Hosting" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Turn on the WebTransport AppContext switch -->
|
||||
<RuntimeHostConfigurationOption Include="Microsoft.AspNetCore.Server.Kestrel.Experimental.WebTransportAndH3Datagrams" Value="true" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
|
@ -22,25 +24,18 @@ public class Program
|
|||
})
|
||||
.ConfigureWebHost(webHost =>
|
||||
{
|
||||
var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false);
|
||||
|
||||
webHost.UseKestrel()
|
||||
.ConfigureKestrel((context, options) =>
|
||||
{
|
||||
var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false);
|
||||
|
||||
options.ConfigureHttpsDefaults(httpsOptions =>
|
||||
{
|
||||
httpsOptions.ServerCertificate = cert;
|
||||
// httpsOptions.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
|
||||
// httpsOptions.AllowAnyClientCertificate();
|
||||
});
|
||||
|
||||
options.ListenAnyIP(5000, listenOptions =>
|
||||
{
|
||||
listenOptions.UseConnectionLogging();
|
||||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
||||
});
|
||||
|
||||
options.ListenAnyIP(5001, listenOptions =>
|
||||
options.Listen(IPAddress.Any, 5001, listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps();
|
||||
listenOptions.UseConnectionLogging();
|
||||
|
@ -49,8 +44,8 @@ public class Program
|
|||
|
||||
options.ListenAnyIP(5002, listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps(StoreName.My, "localhost");
|
||||
listenOptions.UseConnectionLogging();
|
||||
listenOptions.UseHttps(StoreName.My, "localhost");
|
||||
listenOptions.Protocols = HttpProtocols.Http3;
|
||||
});
|
||||
|
||||
|
@ -108,6 +103,14 @@ public class Program
|
|||
listenOptions.UseConnectionLogging();
|
||||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
||||
});
|
||||
|
||||
// Port configured for WebTransport
|
||||
options.Listen(IPAddress.Any, 5007, listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps(GenerateManualCertificate());
|
||||
listenOptions.UseConnectionLogging();
|
||||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
|
||||
});
|
||||
})
|
||||
.UseStartup<Startup>();
|
||||
});
|
||||
|
@ -119,4 +122,54 @@ public class Program
|
|||
|
||||
host.Run();
|
||||
}
|
||||
|
||||
// Adapted from: https://github.com/wegylexy/webtransport
|
||||
// We will need to eventually merge this with existing Kestrel certificate generation
|
||||
// tracked in issue #41762
|
||||
private static X509Certificate2 GenerateManualCertificate()
|
||||
{
|
||||
X509Certificate2 cert = null;
|
||||
var store = new X509Store("KestrelWebTransportCertificates", StoreLocation.CurrentUser);
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
if (store.Certificates.Count > 0)
|
||||
{
|
||||
cert = store.Certificates[^1];
|
||||
|
||||
// rotate key after it expires
|
||||
if (DateTime.Parse(cert.GetExpirationDateString(), null) < DateTimeOffset.UtcNow)
|
||||
{
|
||||
cert = null;
|
||||
}
|
||||
}
|
||||
if (cert == null)
|
||||
{
|
||||
// generate a new cert
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
SubjectAlternativeNameBuilder sanBuilder = new();
|
||||
sanBuilder.AddDnsName("localhost");
|
||||
using var ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
|
||||
CertificateRequest req = new("CN=localhost", ec, HashAlgorithmName.SHA256);
|
||||
// Adds purpose
|
||||
req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection
|
||||
{
|
||||
new("1.3.6.1.5.5.7.3.1") // serverAuth
|
||||
}, false));
|
||||
// Adds usage
|
||||
req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false));
|
||||
// Adds subject alternate names
|
||||
req.CertificateExtensions.Add(sanBuilder.Build());
|
||||
// Sign
|
||||
using var crt = req.CreateSelfSigned(now, now.AddDays(14)); // 14 days is the max duration of a certificate for this
|
||||
cert = new(crt.Export(X509ContentType.Pfx));
|
||||
|
||||
// Save
|
||||
store.Add(cert);
|
||||
}
|
||||
store.Close();
|
||||
|
||||
var hash = SHA256.HashData(cert.RawData);
|
||||
var certStr = Convert.ToBase64String(hash);
|
||||
Console.WriteLine($"\n\n\n\n\nCertificate: {certStr}\n\n\n\n"); // <-- you will need to put this output into the JS API call to allo wthe connection
|
||||
return cert;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ public class KnownHeaders
|
|||
HeaderNames.Connection,
|
||||
HeaderNames.Scheme,
|
||||
HeaderNames.Path,
|
||||
HeaderNames.Protocol,
|
||||
HeaderNames.Method,
|
||||
HeaderNames.Authority,
|
||||
HeaderNames.Host,
|
||||
|
@ -45,7 +46,8 @@ public class KnownHeaders
|
|||
"Method", // :method
|
||||
"Path", // :path
|
||||
"Scheme", // :scheme
|
||||
"Status" // :status
|
||||
"Status", // :status
|
||||
"Protocol" // :protocol
|
||||
};
|
||||
|
||||
public static readonly string[] NonApiHeaders =
|
||||
|
@ -132,6 +134,7 @@ public class KnownHeaders
|
|||
HeaderNames.IfRange,
|
||||
HeaderNames.IfUnmodifiedSince,
|
||||
HeaderNames.MaxForwards,
|
||||
HeaderNames.Protocol,
|
||||
HeaderNames.ProxyAuthorization,
|
||||
HeaderNames.Referer,
|
||||
HeaderNames.Range,
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Http3SettingType = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.Http3SettingType;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
|
||||
|
||||
public class WebTransportTests : Http3TestBase
|
||||
{
|
||||
[Fact]
|
||||
public async Task WebTransportHandshake_ClientToServerPasses()
|
||||
{
|
||||
_serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = true;
|
||||
|
||||
await Http3Api.InitializeConnectionAsync(_noopApplication);
|
||||
var controlStream = await Http3Api.CreateControlStream();
|
||||
var controlStream2 = await Http3Api.GetInboundControlStream();
|
||||
|
||||
var settings = new Http3PeerSettings()
|
||||
{
|
||||
EnableWebTransport = 1,
|
||||
H3Datagram = 1,
|
||||
};
|
||||
|
||||
await controlStream.SendSettingsAsync(settings.GetNonProtocolDefaults());
|
||||
var response1 = await controlStream2.ExpectSettingsAsync();
|
||||
|
||||
await Http3Api.ServerReceivedSettingsReader.ReadAsync().DefaultTimeout();
|
||||
|
||||
Assert.Equal(1, response1[(long)Http3SettingType.EnableWebTransport]);
|
||||
|
||||
var requestStream = await Http3Api.CreateRequestStream();
|
||||
var headersConnectFrame = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "CONNECT"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Protocol, "webtransport"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Authority, "server.example.com"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Origin, "server.example.com")
|
||||
};
|
||||
|
||||
await requestStream.SendHeadersAsync(headersConnectFrame);
|
||||
var response2 = await requestStream.ExpectHeadersAsync();
|
||||
|
||||
Assert.Equal((int)HttpStatusCode.OK, Convert.ToInt32(response2[HeaderNames.Status], null));
|
||||
|
||||
await requestStream.OnDisposedTask.DefaultTimeout();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(
|
||||
((long)Http3ErrorCode.ProtocolError),
|
||||
nameof(CoreStrings.Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader),
|
||||
nameof(HeaderNames.Method), "GET", // incorrect method (verifies that webtransport doesn't break regular Http/3 get)
|
||||
nameof(HeaderNames.Protocol), "webtransport",
|
||||
nameof(HeaderNames.Scheme), "http",
|
||||
nameof(HeaderNames.Path), "/",
|
||||
nameof(HeaderNames.Authority), "server.example.com",
|
||||
nameof(HeaderNames.Origin), "server.example.com")]
|
||||
[InlineData(
|
||||
((long)Http3ErrorCode.ProtocolError),
|
||||
nameof(CoreStrings.Http3MissingAuthorityOrPathPseudoHeaders),
|
||||
nameof(HeaderNames.Method), "CONNECT",
|
||||
nameof(HeaderNames.Protocol), "webtransport",
|
||||
nameof(HeaderNames.Scheme), "http",
|
||||
nameof(HeaderNames.Authority), "server.example.com",
|
||||
nameof(HeaderNames.Origin), "server.example.com")] // no path
|
||||
[InlineData(
|
||||
((long)Http3ErrorCode.ProtocolError),
|
||||
nameof(CoreStrings.Http3MissingAuthorityOrPathPseudoHeaders),
|
||||
nameof(HeaderNames.Method), "CONNECT",
|
||||
nameof(HeaderNames.Protocol), "webtransport",
|
||||
nameof(HeaderNames.Scheme), "http",
|
||||
nameof(HeaderNames.Path), "/",
|
||||
nameof(HeaderNames.Origin), "server.example.com")] // no authority
|
||||
public async Task WebTransportHandshake_IncorrectHeadersRejects(long error, string targetErrorMessage, params string[] headers) // todo replace the "" with CoreStrings.... then push (maybe also update the waitforstreamerror function) and resolve stephen's comment
|
||||
{
|
||||
_serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = true;
|
||||
|
||||
await Http3Api.InitializeConnectionAsync(_noopApplication);
|
||||
var controlStream = await Http3Api.CreateControlStream();
|
||||
var controlStream2 = await Http3Api.GetInboundControlStream();
|
||||
|
||||
var settings = new Http3PeerSettings()
|
||||
{
|
||||
EnableWebTransport = 1,
|
||||
H3Datagram = 1,
|
||||
};
|
||||
|
||||
await controlStream.SendSettingsAsync(settings.GetNonProtocolDefaults());
|
||||
var response1 = await controlStream2.ExpectSettingsAsync();
|
||||
|
||||
await Http3Api.ServerReceivedSettingsReader.ReadAsync().DefaultTimeout();
|
||||
|
||||
Assert.Equal(1, response1[(long)Http3SettingType.EnableWebTransport]);
|
||||
|
||||
var requestStream = await Http3Api.CreateRequestStream();
|
||||
|
||||
var headersConnectFrame = new List<KeyValuePair<string, string>>();
|
||||
for (var i = 0; i < headers.Length; i += 2)
|
||||
{
|
||||
headersConnectFrame.Add(new KeyValuePair<string, string>(GetHeaderFromName(headers[i]), headers[i + 1]));
|
||||
}
|
||||
await requestStream.SendHeadersAsync(headersConnectFrame);
|
||||
|
||||
await requestStream.WaitForStreamErrorAsync((Http3ErrorCode)error, AssertExpectedErrorMessages, GetCoreStringFromName(targetErrorMessage));
|
||||
}
|
||||
|
||||
private static string GetCoreStringFromName(string headerName)
|
||||
{
|
||||
return headerName switch
|
||||
{
|
||||
nameof(CoreStrings.Http3MissingAuthorityOrPathPseudoHeaders) => CoreStrings.Http3MissingAuthorityOrPathPseudoHeaders,
|
||||
nameof(CoreStrings.Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader) => CoreStrings.Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader,
|
||||
_ => throw new Exception("Core string not mapped yet")
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetHeaderFromName(string coreStringName)
|
||||
{
|
||||
return coreStringName switch
|
||||
{
|
||||
nameof(HeaderNames.Method) => HeaderNames.Method,
|
||||
nameof(HeaderNames.Protocol) => HeaderNames.Protocol,
|
||||
nameof(HeaderNames.Scheme) => HeaderNames.Scheme,
|
||||
nameof(HeaderNames.Path) => HeaderNames.Path,
|
||||
nameof(HeaderNames.Authority) => HeaderNames.Authority,
|
||||
nameof(HeaderNames.Origin) => HeaderNames.Origin,
|
||||
_ => throw new Exception("Header name not mapped yet")
|
||||
};
|
||||
}
|
||||
}
|
|
@ -33,6 +33,19 @@ namespace System.Net.Http
|
|||
/// The maximum number of request streams that can be blocked waiting for QPack instructions. The default is 0.
|
||||
/// https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-5
|
||||
/// </summary>
|
||||
QPackBlockedStreams = 0x7
|
||||
QPackBlockedStreams = 0x7,
|
||||
|
||||
/// <summary>
|
||||
/// SETTINGS_ENABLE_WEBTRANSPORT, default is 0 (off)
|
||||
/// https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-01.html#name-http-3-settings-parameter-r
|
||||
/// </summary>
|
||||
EnableWebTransport = 0x2b603742,
|
||||
|
||||
/// <summary>
|
||||
/// H3_DATAGRAM, default is 0 (off)
|
||||
/// indicates that the server suppprts sending individual datagrams over Http/3
|
||||
/// rather than just streams.
|
||||
/// </summary>
|
||||
H3Datagram = 0xffd277
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче