Uma introdução ao módulo ipaddress
**********************************

autor:
   Peter Moody

autor:
   Nick Coghlan


Visão Geral
^^^^^^^^^^^

Este documento tem como objetivo prover uma suave introdução ao módulo
"ipaddress". Ele é direcionado primeiramente para usuários que ainda
não são familiarizados com a terminologia de rede IP, mas também pode
ser útil para engenheiros de rede que querem uma visão geral de como
"ipaddress" representa os conceitos de endereçamento de rede IP.


Criando objetos de Endereço/Rede/Interface
==========================================

Como "ipaddress" é um módulo para inspecionar e manipular endereços
IP, a primeira coisa que você deseja fazer é criar alguns objetos.
Você pode usar "ipaddress" para criar objetos a partir de strings e
inteiros.


Uma observação sobre versões do IP
----------------------------------

Para leitores que não estão particularmente familiarizados com o
endereçamento IP, é importante saber que o Protocolo de Internet (IP)
está atualmente em processo de mudança da versão 4 do protocolo para a
versão 6. Esta transição está ocorrendo em grande parte porque a
versão 4 do protocolo não fornece endereços suficientes para atender
às necessidades do mundo inteiro, principalmente devido ao crescente
número de dispositivos com conexões diretas à Internet.

Explicar os detalhes das diferenças entre as duas versões do protocolo
está além do escopo desta introdução, mas os leitores precisam pelo
menos estar cientes de que essas duas versões existem, e às vezes será
necessário forçar o uso de uma versão ou do outro.


Endereços IP de host
--------------------

Os endereços, muitas vezes chamados de “endereços de host”, são a
unidade mais básica ao trabalhar com endereçamento IP. A maneira mais
simples de criar endereços é usar a função de fábrica
"ipaddress.ip_address()", que determina automaticamente se deve ser
criado um endereço IPv4 ou IPv6 com base no valor passado:

>>> ipaddress.ip_address('192.0.2.1')
IPv4Address('192.0.2.1')
>>> ipaddress.ip_address('2001:DB8::1')
IPv6Address('2001:db8::1')

Os endereços também podem ser criados diretamente a partir de números
inteiros. Os valores que cabem em 32 bits são considerados endereços
IPv4:

   >>> ipaddress.ip_address(3221225985)
   IPv4Address('192.0.2.1')
   >>> ipaddress.ip_address(42540766411282592856903984951653826561)
   IPv6Address('2001:db8::1')

Para forçar o uso de endereços IPv4 ou IPv6, as classes relevantes
podem ser invocadas diretamente. Isto é particularmente útil para
forçar a criação de endereços IPv6 para números inteiros pequenos:

   >>> ipaddress.ip_address(1)
   IPv4Address('0.0.0.1')
   >>> ipaddress.IPv4Address(1)
   IPv4Address('0.0.0.1')
   >>> ipaddress.IPv6Address(1)
   IPv6Address('::1')


Definindo redes
---------------

Endereços de host geralmente são agrupados em redes IP, então
"ipaddress" fornece uma maneira de criar, inspecionar e manipular
definições de rede. Os objetos de rede IP são construídos a partir de
strings que definem o intervalo de endereços de host que fazem parte
dessa rede. A forma mais simples para essa informação é um par de
"endereço de rede/prefixo de rede", onde o prefixo define o número de
bits iniciais que são comparados para determinar se um endereço faz ou
não parte da rede e o endereço de rede define o valor esperado
daqueles bits.

Quanto aos endereços, é fornecida uma função de fábrica que determina
automaticamente a versão correta do IP:

   >>> ipaddress.ip_network('192.0.2.0/24')
   IPv4Network('192.0.2.0/24')
   >>> ipaddress.ip_network('2001:db8::0/96')
   IPv6Network('2001:db8::/96')

Os objetos de rede não podem ter nenhum bit de host definido. O efeito
prático disso é que "192.0.2.1/24" não descreve uma rede. Tais
definições são chamadas de objetos interface, uma vez que a notação
"ip-em-uma-rede" é comumente usada para descrever interfaces de rede
de um computador em uma determinada rede e é descrita mais
detalhadamente na próxima seção.

Por padrão, tentar criar um objeto rede com bits de host definidos
resultará na exceção "ValueError" ser levantada. Para solicitar que os
bits adicionais sejam forçados a zero, o sinalizador "strict=False"
pode ser passada para o construtor:

   >>> ipaddress.ip_network('192.0.2.1/24')
   Traceback (most recent call last):
      ...
   ValueError: 192.0.2.1/24 has host bits set
   >>> ipaddress.ip_network('192.0.2.1/24', strict=False)
   IPv4Network('192.0.2.0/24')

Embora o formato string ofereça significativamente mais flexibilidade,
as redes também podem ser definidas com números inteiros, assim como
os endereços de host. Neste caso, considera-se que a rede contém
apenas o endereço único identificado pelo número inteiro, portanto o
prefixo da rede inclui todo o endereço da rede:

   >>> ipaddress.ip_network(3221225984)
   IPv4Network('192.0.2.0/32')
   >>> ipaddress.ip_network(42540766411282592856903984951653826560)
   IPv6Network('2001:db8::/128')

Tal como acontece com os endereços, a criação de um tipo específico de
rede pode ser forçada chamando diretamente o construtor da classe em
vez de usar a função de fábrica.


Interfaces do host
------------------

Conforme mencionado acima, se você precisar descrever um endereço em
uma rede específica, nem o endereço nem as classes de rede serão
suficientes. Notação como "192.0.2.1/24" é comumente usada por
engenheiros de rede e pessoas que escrevem ferramentas para firewalls
e roteadores como uma abreviação para "o host "192.0.2.1" na rede
"192.0.2.0/24"", Consequentemente, "ipaddress" fornece um conjunto de
classes híbridas que associam um endereço a uma rede específica. A
interface para criação é idêntica àquela para definição de objetos de
rede, exceto que a parte do endereço não está restrita a ser um
endereço de rede.

>>> ipaddress.ip_interface('192.0.2.1/24')
IPv4Interface('192.0.2.1/24')
>>> ipaddress.ip_interface('2001:db8::1/96')
IPv6Interface('2001:db8::1/96')

Entradas de inteiros são aceitas (como acontece com redes), e o uso de
uma versão IP específica pode ser forçado chamando diretamente o
construtor relevante.


Inspecionando objetos endereço/rede/interface
=============================================

Você se deu ao trabalho de criar um objeto
IPv(4|6)(Address|Network|Interface) e provavelmente deseja obter
informações sobre ele. "ipaddress" tenta tornar isso fácil e
intuitivo.

Extraindo a versão do IP:

   >>> addr4 = ipaddress.ip_address('192.0.2.1')
   >>> addr6 = ipaddress.ip_address('2001:db8::1')
   >>> addr6.version
   6
   >>> addr4.version
   4

Obtendo a rede de uma interface:

   >>> host4 = ipaddress.ip_interface('192.0.2.1/24')
   >>> host4.network
   IPv4Network('192.0.2.0/24')
   >>> host6 = ipaddress.ip_interface('2001:db8::1/96')
   >>> host6.network
   IPv6Network('2001:db8::/96')

Descobrindo quantos endereços individuais estão em uma rede:

   >>> net4 = ipaddress.ip_network('192.0.2.0/24')
   >>> net4.num_addresses
   256
   >>> net6 = ipaddress.ip_network('2001:db8::0/96')
   >>> net6.num_addresses
   4294967296

Iterando através dos endereços "utilizáveis" em uma rede:

   >>> net4 = ipaddress.ip_network('192.0.2.0/24')
   >>> for x in net4.hosts():
   ...     print(x)  
   192.0.2.1
   192.0.2.2
   192.0.2.3
   192.0.2.4
   ...
   192.0.2.252
   192.0.2.253
   192.0.2.254

Obtendo a máscara de rede (ou seja, definir bits correspondentes ao
prefixo da rede) ou a máscara de host (qualquer bit que não faça parte
da máscara de rede):

>>> net4 = ipaddress.ip_network('192.0.2.0/24')
>>> net4.netmask
IPv4Address('255.255.255.0')
>>> net4.hostmask
IPv4Address('0.0.0.255')
>>> net6 = ipaddress.ip_network('2001:db8::0/96')
>>> net6.netmask
IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff::')
>>> net6.hostmask
IPv6Address('::ffff:ffff')

Explodindo ou compactando o endereço:

   >>> addr6.exploded
   '2001:0db8:0000:0000:0000:0000:0000:0001'
   >>> addr6.compressed
   '2001:db8::1'
   >>> net6.exploded
   '2001:0db8:0000:0000:0000:0000:0000:0000/96'
   >>> net6.compressed
   '2001:db8::/96'

Embora o IPv4 não tenha suporte a explosão ou compactação, os objetos
associados ainda fornecem as propriedades relevantes para que o código
de versão neutra possa facilmente garantir que o formato mais conciso
ou mais detalhado seja usado para endereços IPv6, ao mesmo tempo que
manipula corretamente os endereços IPv4.


Networks as lists of Addresses
==============================

It's sometimes useful to treat networks as lists.  This means it is
possible to index them like this:

   >>> net4[1]
   IPv4Address('192.0.2.1')
   >>> net4[-1]
   IPv4Address('192.0.2.255')
   >>> net6[1]
   IPv6Address('2001:db8::1')
   >>> net6[-1]
   IPv6Address('2001:db8::ffff:ffff')

It also means that network objects lend themselves to using the list
membership test syntax like this:

   if address in network:
       # do something

Containment testing is done efficiently based on the network prefix:

   >>> addr4 = ipaddress.ip_address('192.0.2.1')
   >>> addr4 in ipaddress.ip_network('192.0.2.0/24')
   True
   >>> addr4 in ipaddress.ip_network('192.0.3.0/24')
   False


Comparações
===========

"ipaddress" provides some simple, hopefully intuitive ways to compare
objects, where it makes sense:

   >>> ipaddress.ip_address('192.0.2.1') < ipaddress.ip_address('192.0.2.2')
   True

A "TypeError" exception is raised if you try to compare objects of
different versions or different types.


Using IP Addresses with other modules
=====================================

Other modules that use IP addresses (such as "socket") usually won't
accept objects from this module directly. Instead, they must be
coerced to an integer or string that the other module will accept:

   >>> addr4 = ipaddress.ip_address('192.0.2.1')
   >>> str(addr4)
   '192.0.2.1'
   >>> int(addr4)
   3221225985


Getting more detail when instance creation fails
================================================

When creating address/network/interface objects using the version-
agnostic factory functions, any errors will be reported as
"ValueError" with a generic error message that simply says the passed
in value was not recognized as an object of that type. The lack of a
specific error is because it's necessary to know whether the value is
*supposed* to be IPv4 or IPv6 in order to provide more detail on why
it has been rejected.

To support use cases where it is useful to have access to this
additional detail, the individual class constructors actually raise
the "ValueError" subclasses "ipaddress.AddressValueError" and
"ipaddress.NetmaskValueError" to indicate exactly which part of the
definition failed to parse correctly.

The error messages are significantly more detailed when using the
class constructors directly. For example:

   >>> ipaddress.ip_address("192.168.0.256")
   Traceback (most recent call last):
     ...
   ValueError: '192.168.0.256' does not appear to be an IPv4 or IPv6 address
   >>> ipaddress.IPv4Address("192.168.0.256")
   Traceback (most recent call last):
     ...
   ipaddress.AddressValueError: Octet 256 (> 255) not permitted in '192.168.0.256'

   >>> ipaddress.ip_network("192.168.0.1/64")
   Traceback (most recent call last):
     ...
   ValueError: '192.168.0.1/64' does not appear to be an IPv4 or IPv6 network
   >>> ipaddress.IPv4Network("192.168.0.1/64")
   Traceback (most recent call last):
     ...
   ipaddress.NetmaskValueError: '64' is not a valid netmask

However, both of the module specific exceptions have "ValueError" as
their parent class, so if you're not concerned with the particular
type of error, you can still write code like the following:

   try:
       network = ipaddress.IPv4Network(address)
   except ValueError:
       print('address/netmask is invalid for IPv4:', address)
