我将ip列表和子网dict模拟为输入:
# ip address list
ip_list = [
'192.168.1.151', '192.168.10.191', '192.168.6.127',
'192.168.2.227', '192.168.2.5', '192.168.3.237',
'192.168.6.188', '192.168.7.209', '192.168.9.10',
# Edited: add some /28, /16 case
'192.168.12.39', '192.168.12.58', '10.63.11.1', '10.63.102.69',
]
# subnet dict
netsets = {
'192.168.1.0/24': 'subnet-A', # {subnet: subnet's name}
'192.168.10.0/24': 'subnet-B',
'192.168.2.0/24': 'subnet-C',
'192.168.3.0/24': 'subnet-C',
'192.168.6.0/24': 'subnet-D',
'192.168.7.0/24': 'subnet-D',
'192.168.9.0/24': 'subnet-E',
# Edited: add some /28, /16 case
'192.168.12.32/28': 'subnet-F',
'192.168.12.48/28': 'subnet-G',
'10.63.0.0/16': 'subnet-I',
}
然后ip_list
中的每个IP地址都需要找到子网的名称。
我们假设每个IP地址都可以在netsets
中找到相应的子网。
像这样的输出:
192.168.1.151 subnet-A
192.168.10.191 subnet-B
192.168.6.127 subnet-D
192.168.2.227 subnet-C
192.168.2.5 subnet-C
192.168.3.237 subnet-C
192.168.6.188 subnet-D
192.168.7.209 subnet-D
192.168.9.10 subnet-E
# add some /28, /16 case
192.168.12.39 subnet-F
192.168.12.58 subnet-G
10.63.11.1 subnet-I
10.63.102.69 subnet-I
我使用netaddr来计算CIDR,这是我的代码:
from netaddr import IPAddress, IPNetwork
def netaddr_test(ips, netsets):
for ip in ips:
for subnet, name in netsets.iteritems():
if IPAddress(ip) in IPNetwork(subnet):
print ip, '\t', name
break
netaddr_test(ip_list, netsets)
但是这段代码太慢了,迭代太多了。时间的复杂性是O(n ** 2)。
一旦我们有数万个ip进行迭代,这段代码就会花费太多时间。
有没有更好的方法来解决这个问题?
答案 0 :(得分:3)
我建议使用经过特别优化的intervaltree模块来快速搜索。因此,可以针对 O(m * log n)时间来解决该任务。例如:
from intervaltree import Interval, IntervalTree
from ipaddress import ip_network, ip_address
# build nets tree
netstree = IntervalTree(
Interval(
ip_network(net).network_address,
ip_network(net).broadcast_address,
name
)
for
net, name
in
netsets.items()
)
# Now you may check ip intervals
for i in ip_list:
ip = ip_address(i)
nets = netstree[ip]
if nets: # set is not empty
netdata = list(nets)[0]
print(netdata.data)
# prints 'subnet-E'
答案 1 :(得分:2)
# ip address list
ip_list = [
'192.168.1.151', '192.168.10.191', '192.168.6.127',
'192.168.2.227', '192.168.2.5', '192.168.3.237',
'192.168.6.188', '192.168.7.209', '192.168.9.10'
]
# subnet dict
netsets = {
'192.168.1.0/24': 'subnet-A', # {subnet: subnet's name}
'192.168.10.0/24': 'subnet-B',
'192.168.2.0/24': 'subnet-C',
'192.168.3.0/24': 'subnet-C',
'192.168.6.0/24': 'subnet-D',
'192.168.7.0/24': 'subnet-D',
'192.168.9.0/24': 'subnet-E',
}
new_netsets = {}
for k,v in netsets.items():
new_netsets['.'.join(k.split('.')[:3])] = v
for IP in ip_list:
newIP = '.'.join(IP.split('.')[:3])
print IP, new_netsets[newIP]
希望这有帮助。
答案 2 :(得分:1)
我建议避免在for循环中创建新实例。这不会降低复杂性(它会增加它),但它会加速netaddr_test
,特别是如果它被调用超过一次。例如:
def _init(ips, netsets):
"""Initialize all objects"""
new_ips = []
new_subs = {}
for ip in ips:
new_ips.append(IPAddress(ip))
for subnet, info in netsets.iteritems():
new_subs[subnet] = {'name': info, 'subnet': IPNetwork(subnet)}
return new_ips, new_subs
def netaddr_test(ips, netsets):
for ip in ips:
for stringnet, info in netsets.iteritems():
if ip in info['subnet']:
print ip, '\t', info['name']
break
ni, ns = _init(ip_list, netsets)
netaddr_test(ni, ns)
更新:用
测试上面的代码ip_list = [
'192.168.1.151', '192.168.10.191', '192.168.6.127',
'192.168.2.227', '192.168.2.5', '192.168.3.237',
'192.168.6.188', '192.168.7.209', '192.168.9.10'
] * 1000
结果:
# Original
$ time python /tmp/test.py > /dev/null
real 0m0.357s
user 0m0.345s
sys 0m0.012s
# Modified
$ time python /tmp/test2.py > /dev/null
real 0m0.126s
user 0m0.122s
sys 0m0.005s
现在,我从未使用netaddr
所以我不确定它如何在内部处理子网。在您的情况下,您可以将子网视为一系列IP,每个IP都是uint_32
,因此您可以将所有内容转换为整数:
# IPs now are
ip_list_int = [3232235927, 3232238271, ...]
netsets_expanded = {
'192.168.1.0/24': {'name': 'subnet-A', 'start': 3232235776, 'end': 3232236031}
netaddr
可用于转换上述格式的数据。在那里,您的netaddr_test
变为(并且仅适用于整数比较):
def netaddr_test(ips, netsets):
for ip in ips:
for subnet, subinfo in netsets.iteritems():
if ip >= subinfo['start'] and ip < subinfo['end']:
print ip, '\t', subinfo.name
break
答案 3 :(得分:1)
一般情况下,你有N个模板和M值来测试匹配,你可以做任何比O(N * M)更好的事情。但是,如果你可以重新制定任务,那么你可以加速它。
我的建议是对模板进行分组,以便您拥有一些高级模板,如果IP匹配它,那么您可以转到最终模板。在你的例子中,这将是
grouped_netsets = {
"192.168.0.0/16": {
'192.168.1.0/24': 'subnet-A', # {subnet: subnet's name}
'192.168.10.0/24': 'subnet-B',
'192.168.2.0/24': 'subnet-C',
'192.168.3.0/24': 'subnet-C',
'192.168.6.0/24': 'subnet-D',
'192.168.7.0/24': 'subnet-D',
'192.168.9.0/24': 'subnet-E',
}
}
def netaddr_test(ips, grouped_netsets):
for ip in ips:
for group, netsets in grouped_netsets.iteritems():
if IPAddress(ip) in IPNetwork(group):
for subnet, name in netsets.iteritems():
if IPAddress(ip) in IPNetwork(subnet):
print(ip, '\t', name)
break
因此,如果ip_list包含任何不以192.168开头的内容,您将通过一次检查将其丢弃。
剩下的唯一问题是编写用于对具有最佳配置的网络进行分组的功能。
答案 4 :(得分:0)
我将ip列表和子网dict模拟为输入:
# ip address list ip_list = [ '192.168.1.151', '192.168.10.191', '192.168.6.127', '192.168.2.227', '192.168.2.5', '192.168.3.237', '192.168.6.188', '192.168.7.209', '192.168.9.10' ] # subnet dict netsets = { '192.168.1.0/24': 'subnet-A', # {subnet: subnet's name} '192.168.10.0/24': 'subnet-B', '192.168.2.0/24':'subnet-C', '192.168.3.0/24': 'subnet-C', '192.168.6.0/24': 'subnet-D', '192.168.7.0/24': 'subnet-D', '192.168.9.0/24':'subnet-E', }
然后ip_list中的每个ip地址都需要找到 子网名称。
我们假设每个IP地址都可以找到相应的子网 netsets。
像这样的输出:
192.168.1.151 subnet-A 192.168.10.191 subnet-B 192.168.6.127 subnet-D 192.168.2.227 subnet-C 192.168.2.5 subnet-C 192.168.3.237 subnet-C 192.168.6.188 subnet-D 192.168.7.209 subnet-D 192.168.9.10 subnet-E
[...] 有没有更好的解决这个问题?
这是一个实现它的两个班轮:
for ip_addr in ip_list:
print "{0}\t{1}".format(ip_addr,netsets[".".join(ip_addr.split('.')[0:-1])+".0/24"])
答案 5 :(得分:0)
假设子网彼此不重叠,您可以将子网转换为两个整数,即范围的开头和结尾。这些数字将被添加到将被排序的列表中。在执行此操作时,我们需要构建一个字典,以后可以使用该字典来检索范围开头的子网名称。
def to_int(ip):
parts = map(int, ip.split('.'))
return parts[0] << 24 | parts[1] << 16 | parts[2] << 8 | parts[3]
def build(netsets):
ranges = []
subnets = {}
for net, name in netsets.iteritems():
ip, size = net.split('/')
start = to_int(ip)
end = start | 0xffffffff >> int(size)
ranges.extend([start, end])
subnets[start] = name
ranges.sort()
return ranges, subnets
搜索IP时,您需要再次将其转为数字,并在列表或范围内执行bisect_left
。如果结果是数字不均匀或IP匹配列表中的任何数字,则IP在子网内。然后,您可以使用范围中的星号从之前构建的字典中获取子网的名称:
def find(ranges, subnets, ip):
num = to_int(ip)
pos = bisect.bisect_left(ranges, to_int(ip))
# Check if first IP in the range
if pos % 2 == 0 and ranges[pos] == num:
pos += 1
if pos % 2:
return subnets[ranges[pos - 1]]
else:
return None
使用上一个构建块,可以使用以下代码轻松获取每个IP的子网:
ranges, subnets = build(netsets)
for ip in ip_list:
print 'ip: {0}, subnet: {1}'.format(ip, find(ranges, subnets, ip))
构建字典和范围列表需要O(m log m)时间,并且通过IP列表将需要O(n log m),其中m是子网的数量,n是IP的数量。解决方案适用于不同大小的不同子网,如果IP不属于任何子网,将打印None
。