Actualización de la clase IP4Mask y Class_IP4 con nuevos métodos para conversiones, análisis de red y utilidades. Se añaden funcionalidades para manejar direcciones IP, incluyendo operaciones aritméticas y análisis de relaciones entre redes. Se mejora la documentación y se ajustan los métodos de autocompletado.
This commit is contained in:
parent
036eeb4291
commit
03964d2ff5
603
ip4_type.py
603
ip4_type.py
|
@ -3,7 +3,7 @@ Clase híbrida para direcciones IPv4
|
|||
"""
|
||||
from class_base import ClassBase
|
||||
from sympy_Base import SympyClassBase
|
||||
from typing import Optional, Union
|
||||
from typing import Optional, Union, List, Tuple
|
||||
import re
|
||||
import sympy
|
||||
|
||||
|
@ -99,26 +99,187 @@ class IP4Mask(ClassBase):
|
|||
|
||||
@staticmethod
|
||||
def Helper(input_str):
|
||||
if re.match(r"^\s*IP4Mask\\b", input_str, re.IGNORECASE):
|
||||
return 'Máscara: IP4Mask[24], IP4Mask[255.255.255.0]. Funciones: get_prefix_int(), get_netmask_str(), hosts_count()'
|
||||
if re.match(r"^\s*IP4Mask\b", input_str, re.IGNORECASE):
|
||||
return 'Máscara: IP4Mask[24], IP4Mask[255.255.255.0]. Funciones: get_prefix_int(), get_mask_str(), hosts_count(), to_hex(), to_binary()'
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def PopupFunctionList():
|
||||
return [
|
||||
("get_prefix_int", "Obtiene prefijo CIDR (ej: 24)"),
|
||||
("get_netmask_str", "Obtiene máscara como string (ej: 255.255.255.0)"),
|
||||
("get_mask_str", "Obtiene máscara como string (ej: 255.255.255.0)"),
|
||||
("to_dotted", "Alias para get_mask_str()"),
|
||||
("to_prefix", "Alias para get_prefix_int()"),
|
||||
("hosts_count", "Número de hosts disponibles"),
|
||||
("total_addresses", "Total de direcciones (incluyendo red/broadcast)"),
|
||||
("to_decimal", "Convierte a entero decimal"),
|
||||
("to_hex", "Convierte a hexadecimal"),
|
||||
("to_binary", "Representación binaria"),
|
||||
("to_wildcard", "Máscara wildcard (inversa)"),
|
||||
("bit_representation", "Representación visual con bits"),
|
||||
("octet_breakdown", "Desglose por octetos"),
|
||||
("is_more_specific", "¿Es más específica que otra máscara?"),
|
||||
("is_subnet_of", "¿Es subred de otra máscara?"),
|
||||
("common_supernet", "Máscara común que contiene ambas"),
|
||||
("next_smaller", "Siguiente máscara más pequeña"),
|
||||
("next_larger", "Siguiente máscara más grande"),
|
||||
("split", "Dividir en dos subredes"),
|
||||
("is_valid", "¿Es una máscara válida?"),
|
||||
("is_host_mask", "¿Es máscara de host (/32)?"),
|
||||
("is_network_mask", "¿Es máscara de red (/0)?"),
|
||||
("efficiency", "Eficiencia de uso de direcciones"),
|
||||
("to_sympy", "Convierte a expresión SymPy para álgebra"),
|
||||
]
|
||||
|
||||
def hosts_count(self):
|
||||
"""Número de hosts disponibles"""
|
||||
return 2**(32 - self._prefix) - 2 if self._prefix < 31 else 0
|
||||
# ========== CONVERSIONES Y REPRESENTACIONES ==========
|
||||
|
||||
def to_decimal(self) -> int:
|
||||
"""Convierte a entero decimal"""
|
||||
return self._mask_int
|
||||
|
||||
def to_hex(self) -> str:
|
||||
"""Convierte a hexadecimal"""
|
||||
return f"0x{self._mask_int:08X}"
|
||||
|
||||
def to_binary(self) -> str:
|
||||
"""Representación binaria de 32 bits"""
|
||||
return f"{self._mask_int:032b}"
|
||||
|
||||
def to_dotted(self) -> str:
|
||||
"""Alias para get_mask_str()"""
|
||||
return self.get_mask_str()
|
||||
|
||||
def to_prefix(self) -> int:
|
||||
"""Alias para get_prefix_int()"""
|
||||
return self.get_prefix_int()
|
||||
|
||||
def to_wildcard(self) -> str:
|
||||
"""Máscara wildcard (inversa)"""
|
||||
wildcard_int = self._mask_int ^ 0xFFFFFFFF
|
||||
return f"{(wildcard_int >> 24) & 0xFF}.{(wildcard_int >> 16) & 0xFF}.{(wildcard_int >> 8) & 0xFF}.{wildcard_int & 0xFF}"
|
||||
|
||||
def bit_representation(self) -> str:
|
||||
"""Representación visual con bits"""
|
||||
binary = self.to_binary()
|
||||
return f"{binary[:8]}.{binary[8:16]}.{binary[16:24]}.{binary[24:32]}".replace('1', '█').replace('0', '.')
|
||||
|
||||
def octet_breakdown(self) -> dict:
|
||||
"""Desglose por octetos"""
|
||||
return {
|
||||
"oct1": (self._mask_int >> 24) & 0xFF,
|
||||
"oct2": (self._mask_int >> 16) & 0xFF,
|
||||
"oct3": (self._mask_int >> 8) & 0xFF,
|
||||
"oct4": self._mask_int & 0xFF
|
||||
}
|
||||
|
||||
# ========== CÁLCULOS DE RED ==========
|
||||
|
||||
def hosts_count(self) -> int:
|
||||
"""Número de hosts disponibles (excluyendo red/broadcast)"""
|
||||
if self._prefix >= 31:
|
||||
return 0
|
||||
return (2 ** (32 - self._prefix)) - 2
|
||||
|
||||
def total_addresses(self) -> int:
|
||||
"""Total de direcciones (incluyendo red/broadcast)"""
|
||||
return 2 ** (32 - self._prefix)
|
||||
|
||||
def subnet_count(self, base_mask: 'IP4Mask') -> int:
|
||||
"""¿Cuántas subredes de este tamaño caben en la máscara base?"""
|
||||
if not isinstance(base_mask, IP4Mask):
|
||||
raise TypeError("base_mask must be IP4Mask instance")
|
||||
if self._prefix < base_mask._prefix:
|
||||
raise ValueError("Target mask must be more specific than base mask")
|
||||
return 2 ** (self._prefix - base_mask._prefix)
|
||||
|
||||
def efficiency(self) -> float:
|
||||
"""Eficiencia de uso de direcciones (0-1)"""
|
||||
if self._prefix >= 31:
|
||||
return 1.0 if self._prefix == 32 else 0.0
|
||||
return self.hosts_count() / self.total_addresses()
|
||||
|
||||
# ========== COMPARACIONES Y ANÁLISIS ==========
|
||||
|
||||
def is_more_specific(self, other_mask: 'IP4Mask') -> bool:
|
||||
"""¿Es más específica (mayor prefijo) que otra máscara?"""
|
||||
if not isinstance(other_mask, IP4Mask):
|
||||
raise TypeError("other_mask must be IP4Mask instance")
|
||||
return self._prefix > other_mask._prefix
|
||||
|
||||
def is_subnet_of(self, other_mask: 'IP4Mask') -> bool:
|
||||
"""¿Es subred (más específica) de otra máscara?"""
|
||||
return self.is_more_specific(other_mask)
|
||||
|
||||
def common_supernet(self, other_mask: 'IP4Mask') -> 'IP4Mask':
|
||||
"""Máscara común que contiene ambas"""
|
||||
if not isinstance(other_mask, IP4Mask):
|
||||
raise TypeError("other_mask must be IP4Mask instance")
|
||||
common_prefix = min(self._prefix, other_mask._prefix)
|
||||
return IP4Mask(common_prefix)
|
||||
|
||||
# ========== UTILIDADES DE PROGRAMADOR ==========
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
"""¿Es una máscara válida?"""
|
||||
return 0 <= self._prefix <= 32
|
||||
|
||||
def is_host_mask(self) -> bool:
|
||||
"""¿Es máscara de host (/32)?"""
|
||||
return self._prefix == 32
|
||||
|
||||
def is_network_mask(self) -> bool:
|
||||
"""¿Es máscara de red (/0)?"""
|
||||
return self._prefix == 0
|
||||
|
||||
def next_smaller(self) -> 'IP4Mask':
|
||||
"""Siguiente máscara más pequeña (más específica)"""
|
||||
if self._prefix >= 32:
|
||||
raise ValueError("Cannot make /32 more specific")
|
||||
return IP4Mask(self._prefix + 1)
|
||||
|
||||
def next_larger(self) -> 'IP4Mask':
|
||||
"""Siguiente máscara más grande (menos específica)"""
|
||||
if self._prefix <= 0:
|
||||
raise ValueError("Cannot make /0 less specific")
|
||||
return IP4Mask(self._prefix - 1)
|
||||
|
||||
def split(self) -> Tuple['IP4Mask', 'IP4Mask']:
|
||||
"""Divide en dos subredes del siguiente tamaño"""
|
||||
if self._prefix >= 32:
|
||||
raise ValueError("Cannot split /32 mask")
|
||||
smaller_mask = self.next_smaller()
|
||||
return (smaller_mask, smaller_mask)
|
||||
|
||||
def to_sympy(self):
|
||||
"""Convierte a SymPy cuando se necesite álgebra"""
|
||||
return sympy.sympify(self._prefix)
|
||||
|
||||
def hosts_expression(self):
|
||||
"""Expresión SymPy para número de hosts"""
|
||||
return 2**(32 - self._prefix)
|
||||
|
||||
# ========== MÉTODOS ESTÁTICOS ==========
|
||||
|
||||
@staticmethod
|
||||
def optimal_for_hosts(host_count: int) -> 'IP4Mask':
|
||||
"""¿Qué máscara para N hosts?"""
|
||||
if host_count <= 0:
|
||||
return IP4Mask(32)
|
||||
if host_count == 1:
|
||||
return IP4Mask(31)
|
||||
|
||||
# Encontrar el menor prefijo que acomode host_count + 2 (red y broadcast)
|
||||
total_needed = host_count + 2
|
||||
prefix = 32
|
||||
while 2**(32 - prefix) < total_needed and prefix > 0:
|
||||
prefix -= 1
|
||||
return IP4Mask(prefix)
|
||||
|
||||
@staticmethod
|
||||
def compare_efficiency(mask_list: List['IP4Mask']) -> List[Tuple['IP4Mask', float]]:
|
||||
"""Eficiencia de uso de cada máscara"""
|
||||
return [(mask, mask.efficiency()) for mask in mask_list]
|
||||
|
||||
|
||||
class Class_IP4(SympyClassBase):
|
||||
"""Clase híbrida para direcciones IPv4"""
|
||||
|
@ -229,9 +390,8 @@ class Class_IP4(SympyClassBase):
|
|||
|
||||
@staticmethod
|
||||
def Helper(input_str):
|
||||
if re.match(r"^\s*IP4\\b", input_str, re.IGNORECASE):
|
||||
return ('Ej: IP4[192.168.1.1/24], IP4[10.0.0.1;8], IP4[172.16.0.5;255.255.0.0]\\n'
|
||||
'Funciones: NetworkAddress(), BroadcastAddress(), Nodes(), get_netmask_str(), get_prefix_length(), mask()')
|
||||
if re.match(r"^\s*IP4\b", input_str, re.IGNORECASE):
|
||||
return ('Ej: IP4[192.168.1.1/24], IP4[10.0.0.1;8]. Funciones: NetworkAddress(), BroadcastAddress(), add_hosts(), next_ip(), mask()')
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
@ -241,10 +401,42 @@ class Class_IP4(SympyClassBase):
|
|||
("BroadcastAddress", "Dirección de broadcast"),
|
||||
("Nodes", "Hosts disponibles"),
|
||||
("mask", "Objeto máscara con sus métodos"),
|
||||
("get_netmask_str", "Obtiene la máscara de red (ej: 255.255.255.0)"),
|
||||
("get_prefix_length", "Obtiene la longitud del prefijo CIDR (ej: 24)"),
|
||||
("add_hosts", "Sumar N hosts a la dirección"),
|
||||
("subtract_hosts", "Restar N hosts a la dirección"),
|
||||
("next_ip", "Siguiente dirección IP"),
|
||||
("previous_ip", "Dirección IP anterior"),
|
||||
("first_host", "Primera IP asignable"),
|
||||
("last_host", "Última IP asignable"),
|
||||
("is_network_address", "¿Es dirección de red?"),
|
||||
("is_broadcast_address", "¿Es dirección de broadcast?"),
|
||||
("is_host_address", "¿Es dirección de host válida?"),
|
||||
("is_first_host", "¿Es la primera IP asignable?"),
|
||||
("is_last_host", "¿Es la última IP asignable?"),
|
||||
("to_integer", "Convierte a entero"),
|
||||
("to_hex", "Convierte a hexadecimal"),
|
||||
("to_binary", "Convierte a binario"),
|
||||
("to_octets", "Convierte a lista de octetos"),
|
||||
("bit_representation", "Representación visual de bits"),
|
||||
("in_same_network", "¿Está en la misma red?"),
|
||||
("is_subnet_of", "¿Está en esa subred?"),
|
||||
("distance_to", "Distancia en direcciones"),
|
||||
("hamming_distance", "Distancia de Hamming"),
|
||||
("parent_network", "Red padre"),
|
||||
("child_networks", "Subredes hijas"),
|
||||
("sibling_networks", "Redes hermanas"),
|
||||
("is_private", "¿Es IP privada?"),
|
||||
("is_loopback", "¿Es loopback?"),
|
||||
("is_multicast", "¿Es multicast?"),
|
||||
("is_linklocal", "¿Es link-local?"),
|
||||
("ip_class", "Clase de IP (A/B/C/D/E)"),
|
||||
("reverse_dns_zone", "Zona DNS reversa"),
|
||||
("ptr_record", "Registro PTR"),
|
||||
("get_netmask_str", "Máscara como string"),
|
||||
("get_prefix_length", "Longitud del prefijo CIDR"),
|
||||
]
|
||||
|
||||
# ========== MÉTODOS BÁSICOS EXISTENTES ==========
|
||||
|
||||
def get_netmask_str(self) -> Optional[str]:
|
||||
return self._mask_obj.get_mask_str() if self._mask_obj else None
|
||||
|
||||
|
@ -298,4 +490,389 @@ class Class_IP4(SympyClassBase):
|
|||
"""Retorna objeto máscara para autocompletado"""
|
||||
if not self._mask_obj:
|
||||
raise ValueError("No mask defined for this IP4 object.")
|
||||
return self._mask_obj
|
||||
return self._mask_obj
|
||||
|
||||
# ========== ARITMÉTICA DE DIRECCIONES ==========
|
||||
|
||||
def add_hosts(self, n: int) -> 'Class_IP4':
|
||||
"""Suma N hosts a la dirección"""
|
||||
new_ip_int = (self._ip_int + n) & 0xFFFFFFFF
|
||||
new_parts = [
|
||||
(new_ip_int >> 24) & 0xFF,
|
||||
(new_ip_int >> 16) & 0xFF,
|
||||
(new_ip_int >> 8) & 0xFF,
|
||||
new_ip_int & 0xFF
|
||||
]
|
||||
new_ip_str = '.'.join(str(x) for x in new_parts)
|
||||
return Class_IP4(new_ip_str, self._mask_obj)
|
||||
|
||||
def subtract_hosts(self, n: int) -> 'Class_IP4':
|
||||
"""Resta N hosts a la dirección"""
|
||||
return self.add_hosts(-n)
|
||||
|
||||
def next_ip(self) -> 'Class_IP4':
|
||||
"""Siguiente dirección IP"""
|
||||
return self.add_hosts(1)
|
||||
|
||||
def previous_ip(self) -> 'Class_IP4':
|
||||
"""Dirección IP anterior"""
|
||||
return self.add_hosts(-1)
|
||||
|
||||
def increment_octet(self, octet_num: int, value: int) -> 'Class_IP4':
|
||||
"""Incrementa un octeto específico (1-4)"""
|
||||
if not 1 <= octet_num <= 4:
|
||||
raise ValueError("octet_num must be between 1 and 4")
|
||||
|
||||
octets = self.to_octets()
|
||||
octets[octet_num - 1] = (octets[octet_num - 1] + value) % 256
|
||||
new_ip_str = '.'.join(str(x) for x in octets)
|
||||
return Class_IP4(new_ip_str, self._mask_obj)
|
||||
|
||||
def next_network(self) -> 'Class_IP4':
|
||||
"""Próxima red del mismo tamaño"""
|
||||
if not self._mask_obj:
|
||||
raise ValueError("No mask defined for network calculation")
|
||||
|
||||
network = self.NetworkAddress()
|
||||
network_size = 2 ** (32 - self._mask_obj.get_prefix_int())
|
||||
next_net = network.add_hosts(network_size)
|
||||
return next_net.NetworkAddress()
|
||||
|
||||
def previous_network(self) -> 'Class_IP4':
|
||||
"""Red anterior del mismo tamaño"""
|
||||
if not self._mask_obj:
|
||||
raise ValueError("No mask defined for network calculation")
|
||||
|
||||
network = self.NetworkAddress()
|
||||
network_size = 2 ** (32 - self._mask_obj.get_prefix_int())
|
||||
prev_net = network.subtract_hosts(network_size)
|
||||
return prev_net.NetworkAddress()
|
||||
|
||||
# ========== ANÁLISIS DE RED ==========
|
||||
|
||||
def is_network_address(self) -> bool:
|
||||
"""¿Es la dirección de red?"""
|
||||
if not self._mask_obj:
|
||||
return False
|
||||
return self._ip_int == self.NetworkAddress()._ip_int
|
||||
|
||||
def is_broadcast_address(self) -> bool:
|
||||
"""¿Es la dirección de broadcast?"""
|
||||
if not self._mask_obj:
|
||||
return False
|
||||
return self._ip_int == self.BroadcastAddress()._ip_int
|
||||
|
||||
def is_host_address(self) -> bool:
|
||||
"""¿Es una dirección de host válida?"""
|
||||
if not self._mask_obj:
|
||||
return True # Sin máscara, asumimos que es host
|
||||
return not (self.is_network_address() or self.is_broadcast_address())
|
||||
|
||||
def is_first_host(self) -> bool:
|
||||
"""¿Es la primera IP asignable?"""
|
||||
if not self._mask_obj:
|
||||
return False
|
||||
return self._ip_int == self.first_host()._ip_int
|
||||
|
||||
def is_last_host(self) -> bool:
|
||||
"""¿Es la última IP asignable?"""
|
||||
if not self._mask_obj:
|
||||
return False
|
||||
return self._ip_int == self.last_host()._ip_int
|
||||
|
||||
def first_host(self) -> 'Class_IP4':
|
||||
"""Primera IP asignable"""
|
||||
if not self._mask_obj:
|
||||
raise ValueError("No mask defined")
|
||||
if self._mask_obj.get_prefix_int() >= 31:
|
||||
return self # En /31 y /32, la IP es el host
|
||||
return self.NetworkAddress().add_hosts(1)
|
||||
|
||||
def last_host(self) -> 'Class_IP4':
|
||||
"""Última IP asignable"""
|
||||
if not self._mask_obj:
|
||||
raise ValueError("No mask defined")
|
||||
if self._mask_obj.get_prefix_int() >= 31:
|
||||
return self # En /31 y /32, la IP es el host
|
||||
return self.BroadcastAddress().subtract_hosts(1)
|
||||
|
||||
def network_range(self) -> Tuple['Class_IP4', 'Class_IP4']:
|
||||
"""Rango de red (primera, última) como tupla"""
|
||||
return (self.first_host(), self.last_host())
|
||||
|
||||
def host_position(self) -> int:
|
||||
"""¿En qué posición está dentro de la red?"""
|
||||
if not self._mask_obj:
|
||||
raise ValueError("No mask defined")
|
||||
network = self.NetworkAddress()
|
||||
return self._ip_int - network._ip_int
|
||||
|
||||
# ========== CONVERSIONES AVANZADAS ==========
|
||||
|
||||
def to_integer(self) -> int:
|
||||
"""Convierte a entero"""
|
||||
return self._ip_int
|
||||
|
||||
def to_hex(self) -> str:
|
||||
"""Convierte a hexadecimal"""
|
||||
return f"0x{self._ip_int:08X}"
|
||||
|
||||
def to_binary(self) -> str:
|
||||
"""Convierte a binario"""
|
||||
return f"{self._ip_int:032b}"
|
||||
|
||||
def to_octets(self) -> List[int]:
|
||||
"""Convierte a lista de octetos"""
|
||||
return [
|
||||
(self._ip_int >> 24) & 0xFF,
|
||||
(self._ip_int >> 16) & 0xFF,
|
||||
(self._ip_int >> 8) & 0xFF,
|
||||
self._ip_int & 0xFF
|
||||
]
|
||||
|
||||
def bit_representation(self) -> str:
|
||||
"""Representación visual de bits"""
|
||||
binary = self.to_binary()
|
||||
return f"{binary[:8]}.{binary[8:16]}.{binary[16:24]}.{binary[24:32]}".replace('1', '█').replace('0', '.')
|
||||
|
||||
def hamming_distance(self, other_ip: 'Class_IP4') -> int:
|
||||
"""Distancia de Hamming (diferencia en bits)"""
|
||||
if not isinstance(other_ip, Class_IP4):
|
||||
raise TypeError("other_ip must be Class_IP4 instance")
|
||||
xor_result = self._ip_int ^ other_ip._ip_int
|
||||
return bin(xor_result).count('1')
|
||||
|
||||
# ========== ANÁLISIS DE RELACIONES ==========
|
||||
|
||||
def in_same_network(self, other_ip: 'Class_IP4') -> bool:
|
||||
"""¿Están en la misma red?"""
|
||||
if not isinstance(other_ip, Class_IP4):
|
||||
raise TypeError("other_ip must be Class_IP4 instance")
|
||||
if not self._mask_obj:
|
||||
raise ValueError("No mask defined for network comparison")
|
||||
|
||||
mask_int = IP4Mask._prefix_to_mask_int(self._mask_obj.get_prefix_int())
|
||||
return (self._ip_int & mask_int) == (other_ip._ip_int & mask_int)
|
||||
|
||||
def is_subnet_of(self, network_ip: 'Class_IP4') -> bool:
|
||||
"""¿Esta IP está en esa red?"""
|
||||
if not isinstance(network_ip, Class_IP4):
|
||||
raise TypeError("network_ip must be Class_IP4 instance")
|
||||
if not network_ip._mask_obj:
|
||||
raise ValueError("network_ip must have a mask defined")
|
||||
|
||||
mask_int = IP4Mask._prefix_to_mask_int(network_ip._mask_obj.get_prefix_int())
|
||||
network_addr = network_ip._ip_int & mask_int
|
||||
this_network = self._ip_int & mask_int
|
||||
return network_addr == this_network
|
||||
|
||||
def distance_to(self, other_ip: 'Class_IP4') -> int:
|
||||
"""Distancia en direcciones"""
|
||||
if not isinstance(other_ip, Class_IP4):
|
||||
raise TypeError("other_ip must be Class_IP4 instance")
|
||||
return abs(self._ip_int - other_ip._ip_int)
|
||||
|
||||
def parent_network(self, larger_mask: IP4Mask) -> 'Class_IP4':
|
||||
"""Red padre con máscara mayor"""
|
||||
if not isinstance(larger_mask, IP4Mask):
|
||||
raise TypeError("larger_mask must be IP4Mask instance")
|
||||
if self._mask_obj and larger_mask.get_prefix_int() >= self._mask_obj.get_prefix_int():
|
||||
raise ValueError("larger_mask must be less specific")
|
||||
|
||||
mask_int = IP4Mask._prefix_to_mask_int(larger_mask.get_prefix_int())
|
||||
parent_int = self._ip_int & mask_int
|
||||
|
||||
parts = [
|
||||
(parent_int >> 24) & 0xFF,
|
||||
(parent_int >> 16) & 0xFF,
|
||||
(parent_int >> 8) & 0xFF,
|
||||
parent_int & 0xFF
|
||||
]
|
||||
parent_str = '.'.join(str(x) for x in parts)
|
||||
return Class_IP4(parent_str, larger_mask)
|
||||
|
||||
def child_networks(self, smaller_mask: IP4Mask) -> List['Class_IP4']:
|
||||
"""Subredes hijas"""
|
||||
if not isinstance(smaller_mask, IP4Mask):
|
||||
raise TypeError("smaller_mask must be IP4Mask instance")
|
||||
if not self._mask_obj:
|
||||
raise ValueError("IP must have a mask defined")
|
||||
if smaller_mask.get_prefix_int() <= self._mask_obj.get_prefix_int():
|
||||
raise ValueError("smaller_mask must be more specific")
|
||||
|
||||
network = self.NetworkAddress()
|
||||
subnet_count = 2 ** (smaller_mask.get_prefix_int() - self._mask_obj.get_prefix_int())
|
||||
subnet_size = 2 ** (32 - smaller_mask.get_prefix_int())
|
||||
|
||||
subnets = []
|
||||
current_ip = network._ip_int
|
||||
for i in range(subnet_count):
|
||||
parts = [
|
||||
(current_ip >> 24) & 0xFF,
|
||||
(current_ip >> 16) & 0xFF,
|
||||
(current_ip >> 8) & 0xFF,
|
||||
current_ip & 0xFF
|
||||
]
|
||||
subnet_str = '.'.join(str(x) for x in parts)
|
||||
subnets.append(Class_IP4(subnet_str, smaller_mask))
|
||||
current_ip += subnet_size
|
||||
|
||||
return subnets
|
||||
|
||||
def sibling_networks(self) -> List['Class_IP4']:
|
||||
"""Redes hermanas del mismo tamaño"""
|
||||
if not self._mask_obj:
|
||||
raise ValueError("IP must have a mask defined")
|
||||
if self._mask_obj.get_prefix_int() == 0:
|
||||
return [self] # /0 no tiene hermanas
|
||||
|
||||
parent_mask = IP4Mask(self._mask_obj.get_prefix_int() - 1)
|
||||
parent = self.parent_network(parent_mask)
|
||||
return parent.child_networks(self._mask_obj)
|
||||
|
||||
# ========== CLASIFICACIÓN DE IPS ==========
|
||||
|
||||
def is_private(self) -> bool:
|
||||
"""¿Es IP privada? (RFC 1918)"""
|
||||
# 10.0.0.0/8
|
||||
if (self._ip_int & 0xFF000000) == 0x0A000000:
|
||||
return True
|
||||
# 172.16.0.0/12
|
||||
if (self._ip_int & 0xFFF00000) == 0xAC100000:
|
||||
return True
|
||||
# 192.168.0.0/16
|
||||
if (self._ip_int & 0xFFFF0000) == 0xC0A80000:
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_loopback(self) -> bool:
|
||||
"""¿Es loopback? (127.0.0.0/8)"""
|
||||
return (self._ip_int & 0xFF000000) == 0x7F000000
|
||||
|
||||
def is_multicast(self) -> bool:
|
||||
"""¿Es multicast? (224.0.0.0/4)"""
|
||||
return (self._ip_int & 0xF0000000) == 0xE0000000
|
||||
|
||||
def is_linklocal(self) -> bool:
|
||||
"""¿Es link-local? (169.254.0.0/16)"""
|
||||
return (self._ip_int & 0xFFFF0000) == 0xA9FE0000
|
||||
|
||||
def ip_class(self) -> str:
|
||||
"""Clase de IP (A/B/C/D/E) - legacy pero útil"""
|
||||
first_octet = (self._ip_int >> 24) & 0xFF
|
||||
if first_octet < 128:
|
||||
return "A"
|
||||
elif first_octet < 192:
|
||||
return "B"
|
||||
elif first_octet < 224:
|
||||
return "C"
|
||||
elif first_octet < 240:
|
||||
return "D" # Multicast
|
||||
else:
|
||||
return "E" # Experimental
|
||||
|
||||
# ========== UTILIDADES DNS ==========
|
||||
|
||||
def reverse_dns_zone(self) -> str:
|
||||
"""Zona DNS reversa"""
|
||||
if not self._mask_obj:
|
||||
raise ValueError("No mask defined for reverse DNS zone")
|
||||
|
||||
prefix = self._mask_obj.get_prefix_int()
|
||||
octets = self.to_octets()
|
||||
|
||||
if prefix >= 24:
|
||||
return f"{octets[2]}.{octets[1]}.{octets[0]}.in-addr.arpa"
|
||||
elif prefix >= 16:
|
||||
return f"{octets[1]}.{octets[0]}.in-addr.arpa"
|
||||
elif prefix >= 8:
|
||||
return f"{octets[0]}.in-addr.arpa"
|
||||
else:
|
||||
return "in-addr.arpa"
|
||||
|
||||
def ptr_record(self) -> str:
|
||||
"""Registro PTR"""
|
||||
octets = self.to_octets()
|
||||
return f"{octets[3]}.{octets[2]}.{octets[1]}.{octets[0]}.in-addr.arpa"
|
||||
|
||||
def generate_host_list(self, count: int) -> List['Class_IP4']:
|
||||
"""Genera lista de IPs consecutivas"""
|
||||
if count <= 0:
|
||||
return []
|
||||
|
||||
result = []
|
||||
current = self
|
||||
for i in range(count):
|
||||
result.append(current)
|
||||
current = current.next_ip()
|
||||
return result
|
||||
|
||||
# ========== MÉTODOS ESTÁTICOS ==========
|
||||
|
||||
@staticmethod
|
||||
def find_common_network(ip_list: List['Class_IP4']) -> 'Class_IP4':
|
||||
"""Menor red que contiene todas las IPs"""
|
||||
if not ip_list:
|
||||
raise ValueError("ip_list cannot be empty")
|
||||
|
||||
if len(ip_list) == 1:
|
||||
return ip_list[0]
|
||||
|
||||
# Encontrar el prefijo común más largo
|
||||
min_ip = min(ip._ip_int for ip in ip_list)
|
||||
max_ip = max(ip._ip_int for ip in ip_list)
|
||||
|
||||
# XOR para encontrar bits diferentes
|
||||
xor_result = min_ip ^ max_ip
|
||||
|
||||
# Contar bits comunes desde la izquierda
|
||||
common_bits = 0
|
||||
for i in range(32):
|
||||
if (xor_result >> (31 - i)) & 1:
|
||||
break
|
||||
common_bits += 1
|
||||
|
||||
# Crear la red común
|
||||
mask = IP4Mask(common_bits)
|
||||
network_int = min_ip & IP4Mask._prefix_to_mask_int(common_bits)
|
||||
|
||||
parts = [
|
||||
(network_int >> 24) & 0xFF,
|
||||
(network_int >> 16) & 0xFF,
|
||||
(network_int >> 8) & 0xFF,
|
||||
network_int & 0xFF
|
||||
]
|
||||
network_str = '.'.join(str(x) for x in parts)
|
||||
return Class_IP4(network_str, mask)
|
||||
|
||||
@staticmethod
|
||||
def detect_overlaps(network_list: List['Class_IP4']) -> List[Tuple['Class_IP4', 'Class_IP4']]:
|
||||
"""Encontrar redes solapadas"""
|
||||
overlaps = []
|
||||
for i, net1 in enumerate(network_list):
|
||||
for net2 in network_list[i+1:]:
|
||||
if net1.is_subnet_of(net2) or net2.is_subnet_of(net1):
|
||||
overlaps.append((net1, net2))
|
||||
return overlaps
|
||||
|
||||
@staticmethod
|
||||
def analyze_distribution(ip_list: List['Class_IP4']) -> dict:
|
||||
"""Análisis estadístico de distribución"""
|
||||
if not ip_list:
|
||||
return {}
|
||||
|
||||
classes = {"A": 0, "B": 0, "C": 0, "D": 0, "E": 0}
|
||||
private_count = 0
|
||||
|
||||
for ip in ip_list:
|
||||
classes[ip.ip_class()] += 1
|
||||
if ip.is_private():
|
||||
private_count += 1
|
||||
|
||||
return {
|
||||
"total": len(ip_list),
|
||||
"classes": classes,
|
||||
"private_count": private_count,
|
||||
"public_count": len(ip_list) - private_count,
|
||||
"common_network": Class_IP4.find_common_network(ip_list)
|
||||
}
|
Loading…
Reference in New Issue