From 24faf62c9f123ba112ff474078041bd5b7f5807f Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Mon, 30 Jun 2025 19:17:45 +0200 Subject: [PATCH 1/5] fix --- AspNetCore.slnx | 1 + src/Servers/HttpSys/HttpSysServer.slnf | 1 + .../MinimalSample/MinimalSample.csproj | 15 +++++ .../HttpSys/samples/MinimalSample/Program.cs | 43 +++++++++++++ .../Tests/NativeInterop/SocketAddressTests.cs | 62 +++++++++++++++++++ .../HttpSys/NativeInterop/SocketAddress.cs | 31 ++++++++-- 6 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj create mode 100644 src/Servers/HttpSys/samples/MinimalSample/Program.cs create mode 100644 src/Servers/HttpSys/test/Tests/NativeInterop/SocketAddressTests.cs diff --git a/AspNetCore.slnx b/AspNetCore.slnx index 2b5b7180956c..dc0f288e146e 100644 --- a/AspNetCore.slnx +++ b/AspNetCore.slnx @@ -890,6 +890,7 @@ + diff --git a/src/Servers/HttpSys/HttpSysServer.slnf b/src/Servers/HttpSys/HttpSysServer.slnf index 7c081bb272c4..1c3ea95d137e 100644 --- a/src/Servers/HttpSys/HttpSysServer.slnf +++ b/src/Servers/HttpSys/HttpSysServer.slnf @@ -35,6 +35,7 @@ "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", "src\\Servers\\HttpSys\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks.csproj", "src\\Servers\\HttpSys\\samples\\HotAddSample\\HotAddSample.csproj", + "src\\Servers\\HttpSys\\samples\\MinimalSample\\MinimalSample.csproj", "src\\Servers\\HttpSys\\samples\\QueueSharing\\QueueSharing.csproj", "src\\Servers\\HttpSys\\samples\\SelfHostServer\\SelfHostServer.csproj", "src\\Servers\\HttpSys\\samples\\TestClient\\TestClient.csproj", diff --git a/src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj b/src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj new file mode 100644 index 000000000000..3ba4390b4e73 --- /dev/null +++ b/src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj @@ -0,0 +1,15 @@ + + + + $(DefaultNetCoreTargetFramework) + Exe + true + + + + + + + + + diff --git a/src/Servers/HttpSys/samples/MinimalSample/Program.cs b/src/Servers/HttpSys/samples/MinimalSample/Program.cs new file mode 100644 index 000000000000..8f76539fad5c --- /dev/null +++ b/src/Servers/HttpSys/samples/MinimalSample/Program.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.HttpSys; +using Microsoft.Extensions.Hosting; + +var builder = WebApplication.CreateBuilder(args); + +builder.WebHost.UseHttpSys(options => +{ + options.UrlPrefixes.Add("http://*:42000"); + + options.Authentication.Schemes = AuthenticationSchemes.None; + options.Authentication.AllowAnonymous = true; +}); + +var app = builder.Build(); + +app.Use(async (context, next) => +{ + var connectionFeature = context.Features.GetRequiredFeature(); + + var response = $""" + ConnectionInfo: + - HttpContext.Connection.LocalPort : {context.Connection.LocalPort} + - HttpContext.Connection.RemotePort : {context.Connection.RemotePort} + - IHttpConnectionFeature.LocalPort : {connectionFeature.LocalPort} + - IHttpConnectionFeature.RemotePort : {connectionFeature.RemotePort} + """; + + await context.Response.WriteAsync(response); + await next(context); +}); + +app.Run(); diff --git a/src/Servers/HttpSys/test/Tests/NativeInterop/SocketAddressTests.cs b/src/Servers/HttpSys/test/Tests/NativeInterop/SocketAddressTests.cs new file mode 100644 index 000000000000..7304f66c0d3d --- /dev/null +++ b/src/Servers/HttpSys/test/Tests/NativeInterop/SocketAddressTests.cs @@ -0,0 +1,62 @@ +// 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 Microsoft.AspNetCore.HttpSys.Internal; +using Windows.Win32.Networking.WinSock; +using Xunit; +using static Microsoft.AspNetCore.HttpSys.Internal.SocketAddress; +using SocketAddress = Microsoft.AspNetCore.HttpSys.Internal.SocketAddress; + +namespace Microsoft.AspNetCore.Server.HttpSys.Tests.NativeInterop; + +public class SocketAddressTests +{ + [Theory] + [InlineData(80)] + [InlineData(443)] + [InlineData(8080)] + [InlineData(32767)] // max signed short + [InlineData(32768)] // min value that causes negative when cast to short + [InlineData(42000)] + [InlineData(65535)] // Max port number + public void IPv6_GetPort_ReturnsCorrectPort_ForAllValidPorts(ushort expectedPort) + { + var nativeIpV6Address = new SOCKADDR_IN6 + { + sin6_family = ADDRESS_FAMILY.AF_INET6, + sin6_port = (ushort)IPAddress.HostToNetworkOrder((short)expectedPort) + }; + + var socketAddress = new SocketAddressIPv6(nativeIpV6Address); + var actualPort = socketAddress.GetPort(); + + Assert.Equal(expectedPort, actualPort); + Assert.True(actualPort >= 0, "Port should never be negative"); + Assert.True(actualPort <= 65535, "Port should not exceed maximum valid port"); + } + + [Theory] + [InlineData(80)] + [InlineData(443)] + [InlineData(8080)] + [InlineData(42000)] + [InlineData(32767)] + [InlineData(32768)] + [InlineData(65535)] + public void IPv4_GetPort_ReturnsCorrectPort_ForAllValidPorts(ushort expectedPort) + { + var nativeIpV4Address = new SOCKADDR_IN + { + sin_family = ADDRESS_FAMILY.AF_INET, + sin_port = (ushort)IPAddress.HostToNetworkOrder((short)expectedPort) + }; + + var socketAddress = new SocketAddressIPv4(nativeIpV4Address); + var actualPort = socketAddress.GetPort(); + + Assert.Equal(expectedPort, actualPort); + Assert.True(actualPort >= 0, "Port should never be negative"); + Assert.True(actualPort <= 65535, "Port should not exceed maximum valid port"); + } +} diff --git a/src/Shared/HttpSys/NativeInterop/SocketAddress.cs b/src/Shared/HttpSys/NativeInterop/SocketAddress.cs index 9c3e7136e30b..523a474f9fe4 100644 --- a/src/Shared/HttpSys/NativeInterop/SocketAddress.cs +++ b/src/Shared/HttpSys/NativeInterop/SocketAddress.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers.Binary; using System.Net; using Windows.Win32.Networking.WinSock; @@ -25,7 +26,8 @@ internal abstract class SocketAddress }; } - private sealed class SocketAddressIPv4 : SocketAddress + // internal for testing + internal sealed class SocketAddressIPv4 : SocketAddress { private readonly SOCKADDR_IN _sockaddr; @@ -36,8 +38,16 @@ internal SocketAddressIPv4(in SOCKADDR_IN sockaddr) internal override int GetPort() { - // sin_port is network byte order - return IPAddress.NetworkToHostOrder((short)_sockaddr.sin_port); + // _sockaddr.sin_port has network byte order. + // --- + // We could use IPAddress.NetworkToHostOrder() here (see https://source.dot.net/#System.Net.Primitives/System/Net/IPAddress.cs,547), + // but it does not have a support for unsigned-short, resulting in incorrect representation of the result. + // --- + // However, BinaryPrimitives.ReverseEndianness() does support unsigned-short (see https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Buffers/Binary/BinaryPrimitives.ReverseEndianness.cs,100) + // which makes a correct conversion for a TCP port number range (0 - 65535). + return BitConverter.IsLittleEndian + ? BinaryPrimitives.ReverseEndianness(_sockaddr.sin_port) + : _sockaddr.sin_port; } internal override IPAddress? GetIPAddress() @@ -47,7 +57,8 @@ internal override int GetPort() } } - private sealed class SocketAddressIPv6 : SocketAddress + // internal for testing + internal sealed class SocketAddressIPv6 : SocketAddress { private readonly SOCKADDR_IN6 _sockaddr; @@ -58,8 +69,16 @@ internal SocketAddressIPv6(in SOCKADDR_IN6 sockaddr) internal override int GetPort() { - // sin6_port is network byte order - return IPAddress.NetworkToHostOrder((short)_sockaddr.sin6_port); + // _sockaddr.sin6_port has network byte order. + // --- + // We could use IPAddress.NetworkToHostOrder() here (see https://source.dot.net/#System.Net.Primitives/System/Net/IPAddress.cs,547), + // but it does not have a support for unsigned-short, resulting in incorrect representation of the result. + // --- + // However, BinaryPrimitives.ReverseEndianness() does support unsigned-short (see https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Buffers/Binary/BinaryPrimitives.ReverseEndianness.cs,100) + // which makes a correct conversion for a TCP port number range (0 - 65535). + return BitConverter.IsLittleEndian + ? BinaryPrimitives.ReverseEndianness(_sockaddr.sin6_port) + : _sockaddr.sin6_port; } internal override IPAddress? GetIPAddress() From 3a7d849e5cfdb085faf376c826140086214b9e8d Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Mon, 30 Jun 2025 19:49:57 +0200 Subject: [PATCH 2/5] easier cast --- .../MinimalSample/MinimalSample.csproj | 1 - .../HttpSys/NativeInterop/SocketAddress.cs | 20 ++----------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj b/src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj index 3ba4390b4e73..bc36bce6be59 100644 --- a/src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj +++ b/src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj @@ -3,7 +3,6 @@ $(DefaultNetCoreTargetFramework) Exe - true diff --git a/src/Shared/HttpSys/NativeInterop/SocketAddress.cs b/src/Shared/HttpSys/NativeInterop/SocketAddress.cs index 523a474f9fe4..7883a4c1e166 100644 --- a/src/Shared/HttpSys/NativeInterop/SocketAddress.cs +++ b/src/Shared/HttpSys/NativeInterop/SocketAddress.cs @@ -39,15 +39,7 @@ internal SocketAddressIPv4(in SOCKADDR_IN sockaddr) internal override int GetPort() { // _sockaddr.sin_port has network byte order. - // --- - // We could use IPAddress.NetworkToHostOrder() here (see https://source.dot.net/#System.Net.Primitives/System/Net/IPAddress.cs,547), - // but it does not have a support for unsigned-short, resulting in incorrect representation of the result. - // --- - // However, BinaryPrimitives.ReverseEndianness() does support unsigned-short (see https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Buffers/Binary/BinaryPrimitives.ReverseEndianness.cs,100) - // which makes a correct conversion for a TCP port number range (0 - 65535). - return BitConverter.IsLittleEndian - ? BinaryPrimitives.ReverseEndianness(_sockaddr.sin_port) - : _sockaddr.sin_port; + return (ushort)IPAddress.NetworkToHostOrder((short)_sockaddr.sin_port); } internal override IPAddress? GetIPAddress() @@ -70,15 +62,7 @@ internal SocketAddressIPv6(in SOCKADDR_IN6 sockaddr) internal override int GetPort() { // _sockaddr.sin6_port has network byte order. - // --- - // We could use IPAddress.NetworkToHostOrder() here (see https://source.dot.net/#System.Net.Primitives/System/Net/IPAddress.cs,547), - // but it does not have a support for unsigned-short, resulting in incorrect representation of the result. - // --- - // However, BinaryPrimitives.ReverseEndianness() does support unsigned-short (see https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Buffers/Binary/BinaryPrimitives.ReverseEndianness.cs,100) - // which makes a correct conversion for a TCP port number range (0 - 65535). - return BitConverter.IsLittleEndian - ? BinaryPrimitives.ReverseEndianness(_sockaddr.sin6_port) - : _sockaddr.sin6_port; + return (ushort)IPAddress.NetworkToHostOrder((short)_sockaddr.sin6_port); } internal override IPAddress? GetIPAddress() From 61b437b007ecf70ee628c3b6921278ba11a948e4 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Mon, 30 Jun 2025 19:57:33 +0200 Subject: [PATCH 3/5] explain casting to ushort --- src/Shared/HttpSys/NativeInterop/SocketAddress.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Shared/HttpSys/NativeInterop/SocketAddress.cs b/src/Shared/HttpSys/NativeInterop/SocketAddress.cs index 7883a4c1e166..b38af58c0638 100644 --- a/src/Shared/HttpSys/NativeInterop/SocketAddress.cs +++ b/src/Shared/HttpSys/NativeInterop/SocketAddress.cs @@ -39,6 +39,7 @@ internal SocketAddressIPv4(in SOCKADDR_IN sockaddr) internal override int GetPort() { // _sockaddr.sin_port has network byte order. + // cast to ushort is important to avoid negative values for the TCP port return (ushort)IPAddress.NetworkToHostOrder((short)_sockaddr.sin_port); } @@ -62,6 +63,7 @@ internal SocketAddressIPv6(in SOCKADDR_IN6 sockaddr) internal override int GetPort() { // _sockaddr.sin6_port has network byte order. + // cast to ushort is important to avoid negative values for the TCP port return (ushort)IPAddress.NetworkToHostOrder((short)_sockaddr.sin6_port); } From 1dc9c045eb9bf3c6aaf91845ec34308b6d0b6d80 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Mon, 30 Jun 2025 20:48:23 +0200 Subject: [PATCH 4/5] add launch settings for sample? --- .../MinimalSample/Properties/launchSettings.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/Servers/HttpSys/samples/MinimalSample/Properties/launchSettings.json diff --git a/src/Servers/HttpSys/samples/MinimalSample/Properties/launchSettings.json b/src/Servers/HttpSys/samples/MinimalSample/Properties/launchSettings.json new file mode 100644 index 000000000000..89042ac01c9c --- /dev/null +++ b/src/Servers/HttpSys/samples/MinimalSample/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "MinimalSample": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:42000", + "nativeDebugging": true + } + } +} From e4af49cdf8af2920486a0b87d927277d506ba318 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Mon, 30 Jun 2025 21:21:38 +0200 Subject: [PATCH 5/5] screw it, no sample --- AspNetCore.slnx | 1 - src/Servers/HttpSys/HttpSysServer.slnf | 3 +- .../MinimalSample/MinimalSample.csproj | 14 ------ .../HttpSys/samples/MinimalSample/Program.cs | 43 ------------------- .../Properties/launchSettings.json | 10 ----- 5 files changed, 1 insertion(+), 70 deletions(-) delete mode 100644 src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj delete mode 100644 src/Servers/HttpSys/samples/MinimalSample/Program.cs delete mode 100644 src/Servers/HttpSys/samples/MinimalSample/Properties/launchSettings.json diff --git a/AspNetCore.slnx b/AspNetCore.slnx index dc0f288e146e..2b5b7180956c 100644 --- a/AspNetCore.slnx +++ b/AspNetCore.slnx @@ -890,7 +890,6 @@ - diff --git a/src/Servers/HttpSys/HttpSysServer.slnf b/src/Servers/HttpSys/HttpSysServer.slnf index 1c3ea95d137e..b9171033dd0d 100644 --- a/src/Servers/HttpSys/HttpSysServer.slnf +++ b/src/Servers/HttpSys/HttpSysServer.slnf @@ -35,7 +35,6 @@ "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", "src\\Servers\\HttpSys\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks.csproj", "src\\Servers\\HttpSys\\samples\\HotAddSample\\HotAddSample.csproj", - "src\\Servers\\HttpSys\\samples\\MinimalSample\\MinimalSample.csproj", "src\\Servers\\HttpSys\\samples\\QueueSharing\\QueueSharing.csproj", "src\\Servers\\HttpSys\\samples\\SelfHostServer\\SelfHostServer.csproj", "src\\Servers\\HttpSys\\samples\\TestClient\\TestClient.csproj", @@ -56,4 +55,4 @@ "src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj" ] } -} \ No newline at end of file +} diff --git a/src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj b/src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj deleted file mode 100644 index bc36bce6be59..000000000000 --- a/src/Servers/HttpSys/samples/MinimalSample/MinimalSample.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - $(DefaultNetCoreTargetFramework) - Exe - - - - - - - - - diff --git a/src/Servers/HttpSys/samples/MinimalSample/Program.cs b/src/Servers/HttpSys/samples/MinimalSample/Program.cs deleted file mode 100644 index 8f76539fad5c..000000000000 --- a/src/Servers/HttpSys/samples/MinimalSample/Program.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.InteropServices; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Server.HttpSys; -using Microsoft.Extensions.Hosting; - -var builder = WebApplication.CreateBuilder(args); - -builder.WebHost.UseHttpSys(options => -{ - options.UrlPrefixes.Add("http://*:42000"); - - options.Authentication.Schemes = AuthenticationSchemes.None; - options.Authentication.AllowAnonymous = true; -}); - -var app = builder.Build(); - -app.Use(async (context, next) => -{ - var connectionFeature = context.Features.GetRequiredFeature(); - - var response = $""" - ConnectionInfo: - - HttpContext.Connection.LocalPort : {context.Connection.LocalPort} - - HttpContext.Connection.RemotePort : {context.Connection.RemotePort} - - IHttpConnectionFeature.LocalPort : {connectionFeature.LocalPort} - - IHttpConnectionFeature.RemotePort : {connectionFeature.RemotePort} - """; - - await context.Response.WriteAsync(response); - await next(context); -}); - -app.Run(); diff --git a/src/Servers/HttpSys/samples/MinimalSample/Properties/launchSettings.json b/src/Servers/HttpSys/samples/MinimalSample/Properties/launchSettings.json deleted file mode 100644 index 89042ac01c9c..000000000000 --- a/src/Servers/HttpSys/samples/MinimalSample/Properties/launchSettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "profiles": { - "MinimalSample": { - "commandName": "Project", - "launchBrowser": true, - "applicationUrl": "http://localhost:42000", - "nativeDebugging": true - } - } -}