如何在Python中检查IP是否在网络中?

140

如何在Python中检查IP地址(比如192.168.0.1)是否属于某个网络(比如192.168.0.0/24)?

在Python中是否有一些常规工具用于IP地址的操作,例如主机查找、将IP地址转换为整数、将带有子网掩码的网络地址转换成整数等等?希望这些工具能够在Python 2.5标准库中找到。


这个问题似乎是一个不错的经典问题,适用于非常旧的2.x版本的答案,但对于3.x版本已经过时。请参见如何组织和分配“Python/pandas比较IP地址/CIDR”的经典问题? - smci
@smci,我不明白为什么;phihag在https://dev59.com/8HRA5IYBdhLWcg3wzhRY#1004527上的回答对于Python 3来说是一个完美的答案,并且自2014年以来一直存在。我已经撤销了您使该答案无效的编辑。 - Mark Amery
@Staale - 你应该更新你的答案,以一个没有重大漏洞的答案。其他答案使用内置库来完成相同的事情,只需要1/10的代码,而且没有任何漏洞。 - Addison
31个回答

202

使用 ipaddress (自Python 3.3起在标准库中可用, 在PyPi上可用于2.6/2.7版本):

>>> import ipaddress
>>> ipaddress.ip_address('192.168.0.1') in ipaddress.ip_network('192.168.0.0/24')
True

如果您想以这种方式评估大量的IP地址,则可能需要预先计算子网掩码,例如:
n = ipaddress.ip_network('192.0.0.0/16')
netw = int(n.network_address)
mask = int(n.netmask)

然后,对于每个地址,使用其中一个方法计算其二进制表示:

a = int(ipaddress.ip_address('192.0.43.10'))
a = struct.unpack('!I', socket.inet_pton(socket.AF_INET, '192.0.43.10'))[0]
a = struct.unpack('!I', socket.inet_aton('192.0.43.10'))[0]  # IPv4 only

最后,您可以简单地检查:
in_network = (a & mask) == netw

4
请注意,对于我们而言,python-ipaddr的表现相当缓慢,因此在需要频繁比较的某些情况下它可能不太适用。个人经验因人而异,所以请进行自己的基准测试。 - drdaeman
在某些版本中,您可能需要提供一个Unicode字符串而不是Python str类型,例如 ipaddress.ip_address(u'192.168.0.1') in ipaddress.ip_network(u'192.168.0.0/24') - Moondoggy
3
我担心这种方法正在对网络地址列表进行迭代,但是ipaddress模块覆盖了__contains__方法,通过比较网络和广播地址的整数表示来以高效的方式执行此操作,因此请放心,如果这是您的担忧。 - avatarofhope2
1
要检查一个网络是否包含另一个网络,可以使用 ipaddress 中的 overlaps 方法,例如:ipaddress.ip_network('192.168.0.16/28').overlaps(ipaddress.ip_network('192.168.0.0/24')) - trebor

175

我喜欢使用netaddr来完成此操作:

from netaddr import CIDR, IP

if IP("192.168.0.1") in CIDR("192.168.0.0/24"):
    print "Yay!"

正如arno_v在评论中指出的那样,netaddr的新版本可以这样做:

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

netaddr.all_matching_cidrs("192.168.0.1", ["192.168.0.0/24","212.11.64.0/19"] ) [IPNetwork('192.168.0.0/24')]
- user221014
28
дҪҝз”Ёж–°зүҲжң¬зҡ„д»Јз Ғпјҡ д»ҺnetaddrжЁЎеқ—еҜје…ҘIPNetworkе’ҢIPAddressзұ» еҲӨж–ӯIPAddress("192.168.0.1")жҳҜеҗҰеңЁIPNetwork("192.168.0.0/24")дёӯ - arno_v
1
请注意,尽管这种方法具有良好的API,但需要安装第三方模块。而另一个回答讨论的内置ipaddress模块则不需要安装。 - Oliver

63

适用于Python 3

import ipaddress
ipaddress.IPv4Address('192.168.1.1') in ipaddress.IPv4Network('192.168.0.0/24')
ipaddress.IPv4Address('192.168.1.1') in ipaddress.IPv4Network('192.168.0.0/16')

输出:

False
True

6
标准库使用巧妙的位运算检查,因此这是最优解。 https://github.com/python/cpython/blob/3.8/Lib/ipaddress.py#L690 - rocketspacer

30

使用 Python >= 3.7 ipaddress:

import ipaddress

address = ipaddress.ip_address("192.168.0.1")
network = ipaddress.ip_network("192.168.0.0/16")

print(network.supernet_of(ipaddress.ip_network(f"{address}/{address.max_prefixlen}")))

解释

你可以将IP地址看作是带有最大可能子网掩码的网络(对于IPv4为/32,IPv6为/128

检查192.168.0.1是否在192.168.0.0/16中,本质上与检查192.168.0.1/32是否为192.168.0.0/16的子网相同。


不确定为什么这个答案还没有排在前面。 - Filippo Vitale
感谢您对 Python >= 3.7 约束的有益回答!我在文档中看到它是在3.3版本中引入的。 - Romain
3
更加简单,一切都内置了:if IPv4Address('192.0.2.6') in IPv4Network('192.0.2.0/28') - benzkji
@benzkji 是的,我已经在 petertc 的回答下面发表了评论 https://dev59.com/8HRA5IYBdhLWcg3wzhRY#51472420 - rocketspacer

28

这篇文章展示了如何使用socketstruct模块,不需要太多额外的努力即可实现。我在文章中添加了以下内容:

import socket,struct

def makeMask(n):
    "return a mask of n bits as a long integer"
    return (2L<<n-1) - 1

def dottedQuadToNum(ip):
    "convert decimal dotted quad string to long integer"
    return struct.unpack('L',socket.inet_aton(ip))[0]

def networkMask(ip,bits):
    "Convert a network address to a long integer" 
    return dottedQuadToNum(ip) & makeMask(bits)

def addressInNetwork(ip,net):
   "Is an address in a network"
   return ip & net == net

address = dottedQuadToNum("192.168.1.1")
networka = networkMask("10.0.0.0",24)
networkb = networkMask("192.168.0.0",24)
print (address,networka,networkb)
print addressInNetwork(address,networka)
print addressInNetwork(address,networkb)

这将输出:

False
True

如果你只需要一个接受字符串的单一函数,它将如下所示:

import socket,struct

def addressInNetwork(ip,net):
   "Is an address in a network"
   ipaddr = struct.unpack('L',socket.inet_aton(ip))[0]
   netaddr,bits = net.split('/')
   netmask = struct.unpack('L',socket.inet_aton(netaddr))[0] & ((2L<<int(bits)-1) - 1)
   return ipaddr & netmask == netmask

8
此外,无论大小端如何,在' L '解包为4个字节之外的其他内容的体系结构上,struct.unpack('L',socket.inet_aton(ip))[0]都会失败。 - Rafał Dowgird
5
延续Rafal的评论,要在64位Python解释器上使其工作,将问题中的行替换为:return struct.unpack('<L',socket.inet_aton(ip))[0] - nitwit
12
我认为你的解决方案存在严重漏洞: addressInNetwork('172.7.1.1', '172.3.0.0/16') -> True (在我的64位操作系统中,我将'L'转换为'<L') - Taha Jahangir
23
警告:这个解决方案存在严重的漏洞:“addressInNetwork('172.7.1.1', '172.3.0.0/16') -> True” - Taha Jahangir
7
这个答案有一个“缺陷”。请参见答案:https://dev59.com/8HRA5IYBdhLWcg3wzhRY#23230273 - Debanshu Kundu
显示剩余5条评论

15

这段代码在我的Linux x86系统上运行良好。我没有考虑过字节顺序问题,但我已经使用超过200K个IP地址对8个不同的网络字符串进行了测试,并且ipaddr模块的结果与此代码相同。

def addressInNetwork(ip, net):
   import socket,struct
   ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16)
   netstr, bits = net.split('/')
   netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16)
   mask = (0xffffffff << (32 - int(bits))) & 0xffffffff
   return (ipaddr & mask) == (netaddr & mask)

示例:

>>> print addressInNetwork('10.9.8.7', '10.9.1.0/16')
True
>>> print addressInNetwork('10.9.8.7', '10.9.1.0/24')
False

2
简洁快速。对于一些简单的逻辑操作,不需要使用库。 - Chris Koston

10

在可能的情况下,我建议使用内置的ipaddress模块。虽然它只能在Python 3中使用,但它非常容易使用并支持IPv6。而且为什么你还没有使用Python 3呢,对吧


被接受的答案不起作用...这让我很生气。掩码是反向的,而且不能与任何不是简单的8位块(例如/24)的位一起使用。我改编了答案,现在它可以很好地工作。

    import socket,struct
    
    def addressInNetwork(ip, net_n_bits):  
      ipaddr = struct.unpack('!L', socket.inet_aton(ip))[0]
      net, bits = net_n_bits.split('/')
      netaddr = struct.unpack('!L', socket.inet_aton(net))[0]
      netmask = (0xFFFFFFFF >> int(bits)) ^ 0xFFFFFFFF
      return ipaddr & netmask == netaddr

这里有一个函数,返回一个带点的二进制字符串,以帮助可视化掩码。有点像ipcalc的输出。

    def bb(i):
     def s = '{:032b}'.format(i)
     def return s[0:8]+"."+s[8:16]+"."+s[16:24]+"."+s[24:32]

例如:

Python 的屏幕截图


8

我不太喜欢在没有必要的情况下使用模块。这项工作只需要进行简单的数学计算,因此这里是我的简单函数来完成任务:

def ipToInt(ip):
    o = map(int, ip.split('.'))
    res = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3]
    return res

def isIpInSubnet(ip, ipNetwork, maskLength):
    ipInt = ipToInt(ip)#my test ip, in int form

    maskLengthFromRight = 32 - maskLength

    ipNetworkInt = ipToInt(ipNetwork) #convert the ip network into integer form
    binString = "{0:b}".format(ipNetworkInt) #convert that into into binary (string format)

    chopAmount = 0 #find out how much of that int I need to cut off
    for i in range(maskLengthFromRight):
        if i < len(binString):
            chopAmount += int(binString[len(binString)-1-i]) * 2**i

    minVal = ipNetworkInt-chopAmount
    maxVal = minVal+2**maskLengthFromRight -1

    return minVal <= ipInt and ipInt <= maxVal

然后要使用它:

>>> print isIpInSubnet('66.151.97.0', '66.151.97.192',24) 
True
>>> print isIpInSubnet('66.151.97.193', '66.151.97.192',29) 
True
>>> print isIpInSubnet('66.151.96.0', '66.151.97.192',24) 
False
>>> print isIpInSubnet('66.151.97.0', '66.151.97.192',29) 

这就是了,这种方法比上面使用的包含模块的解决方案要快得多。

{TypeError}'map' object is not subscriptable. You need a o = list(o) after o = map(int, ip.split('.')) - gies0r

7

在2.5标准库中没有,但使用ipaddr非常容易实现。我相信在3.3版本中以ipaddress的名称存在。

import ipaddr

a = ipaddr.IPAddress('192.168.0.1')
n = ipaddr.IPNetwork('192.168.0.0/24')

#This will return True
n.Contains(a)

这绝对是我在这里看到的众多选择中最喜欢的(评论时为2017年)。谢谢! - rsaw

6

我尝试了Dave Webb的解决方案,但遇到了一些问题:

最根本的是-应该通过将IP地址与掩码进行AND运算来检查匹配,然后检查结果是否与网络地址完全匹配。而不是像以前那样将IP地址与网络地址进行AND运算。

我还注意到,仅仅忽略字节序的行为,假设一致性会使你免受困扰,只适用于八位边界(/24,/16)的掩码。为了让其他掩码(/23,/21)正常工作,我在结构命令中添加了“大于”并更改了创建二进制掩码的代码,从所有“1”开始,并向左移动(32-mask)。

最后,我添加了一个简单的检查,确保网络地址对于掩码是有效的,如果无效就打印一个警告。

以下是结果:

def addressInNetwork(ip,net):
    "Is an address in a network"
    ipaddr = struct.unpack('>L',socket.inet_aton(ip))[0]
    netaddr,bits = net.split('/')
    netmask = struct.unpack('>L',socket.inet_aton(netaddr))[0]
    ipaddr_masked = ipaddr & (4294967295<<(32-int(bits)))   # Logical AND of IP address and mask will equal the network address if it matches
    if netmask == netmask & (4294967295<<(32-int(bits))):   # Validate network address is valid for mask
            return ipaddr_masked == netmask
    else:
            print "***WARNING*** Network",netaddr,"not valid with mask /"+bits
            return ipaddr_masked == netmask

这似乎在64位上可靠运行(“L”失败,因为值为32位),并以合理的顺序返回值(ipaddr将为192.168.0.1的0xC0A80001)。它还处理“192.168.0.1/24”作为“192.168.0.1”的子网掩码(不是标准,但可能且易于纠正)。 - IBBoard
在Python 2.4上完美运行。 - xlash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接