如何有效地检查给定的IP地址是否属于Python中的IP子网?

时间:2017-05-30 12:34:29

标签: python performance ip cidr

我有一组大约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')

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

3 个答案:

答案 0 :(得分:0)

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

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)])

答案 1 :(得分:0)

好的,所以排序需要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"

答案 2 :(得分:0)

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

要查看您的子网中是否有IP地址,请使用与32位数字ipaddr相同的方式对IP地址进行编码,然后(类似未经测试的代码)

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 ... )

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