0%
Calcular o intervalo de endereços de uma rede CIDR em Python

Calcular o intervalo de endereços de uma rede CIDR em Python

Aprenda a determinar o número de endereços IP e o intervalo completo de uma rede CIDR com Python. Tutorial detalhado usando o módulo ipaddress, com exemplos práticos.

I

InSkillCoach

· min

Calcular o intervalo de endereços de uma rede CIDR em Python

CIDR (Classless Inter-Domain Routing) é um método de alocação de endereços IP e de roteamento IP que substituiu o sistema anterior de classes de endereços de rede. Entender como calcular o intervalo de endereços IP de uma rede CIDR é essencial para a gestão de redes, a segurança da informação e a administração de sistemas.

Neste guia, vamos explorar como usar Python para determinar de forma rápida e eficiente o intervalo de endereços IP de uma rede CIDR.

O que é a notação CIDR?

A notação CIDR é composta por duas partes:

  • Um endereço IP (IPv4 ou IPv6)
  • Um sufixo que indica o tamanho do prefixo de rede (a máscara de sub-rede)

Por exemplo, 192.168.1.0/24 indica:

  • 192.168.1.0 é o endereço de rede
  • /24 significa que os 24 primeiros bits são fixos (o que corresponde a uma máscara de sub-rede 255.255.255.0)
  • Essa rede compreende todos os endereços de 192.168.1.0 a 192.168.1.255

Usando o módulo ipaddress

O Python inclui um poderoso módulo chamado ipaddress em sua biblioteca padrão (desde o Python 3.3), que facilita bastante os cálculos relacionados a endereços IP e redes.

Instalação

Nenhuma instalação é necessária, pois o módulo ipaddress faz parte da biblioteca padrão do Python:

import ipaddress

Determinar o intervalo de endereços de uma rede CIDR

Veja como calcular o intervalo de endereços de uma rede CIDR:

import ipaddress

def get_cidr_range(cidr_notation):
    """
    Calcula o intervalo de endereços IP de uma rede CIDR.
    
    Args:
        cidr_notation (str): Notação CIDR (ex: '192.168.1.0/24')
        
    Returns:
        dict: Dicionário contendo as informações sobre a rede
    """
    try:
        network = ipaddress.ip_network(cidr_notation, strict=False)
        
        # Coleta das informações de rede
        network_info = {
            'network_address': str(network.network_address),
            'broadcast_address': str(network.broadcast_address) if hasattr(network, 'broadcast_address') else None,
            'netmask': str(network.netmask),
            'prefix_length': network.prefixlen,
            'num_addresses': network.num_addresses,
            'first_usable_address': str(next(network.hosts())) if network.num_addresses > 1 else None,
            'last_usable_address': str(list(network.hosts())[-1]) if network.num_addresses > 1 else None,
            'address_range': f"{next(network.hosts())} - {list(network.hosts())[-1]}" if network.num_addresses > 1 else None,
            'is_private': network.is_private,
            'version': 'IPv4' if network.version == 4 else 'IPv6'
        }
        
        return network_info
        
    except ValueError as e:
        return {'error': str(e)}

# Exemplos de uso
cidr_examples = [
    '192.168.1.0/24',  # Rede local clássica (/24)
    '10.0.0.0/8',      # Rede privada de classe A
    '172.16.0.0/12',   # Rede privada de classe B
    '8.8.8.0/24',      # Rede pública (contendo o DNS do Google)
    '192.168.0.1/32',  # Um único endereço IP
    '2001:db8::/32',   # Rede IPv6
]

for cidr in cidr_examples:
    print(f"\nInformations pour le réseau {cidr}:")
    network_info = get_cidr_range(cidr)
    
    if 'error' in network_info:
        print(f"Erreur: {network_info['error']}")
        continue
    
    # Exibição das informações de rede
    for key, value in network_info.items():
        print(f"- {key}: {value}")

Resultado para uma rede IPv4 (/24)

Informations pour le réseau 192.168.1.0/24:
- network_address: 192.168.1.0
- broadcast_address: 192.168.1.255
- netmask: 255.255.255.0
- prefix_length: 24
- num_addresses: 256
- first_usable_address: 192.168.1.1
- last_usable_address: 192.168.1.254
- address_range: 192.168.1.1 - 192.168.1.254
- is_private: True
- version: IPv4

Entendendo o número de endereços IP em uma rede CIDR

O número de endereços IP em uma rede CIDR pode ser calculado com a fórmula: 2^(32-prefixo) para IPv4 e 2^(128-prefixo) para IPv6.

Aqui está uma função que explica essa relação:

def explain_cidr_size(prefix_length, ip_version=4):
    """
    Explica o tamanho de uma rede CIDR e calcula o número de endereços IP.
    
    Args:
        prefix_length (int): Comprimento do prefixo CIDR (ex: 24 em /24)
        ip_version (int): Versão do IP (4 ou 6)
        
    Returns:
        dict: Explicação e cálculos
    """
    if ip_version == 4:
        total_bits = 32
        max_prefix = 32
    else:  # IPv6
        total_bits = 128
        max_prefix = 128
    
    if prefix_length < 0 or prefix_length > max_prefix:
        return {'error': f'Préfixe invalide pour IPv{ip_version}'}
    
    host_bits = total_bits - prefix_length
    num_addresses = 2 ** host_bits
    
    if ip_version == 4:
        # Em IPv4, geralmente contamos os endereços utilizáveis
        # (excluindo o endereço de rede e o de broadcast)
        usable_addresses = max(0, num_addresses - 2) if prefix_length < 31 else num_addresses
    else:
        # Em IPv6, geralmente não se subtrai nenhum endereço
        usable_addresses = num_addresses
    
    explanation = {
        'prefix_length': prefix_length,
        'ip_version': f'IPv{ip_version}',
        'total_bits': total_bits,
        'network_bits': prefix_length,
        'host_bits': host_bits,
        'formula': f'2^{host_bits}',
        'total_addresses': num_addresses,
        'usable_addresses': usable_addresses
    }
    
    return explanation

# Tabela comparativa de diferentes prefixos CIDR
print("Tableau comparatif des tailles de réseaux CIDR pour IPv4:")
print("-" * 80)
print(f"{'Préfixe':<10}{'Masque':<15}{'Bits hôte':<15}{'Nombre d\'adresses':<20}{'Adresses utilisables':<20}")
print("-" * 80)

for prefix in [8, 16, 20, 24, 27, 28, 29, 30, 31, 32]:
    info = explain_cidr_size(prefix)
    
    # Cálculo da máscara de sub-rede
    if prefix == 32:
        mask = "255.255.255.255"
    else:
        mask_int = (0xffffffff >> (32 - prefix)) << (32 - prefix)
        mask = '.'.join([str((mask_int >> i) & 0xff) for i in [24, 16, 8, 0]])
    
    print(f"/{prefix:<9}{mask:<15}{info['host_bits']:<15}{info['total_addresses']:<20}{info['usable_addresses']:<20}")

Resultado da tabela comparativa

Tableau comparatif des tailles de réseaux CIDR pour IPv4:
--------------------------------------------------------------------------------
Préfixe   Masque          Bits hôte       Nombre d'adresses  Adresses utilisables
--------------------------------------------------------------------------------
/8        255.0.0.0       24              16777216           16777214          
/16       255.255.0.0     16              65536              65534             
/20       255.255.240.0   12              4096               4094              
/24       255.255.255.0   8               256                254               
/27       255.255.255.224 5               32                 30                
/28       255.255.255.240 4               16                 14                
/29       255.255.255.248 3               8                  6                 
/30       255.255.255.252 2               4                  2                 
/31       255.255.255.254 1               2                  2                 
/32       255.255.255.255 0               1                  1

Casos particulares de CIDR

Redes /31 - Ponto a ponto (RFC 3021)

A RFC 3021 introduziu o uso do prefixo /31 para enlaces ponto a ponto, permitindo economizar endereços IP:

def analyze_point_to_point_network(cidr_notation):
    """
    Analisa uma rede ponto a ponto com prefixo /31.
    
    Args:
        cidr_notation (str): Notação CIDR (deve ser /31)
        
    Returns:
        dict: Informações sobre o enlace ponto a ponto
    """
    try:
        network = ipaddress.ip_network(cidr_notation, strict=False)
        
        if network.prefixlen != 31:
            return {'error': 'Cette fonction est conçue pour les réseaux /31 uniquement'}
        
        # Em uma rede /31, os dois endereços são utilizáveis (RFC 3021)
        addresses = list(network.hosts())
        
        if len(addresses) != 2:
            return {'error': 'Nombre d\'adresses incorrect pour un réseau /31'}
        
        return {
            'network': str(network),
            'device_a': str(addresses[0]),
            'device_b': str(addresses[1]),
            'note': 'Configuration point à point (RFC 3021)'
        }
        
    except ValueError as e:
        return {'error': str(e)}

# Exemplo de enlace ponto a ponto
p2p_example = '192.168.1.0/31'
p2p_info = analyze_point_to_point_network(p2p_example)

print(f"\nLiaison point à point {p2p_example}:")
for key, value in p2p_info.items():
    print(f"- {key}: {value}")

Redes /32 - Endereço único

Para endereços individuais (como as interfaces de loopback):

def analyze_host_address(ip_address):
    """
    Analisa um endereço IP individual (prefixo /32).
    
    Args:
        ip_address (str): Endereço IP (com ou sem /32)
        
    Returns:
        dict: Informações sobre o endereço
    """
    try:
        # Garantir que o endereço tenha um prefixo /32
        if '/' not in ip_address:
            ip_address = f"{ip_address}/32"
        
        host = ipaddress.ip_interface(ip_address)
        
        if host.network.prefixlen != 32:
            return {'error': 'Cette fonction est conçue pour les adresses /32 uniquement'}
        
        return {
            'address': str(host.ip),
            'prefix': host.network.prefixlen,
            'network': str(host.network),
            'is_private': host.ip.is_private,
            'note': 'Adresse unique (souvent utilisée pour les interfaces loopback)'
        }
        
    except ValueError as e:
        return {'error': str(e)}

# Exemplo de endereço único
host_example = '10.0.0.1/32'
host_info = analyze_host_address(host_example)

print(f"\nAdresse unique {host_example}:")
for key, value in host_info.items():
    print(f"- {key}: {value}")

Gerenciamento de sub-redes (Subnetting)

Dividir uma rede em sub-redes é uma tarefa comum para administradores de rede. O Python facilita essa operação:

def create_subnets(cidr_notation, new_prefix_length):
    """
    Divide uma rede CIDR em sub-redes.
    
    Args:
        cidr_notation (str): Notação CIDR da rede pai
        new_prefix_length (int): Comprimento do prefixo para as sub-redes
        
    Returns:
        list: Lista das sub-redes
    """
    try:
        network = ipaddress.ip_network(cidr_notation, strict=False)
        
        if new_prefix_length <= network.prefixlen:
            return {'error': 'Le nouveau préfixe doit être plus grand que le préfixe du réseau parent'}
        
        subnets = list(network.subnets(new_prefix=new_prefix_length))
        
        # Limitar o número de sub-redes exibidas para evitar uma saída muito extensa
        display_limit = 10
        total_subnets = len(subnets)
        
        result = {
            'parent_network': str(network),
            'parent_prefix': network.prefixlen,
            'new_prefix': new_prefix_length,
            'total_subnets': total_subnets,
            'subnets_sample': [str(subnet) for subnet in subnets[:display_limit]]
        }
        
        if total_subnets > display_limit:
            result['note'] = f"Affichage limité aux {display_limit} premiers sous-réseaux sur un total de {total_subnets}"
        
        return result
        
    except ValueError as e:
        return {'error': str(e)}

# Exemplo de divisão em sub-redes
subnet_example = '192.168.0.0/24'
new_prefix = 26

subnet_info = create_subnets(subnet_example, new_prefix)

print(f"\nDivision de {subnet_example} en sous-réseaux /{new_prefix}:")
for key, value in subnet_info.items():
    if key == 'subnets_sample':
        print(f"- Exemples de sous-réseaux:")
        for i, subnet in enumerate(value, 1):
            print(f"  {i}. {subnet}")
    else:
        print(f"- {key}: {value}")

Aplicação prática: planejamento de rede

Aqui está um exemplo de aplicação prática para planejar uma rede com várias sub-redes que têm necessidades específicas em termos de número de hosts:

def plan_network(base_network, requirements):
    """
    Planeja uma rede alocando sub-redes de acordo com requisitos específicos.
    
    Args:
        base_network (str): Rede base em notação CIDR
        requirements (dict): Dicionário com o nome da sub-rede e o número de hosts necessários
        
    Returns:
        dict: Plano de sub-redes alocadas
    """
    try:
        network = ipaddress.ip_network(base_network, strict=False)
        sorted_requirements = sorted(requirements.items(), key=lambda x: x[1], reverse=True)
        
        allocated_subnets = {}
        next_network = network
        
        for name, hosts_needed in sorted_requirements:
            # Calcular o prefixo necessário para o número de hosts
            # Fórmula: 2^(32-prefix) - 2 ≥ hosts_needed (para IPv4)
            prefix = 32
            while prefix > 0 and (2 ** (32 - prefix) - 2 < hosts_needed):
                prefix -= 1
            
            if prefix <= network.prefixlen:
                return {'error': f"Impossible d'allouer suffisamment d'adresses pour {name}"}
            
            # Obter a próxima sub-rede disponível
            try:
                subnet = next(next_network.subnets(new_prefix=prefix))
                allocated_subnets[name] = {
                    'subnet': str(subnet),
                    'prefix': prefix,
                    'addresses_total': subnet.num_addresses,
                    'addresses_usable': subnet.num_addresses - 2,
                    'network_address': str(subnet.network_address),
                    'broadcast_address': str(subnet.broadcast_address),
                    'first_usable': str(next(subnet.hosts())),
                    'last_usable': str(list(subnet.hosts())[-1])
                }
                
                # Atualizar a rede restante
                remaining_networks = list(next_network.address_exclude(subnet))
                if remaining_networks:
                    next_network = remaining_networks[0]
                else:
                    break
                    
            except StopIteration:
                return {'error': f"Espace d'adressage épuisé, impossible d'allouer pour {name}"}
        
        return {
            'base_network': str(network),
            'allocated_subnets': allocated_subnets
        }
        
    except ValueError as e:
        return {'error': str(e)}

# Exemplo de planejamento de rede corporativa
base_network = '10.0.0.0/22'  # Bloco /22 (1022 endereços utilizáveis)

# Requisitos para diferentes departamentos
department_requirements = {
    'Bureau': 500,         # Escritório precisa de 500 hosts
    'Serveurs': 100,       # Servidores precisam de 100 hosts
    'Invités': 200,        # Rede de convidados precisa de 200 hosts
    'IoT': 50,             # Dispositivos IoT precisam de 50 endereços
    'Management': 30       # Rede de gerenciamento precisa de 30 endereços
}

network_plan = plan_network(base_network, department_requirements)

print(f"\nPlan de réseau pour {base_network}:")
if 'error' in network_plan:
    print(f"Erreur: {network_plan['error']}")
else:
    print(f"Réseau de base: {network_plan['base_network']}")
    print("\nAllocation des sous-réseaux:")
    for dept, subnet_info in network_plan['allocated_subnets'].items():
        print(f"\n{dept}:")
        for key, value in subnet_info.items():
            print(f"- {key}: {value}")

Compatibilidade com IPv6

Todas as funções acima também funcionam com IPv6, embora alguns conceitos, como o endereço de broadcast, não existam em IPv6:

def get_ipv6_cidr_range(cidr_notation):
    """
    Calcula a faixa de endereços de uma rede IPv6 CIDR.
    
    Args:
        cidr_notation (str): Notação CIDR IPv6 (ex.: '2001:db8::/64')
        
    Returns:
        dict: Informações sobre a rede IPv6
    """
    try:
        network = ipaddress.ip_network(cidr_notation, strict=False)
        
        if network.version != 6:
            return {'error': 'Cette fonction est conçue pour les réseaux IPv6 uniquement'}
        
        # Limitar a exibição a alguns endereços no IPv6
        # (caso contrário, teríamos milhões ou bilhões de entradas)
        hosts_iter = network.hosts()
        first_host = next(hosts_iter)
        
        # No IPv6, mostramos apenas o primeiro e o último endereço
        # pois geralmente há um número muito grande deles
        sample_hosts = [str(first_host)]
        
        # Para redes /64 e maiores, calcular o último endereço é muito custoso
        # então o construímos manualmente
        if network.prefixlen <= 64:
            # Para um prefixo ≤ 64, calcular explicitamente o último endereço
            last_host_str = str(network.network_address + (1 << (128 - network.prefixlen)) - 1)
            sample_note = "Trop d'adresses pour énumérer"
        else:
            # Para um prefixo > 64, podemos enumerar os hosts
            # Avançar no iterador para encontrar o último endereço
            last_host = None
            count = 0
            limit = 1000  # Limitar a iteração para prefixos maiores
            
            for host in hosts_iter:
                last_host = host
                count += 1
                if count >= limit:
                    break
            
            last_host_str = str(last_host) if last_host else "..."
            sample_note = f"Échantillon limité à {count+1} adresses" if count < limit else f"Échantillon limité aux premières et dernières adresses"
        
        sample_hosts.append(last_host_str)
        
        return {
            'network_address': str(network.network_address),
            'prefix_length': network.prefixlen,
            'num_addresses': network.num_addresses,
            'address_sample': sample_hosts,
            'sample_note': sample_note,
            'is_private': network.is_private,
            'is_global': network.is_global,
            'version': 'IPv6'
        }
        
    except ValueError as e:
        return {'error': str(e)}

# Exemplos de redes IPv6
ipv6_examples = [
    '2001:db8::/32',   # Bloco de documentação
    '2001:db8::/48',   # Alocação típica para um site
    '2001:db8::/64',   # Rede típica para um segmento
    'fe80::/64',       # Link-local
    'fd00::/8'         # Unique Local Address (ULA)
]

for cidr in ipv6_examples:
    print(f"\nInformations pour le réseau IPv6 {cidr}:")
    network_info = get_ipv6_cidr_range(cidr)
    
    if 'error' in network_info:
        print(f"Erreur: {network_info['error']}")
        continue
    
    for key, value in network_info.items():
        print(f"- {key}: {value}")

Conclusão

O gerenciamento de redes CIDR é uma habilidade fundamental para profissionais de TI. O módulo ipaddress do Python oferece uma maneira elegante e poderosa de trabalhar com endereços IP e redes.

Estes são os principais pontos a lembrar:

  1. A notação CIDR combina um endereço IP e um prefixo para definir uma rede
  2. O prefixo determina quantos endereços IP estão disponíveis na rede
  3. O Python facilita os cálculos de rede com o módulo ipaddress
  4. Os conceitos se aplicam tanto ao IPv4 quanto ao IPv6, com algumas diferenças

Esses conhecimentos são essenciais para:

  • Planejar e projetar redes corporativas
  • Entender a segmentação de rede
  • Configurar equipamentos de rede
  • Desenvolver aplicações de monitoramento ou de automação de rede
  • Melhorar a segurança da rede por meio de uma segmentação adequada

Para aprofundar seus conhecimentos, consulte a documentação oficial do módulo ipaddress e as RFCs associadas aos padrões da Internet.

InSkillCoach

Sobre InSkillCoach

Especialista em treinamento e tecnologias

Coach especializado em tecnologias avançadas e IA, apoiado pela GNeurone Inc.

Certificações:

  • AWS Certified Solutions Architect – Professional
  • Certificações Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
740
170

Comentários

Os comentários são alimentados pelo GitHub Discussions

Conecte-se com o GitHub para participar da discussão

Link copiado!