【问题标题】:How to efficiently check if a given IP Address belong to an IP subnetwork in Python?如何有效地检查给定的 IP 地址是否属于 Python 中的 IP 子网?
【发布时间】:2017-10-30 22:33:57
【问题描述】:

我有一组大约 200,000 个 IP 地址和 10,000 个形式的子网 (1.1.1.1/24)。对于每个 IP 地址,我需要检查它是否属于这些子网之一,但由于它是一个如此大的数据集并且我的计算能力较低,因此我想要一个有效的实现。

在搜索时,我找到了一种方法(https://stackoverflow.com/a/820124/7995937):

from netaddr import IPNetwork, IPAddress
if IPAddress("192.168.0.1") in IPNetwork("192.168.0.0/24"):
     print "Yay!"

但由于我必须循环超过 200,000 个 IP 地址,并且每个地址循环超过 10,000 个子网,我不确定这是否有效。 我的第一个疑问是,检查“IPNetwork() 中的 IPAddress()”只是线性扫描还是以某种方式优化?

我想出的另一个解决方案是列出 IP 子网中包含的所有 IP(大约 13,000,000 个 IP,没有重复),然后对其进行排序。如果我这样做,那么在我对 200,000 个 IP 地址的循环中,我只需要在更大的 IP 地址集上对每个 IP 进行二进制搜索。

for ipMasked in ipsubnets:  # Here ipsubnets is the list of all subnets
        setUnmaskedIPs = [str(ip) for ip in IPNetwork(ipMasked)]
        ip_list = ip_list + setUnmaskedIPs
ip_list = list(set(ip_list))  # To eliminate duplicates
ip_list.sort()

然后我可以通过以下方式执行二进制搜索:

for ip in myIPList:  # myIPList is the list of 200,000 IPs
    if bin_search(ip,ip_list):
        print('The ip is present')

这种方法比另一种更有效吗?或者还有其他更有效的方法来执行此任务吗?

【问题讨论】:

  • 如前所述,最快的是使用集合。其他相关话题:stackoverflow.com/questions/5993621/…
  • 将 IPv4 字符串转换为 32 位 int 很简单,所以如果我必须创建一个这样的库,我可能会在内部使用 int 和二进制运算符,这将非常有效。像往常一样,您应该首先测量是否真的存在性能问题。

标签: python performance ip cidr


【解决方案1】:

好的,所以排序需要 O(nlogn),如果是 13,000,000,你最终会做 O(13000000log(13000000))。然后你正在迭代超过 200000 个 IP 并在 13000000 上的排序列表上进行二进制搜索 O(logn)。 我真诚地怀疑这是最好的解决方案。我建议你使用地图

from netaddr import IPNetwork, IPAddress
l_ip_address = map(IPAddress, list_of_ip_address)
l_ip_subnet = map(IPNetwork, list_of_subnets)

if any(x in y for x in l_ip_address for y in l_ip_subnet):
    print "FOUND"

【讨论】:

  • 你能详细说明地图的作用吗?如果我们在 x in l_ip_addressy in l_ip_subnet 上循环,它如何提高复杂性?
  • map 从 IP 地址字符串列表中创建另一个类型为 IPAddress 的列表。因此,它可以节省您每次在循环中将字符串转换为 IPAddress 的时间。
【解决方案2】:

这可能不是最佳可能的解决方案,但我建议使用集合而不是列表。集合已针对检查集合中是否存在任何给定值进行了优化,因此您将二进制搜索替换为单个操作。而不是:

ip_list = list(set(ip_list))

只是做:

ip_set = set(ip_list)

然后你的代码的另一部分变成:

for ip in myIPList:  # myIPList is the list of 200,000 IPs
    if ip in ip_set:
        print('The ip is present')

编辑:为了让事情更节省内存,您也可以跳过创建中间列表:

ip_set = set()
for ipMasked in ipsubnets: 
    ip_set.update([str(ip) for ip in IPNetwork(ipMasked)])

【讨论】:

    【解决方案3】:

    如果该地址的 N 个前导位与 N 位子网之一的 N 个前导位匹配,则您在子网中的 IP 地址。因此,首先制作一个空集列表。将每个子网编码为 32 位整数,并屏蔽掉尾随位。例如,1.2.3.4/23 等于 (0x01020304 & 0xfffffe00) 等于 0x01020200。将此号码添加到列表中的第 23 组,即subnets[23]。继续所有子网。

    要查看 IP 地址是否在您的子网中,请将 IP 地址以与 32 位数字 ipaddr 相同的方式编码,然后(类似于未经测试的代码)

    for N in range( 32, 0, -1)
        mask = ( 0xffffffff >> (32-N) ) << (32-N)
        if (ipaddr & mask) in subnets[N] :
            # have found ipaddr in one of our subnets
            break # or do whatever...
    else
        # have not found  ipaddr
    

    在最坏的情况下在 O(log N) 中查找集合中的数字,其中 N 是集合中元素的数量。对于不在子网集中的 IP 地址的最坏情况,此代码最多执行 32 次。如果预计大多数地址都存在,则可以进行优化以首先测试具有最多元素的集合。那可能是

    for N in ( 24, 16, 8, 29, 23, 28, 27, 26, 25, 22, 15, 21 ... )
    

    或者您可以在运行时计算最佳序列。

    【讨论】:

      猜你喜欢
      • 2021-06-20
      • 2010-12-02
      • 2019-02-09
      • 2012-01-25
      • 1970-01-01
      • 1970-01-01
      • 2014-12-31
      • 2011-09-06
      • 2013-06-21
      相关资源
      最近更新 更多