Allow interface names in IPv6 link-local addresses (dotnet/corefx#35278)
authorEgor Bogatov <egorbo@gmail.com>
Sat, 15 Jun 2019 21:44:42 +0000 (00:44 +0300)
committerDavid Shulman <david.shulman@microsoft.com>
Sat, 15 Jun 2019 21:44:41 +0000 (14:44 -0700)
* Handle interface names in ipv6 addresses (link-local)

* fix test projects

* Address feedback

* Add SetLastError to if_nametoindex

* fix tests

* fix tests

* Add more tests

* move native impl to pal_networking.c/h

* add newlines to pal_networking

* fix build

* remove "Vista" comment from Interop.if_nametoindex.cs

* test fix for Uri.cs

* undo changes

* Always return 0 for if_nametoindex on UAP

Commit migrated from https://github.com/dotnet/corefx/commit/fef0f817013ca7ebd39857b7aeecd369a9c36af5

12 files changed:
src/libraries/Common/src/Interop/Unix/System.Native/Interop.InterfaceNameToIndex.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.if_nametoindex.cs [new file with mode: 0644]
src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs
src/libraries/Common/src/System/Net/NetworkInformation/InterfaceInfoPal.Unix.cs [new file with mode: 0644]
src/libraries/Common/src/System/Net/NetworkInformation/InterfaceInfoPal.Windows.cs [new file with mode: 0644]
src/libraries/Native/Unix/System.Native/pal_networking.c
src/libraries/Native/Unix/System.Native/pal_networking.h
src/libraries/System.Net.Primitives/src/System.Net.Primitives.csproj
src/libraries/System.Net.Primitives/src/System/Net/IPAddressParser.cs
src/libraries/System.Net.Primitives/tests/FunctionalTests/IPAddressParsing.cs
src/libraries/System.Net.Primitives/tests/PalTests/System.Net.Primitives.Pal.Tests.csproj
src/libraries/System.Net.Primitives/tests/UnitTests/System.Net.Primitives.UnitTests.Tests.csproj

diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.InterfaceNameToIndex.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.InterfaceNameToIndex.cs
new file mode 100644 (file)
index 0000000..36f8686
--- /dev/null
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class Sys
+    {
+        [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_InterfaceNameToIndex", SetLastError = true)]
+        public static extern unsafe uint InterfaceNameToIndex(string name);
+    }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.if_nametoindex.cs b/src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.if_nametoindex.cs
new file mode 100644 (file)
index 0000000..2925591
--- /dev/null
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class IpHlpApi
+    {
+#if !uap
+        [DllImport(Interop.Libraries.IpHlpApi, SetLastError = true)]
+        internal extern static uint if_nametoindex(string name);
+#else
+        internal static uint if_nametoindex(string name) => 0;
+#endif
+    }
+}
index ef26fdb..b0c85e3 100644 (file)
@@ -157,11 +157,6 @@ namespace System
                                 {
                                     goto case '/';
                                 }
-                                else if (name[i] < '0' || name[i] > '9')
-                                {
-                                    // scope ID must only contain digits
-                                    return false;
-                                }
                             }
                             break;
                         case ']':
diff --git a/src/libraries/Common/src/System/Net/NetworkInformation/InterfaceInfoPal.Unix.cs b/src/libraries/Common/src/System/Net/NetworkInformation/InterfaceInfoPal.Unix.cs
new file mode 100644 (file)
index 0000000..8e9b2fc
--- /dev/null
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.NetworkInformation
+{
+    internal static class InterfaceInfoPal
+    {
+        public static uint InterfaceNameToIndex(string interfaceName)
+        {
+            return Interop.Sys.InterfaceNameToIndex(interfaceName);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/libraries/Common/src/System/Net/NetworkInformation/InterfaceInfoPal.Windows.cs b/src/libraries/Common/src/System/Net/NetworkInformation/InterfaceInfoPal.Windows.cs
new file mode 100644 (file)
index 0000000..3ea7c72
--- /dev/null
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.NetworkInformation
+{
+    internal static class InterfaceInfoPal
+    {
+        public static uint InterfaceNameToIndex(string interfaceName)
+        {
+            return Interop.IpHlpApi.if_nametoindex(interfaceName);
+        }
+    }
+}
\ No newline at end of file
index 273f344..abf5e74 100644 (file)
@@ -27,6 +27,7 @@
 #include <netdb.h>
 #include <netinet/in.h>
 #include <netinet/tcp.h>
+#include <net/if.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
@@ -50,7 +51,6 @@
 #include <sys/uio.h>
 #endif
 #if !HAVE_IN_PKTINFO
-#include <net/if.h>
 #if HAVE_GETIFADDRS
 #include <ifaddrs.h>
 #endif
@@ -2689,3 +2689,11 @@ int32_t SystemNative_SendFile(intptr_t out_fd, intptr_t in_fd, int64_t offset, i
     return SystemNative_ConvertErrorPlatformToPal(errno);
 #endif
 }
+
+uint32_t SystemNative_InterfaceNameToIndex(char* interfaceName)
+{
+    assert(interfaceName != NULL);
+    if (interfaceName[0] == '%')
+        interfaceName++;
+    return if_nametoindex(interfaceName);
+}
index e739dd1..e6c65ef 100644 (file)
@@ -419,3 +419,5 @@ DLLEXPORT char* SystemNative_GetPeerUserName(intptr_t socket);
 DLLEXPORT void SystemNative_GetDomainSocketSizes(int32_t* pathOffset, int32_t* pathSize, int32_t* addressSize);
 
 DLLEXPORT int32_t SystemNative_SendFile(intptr_t out_fd, intptr_t in_fd, int64_t offset, int64_t count, int64_t* sent);
+
+DLLEXPORT uint32_t SystemNative_InterfaceNameToIndex(char* interfaceName);
index 9e44398..adef015 100644 (file)
     <Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.LocalFree.cs">
       <Link>Common\Interop\Windows\Kernel32\Interop.LocalFree.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Net\NetworkInformation\InterfaceInfoPal.Windows.cs">
+      <Link>Common\System\Net\NetworkInformation\InterfaceInfoPal.Windows.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\IpHlpApi\Interop.if_nametoindex.cs">
+      <Link>Common\Interop\Windows\IpHlpApi\Interop.if_nametoindex.cs</Link>
+    </Compile>
   </ItemGroup>
   <ItemGroup Condition=" '$(TargetsUnix)' == 'true'">
     <Compile Include="System\Net\SocketException.Unix.cs" />
     <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.IPAddress.cs">
       <Link>Common\Interop\Unix\System.Native\Interop.IPAddress.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Net\NetworkInformation\InterfaceInfoPal.Unix.cs">
+      <Link>Common\System\Net\NetworkInformation\InterfaceInfoPal.Unix.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.InterfaceNameToIndex.cs">
+      <Link>Common\Interop\Unix\System.Native\Interop.InterfaceNameToIndex.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.SocketAddress.cs">
       <Link>Interop\Unix\System.Native\Interop.SocketAddress.cs</Link>
     </Compile>
index eb94c38..ce0674d 100644 (file)
@@ -7,6 +7,8 @@ using System.IO;
 using System.Net.Sockets;
 using System.Runtime.InteropServices;
 using System.Text;
+using System.Globalization;
+using System.Net.NetworkInformation;
 
 namespace System.Net
 {
@@ -210,33 +212,22 @@ namespace System.Net
                 string scopeId = null;
                 IPv6AddressHelper.Parse(ipSpan, numbers, 0, ref scopeId);
 
-                long result = 0;
-                if (!string.IsNullOrEmpty(scopeId))
+                if (scopeId?.Length > 1)
                 {
-                    if (scopeId.Length < 2)
+                    if (uint.TryParse(scopeId.AsSpan(1), NumberStyles.None, CultureInfo.InvariantCulture, out scope))
                     {
-                        scope = 0;
-                        return false;
+                        return true; // scopeId is a numeric value
                     }
-
-                    for (int i = 1; i < scopeId.Length; i++)
+                    uint interfaceIndex = InterfaceInfoPal.InterfaceNameToIndex(scopeId);
+                    if (interfaceIndex > 0)
                     {
-                        char c = scopeId[i];
-                        if (c < '0' || c > '9')
-                        {
-                            scope = 0;
-                            return false;
-                        }
-                        result = (result * 10) + (c - '0');
-                        if (result > uint.MaxValue)
-                        {
-                            scope = 0;
-                            return false;
-                        }
+                        scope = interfaceIndex;
+                        return true; // scopeId is a known interface name
                     }
+                    // scopeId is an unknown interface name
                 }
-
-                scope = (uint)result;
+                // scopeId is not presented
+                scope = 0;
                 return true;
             }
 
index 4baf57e..8cc47e5 100644 (file)
@@ -286,6 +286,7 @@ namespace System.Net.Primitives.Functional.Tests
             new object[] { "1::%1", "1::%1" },
             new object[] { "::1%12", "::1%12" },
             new object[] { "::%123", "::%123" },
+            new object[] { "Fe08::1%unknowninterface", "fe08::1" },
             // v4 as v6
             new object[] { "FE08::192.168.0.1", "fe08::c0a8:1" }, // Output is not IPv4 mapped
             new object[] { "::192.168.0.1", "::192.168.0.1" },
@@ -344,6 +345,25 @@ namespace System.Net.Primitives.Functional.Tests
             }
         }
 
+        public static readonly object[][] ScopeIds =
+        {
+            new object[] { "Fe08::1%123", 123},
+            new object[] { "Fe08::1%12345678", 12345678},
+            new object[] { "fe80::e8b0:63ff:fee8:6b3b%9", 9},
+            new object[] { "fe80::e8b0:63ff:fee8:6b3b", 0},
+            new object[] { "fe80::e8b0:63ff:fee8:6b3b%abcd0", 0},
+            new object[] { "::%unknownInterface", 0},
+            new object[] { "::%0", 0},
+        };
+
+        [Theory]
+        [MemberData(nameof(ScopeIds))]
+        public void ParseIPv6_ExtractsScopeId(string address, int expectedScopeId)
+        {
+            IPAddress ip = Parse(address);
+            Assert.Equal(expectedScopeId, ip.ScopeId);
+        }
+
         public static IEnumerable<object[]> InvalidIpv6Addresses()
         {
             yield return new object[] { "[:]" }; // malformed
@@ -399,9 +419,7 @@ namespace System.Net.Primitives.Functional.Tests
             yield return new object[] { "G::" }; // invalid hex
             yield return new object[] { "FFFFF::" }; // invalid value
             yield return new object[] { ":%12" }; // colon scope
-            yield return new object[] { "::%1a" }; // alphanumeric scope
             yield return new object[] { "[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:443/" }; // errneous ending slash after ignored port
-            yield return new object[] { "::1234%0x12" }; // invalid scope ID
 
             yield return new object[] { "e3fff:ffff:ffff:ffff:ffff:ffff:ffff:abcd" }; // 1st number too long
             yield return new object[] { "3fff:effff:ffff:ffff:ffff:ffff:ffff:abcd" }; // 2nd number too long
index 06457de..88a2e06 100644 (file)
     <Compile Include="$(CommonPath)\Interop\Windows\NtDll\Interop.NtStatus.cs">
       <Link>ProductionCode\Common\Interop\Windows\NtDll\Interop.NtStatus.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Net\NetworkInformation\InterfaceInfoPal.Windows.cs">
+      <Link>ProductionCode\System\Net\NetworkInformation\InterfaceInfoPal.Windows.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\IpHlpApi\Interop.if_nametoindex.cs">
+      <Link>ProductionCode\Common\Interop\Windows\IpHlpApi\Interop.if_nametoindex.cs</Link>
+    </Compile>
   </ItemGroup>
   <ItemGroup Condition=" '$(TargetsUnix)' == 'true' ">
     <Compile Include="..\..\src\System\Net\SocketException.Unix.cs">
     <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.SocketAddress.cs">
       <Link>ProductionCode\Common\Interop\Unix\System.Native\Interop.SocketAddress.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Net\NetworkInformation\InterfaceInfoPal.Unix.cs">
+      <Link>ProductionCode\Common\System\Net\NetworkInformation\InterfaceInfoPal.Unix.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.InterfaceNameToIndex.cs">
+      <Link>ProductionCode\Common\Interop\Unix\System.Native\Interop.InterfaceNameToIndex.cs</Link>
+    </Compile>
   </ItemGroup>
 </Project>
index 36f7e64..36bdc6c 100644 (file)
     <Compile Include="$(CommonPath)\System\Net\SocketAddressPal.Windows.cs">
       <Link>Common\System\Net\SocketAddressPal.Windows.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Net\NetworkInformation\InterfaceInfoPal.Windows.cs">
+      <Link>ProductionCode\System\Net\NetworkInformation\InterfaceInfoPal.Windows.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\IpHlpApi\Interop.if_nametoindex.cs">
+      <Link>ProductionCode\Common\Interop\Windows\IpHlpApi\Interop.if_nametoindex.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs">
+      <Link>ProductionCode\Common\Interop\Windows\Interop.Libraries.cs</Link>
+    </Compile>
   </ItemGroup>
   <ItemGroup Condition=" '$(TargetsUnix)' == 'true' ">
     <Compile Include="..\..\src\System\Net\SocketException.Unix.cs">
       <Link>ProductionCode\System\Net\SocketException.Unix.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Net\NetworkInformation\InterfaceInfoPal.Unix.cs">
+      <Link>ProductionCode\System\Net\NetworkInformation\InterfaceInfoPal.Unix.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\System\Net\SocketAddressPal.Unix.cs">
       <Link>Common\System\Net\SocketAddressPal.Unix.cs</Link>
     </Compile>
     <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.SocketAddress.cs">
       <Link>ProductionCode\Common\Interop\Unix\System.Native\Interop.SocketAddress.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.InterfaceNameToIndex.cs">
+      <Link>ProductionCode\Common\Interop\Unix\System.Native\Interop.InterfaceNameToIndex.cs</Link>
+    </Compile>
   </ItemGroup>
 </Project>