From 1a553b49c737c55777812a284e9d268d06dc4f39 Mon Sep 17 00:00:00 2001 From: Austin Wise Date: Sun, 11 May 2025 16:25:29 -0700 Subject: [PATCH] Don't mutating the ScopeId on static readonly IPAddress fields The Address field was made readonly in dotnet/corefx#33531. Somehow ScopeId, the only other mutatable property, was missed. I did not see discussion of this in that PR or the related issue. An example program that illistrates the problem: ```csharp Console.WriteLine(IPAddress.IPv6Loopback); IPAddress.IPv6Loopback.ScopeId = 1; Console.WriteLine(IPAddress.IPv6Loopback); ``` This prints: ```txt ::1 ::1%1 ``` I think the right behavior is to throw when setting the ScopeId (this pr). Contributes to #27870 --- .../src/System/Net/IPAddress.cs | 14 +++++++++++--- .../tests/FunctionalTests/IPAddressTest.cs | 4 ++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs b/src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs index 541e96219c1c03..8df7c79d445bc8 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs @@ -28,11 +28,11 @@ public class IPAddress : ISpanFormattable, ISpanParsable, IUtf8SpanFo internal const uint LoopbackMaskHostOrder = 0xFF000000; - public static readonly IPAddress IPv6Any = new IPAddress((ReadOnlySpan)[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0); - public static readonly IPAddress IPv6Loopback = new IPAddress((ReadOnlySpan)[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 0); + public static readonly IPAddress IPv6Any = new ReadOnlyIPAddress([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0); + public static readonly IPAddress IPv6Loopback = new ReadOnlyIPAddress([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 0); public static readonly IPAddress IPv6None = IPv6Any; - private static readonly IPAddress s_loopbackMappedToIPv6 = new IPAddress((ReadOnlySpan)[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1], 0); + private static readonly IPAddress s_loopbackMappedToIPv6 = new ReadOnlyIPAddress([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1], 0); /// /// For IPv4 addresses, this field stores the Address. @@ -438,6 +438,11 @@ public long ScopeId ThrowSocketOperationNotSupported(); } + if (this is ReadOnlyIPAddress) + { + ThrowSocketOperationNotSupported(); + } + // Consider: Since scope is only valid for link-local and site-local // addresses we could implement some more robust checking here ArgumentOutOfRangeException.ThrowIfNegative(value); @@ -772,6 +777,9 @@ private sealed class ReadOnlyIPAddress : IPAddress { public ReadOnlyIPAddress(ReadOnlySpan newAddress) : base(newAddress) { } + + public ReadOnlyIPAddress(ReadOnlySpan address, long scopeid) : base(address, scopeid) + { } } } } diff --git a/src/libraries/System.Net.Primitives/tests/FunctionalTests/IPAddressTest.cs b/src/libraries/System.Net.Primitives/tests/FunctionalTests/IPAddressTest.cs index 6175d76415975e..0f02032c28e4fd 100644 --- a/src/libraries/System.Net.Primitives/tests/FunctionalTests/IPAddressTest.cs +++ b/src/libraries/System.Net.Primitives/tests/FunctionalTests/IPAddressTest.cs @@ -346,6 +346,10 @@ public static void Address_ReadOnlyStatics_Set_Failure() Assert.Throws(() => IPAddress.Broadcast.Address = MaxAddress - 1); Assert.Throws(() => IPAddress.Loopback.Address = MaxAddress - 1); Assert.Throws(() => IPAddress.None.Address = MaxAddress - 1); + + Assert.Throws(() => IPAddress.IPv6Any.ScopeId = 1); + Assert.Throws(() => IPAddress.IPv6Loopback.ScopeId = 1); + Assert.Throws(() => IPAddress.IPv6None.ScopeId = 1); } #pragma warning restore 618 }