27

我正在编写一个程序,向用户显示他们的 IP 地址、子网掩码和默认网关。我可以得到前两个,但对于最后一个,这是我出现的:

GatewayIPAddressInformationCollection gwc = 
    System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0].GetIPProperties().GatewayAddresses;

当然,这会返回一个GatewayIPAddressInformation. 那么,如果一台计算机有多个网关,我如何确定哪个是默认网关呢?

在实践中,我只见过这个集合包含一个条目,但由于它是作为一个集合实现的,因此某些计算机包含多个网关是理所当然的,这些网关都没有标记为“默认”。那么有没有办法确定默认值还是只是猜测?

4

9 回答 9

44

可能是第一个启用的网络接口的第一个有效且启用的网关地址:

public static IPAddress GetDefaultGateway()
{
    return NetworkInterface
        .GetAllNetworkInterfaces()
        .Where(n => n.OperationalStatus == OperationalStatus.Up)
        .Where(n => n.NetworkInterfaceType != NetworkInterfaceType.Loopback)
        .SelectMany(n => n.GetIPProperties()?.GatewayAddresses)
        .Select(g => g?.Address)
        .Where(a => a != null)
         // .Where(a => a.AddressFamily == AddressFamily.InterNetwork)
         // .Where(a => Array.FindIndex(a.GetAddressBytes(), b => b != 0) >= 0)
        .FirstOrDefault();
}

我还添加了一些进一步的注释检查,其他人在这里指出这些检查很有用。您可以检查其中AddressFamily一项以区分 IPv4 和 IPv6。后一种可用于过滤掉 0.0.0.0 个地址。

上述解决方案将为您提供一个有效/连接的接口,并且足以满足 99% 的情况。也就是说,如果您有多个可以路由流量的有效接口,并且您需要 100% 准确,那么执行此操作的方法就是GetBestInterface找到用于路由到特定 IP 地址的接口。这还可以处理您可能通过不同的适配器路由特定地址范围的情况(例如10.*.*.*,通过 VPN,其他所有内容都通过您的路由器)

[DllImport("iphlpapi.dll", CharSet = CharSet.Auto)]
private static extern int GetBestInterface(UInt32 destAddr, out UInt32 bestIfIndex);

public static IPAddress GetGatewayForDestination(IPAddress destinationAddress)
{
    UInt32 destaddr = BitConverter.ToUInt32(destinationAddress.GetAddressBytes(), 0);

    uint interfaceIndex;
    int result = GetBestInterface(destaddr, out interfaceIndex);
    if (result != 0)
        throw new Win32Exception(result);

    foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
    {
        var niprops = ni.GetIPProperties();
        if (niprops == null)
            continue;

        var gateway = niprops.GatewayAddresses?.FirstOrDefault()?.Address;
        if (gateway == null)
            continue;

        if (ni.Supports(NetworkInterfaceComponent.IPv4))
        {
            var v4props = niprops.GetIPv4Properties();
            if (v4props == null)
                continue;

            if (v4props.Index == interfaceIndex)
                return gateway;
        }

        if (ni.Supports(NetworkInterfaceComponent.IPv6))
        {
            var v6props = niprops.GetIPv6Properties();
            if (v6props == null)
                continue;

            if (v6props.Index == interfaceIndex)
                return gateway;
        }
    }

    return null;
}

这两个示例可以封装到一个辅助类中并在适当的情况下使用:您有,或者您还没有记住目标地址。

于 2012-11-29T21:26:52.287 回答
4

命令返回的第一个 IP 地址traceroute将是网关。您可以使用这个事实来获取网关。可以tracerout在这里找到一个很好的实现: TraceRoute and Ping in C#

于 2015-04-07T14:34:51.003 回答
2

我知道这是一个稍微老一点的问题,但是我刚刚发现需要检索本地网关的 IPV4 地址。当涉及到我自己的系统时,接受的答案并不完全符合要求,因此,我将其修改为套件,我相信其他人也将能够使用此解决方案。

由于我还没有足够的代表发表评论,我不得不将其添加为“问题”:

public static IPAddress GetDefaultGateway()
    {
        IPAddress result = null;
        var cards = NetworkInterface.GetAllNetworkInterfaces().ToList();
        if (cards.Any())
        {
            foreach (var card in cards)
            {
                var props = card.GetIPProperties();
                if (props == null)
                    continue;

                var gateways = props.GatewayAddresses;
                if (!gateways.Any())
                    continue;

                var gateway =
                    gateways.FirstOrDefault(g => g.Address.AddressFamily.ToString() == "InterNetwork");
                if (gateway == null)
                    continue;

                result = gateway.Address;
                break;
            };
        }

        return result;
    }
于 2015-02-16T16:35:30.307 回答
2

根据@midspace 对@caesay 答案的评论,这是一个更好的答案:

public static IPAddress GetDefaultGateway()
{
    var gateway_address = NetworkInterface.GetAllNetworkInterfaces()
        .Where(e => e.OperationalStatus == OperationalStatus.Up)
        .SelectMany(e => e.GetIPProperties().GatewayAddresses)
        .FirstOrDefault();
    if (gateway_address == null) return null;
    return gateway_address.Address;
}

我警告这不是一个完整的解决方案,如果您正在寻找负责互联网访问的主接口,您应该结合其他方法,例如使用 win32 API GetBestInterface来找到连接到目标地址的最佳接口。

你可以在这里找到示例用法:http ://www.pinvoke.net/default.aspx/iphlpapi.getbestinterface

于 2016-03-22T08:41:36.020 回答
2

一直在寻找一种奇怪的技巧来确定本地 IP 地址吗?然而,与早期的方法相比,它做得非常稳健吗?

[对@caesay 的回复见结尾]

这可能是您正在寻找的。

using (var s = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Dgram,ProtocolType.Udp))
{
    s.Bind( new System.Net.IPEndPoint(System.Net.IPAddress.Any,0));
    s.Connect("google.com",0);

    var ipaddr = s.LocalEndPoint as System.Net.IPEndPoint;
    var addr = ipaddr.Address.ToString();
    Console.WriteLine(addr);
}

这将创建一个 UDP 套接字,但不会在其上发送数据。然后,您可以查看套接字绑定到的本地接口和地址。您必须提供我使用 google.com 的 IP 地址或主机名,操作系统将使用它来确定套接字将绑定到哪个接口,以及使用此方法可以找到哪个 IP 地址。对于您使用的 99% 以上的 IP 地址或主机名,您将获得用于通过默认路由进行流量的接口+地址。

虽然这有点迟钝,但我怀疑它比上面人们使用 PInvoke 调用并扫描结构以查看他们是否可以找到接口的方法更可靠。

我当然发现它在我的 3x 双接口系统上比使用启发式扫描 NetworkInterface.GetAllNetworkInterfaces() 的结果来尝试找出默认接口地址更强大。

[更新/回复@caesay 2/6/2019] @caesay 在这里提出了一个很好的观点,所以我将示例从使用 UdpClient 切换到了 Socket,并在 Bind() 之后显式调用了 Socket.Connect()。我还查看了 Connect() 和 LocalEndPoint 的框架源代码,它们都立即调用了它们相应的 Win32 操作系统系统调用,而不会推迟或懒惰地调用操作系统。Win32绑定页面说“应用程序可以在调用bind后使用getsockname来学习地址和已分配给套接字的端口。如果Internet地址等于INADDR_ANY或in6addr_any,getsockname不一定提供地址,直到套接字被连接的”。现在已经明确解决了这种情况,并且在查看了框架源之后,IP 地址在 Connect() 调用之后应该始终可用。

于 2019-01-12T02:03:36.070 回答
1
NetworkInterface[] allNICs = NetworkInterface.GetAllNetworkInterfaces();
foreach (var nic in allNICs)
{
    var ipProp = nic.GetIPProperties();
    var gwAddresses = ipProp.GatewayAddresses;
    if (gwAddresses.Count > 0 &&
        gwAddresses.FirstOrDefault(g => g.Address.AddressFamily == AddressFamily.InterNetwork) != null)
    {
        localIP = ipProp.UnicastAddresses.First(d => d.Address.AddressFamily == AddressFamily.InterNetwork).Address;
    }
}
Debug.Print(localIP.ToString());
于 2015-10-14T02:25:49.287 回答
1

我刚刚遇到这个问题,将使用以下代码 - 基本上它会查找不是环回且已启动且具有网关和单播地址的接口。然后我从界面获得一个非瞬态 IPV4 地址。

public static class NetworkInterfaces
{
    public static NetworkInterface GetDefaultInterface()
    {
        var interfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();
        foreach (var intf in interfaces)
        {
            if (intf.OperationalStatus != OperationalStatus.Up)
            {
                continue;
            }
            if (intf.NetworkInterfaceType == NetworkInterfaceType.Loopback)
            {
                continue;
            }

            var properties = intf.GetIPProperties();
            if (properties == null)
            {
                continue;
            }
            var gateways = properties.GatewayAddresses;
            if ((gateways == null) || (gateways.Count == 0))
            {
                continue;
            }
            var addresses = properties.UnicastAddresses;
            if ((addresses == null) || (addresses.Count == 0))
            {
                continue;
            }
            return intf;
        }
        return null;
    }

    public static IPAddress GetDefaultIPV4Address(NetworkInterface intf)
    {
        if (intf == null)
        {
            return null;
        }
        foreach (var address in intf.GetIPProperties().UnicastAddresses)
        {
            if (address.Address.AddressFamily != AddressFamily.InterNetwork)
            {
                continue;
            }
            if (address.IsTransient)
            {
                continue;
            }
            return address.Address;
        }
        return null;
    }
}
于 2017-02-03T11:34:15.817 回答
1

(就在发布之后,我注意到一个较早的答案,其中提到了使用 traceroute 的想法。如果我看到了,我可能不会做出回应。但我已经包含了完整/紧凑的代码。)

这是另一种使用 traceroute 类型方法查找默认网关的方法。ping 一个 TTL 为 1 的 Internet(或您想访问的其他网络)上的地址,让 ping 告诉您第一跳 IP 地址是什么。那应该是您正在寻找的网关。

我想如果网关配置为不回复 ping,这可能会出错,但我认为我从未听说过以这种方式设置的网络。

注意:如果您向它传递一个 IPV6 地址,这种例程甚至可以工作。潜在的问题是它似乎返回了一个具有全局范围的 IPV6 地址,而不是链接本地范围。我没有足够的使用 IPV6 的经验来知道这是好是坏。但我知道 Windows ipconfig 命令显示接口网关的链路本地 IPV6 地址。

using System.Net
using System.Net.NetworkInformation

// ping internet address with ttl=1 and return address of host where ttl expired
public static IPAddress FindDefaultGateway(IPAddress netaddr = null)
{
    // user can provide an ip address that exists on the network they want to connect to, 
    // or this routine will default to 1.1.1.1 (IP of a popular internet dns provider)
    if (netaddr is null)
        netaddr = IPAddress.Parse("1.1.1.1"); 

    PingReply reply = default;  
    using var ping = new Ping();
    var options = new PingOptions(1, true); // ttl=1, dont fragment=true
    try { 
        // I arbitrarily used a 200ms timeout; tune as you see fit.
        reply = ping.Send(netaddr, 200, new byte[0], options); 
    }
    catch (PingException) {
        System.Diagnostics.Debug.WriteLine("Gateway not available");
        return default;
    }
    if (reply.Status != IPStatus.TtlExpired) {
        System.Diagnostics.Debug.WriteLine("Gateway not available");
        return default;
    }
    return reply.Address;
}
于 2020-05-31T23:56:51.527 回答
0

我认为你应该遍历这个集合并显示所有网关,因为如果有很多网关到一个适配器,它们都被认为是该适配器的默认值

于 2012-11-29T21:45:10.317 回答