检测当前连接是否同时支持 IPv4 和 IPv6

Detect if the current connection supports both IPv4 and IPv6

我需要在 WinRT 应用程序 中检测哪些协议可以访问互联网 (IPv4/IPv6/both)。我有以下代码来确定支持的协议:

enum IpVersion
{
    None = 0,
    IPv4 = 1,
    IPv6 = 2,
    IPv46 = 3
}

IpVersion GetIpVersion(ConnectionProfile profile)
{
    var result = IpVersion.None;

    if (profile != null && profile.NetworkAdapter != null)
    {
        var hostnames = NetworkInformation.GetHostNames().Where(h => h.IPInformation != null && 
                                                                h.IPInformation.NetworkAdapter != null &&
                                                                h.IPInformation.NetworkAdapter.NetworkAdapterId == profile.NetworkAdapter.NetworkAdapterId);
        foreach (var hostname in hostnames)
        {
            if (hostname.Type == HostNameType.Ipv4)
            {
                result |= IpVersion.IPv4;
            }
            else if (hostname.Type == HostNameType.Ipv6)
            {
                result |= IpVersion.IPv6;
            }
        }
    }

    return result;
}  

我是这样使用的:

GetIpVersion(NetworkInformation.GetInternetConnectionProfile()); 

现在我想知道每个可用协议是否都可以访问互联网。当然我可以 ping 一些东西,但我想知道是否有一些 SDK 方法。这些信息可在 Wi-Fi status window 中找到:

有一种方法,可以return,例如NetworkConnectivityLevel.InternetAccess,但它不包含有关连接的协议的信息。

bool internetAccess = connectionProfile.GetNetworkConnectivityLevel() == NetworkConnectivityLevel.InternetAccess

在您的代码中,ConnectionProfile 类型是什么?如果没有 a good, minimal, complete code example 以及对该代码的作用以及为什么与您想要的不同的精确解释,则很难理解这个问题。

就是说,如果我理解正确的话,您是在尝试确定您与 Internet 的连接是否同时支持 IPv4 和 IPv6。如果是这样,那么我看不出有任何方法可以从 API 级别执行此操作。本地 PC 实际上可以安装 IPv4 协议,而无需连接到允许传输该协议上的流量的网络。即使 LAN 支持该协议,Internet 连接本身也可能只支持 IPv6。

同样的事情也适用于另一种方式(即具有本地 IPv6 支持,但仅通过 Internet 支持 IPv4)。

在我看来,唯一可靠的方法与许多其他情况下所需的方法相同:只需尝试一下,看看它是否有效。 IE。尝试通过所需的协议版本连接到 Internet 上的远程端点;如果失败,则不支持。如果成功,支持。


编辑:

感谢您更新问题。它仍然不是最好的代码示例,但它稍微提炼了问题。

我还不是 100% 了解您需要做的事情,也不是我是否有对您最有用的方法。但是我认为这里有一个简短的程序可以满足您的需求:

XAML:

<Page x:Class="TestSO32781692NetworkProtocolConnectivity.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:TestSO32781692NetworkProtocolConnectivity"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
      mc:Ignorable="d">

  <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel.Resources>
      <Style TargetType="TextBlock">
        <Setter Property="FontSize" Value="24"/>
      </Style>
    </StackPanel.Resources>
    <StackPanel Orientation="Horizontal" Margin="10, 50, 10, 0">
      <TextBlock Text="IpV4: "/>
      <TextBlock Text="{Binding IpV4}"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal" Margin="10, 10, 10, 0">
      <TextBlock Text="IpV6: "/>
      <TextBlock Text="{Binding IpV6}"/>
    </StackPanel>
    <Button Content="Check Network" Click="Button_Click"/>
    <ListBox ItemsSource="{Binding Profiles}"/>
  </StackPanel>
</Page>

C#:

using System;
using System.Collections.ObjectModel;
using System.Linq;
using Windows.Networking;
using Windows.Networking.Connectivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace TestSO32781692NetworkProtocolConnectivity
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public static readonly DependencyProperty IpV4Property = DependencyProperty.Register(
            "IpV4", typeof(bool), typeof(MainPage), new PropertyMetadata(false));
        public static readonly DependencyProperty IpV6Property = DependencyProperty.Register(
            "IpV6", typeof(bool), typeof(MainPage), new PropertyMetadata(false));
        public static readonly DependencyProperty ProfilesProperty = DependencyProperty.Register(
            "Profiles", typeof(ObservableCollection<string>), typeof(MainPage), new PropertyMetadata(new ObservableCollection<string>()));

        public bool IpV4
        {
            get { return (bool)GetValue(IpV4Property); }
            set { SetValue(IpV4Property, value); }
        }

        public bool IpV6
        {
            get { return (bool)GetValue(IpV6Property); }
            set { SetValue(IpV6Property, value); }
        }

        public ObservableCollection<string> Profiles
        {
            get { return (ObservableCollection<string>)GetValue(ProfilesProperty); }
            set { SetValue(ProfilesProperty, value); }
        }

        public MainPage()
        {
            this.InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            bool ipV4 = false, ipV6 = false;
            ConnectionProfile internetProfile = NetworkInformation.GetInternetConnectionProfile();

            Profiles.Clear();
            Profiles.Add("Internet profile: " + internetProfile.ProfileName);

            var hostNames = NetworkInformation.GetHostNames()
                .Where(h => h.IPInformation != null &&
                       h.IPInformation.NetworkAdapter != null);

            foreach (HostName hostName in hostNames)
            {
                ConnectionProfile hostConnectedProfile =
                    await hostName.IPInformation.NetworkAdapter.GetConnectedProfileAsync();

                if (hostConnectedProfile.NetworkAdapter.NetworkAdapterId == internetProfile.NetworkAdapter.NetworkAdapterId)
                {
                    Profiles.Add("Host adapter: " + hostName.DisplayName);
                    if (hostName.Type == HostNameType.Ipv4)
                    {
                        ipV4 = true;
                    }
                    else if (hostName.Type == HostNameType.Ipv6)
                    {
                        ipV6 = true;
                    }
                }
            }

            IpV4 = ipV4;
            IpV6 = ipV6;
        }
    }
}

请注意,此代码示例未使用 NetworkInformation.GetConnectionProfiles() method. Though that seemed like a promising way to extend your current approach, and though the documentation 实际上承诺 "Calling the GetConnectionProfiles method retrieves profiles for all connections currently established on the device, including the Internet connection." [强调我的],结果并非如此.特别是,至少在我安装并启用了 Hyper-V 的机器上(对于那些进行 WinRT/Windows Phone 开发的人来说是常见的情况:)),ConnectionProfile 对象 return由 NetworkInformation.GetInternetConnectionProfile() 编辑实际上 包含在 return 由 NetworkInformation.GetConnectionProfiles() 编辑的配置文件集合中。

因此,上面的代码示例只是简单地识别任何主机名对象,这些对象似乎与 ConnectionProfile return 编辑的 GetInternetConnectionProfile().[=30= 相对应]

不幸的是,我实际上并没有 IPv6 支持,所以我无法对此进行全面测试。 我能够在不同的网络上测试以上内容支持 IPv6 到 Internet,我现在可以确认,至少在我的测试中,它按预期工作。该代码可正确检测 IPv4 和 IPv6 协议上的 Internet 连接。我希望以上内容能满足您的需求。


顺便说一下,除了(如您所述)使用 GetNetworkConnectivityLevel() 没有提供实际使用的协议这一明显问题,我发现该方法甚至没有 return 至少直观上会被考虑的信息正确。

特别是,调用 FindConnectionProfilesAsync(new ConnectionProfileFilter { IsConnected = true }) 会 return 与我用来连接到 Internet 的连接(例如无线网络)相对应的配置文件,但是当我调用 GetNetworkConnectivityLevel()在该配置文件上,它仅 returns LocalAccess。我猜这与我在上面提到的安装 Hyper-V 的问题有关。

这可以通过比较由 GetConnectedProfileAsync() 方法编辑的 ConnectionProfile return 与由 return 编辑的每个连接配置文件的 NetworkAdapter 来解决FindConnectionProfilesAsync() 到 return 由 GetInternetConnectionProfile() 编辑的个人资料。间接访问顶级配置文件的网络适配器的配置文件似乎会产生预期的 Internet 连接配置文件。

当然,解决该问题并不能解决所用协议的问题。我仅在其他人更多地针对配置文件管理方面而不是协议连接问题查看此答案时才提及它。

理解问题:

我们实际上需要了解有关互联网连接的哪些信息才能假设存在通过特定协议的连接?

我们需要检查两件事:

  1. 如果我们能够使用 IP 地址进行连接。
  2. 如果我们能够使用 DNS 服务器将域名解析为 IP 地址。

解法:

我们可以通过域名解析来查看支持哪些协议。

它有效,因为如果我们能够连接到 DNS 服务器,我们就能够使用 IP 地址通过该协议进行连接(我的意思是一般来说,假设没有防火墙规则、网络问题等)。然后,如果我们得到某个域的 IPv6 地址的响应,则意味着 DNS 服务器已设置并为该协议工作。

代码示例:

// the order is important, if we want to support bitwise OR: IPv4 | IPv6 equals IPv4and6
public enum IpVersion
{
    None,
    IPv4,
    IPv6,
    IPv4and6
}

public async Task<IpVersion> GetCurrentIpVersion()
{
    try
    {
        // resolves domain name to IP addresses (may contain several)
        var endPointPairs = await DatagramSocket.GetEndpointPairsAsync(new HostName("google.com"), "0");
        if (endPointPairs == null)
        {
            return IpVersion.None;
        }

        // detect which IP version is supported
        var result = IpVersion.None;
        foreach (var endPoint in endPointPairs)
        {
            if (endPoint.RemoteHostName != null)
            {
                if (endPoint.RemoteHostName.Type == HostNameType.Ipv4)
                {
                    result |= IpVersion.IPv4;
                }
                else if (endPoint.RemoteHostName.Type == HostNameType.Ipv6)
                {
                    result |= IpVersion.IPv6;
                }
            }
        }
        return result;
    }
    catch
    {
        return IpVersion.None;
    }
}

测试:
我在仅 IPv4、仅 IPv6 和 IPv4 + IPv6 网络上对其进行了测试 - 按预期工作。

示例项目:
IpVersionDetection - GitHub

我在这里也描述了这个解决方案:
WinRT - how to detect supported IP version