如何在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个回答

5

自 Python 3.7 开始, 您可以使用标准库中的 subnet_ofsupernet_of 辅助方法:

如果只想针对单个 IP 进行测试,您可以使用子网掩码 /32,表示"仅此 IP 地址"作为子网,或者将 IP 地址传递给 IPv4NeworkIPv6Nework 构造函数,它们将返回一个子网值。

因此,对于您的示例:

from ipaddress import IPv4Network, IPv4Address

# Store IP Address as variable
>>> myip = IPv4Address('192.168.0.1')
>>> myip
IPv4Address('192.168.0.1')

# This treats the IP as a subnet
>>> myip_subnet = IPv4Network(myip)
>>> myip_subnet
IPv4Network('192.168.0.1/32')

# The other subnet to test membership against
>>> other_subnet = IPv4Network('192.168.0.0/24')
>>> other_subnet
IPv4Network('192.168.0.0/24')

# Now we can test
>>> myip_subnet.subnet_of(other_subnet)
True

在Python中,有通用的工具可以处理IP地址吗?比如主机查找、IP地址转换为整数、具有子网掩码的网络地址转换为整数等等?希望这些工具在Python 2.5的标准库中能够使用。

在Python 3中,有一个名为ipaddress模块,该模块提供了IPv4和IPv6操作工具。您可以通过强制转换将它们转换为int,例如int(IPv4Address('192.168.0.1'))。还有许多其他有用的函数在ipaddress模块中可用于主机等。

这是Python 3的最佳答案,用于检查一个子网是否完全被另一个子网所覆盖。 - chrisinmtown

4
马克的代码几乎正确。完整版本的代码是 -
def addressInNetwork3(ip,net):
    '''This function allows you to check if on IP belogs to a Network'''
    ipaddr = struct.unpack('=L',socket.inet_aton(ip))[0]
    netaddr,bits = net.split('/')
    netmask = struct.unpack('=L',socket.inet_aton(calcDottedNetmask(int(bits))))[0]
    network = struct.unpack('=L',socket.inet_aton(netaddr))[0] & netmask
    return (ipaddr & netmask) == (network & netmask)

def calcDottedNetmask(mask):
    bits = 0
    for i in xrange(32-mask,32):
        bits |= (1 << i)
    return "%d.%d.%d.%d" % ((bits & 0xff000000) >> 24, (bits & 0xff0000) >> 16, (bits & 0xff00) >> 8 , (bits & 0xff))

很明显,这段内容与上面的内容来源相同...

非常重要的一点是,第一段代码存在一个小错误 - IP地址 255.255.255.255 也会显示为任何子网的有效IP地址。我花费了很长时间才使这段代码正常工作,感谢Marc提供了正确的答案。


经过尝试和测试。在此页面的所有套接字/结构示例中,这是唯一正确的一个。 - Zabuzzman

4
依赖于“struct”模块可能会导致字节序和类型大小的问题,并且并不需要。socket.inet_aton()也是如此。Python非常适用于点分十进制IP地址:
def ip_to_u32(ip):
  return int(''.join('%02x' % int(d) for d in ip.split('.')), 16)

我需要在每个socket accept()调用中执行IP匹配,根据一整组允许的源网络,因此我将掩码和网络预先计算为整数:

SNS_SOURCES = [
  # US-EAST-1
  '207.171.167.101',
  '207.171.167.25',
  '207.171.167.26',
  '207.171.172.6',
  '54.239.98.0/24',
  '54.240.217.16/29',
  '54.240.217.8/29',
  '54.240.217.64/28',
  '54.240.217.80/29',
  '72.21.196.64/29',
  '72.21.198.64/29',
  '72.21.198.72',
  '72.21.217.0/24',
  ]

def build_masks():
  masks = [ ]
  for cidr in SNS_SOURCES:
    if '/' in cidr:
      netstr, bits = cidr.split('/')
      mask = (0xffffffff << (32 - int(bits))) & 0xffffffff
      net = ip_to_u32(netstr) & mask
    else:
      mask = 0xffffffff
      net = ip_to_u32(cidr)
    masks.append((mask, net))
  return masks

那么我可以快速查看给定的IP地址是否属于其中一个网络:

ip = ip_to_u32(ipstr)
for mask, net in cached_masks:
  if ip & mask == net:
    # matched!
    break
else:
  raise BadClientIP(ipstr)

不需要导入任何模块,代码匹配速度非常快。

这个 cached_masks 是指什么? - ajin

3
选中的答案有一个错误。
以下是正确的代码:
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 = ((1L << int(bits)) - 1)
   return ipaddr & netmask == netaddr & netmask

注意:ipaddr & netmask == netaddr & netmask而不是ipaddr & netmask == netmask
我还将((2L<<int(bits)-1) - 1)替换为((1L << int(bits)) - 1),因为后者更易于理解。

我认为掩码转换 ((2L<<int(bits)-1) - 1) 是正确的。例如,如果掩码是16,则应该是“255.255.0.0”或65535L,但 ((1L << int(bits)) - 1) 得到的是32767L,这是不正确的。 - Chris.Q
@Chris.Q,我的系统上使用((1L << int(bits)) - 1),当bits设置为16时,结果为65535L! - Debanshu Kundu
另外,对于 bits 设置为 0((2L<<int(bits)-1) - 1) 会引发错误。 - Debanshu Kundu
是的,实际上除了/0、/8、/16、/32之外,没有其他值能正常工作。 - Debanshu Kundu

2

这是我为最长前缀匹配编写的一个类:

#!/usr/bin/env python

class Node:
def __init__(self):
    self.left_child = None
    self.right_child = None
    self.data = "-"

def setData(self, data): self.data = data
def setLeft(self, pointer): self.left_child = pointer
def setRight(self, pointer): self.right_child = pointer
def getData(self): return self.data
def getLeft(self): return self.left_child
def getRight(self): return self.right_child

def __str__(self):
        return "LC: %s RC: %s data: %s" % (self.left_child, self.right_child, self.data)


class LPMTrie:      

def __init__(self):
    self.nodes = [Node()]
    self.curr_node_ind = 0

def addPrefix(self, prefix):
    self.curr_node_ind = 0
    prefix_bits = ''.join([bin(int(x)+256)[3:] for x in prefix.split('/')[0].split('.')])
    prefix_length = int(prefix.split('/')[1])
    for i in xrange(0, prefix_length):
        if (prefix_bits[i] == '1'):
            if (self.nodes[self.curr_node_ind].getRight()):
                self.curr_node_ind = self.nodes[self.curr_node_ind].getRight()
            else:
                tmp = Node()
                self.nodes[self.curr_node_ind].setRight(len(self.nodes))
                tmp.setData(self.nodes[self.curr_node_ind].getData());
                self.curr_node_ind = len(self.nodes)
                self.nodes.append(tmp)
        else:
            if (self.nodes[self.curr_node_ind].getLeft()):
                self.curr_node_ind = self.nodes[self.curr_node_ind].getLeft()
            else:
                tmp = Node()
                self.nodes[self.curr_node_ind].setLeft(len(self.nodes))
                tmp.setData(self.nodes[self.curr_node_ind].getData());
                self.curr_node_ind = len(self.nodes)
                self.nodes.append(tmp)

        if i == prefix_length - 1 :
            self.nodes[self.curr_node_ind].setData(prefix)

def searchPrefix(self, ip):
    self.curr_node_ind = 0
    ip_bits = ''.join([bin(int(x)+256)[3:] for x in ip.split('.')])
    for i in xrange(0, 32):
        if (ip_bits[i] == '1'):
            if (self.nodes[self.curr_node_ind].getRight()):
                self.curr_node_ind = self.nodes[self.curr_node_ind].getRight()
            else:
                return self.nodes[self.curr_node_ind].getData()
        else:
            if (self.nodes[self.curr_node_ind].getLeft()):
                self.curr_node_ind = self.nodes[self.curr_node_ind].getLeft()
            else:
                return self.nodes[self.curr_node_ind].getData()

    return None

def triePrint(self):
    n = 1
    for i in self.nodes:
        print n, ':'
        print i
        n += 1

这里是一个测试程序:

n=LPMTrie()
n.addPrefix('10.25.63.0/24')
n.addPrefix('10.25.63.0/16')
n.addPrefix('100.25.63.2/8')
n.addPrefix('100.25.0.3/16')
print n.searchPrefix('10.25.63.152')
print n.searchPrefix('100.25.63.200')
#10.25.63.0/24
#100.25.0.3/16

2

从netaddr导入all_matching_cidrs

>>> from netaddr import all_matching_cidrs
>>> all_matching_cidrs("212.11.70.34", ["192.168.0.0/24","212.11.64.0/19"] )
[IPNetwork('212.11.64.0/19')]

这个方法的使用方法如下:
>>> help(all_matching_cidrs)

Help on function all_matching_cidrs in module netaddr.ip:

all_matching_cidrs(ip, cidrs)
    Matches an IP address or subnet against a given sequence of IP addresses and subnets.

    @param ip: a single IP address or subnet.

    @param cidrs: a sequence of IP addresses and/or subnets.

    @return: all matching IPAddress and/or IPNetwork objects from the provided
    sequence, an empty list if there was no match.

基本上,您需要将IP地址作为第一个参数提供,将CIDR列表作为第二个参数提供。 返回一系列命中结果。


2

之前的解决方案在ip和net==net时存在一个bug。正确的ip查找方法是ip&netmask=net。

修复后的代码:

import socket
import 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 addressInNetwork(ip,net,netmask):
   "Is an address in a network"
   print "IP "+str(ip) + " NET "+str(net) + " MASK "+str(netmask)+" AND "+str(ip & netmask)
   return ip & netmask == net

def humannetcheck(ip,net):
        address=dottedQuadToNum(ip)
        netaddr=dottedQuadToNum(net.split("/")[0])
        netmask=makeMask(long(net.split("/")[1]))
        return addressInNetwork(address,netaddr,netmask)


print humannetcheck("192.168.0.1","192.168.0.0/24");
print humannetcheck("192.169.0.1","192.168.0.0/24");

2
#这段代码可以正常工作,不需要奇怪的逐字节处理
def addressInNetwork(ip, net):
    '''判断一个地址是否在网络中'''
    # 将地址转换为主机顺序,以便移位有意义
    ip = struct.unpack('>L', socket.inet_aton(ip))[0]
    netaddr, bits = net.split('/')
    netaddr = struct.unpack('>L', socket.inet_aton(netaddr))[0]
    # 必须将所有1值左移,/32=零移位,/0= 32左移位
    netmask = (0xffffffff << (32-int(bits))) & 0xffffffff
    # 只要是正确的网络地址,就没有必要屏蔽网络地址
    return (ip & netmask) == netaddr

代码在64位操作系统上没有正常工作,因为netmask的值不正确。我已经擅自修复了它。 - drdaeman

1
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('/')
    netaddr = struct.unpack('!L',socket.inet_aton(netaddr))[0]
    netmask = ((1<<(32-int(bits))) - 1)^0xffffffff
    return ipaddr & netmask == netaddr & netmask
print addressInNetwork('10.10.10.110','10.10.10.128/25')
print addressInNetwork('10.10.10.110','10.10.10.0/25')
print addressInNetwork('10.10.10.110','10.20.10.128/25')

$ python check-subnet.py



你能解释一下你正在添加到已经给出的答案中的内容吗? - David Guyon
给出的答案存在一些问题,无法处理CIDR。我只是改变了IP地址的字节顺序。就像这样:`>>> struct.unpack('!L',socket.inet_aton('10.10.10.110'))[0] 168430190
socket.inet_ntoa(struct.pack('!L', 168430190)) '10.10.10.110'`
- Johnson
2
谢谢你的回答。我想这是你应该添加到你的答案中以澄清的内容。解释“什么”,“为什么”和最后“如何”是StackOverflow精神的体现。你的答案只包含了“如何” :(。我让你通过编辑来完善你的答案;)。 - David Guyon

1
感谢您的脚本!我已经花了很长时间使所有东西都能正常工作...所以我在这里分享。
  • 使用netaddr类比使用二进制转换慢10倍,因此如果您想在大量IP列表上使用它,您应该考虑不使用netaddr类
  • makeMask函数无法正常工作! 仅适用于/8、/16、/24
    例如:

    bits = "21" ; socket.inet_ntoa(struct.pack('=L',(2L << int(bits)-1) - 1))
    '255.255.31.0' 应该是 255.255.248.0

    因此,我使用了另一个函数calcDottedNetmask(mask)来自http://code.activestate.com/recipes/576483-convert-subnetmask-from-cidr-notation-to-dotdecima/
    例如:


#!/usr/bin/python
>>> calcDottedNetmask(21)
>>> '255.255.248.0'

另一个问题是确定IP地址是否属于网络的过程!基本操作应该是比较(ipaddr&netmask)和(network&netmask)。例如:目前这个函数是错的。

#!/usr/bin/python
>>> addressInNetwork('188.104.8.64','172.16.0.0/12')
>>>True which is completely WRONG!!

所以我的新的InNetwork地址函数看起来像这样:


#!/usr/bin/python
import socket,struct
def addressInNetwork(ip,net):
    '''This function allows you to check if on IP belogs to a Network'''
    ipaddr = struct.unpack('=L',socket.inet_aton(ip))[0]
    netaddr,bits = net.split('/')
    netmask = struct.unpack('=L',socket.inet_aton(calcDottedNetmask(bits)))[0]
    network = struct.unpack('=L',socket.inet_aton(netaddr))[0] & netmask
    return (ipaddr & netmask) == (network & netmask)

def calcDottedNetmask(mask):
    bits = 0
    for i in xrange(32-int(mask),32):
        bits |= (1 > 24, (bits & 0xff0000) >> 16, (bits & 0xff00) >> 8 , (bits & 0xff))


现在,答案是正确的!!


#!/usr/bin/python
>>> addressInNetwork('188.104.8.64','172.16.0.0/12')
False

我希望这能帮助其他人,为他们节省时间!


1
以上代码的当前版本在最后一行给出了一个回溯,因为您可以将一个整数和一个元组"|="。 - Sean Reifschneider

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