Dada una dirección IP (digamos 192.168.0.1), ¿cómo verifico si está en una red (digamos 192.168.0.0/24) en Python?

¿Existen herramientas generales en Python para la manipulación de direcciones IP? ¿Cosas como búsquedas de host, dirección IP a int, dirección de red con máscara de red a int y así sucesivamente? Con suerte en la biblioteca estándar de Python para 2.5.

108
Staale 4 may. 2009 a las 12:59

25 respuestas

La mejor respuesta

Este artículo muestra que puede hacerlo con socket y Módulos struct sin demasiado esfuerzo adicional. Agregué un poco al artículo de la siguiente manera:

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)

Esto produce:

False
True

Si solo desea una única función que tome cadenas, se vería así:

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
30
Dave Webb 4 may. 2009 a las 10:12

Utilizando Python3 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}")))

Explicación

Puede pensar en una Dirección IP como una Red con la máscara de red más grande posible (/32 para IPv4, /128 para IPv6)

Verificar si 192.168.0.1 está en 192.168.0.0/16 es esencialmente lo mismo que verificar si 192.168.0.1/32 es una subred de < strong> 192.168.0.0/16

1
rocketspacer 26 dic. 2019 a las 07:16

Probé la solución de Dave Webb pero tuve algunos problemas:

Lo más fundamental: una coincidencia debe verificarse marcando AND la dirección IP con la máscara, luego verificando que el resultado coincida exactamente con la dirección de red. No ANDing la dirección IP con la dirección de red como se hizo.

También noté que ignorar el comportamiento de Endian suponiendo que la consistencia lo salvará solo funcionará para las máscaras en los límites de los octetos (/ 24, / 16). Para que otras máscaras (/ 23, / 21) funcionen correctamente, agregué un "mayor que" a los comandos de estructura y cambié el código para crear la máscara binaria para comenzar con todo "1" y cambiar a la izquierda por (máscara 32 )

Finalmente, agregué una simple verificación de que la dirección de red es válida para la máscara e imprimí una advertencia si no es así.

Aquí está el resultado:

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
6
Paul McDonnell 10 may. 2012 a las 12:57

Para python3

In [64]: ipaddress.IPv4Address('192.168.1.1') in ipaddress.IPv4Network('192.168.0.0/24')
Out[64]: False
16
okwap 23 jul. 2018 a las 06:04

Me gusta usar netaddr para eso:

from netaddr import CIDR, IP

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

Como arno_v señaló en los comentarios, la nueva versión de netaddr lo hace así:

from netaddr import IPNetwork, IPAddress
if IPAddress("192.168.0.1") in IPNetwork("192.168.0.0/24"):
    print "Yay!"
149
nosklo 20 feb. 2013 a las 14:33

No está en la biblioteca estándar para 2.5, pero ipaddr lo hace muy fácil. Creo que está en 3.3 bajo el nombre de 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)
4
James Robinson 25 sep. 2013 a las 21:42

Hay una API que se llama SubnetTree disponible en python que hace este trabajo muy bien. Este es un ejemplo simple :

import SubnetTree
t = SubnetTree.SubnetTree()
t.insert("10.0.1.3/32")
print("10.0.1.3" in t)

Este es el enlace

1
salaheddine 21 oct. 2015 a las 16:14

Probé un subconjunto de soluciones propuestas en estas respuestas ... sin éxito, finalmente adapté y arreglé el código propuesto y escribí mi función fija.

Lo probé y funciona al menos en pequeñas arquitecturas endian, por ejemplo, x86, si a alguien le gusta probar una arquitectura endian grande, por favor envíeme sus comentarios.

El código IP2Int proviene de esta publicación, el otro método es una solución de trabajo completa (para mis casos de prueba) de Propuestas anteriores en esta pregunta.

El código:

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


def addressInNetwork(ip, net_n_bits):
    ipaddr = IP2Int(ip)
    net, bits = net_n_bits.split('/')
    netaddr = IP2Int(net)
    bits_num = int(bits)
    netmask = ((1L << bits_num) - 1) << (32 - bits_num)
    return ipaddr & netmask == netaddr & netmask

Espero sea útil

0
Fabiano Tarlao 19 jul. 2018 a las 19:32

El código de Marc es casi correcto. Una versión completa del código es:

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))

Obviamente de las mismas fuentes que arriba ...

Una nota muy importante es que el primer código tiene una pequeña falla: la dirección IP 255.255.255.255 también se muestra como una IP válida para cualquier subred. Tuve mucho tiempo para que este código funcionara y gracias a Marc por la respuesta correcta.

4
Nitin Khanna 22 jun. 2015 a las 17:57

Si no desea importar otros módulos, puede elegir:

def ip_matches_network(self, network, ip):
    """
    '{:08b}'.format(254): Converts 254 in a string of its binary representation

    ip_bits[:net_mask] == net_ip_bits[:net_mask]: compare the ip bit streams

    :param network: string like '192.168.33.0/24'
    :param ip: string like '192.168.33.1'
    :return: if ip matches network
    """
    net_ip, net_mask = network.split('/')
    net_mask = int(net_mask)
    ip_bits = ''.join('{:08b}'.format(int(x)) for x in ip.split('.'))
    net_ip_bits = ''.join('{:08b}'.format(int(x)) for x in net_ip.split('.'))
    # example: net_mask=24 -> compare strings at position 0 to 23
    return ip_bits[:net_mask] == net_ip_bits[:net_mask]
0
Benny93 10 oct. 2017 a las 08:45

Este código me funciona en Linux x86. Realmente no he pensado en los problemas de resistencia, pero lo he probado en el módulo "ipaddr" utilizando más de 200K direcciones IP probadas en 8 cadenas de red diferentes, y los resultados de ipaddr son los mismos que este código.

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)

Ejemplo:

>>> print addressInNetwork('10.9.8.7', '10.9.1.0/16')
True
>>> print addressInNetwork('10.9.8.7', '10.9.1.0/24')
False
10
Sean Reifschneider 16 dic. 2010 a las 20:16

Desde netaddr import 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')]

Aquí está el uso de este método:

>>> 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.

Básicamente, proporciona una dirección IP como primer argumento y una lista de sidras como segundo argumento. Se devuelve una lista de resultados.

2
user221014user221014 6 sep. 2011 a las 17:33

Aquí está mi código

# -*- coding: utf-8 -*-
import socket


class SubnetTest(object):
    def __init__(self, network):
        self.network, self.netmask = network.split('/')
        self._network_int = int(socket.inet_aton(self.network).encode('hex'), 16)
        self._mask = ((1L << int(self.netmask)) - 1) << (32 - int(self.netmask))
        self._net_prefix = self._network_int & self._mask

    def match(self, ip):
        '''
        判断传入的 IP 是不是本 Network 内的 IP
        '''
        ip_int = int(socket.inet_aton(ip).encode('hex'), 16)
        return (ip_int & self._mask) == self._net_prefix

st = SubnetTest('100.98.21.0/24')
print st.match('100.98.23.32')
0
ruohanc 17 abr. 2017 a las 07:44
#This works properly without the weird byte by byte handling
def addressInNetwork(ip,net):
    '''Is an address in a network'''
    # Convert addresses to host order, so shifts actually make sense
    ip = struct.unpack('>L',socket.inet_aton(ip))[0]
    netaddr,bits = net.split('/')
    netaddr = struct.unpack('>L',socket.inet_aton(netaddr))[0]
    # Must shift left an all ones value, /32 = zero shift, /0 = 32 shift left
    netmask = (0xffffffff &lt&lt (32-int(bits))) & 0xffffffff
    # There's no need to mask the network address, as long as its a proper network address
    return (ip & netmask) == netaddr 
2
drdaeman 6 jun. 2012 a las 13:02

Aquí hay una clase que escribí para la coincidencia de prefijo más larga:

#!/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

Y aquí hay un programa de prueba:

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
Amir 2 dic. 2016 a las 02:03
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
Falso
Cierto
Falso

1
Ajean 18 feb. 2016 a las 22:01

De varias fuentes anteriores, y de mi propia investigación, así es como conseguí el cálculo de subred y dirección. Estas piezas son suficientes para resolver la pregunta y otras preguntas relacionadas.

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

    @staticmethod
    def numToDottedQuad(n):
        "convert long int to dotted quad string"
        return socket.inet_ntoa(struct.pack('>L', n))

    @staticmethod
    def makeNetmask(mask):
        bits = 0
        for i in xrange(32-int(mask), 32):
            bits |= (1 << i)
        return bits

    @staticmethod
    def ipToNetAndHost(ip, maskbits):
        "returns tuple (network, host) dotted-quad addresses given"
        " IP and mask size"
        # (by Greg Jorgensen)
        n = iptools.dottedQuadToNum(ip)
        m = iptools.makeMask(maskbits)
        net = n & m
        host = n - mask
        return iptools.numToDottedQuad(net), iptools.numToDottedQuad(host)
0
Nathan Garabedian 20 ago. 2015 a las 20:35

La respuesta elegida tiene un error.

El siguiente es el código correcto:

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

Nota: ipaddr & netmask == netaddr & netmask en lugar de ipaddr & netmask == netmask.

También reemplazo ((2L<<int(bits)-1) - 1) con ((1L << int(bits)) - 1), ya que este último parece más comprensible.

2
Debanshu Kundu 25 abr. 2014 a las 10:15

La respuesta aceptada no funciona ... lo que me está enojando. La máscara está al revés y no funciona con ningún bit que no sea un simple bloque de 8 bits (por ejemplo, / 24). Adapte la respuesta, y funciona muy bien.

    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

Aquí hay una función que devuelve una cadena binaria punteada para ayudar a visualizar el enmascaramiento ... algo así como la salida ipcalc.

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

Eg:

screen shot of python

5
jorb 5 jun. 2015 a las 21:38

No soy fanático de usar módulos cuando no son necesarios. Este trabajo solo requiere matemática simple, así que aquí está mi función simple para hacer el trabajo:

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

Luego para usarlo:

>>> 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) 

Eso es todo, esto es mucho más rápido que las soluciones anteriores con los módulos incluidos.

6
Landon 29 abr. 2015 a las 17:41

Aquí está la solución usando el paquete netaddr

from netaddr import IPNetwork, IPAddress


def network_has_ip(network, ip):

    if not isinstance(network, IPNetwork):
        raise Exception("network parameter must be {0} instance".format(IPNetwork.__name__))

    if not isinstance(ip, IPAddress):
        raise Exception("ip parameter must be {0} instance".format(IPAddress.__name__))

    return (network.cidr.ip.value & network.netmask.value) == (ip.value & network.netmask.value)
0
chasank 10 feb. 2020 a las 10:41

No sé nada de la biblioteca estándar, pero PySubnetTree es una biblioteca de Python que hará subred pareo.

1
RichieHindle 5 mar. 2018 a las 21:04

Utilizando ipaddress (en stdlib desde 3.3, en PyPi para 2.6 / 2.7):

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

Si desea evaluar un lote de direcciones IP de esta manera, probablemente desee calcular la máscara de red por adelantado, como

n = ipaddress.ip_network('192.0.0.0/16')
netw = int(n.network_address)
mask = int(n.netmask)

Luego, para cada dirección, calcule la representación binaria con uno de

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

Finalmente, simplemente puede verificar:

in_network = (a & mask) == netw
124
phihag 15 oct. 2014 a las 14:11

Confiar en el módulo "struct" puede causar problemas con la endianidad y los tamaños de letra, y simplemente no es necesario. Tampoco es socket.inet_aton (). Python funciona muy bien con direcciones IP de cuatro puntos:

def ip_to_u32(ip):
  return int(''.join('%02x' % int(d) for d in ip.split('.')), 16)

Necesito hacer una coincidencia de IP en cada llamada de socket (), contra un conjunto completo de redes de origen permitidas, por lo que precalculo máscaras y redes, como enteros:

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

Entonces puedo ver rápidamente si una determinada IP está dentro de una de esas redes:

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

No se necesitan importaciones de módulos, y el código es muy rápido en la coincidencia.

4
gstein 6 nov. 2017 a las 06:47

En relación con todo lo anterior, creo que socket.inet_aton () devuelve bytes en orden de red, por lo que la forma correcta de descomprimirlos es probablemente

struct.unpack('!L', ... )
1
Quentin Stafford-Fraser 11 ene. 2013 a las 14:39