IPv4和IPv6 DIY CIDR成员资格和范围
有一个名为ipaddress
的模块,提供了所有所需的功能。以下不基于它-仅展示了另一种可能的方法。
构建模块
def ipv4_mask(cidr):
mask = 2**32 - 2**(32 - int(cidr))
return (mask >> sh & 0xff for sh in (24, 16, 8, 0))
def ipv6_mask(cidr):
mask = 2**128 - 2**(128 - int(cidr))
return (mask >> sh & 0xff for sh in range(120, -1, -8))
def ipv4_bytes(ip):
return (int(b) for b in ip.split('.'))
def ipv6_bytes(ip):
words = ip.split(':')
filled = False
for word in words:
if word:
yield int(word[:-2] or '0', 16)
yield int(word[-2:], 16)
elif filled:
yield 0
yield 0
else:
filled = True
for _ in range(9 - len(words)):
yield 0
yield 0
除了IPv6字节函数之外,所有基本功能都非常简单。不同的IPv6地址格式需要更多的逻辑来解析,而不是简单的IPv4格式。例如,回环可以表示为
::1
。或者连续的0可以用相邻的冒号表示,如:
aaaa::1111
表示
aaaa:0:0:0:0:0:0:1111
。
成员资格检查
要确定IP是否在由IP和CIDR网络掩码位说明符定义的IP范围内,如果将网络掩码应用为其预期的方式(作为掩码),则无需计算起始和结束地址。下面的两个函数是确定IPv4地址是否是CIDR标记的网络IP成员的示例。另一个显示IPv6测试以确定一个子网是否在另一个子网中。
使用上述内容作为构建块,我们可以构建自定义函数用于ipv4或ipv6.:
def ipv4_cidr_member_of(ip1, ip2):
ip2, m = ip2.split('/')
return not any((a ^ b) & m
for a, b, m in
zip(ipv4_bytes(ip1),
ipv4_bytes(ip2),
ipv4_mask(m)))
def ipv6_cidr_subnet_of(ip1, ip2):
ip1, m1 = ip1.split('/')
ip2, m2 = ip2.split('/')
return int(m1) >= int(m2) and \
not any((a ^ b) & m
for a, b, m in
zip(ipv6_bytes(ip1),
ipv6_bytes(ip2),
ipv6_mask(m2)))
>>> ipv6_cidr_subnet_of('aaaa:bbbb:cccc:dddd:1100::/72',
... 'aaaa:bbbb:cccc:dddd::/64')
True
>>> ipv4_cidr_member_of('11.22.33.44', '11.22.33.0/24')
True
>>>
采用这种方法,比较通常涉及异或两个IP字节,然后与网络掩码进行与运算。IPv4算法可以通过将以“
ipv4_
”开头的函数更改为“
ipv6_
”来简单地转换为IPv6,反之亦然。使用构建块,IPv4或IPv6的算法在此级别上是相同的。
使用构建块,可以创建自定义函数来确定两个CIDR表示的IP地址是否都位于同一网络上,或者一个是否与另一个位于同一网络中 - 这类似于逻辑中的...subnet_of()
函数。
范围
请记住,如果您将掩码视为真正的掩码,则不必计算子网的范围以确定成员身份;如果出于任何原因您想要范围,则可以像上面的其他示例一样应用IP和网络掩码来获取它。
>>> def ipv4_cidr_range_bytes(ip):
... ip, m = ip.split('/')
... ip = list(ipv4_bytes(ip))
... m = list(ipv4_mask(m))
... start = [ b & m for b, m in zip(ip, m)]
... end = [(b | ~m) & 0xff for b, m in zip(ip, m)]
... return start, end
...
>>> ipv4_cidr_range_bytes('11.22.34.0/23')
([11, 22, 34, 0], [11, 22, 35, 255])
>>>
>>> # For IPv6, the above function could have been converted to look
>>> # just like it, but let's mix it up for fun with a single pass
>>> # over the data with zip(), then group into bytes objects with map()
>>>
>>> def ipv6_cidr_range_bytes(ip):
... ip, m = ip.split('/')
... s, e = map(lambda *x: bytes(x),
... *((b & m, (b | ~m) & 0xff)
... for b, m in zip(ipv6_bytes(ip),
... ipv6_mask(m))))
... return s, e
...
>>> ipv6_cidr_range_bytes('aaaa:bbbb:cccc:dddd:1100::/72')
(b'\xaa\xaa\xbb\xbb\xcc\xcc\xdd\xdd\x11\x00\x00\x00\x00\x00\x00\x00',
b'\xaa\xaa\xbb\xbb\xcc\xcc\xdd\xdd\x11\xff\xff\xff\xff\xff\xff\xff')
效率
这些函数似乎比使用ipaddress
对象和方法稍微快一些:
>>>
>>> timeit.timeit("a = ip_network('192.168.1.0/24'); "
"b = ip_network('192.168.1.128/30'); "
... "b.subnet_of(a)", globals=globals(), number=10**4)
0.2772132240352221
>>>
>>>
>>> timeit.timeit("ipv4_cidr_subnet_of('192.168.1.128/30', '192.168.1.0/24')",
... globals=globals(), number=10**4)
0.07261682399985148
>>>
缓存
如果应用程序中存在重复的比较 - 相同的IP经常出现,可以使用functools.lru_cache
装饰函数,可能会获得更高的效率:
from functools import lru_cache
@lru_cache
def ipv6_cidr_member_of(ip1, ip2):
ip1 = ipv6_bytes(ip1)
ip2, m = ip2.split('/')
ip2 = ipv6_bytes(ip2)
m = ipv6_mask(m)
return not any((a ^ b) & m for a, b, m in zip(ip1, ip2, m))
这将缓存参数和返回值,因此当在
ip2
中再次检查是否属于相同的
ip1
时,缓存会快速返回上次计算的值,函数体不需要重新执行操作。
>>>
>>> timeit.timeit("ipv6_cidr_member_of('aaaa:bbbb:cccc:dddd:11af:23af::',"
... "'aaaa:bbbb:cccc:dddd::/64')",
... globals=globals(), number=5)
0.00011115199959021993
>>>
>>>
>>>
>>> timeit.timeit("ipv6_cidr_member_of('aaaa:bbbb:cccc:dddd:11af:23af::',"
... "'aaaa:bbbb:cccc:dddd::/64')",
... globals=globals(), number=5)
4.458599869394675e-05
这个测试只展示了5个循环。缓存命中与未命中的比率越高,效率提升就越大。