获取默认网关

时间:2012-11-29 21:15:51

标签: c# network-programming network-protocols

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

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

当然,这会返回GatewayIPAddressInformation的集合。因此,如果计算机有多个网关,我如何确定哪个是默认网关?

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

8 个答案:

答案 0 :(得分:37)

它应该是第一个启用的网络接口的第一个有效和启用的网关地址:

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地址时遇到问题,可以使用后者。

尽管如此,建议的方法是使用GetBestInterface来查找路由到特定IP地址的接口。如果你已经考虑了目的地IP地址,那么最好使用它 - 所以我也在下面列举了一个例子:

[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;
}

答案 1 :(得分:3)

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

答案 2 :(得分: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 OS系统调用,而不会延迟或懒于调用OS。 Win32绑定页面说:“应用程序可以在调用bind之后使用getsockname来了解地址和已分配给套接字的端口。如果Internet地址等于INADDR_ANY或in6addr_any,则在套接字被分配之前,getsockname不一定能够提供地址。连接的”。 现在已经明确解决了这种情况,并且在检查了框架源之后,在Connect()调用之后IP地址应该始终可用。

答案 3 :(得分: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());

答案 4 :(得分:1)

根据@ 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

答案 5 :(得分:1)

我刚刚遇到这个并将使用以下代码 - 基本上它会查找不环回并且已启动且具有网关和单播地址的接口。然后,从接口I获得非瞬态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;
    }
}

答案 6 :(得分:0)

我认为您应该遍历此集合并显示所有网关,因为如果有许多适配器的网关,则它们都被视为默认为该适配器

答案 7 :(得分:0)

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

这是使用traceroute类型方法查找默认网关的另一种方法。以TTL值为1的方式对Internet(或您要访问的其他网络)上的地址执行ping操作,然后让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;
}