检查IP是否在Python的CIDR范围内

时间:2016-09-06 22:46:08

标签: python ip

我知道这里有一些类似的问题,但是他们大多想要找到范围本身(它使用一些库,比如stackoverflow说的是我的问题的一个例子)并且是另一种语言。

我有办法将子网转换为子网中ip的范围的开头和结尾(好吧,措辞不好,它就像1.1.1.1/16 -> (1.1.0.0 , 1.1.255.255)

我现在想检查1.1.2.2是否在此子网内。我可以简单地进行><进行比较吗?

ip_range = ('1.1.0.0', '1.1.255.255')
if '1.1.2.2' >= ip_range[0] and '1.1.2.2' <= ip_range[1]:
     return True

当我测试它时,它可以工作,但我不知道它是否总能用于任何ipv4 ip。我假设我只是比较ASCII顺序,所以这应该始终有效,但有没有例外?

6 个答案:

答案 0 :(得分:6)

在Python 3.3及更高版本中,您应该使用ipaddress模块。

from ipaddress import ip_network, ip_address

net = ip_network("1.1.0.0/16")
print(ip_address("1.1.2.2") in net)    # True

答案 1 :(得分:4)

您的代码会比较字符串,而不是数字。我建议改用元组:

>>> ip_range = [(1,1,0,0), (1,1,255,255)]
>>> testip = (1,1,2,2)
>>> testip > ip_range[0] and testip < ip_range[1]
True
>>> testip = (1,3,1,1)
>>> testip > ip_range[0] and testip < ip_range[1]
False

答案 2 :(得分:4)

你不能真正对点分隔的数字列表进行字符串比较,因为你的测试只会因输入1.1.99.99而失败,因为'9'只是大于'2'

>>> '1.1.99.99' < '1.1.255.255'
False

因此,您可以通过理解表达式将输入转换为整数元组

def convert_ipv4(ip):
    return tuple(int(n) for n in ip.split('.'))

请注意缺少类型检查,但如果您的输入是正确的IP地址,那就没问题了。由于你有一个2元组的IP地址,你可以创建一个以开始和结束为参数的函数,通过参数列表传递该元组,并只用一个语句返回(因为Python允许比较链接)。也许就像:

def check_ipv4_in(addr, start, end):
    return convert_ipv4(start) < convert_ipv4(addr) < convert_ipv4(end)

测试一下。

>>> ip_range = ('1.1.0.0', '1.1.255.255')
>>> check_ipv4_in('1.1.99.99', *ip_range)
True

使用此方法,您可以懒洋洋地将其扩展为IPv6,但是需要转换为十六进制(而不是整数)。

答案 3 :(得分:1)

这通常不起作用,因为字符串比较处于整理顺序,而不是四个字段的数值。例如,'1.1.2.2'&gt; '1.1.128.1' - 第5个角色的关键位置,'1'对'2'。

如果您想比较字段,请尝试分成列表:

ip_vals = [int(x) for x in ip_range.split('.')]

ip_vals现在是一个值列表;您可以比较列表并获得我认为您想要的结果。

答案 4 :(得分:0)

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字节功能外,所有基本功能都非常简单。与简单的IPv4格式相比,IPv6地址的不同格式需要更多的逻辑来进行解析。例如,环回可以表示为::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字节进行XOR运算,然后与网络掩码进行AND运算。只需将以“ ipv4_”开头的功能更改为“ ipv6_”即可,将IPv4算法转换为IPv6,反之亦然。使用构建块,IPv4或IPv6的算法在此级别是相同的。

使用构建块,可以创建自定义功能,例如确定两个CIDR标记的IP地址是否都在同一网络上,或者一个与另一个在同一网络内,这类似于{{ 1}}具有逻辑功能。

范围

请记住,如果您将掩码视为真实掩码,则无需计算子网范围即可确定成员资格。如果出于任何原因想要该范围,可以使用IP和网络掩码以与上述其他示例类似的方式获得它。

...subnet_of()

效率

该功能似乎比使用>>> 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

缓存

如果在应用程序中重复相同的比较-经常重复相同的IP,则>>> # Using the ipaddress module: >>> 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 >>> >>> # Using this code: >>> timeit.timeit("ipv4_cidr_subnet_of('192.168.1.128/30', '192.168.1.0/24')", ... globals=globals(), number=10**4) 0.07261682399985148 >>> 可用于修饰功能并可能获得更高的效率:

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)) 中再次检查相同的ip1的成员身份时,缓存会快速返回上一次计算的值,并且函数主体无需重做操作。

ip2

此测试仅显示5个周期。高速缓存命中与未命中的比率越高,效率的收益就越高。

答案 5 :(得分:0)

对于python 2 & 3,请使用:

from ipaddress import ip_network, ip_address

def in_cidr(ip, cidr):
  return ip_address(ip) in ip_network(cidr)

Demo


对于pyhton 2.7,请使用:

pip install ipaddress