如何使用CIDR表示法判断一个IP地址是否属于一组IP地址范围内?

33

这里我有一个静态引用的范围需要检查:

private static List<string> Ip_Range = new List<string>()
{
    "12.144.86.0/23",
    "31.201.1.176/30",
    "46.36.198.101/32",
    "46.36.198.102/31",
    "46.36.198.104/31",
    "46.136.172.0/24",
    "63.65.11.0/24",
    "63.65.12.0/25",
    "63.65.12.128/26",
    "63.65.12.192/27",
    "63.65.12.224/28",
    "63.65.12.240/29",
    "63.65.12.248/30",
    "63.65.12.252/31",
    "63.65.12.254/32",
    "65.173.56.0/21",
    "67.23.241.179/32",
    "67.23.241.180/30",
    "67.23.241.184/29",
    "67.23.241.192/30",
    "67.23.241.196/31",
    "67.23.241.198/32",
    "72.32.164.56/29",
    "72.46.244.32/28",
    "74.91.16.48/29",
    "74.91.16.208/29",
    "74.91.20.48/28",
    "74.91.20.64/29",
    "74.112.134.120/29",
    "74.112.135.104/29",
    "74.205.37.16/29",
    "78.24.205.32/28",
    "98.129.27.88/29",
    "98.129.91.40/29",
    "166.114.0.0/16",
    "167.157.0.0/16",
    "174.143.165.80/29",
    "186.0.156.0/22",
    "186.2.0.0/17",
    "186.27.0.0/17",
    "190.0.248.0/21",
    "190.3.184.0/21"
};

以下是我对它的工作原理的伪代码:

public static bool IpIsWithinRange(string ip) //Something like 127.0.0.1 or 184.56.26.35
{
    IPAddress incomingIp = IPAddress.Parse(ip);
    foreach (var subnet in Ip_Range)
    {
        IPAddress sub = IPAddress.Parse(subnet); ?????
        if (incomingIp "is in" sub) ?
            return true;            
    }
    return false;
}

有关如何编写这个功能的建议吗?


2
IPNetwork 项目应该能够处理您所寻求的大部分内容。 - M.Babcock
3
@Tony:给出正确方向的推动同样是很好的。这就是这个网站的目的。 :) 我已经用Ruby完成了这个任务,但我不太熟悉如何在C#中实现。 - Only Bolivian Here
1
那么,你是如何在Ruby中解决这个问题的?你在翻译到C#的哪一部分遇到了困难? - Greg Hewgill
2
@M.Babcock IPNetwork项目已经迁移到github.com/lduchosal/ipnetwork。我认为我已经在这里的所有答案中更改了所有引用,但是如果您还记得在其他地方链接到它,如果您愿意,您可能也想更新这些引用。 - Andrew Morton
这个回答解决了你的问题吗?如何检查输入的IP是否在特定的IP范围内 - Michael Freidgeim
显示剩余3条评论
7个回答

42

如果您不想/不能向项目添加另一个库(如IPnetwork),只需处理IPv4 CIDR范围,那么这里是解决问题的快速方法。

// true if ipAddress falls inside the CIDR range, example
// bool result = IsInRange("10.50.30.7", "10.0.0.0/8");
private bool IsInRange(string ipAddress, string CIDRmask)
{
    string[] parts = CIDRmask.Split('/');

    int IP_addr = BitConverter.ToInt32(IPAddress.Parse(ipAddress).GetAddressBytes(), 0);
    int CIDR_addr = BitConverter.ToInt32(IPAddress.Parse(parts[0]).GetAddressBytes(), 0);
    int CIDR_mask = IPAddress.HostToNetworkOrder(-1 << (32 - int.Parse(parts[1])));

    return ((IP_addr & CIDR_mask) == (CIDR_addr & CIDR_mask));
}

以上内容将让您快速检查给定的IPv4地址是否在给定的CIDR范围内;请注意,上述代码非常简单,您需要在将给定的IP(字符串)和CIDR范围提供给函数之前自行检查它们是否正确(您可以使用tryparse或类似方法...)


5
这并不会影响结果,但你可能应该交换被解析的 IP_addr 和 CIDR_addr 值。 - Jeffrey Harmon
1
这不符合CIDR范围0.0.0.0/0,换句话说就是全部。除此之外,它很棒! - pijemcolu
如果CIDR掩码为零,我只是添加了一个if语句来返回true,因为任何有效的内容都会匹配。 - pijemcolu
根据Jeffrey的建议,IP_addr和CIDR_addr变量已经交换。 - Jahorse
有人能解释一下这行代码吗: IPAddress.HostToNetworkOrder(-1 << (32 - int.Parse(parts[1]))); ? 我不明白为什么要用-1以及为什么要使用主机到网络顺序函数... :') - KitAndKat

26

决定回答自己的问题,以便让人们受益。如果可以改进,请随意!

我使用了IPNetwork库,效果非常好!

nuget install IPNetwork2

以下是我使用的代码:

using System.Net;

public static class RedirectHelpers
{
    public static bool IpIsWithinBoliviaRange(string ip)
    {
        IPAddress incomingIp = IPAddress.Parse(ip);
        foreach (var subnet in Bolivia_Ip_Range)
        {
            IPNetwork network = IPNetwork.Parse(subnet);

            if (IPNetwork.Contains(network, incomingIp))
                return true;
        }
        return false;
    }

    private static List<string> Bolivia_Ip_Range = new List<string>()
    {
        "12.144.86.0/23",
        "31.201.1.176/30",
        "46.36.198.101/32",
        "46.36.198.102/31",
        "46.36.198.104/31",
        "46.136.172.0/24",
        "63.65.11.0/24",
        "63.65.12.0/25",
        "63.65.12.128/26",
        "63.65.12.192/27",
        "63.65.12.224/28",
        "63.65.12.240/29",
        "63.65.12.248/30",
        "63.65.12.252/31",
        "63.65.12.254/32",
        "65.173.56.0/21",
        "67.23.241.179/32",
        "67.23.241.180/30",
        "67.23.241.184/29",
        "67.23.241.192/30",
        "67.23.241.196/31",
        "67.23.241.198/32",
        "72.32.164.56/29",
        "72.46.244.32/28",
        "74.91.16.48/29",
        "74.91.16.208/29",
        "74.91.20.48/28",
        "74.91.20.64/29",
        "74.112.134.120/29",
        "74.112.135.104/29",
        "74.205.37.16/29",
        "78.24.205.32/28",
        "98.129.27.88/29",
        "98.129.91.40/29",
        "166.114.0.0/16",
        "167.157.0.0/16",
        "174.143.165.80/29",
        "186.0.156.0/22",
        "186.2.0.0/17",
        "186.27.0.0/17",
        "190.0.248.0/21",
        "190.3.184.0/21",
        "166.114.0.0/16",
        "167.157.0.0/16",
        "186.2.0.0/18",
        "190.11.64.0/20",
        "190.11.80.0/20",
        "190.103.64.0/20",
        "190.104.0.0/19",
        "190.107.32.0/20",
        "190.129.0.0/17",
        "190.181.0.0/18",
        "190.186.0.0/18",
        "190.186.64.0/18",
        "190.186.128.0/18",
        "200.7.160.0/20",
        "200.58.64.0/20",
        "200.58.80.0/20",
        "200.58.160.0/20",
        "200.58.176.0/20",
        "200.75.160.0/20",
        "200.85.128.0/20",
        "200.87.0.0/17",
        "200.87.128.0/17",
        "200.105.128.0/19",
        "200.105.160.0/19",
        "200.105.192.0/19",
        "200.112.192.0/20",
        "200.119.192.0/20",
        "200.119.208.0/20",
        "201.222.64.0/19",
        "201.222.96.0/19"
    };
}

你的IpIsWithinBoliviaRange方法中的大部分代码(基本上就是foreach循环)可以用一个简单的LINQ查询来替代:return Bolivia_Ip_Range.Any(subnet => IPNetwork.Contains(IPNetwork.Parse(subnet), incomingIp)); - M.Babcock
1
第一印象,这个解决方案看起来非常低效。你在每个子网中的每个调用方法上执行IPNetwork network = IPNetwork.Parse(subnet) !!!! 你知道它总是解析为相同的结果,那么为什么不将其缓存起来?同样地,你也可以(根据你可能收到的重复IP访问者数量)针对传入的IP地址缓存最终结果以节省查找时间 - 尽管如果预计有大量不同的IP地址并且重复率较低,则这可能实际上并不是一个好主意。 - arcseldon

11

幸运的是,大部分工作已经为您完成(所以我们不需要再做一次)。请查看IPNetwork项目。使用IPNetwork.Parse解析所有CIDR地址。然后,要查看特定IPAddress是否在范围内,只需使用IPNetwork.Contains方法即可。


我感到有些无聊,这里有一个方法可以用来测试一个IP地址是否在范围内:

private Dictionary<string, IPNetwork> netCache = null;
public bool IsInRange(string ipAddress)
{
    if (netCache == null)
        netCache = Ip_Range.ToDictionary((keyItem) => keyItem, (valueItem) => IPNetwork.Parse(valueItem));

    return netCache.Values.Any(net => IPNetwork.Contains(net, IPAddress.Parse(ipAddress)));
}

这取决于您问题中的 Ip_Range 列表,但它将它们转换为IPNetwork实例(由于简洁起见,省略了一些健全性检查)。

用法:

List<string> addrList = new List<string> { "12.144.86.1", "10.254.6.172" };
addrList.ForEach(addr => Console.WriteLine("{0}: {1}", addr, IsInRange(addr)));

测试输出:

12.144.86.1: True
10.254.6.172: False

当然,对它仍然有很多可以(而且可能应该)做的事情,但这证明了这个概念。


无法在 VB.Net 项目中运行 IPNetwork... Intellisense 喜欢它,但在运行时会出错,想要找到 IPNetwork.cs! :( 可能只限于 C#? - Compassionate Narcissist
1
@PatTrainor - 请开一个新的问题,以便社区中的适当成员可以审查您的情况。我可以向您保证,无法限制能够使用汇编语言的语言。 - M.Babcock
我完全同意你的看法!这是我见过的最奇怪的事情...我会创建一个新的线程...[也没有办法问作者了...] - Compassionate Narcissist
@M.Babcock。请问您为什么使用字典而不是列表? - fravelgue
@fravelgue - 这只是我在3年前写答案时想到的。可以争论地说,对于所呈现的代码,有几个性能改进需要使用字典。 - M.Babcock

4
    public static bool IpIsInRange(string subnet, string ip)
    {
        var splitSubnet = subnet.Split('/');
        var maskBits = 32 - int.Parse(splitSubnet[1]);
        if (maskBits == 32)
        {
            return true;
        }
        var subnetIp = BitConverter.ToInt32(IPAddress.Parse(splitSubnet[0]).GetAddressBytes().Reverse().ToArray(), 0) >> maskBits << maskBits;
        var clientIp = BitConverter.ToInt32(IPAddress.Parse(ip).GetAddressBytes().Reverse().ToArray(), 0) >> maskBits << maskBits;
        return subnetIp == clientIp;
    }

2
如果您理解CIDR表示法,那么您可以在解析方法中轻松进行计算。
您基本上知道IPv4地址长度为32位,并且CIDR表示法意味着“/”后面的位数是网络地址位(即掩码位),因此剩余的位表示子网中主机的数量。
来自维基百科article
“通过掩码或前缀定义的子网的地址数量可以计算为2address大小-前缀大小,在IPv6中的地址大小为128,在IPv4中为32。例如,在IPv4中,前缀大小为/29,则为:232-29 = 23 = 8个地址。”
所以,您可以(不,我不会为您计算详细信息)将您的地址转换为二进制,并使用给定的掩码(也以二进制形式)进行二进制AND运算,然后您还剩下IP地址的网络地址部分,它应该与您正在检查的地址匹配,以查看它是否在特定子网中。

你说“我不会为你解决细节问题”这一事实表明,这很可能是在重新发明轮子(像这样的日常问题很快就会被自动化解决)。 - arcseldon

1

首先,你应该使用这个:

IPNetwork ipnetwork = IPNetwork.Parse("192.168.168.100/29");
Console.WriteLine("CIDR: {0}", ipnetwork.Cidr);

输出

CIDR: 29

ipnetwork.codeplex.com 已经迁移至 github.com/lduchosal/ipnetwork - Andrew Morton

-1
我使用以下方法来确定给定的IP地址是公共的还是私有/内部的:
private bool IsInternalIpAddress(string ipAddress)
    {
        // notes: http://whatismyipaddress.com/private-ip

        var internalIpRanges = Enumerable
            .Range(16, 31)
            .Select(i => "172.{0}.".FormatWith(i))
            .Concat(new[] {"192.168.", "10.", "127."})
            .ToArray();

        return ipAddress.StartsWith(internalIpRanges);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接