i
Novas estruturas de dados e funções para tratar o IPv6 em C/C++
A programação para IPv6 não difere substancialmente daquela feita para o IPv4. No entanto, as diferenças entre os protocolos, em especial no tamanho dos campos de endereçamento, exigem novas estruturas de dados e funções, capazes de tratá-las. Conheça neste artigo as novas estruturas e funções presentes na biblioteca das linguagens C/C++.
Estruturas de dados de endereços
A maioria das funções utilizadas na criação de sockets recebe como um dos argumentos um ponteiro para uma estrutura de dados de endereços. Cada protocolo possui sua própria estrutura de endereço, cujos nomes iniciam comsockaddr_
e terminam com um sufixo único dependendo do protocolo. Estas estruturas armazenam infirmações como a família de endereços, os números das portas utilizadas e obviamente os endereços de rede.
Estrutura de endereços para socket IPv4
A estrutura de endereço para socket IPv4 é chamadasockaddr_in
e sua definição encontra-se no arquivo de cabeçalho <netinet/in.h>
.
struct sockaddr_in
{
uint8_t sin_len; /*tamanho da estrutura*/
sa_family_t sin_family; /*AF_INET*/
in_port_t sin_port; /*número da porta TCP ou UDP*/
/* de 16 bits*/
struct in_addr sin_addr; /*endereço IPv4 de 32 bits*/
};
struct in_addr
{
in_addr_t s_addr; /*endereço IPv4 de 32 bits*/
};
O campo sin_family
indica a família de endereços, que para os endereços IPv4 é sempre definido como AF_INET
. O sin_addr
contém o endereço de rede de 32 bits. O campo sin_port
representa o número da porta da camada de transporte.
É importante destacar que esta estrutura não é capaz de armazenar os 16 octetos do endereço IPv6, bem como as demais informações inerentes ao novo protocolo.
Estrutura de endereços para socket IPv6
A estrutura de endereço para socket IPv6 é chamadasockaddr_in6
e sua definição também é encontrada no arquivo de cabeçalho <netinet/in.h>
.
struct sockaddr_in6
{
uint8_t sin6_len; /*tamanho da estrutura*/
sa_family_t sin6_family; /*AF_INET6*/
in_port_t sin6_port; /*numero da porta TCP ou UDP*/
uint32_t sin6_flowinfo; /*fluxo informações, indefinido*/
struct in6_addr sin6_addr; /*endereço IPv6 de 128 bits*/
uint32_t sin6_scope_id; /*conjunto de interfaces de um escopo*/
};
Os campos sin6_family
, sin6_port
, e sin6_addr
têm a mesma função dos campos correspondentes na estrutura sockaddr_in
. Entretanto, o campo sin6_family
é definido como AF_INET6
, a família de endereços IPv6, e o campo sin6_addr
armazena 128 bits em vez de apenas 32 bits. A área sin6_flowinfo
é usada para controle de fluxo, mas ainda ainda não foi padronizada. O campo sin6_scope_id
identifica por qual interface trafegará o pacote.
Estrutura de endereços genérica
Na API para sockets IPv6, foi definida também, uma nova estrutura de endereços genérica de modo a preencher alguma das lacunas existentes na estruturasockaddr
(estrutura genérica da API para IPv4). Esta nova estrutura, sockaddr_storage
, é capaz de armazenar qualquer tipo de endereço suportado pelo sistema. Deste modo, é possível passar como argumento para algumas funções um ponteiro para esta estrutura genérica em vez de um ponteiro para um tipo específico de endereço, permitindo ao programa armazenar tanto um endereço endereço IPv4 quanto um endereço IPv6.
struct sockaddr_storage
{
uint8_t ss_len; /*tamanho da estrutura*/
sa_family_t ss_family; /* família de endereço: AF_xxx*/
};
Importante destacar alguns outros aspectos da estrutura sockaddr_storage
. Os campos desta estrutrura não são visíveis aos usuários, exceto ss_family
e ss_len
. Por isso, ela deve ser convertida ou copiada para a estrutura de endereços adequada (através do endereço indicado no ss_family
) para que possa acessar quaisquer outros campos.
Funções IPv6
Grande parte das funções não sofreu nenhuma alteração nesta nova API voltada ao protocolo IPv6, como por exemplo:read(), write(), bind(), select(), listen(), connect(), accept(), sendmsg(), recvmsg(), getservbyname()
, etc. As funções para conversão de ordem binária htons(), htonl(), ntohs() e ntohl()
, também não foram alteradas.
Entretanto, algumas funções que lidavam apenas com IPv4 ficaram obsoletas, dando origem a outras que trabalham com qualquer protocolo.
As principais alterações são nas funções referentes a criação de socktes, codificação e decodificação de endereços, e a conversão entre nomes e endereços.
A única alteração na criação de um socket IPv6, foi na passagem do primeiro parâmetro da função socket()
. Ao invés de passarmos AF_INET
ou PF_INET
, para protocolo IPv4, utilizaremos AF_INET6
ou PF_INET6
, indicando a qual família de endereços pertence o endereço. Nas funções de codificação de nomes e endereços, que transformam os endereços de representações binárias para texto e vice-versa, utilizadas apenas com IPv4 como inet_aton(), inet_ntoa()
e inet_addr()
, foram substituídas por inet_pton()
- converte o endereço especificado em formato texto para binário – e inet_ntop()
- converte a representação do endereço de binário para texto. Estas duas novas funções são capazes de trabalhar com os dois protocolos.
int inet_pton(int familia, const char *origem, void *destino);
const char* inet_ntop(int familia, const void* origem,
char* destino, size_t tamanho);
Em relação à conversão entre nomes e endereços IPs, que possibilita determinar o endereço IP a partir de um nome ou o nome a partir do IP, está pode ser realizada em máquinas com IPv4 pelas funções gethostbyname()
e gethostbyaddr()
. Estas funções retornam um ponteiro para a estrutura hostent
que contem todos os endereços do host.
struct hostent
{
char h_name; /*nome oficial do host*/
char **h_aliases; /*nomes alternativos*/
int h_addrtype; /*tipo de endereço do host*/
int h_length; /*tamanho do endereço do host*/
char **h_addr_list; /*endereços do host em forma binária*/
}
No entanto, estas funções suportam apenas endereços IPv4. Uma solução seria a utilização da função gethostbyname2()
, capaz de resolver tanto endereços IPv4 como IPv6.
struct hostent *gethostbyname2(const char *name, int af);
A função gethostbyname2()
retorna o mesmo ponteiro que a função gethostbyname()
, diferenciando-se dela apenas por receber como segundo parâmetro qual tipo de família será armazenada na estrutura. Outra alternativa é a utilização das funções getaddrinfo()
e getnameinfo()
apresentadas no RFC 3493, e definidas no arquivo de cabeçalho <netdb.h>
.
int getaddrinfo( const char *hostname, const char *service,
const struct addrinfo *hints, struct addrinfo **result );
int getnameinfo(const struct sockaddr *sockaddr, socklen_t addrlen, char *host,
socklen_t hostlen, char *service, socklen_t servicelen, int flags);
A função getaddrinfo()
recebe quatro parâmetros. O primeiro parâmetro é um ponteiro para o nome ou endereço IP do host. O segundo parâmetro é um ponteiro para o tipo de serviço da camada de transporte (TCP ou UDP) ou número de porta. O terceiro é um ponteiro para uma estrutura addrinfo
que armazenará as informações que serão retornadas. E o quarto parâmetro é um ponteiro que retornará as informações.
Esta função retorna um ponteiro para uma lista ligada de estruturas addrinfo
com informações que serão utilizadas na criação dos sockets. A definição da struct addrinfo
também é encontrado em <netdb.h>
e possui o seguinte formato:
struct addrinfo
{
ai_flags; /*AI_PASSIVE, AI_CANONNAME*/
int ai_family; /* AF_xxx*/
int ai_socktype; /*SOCK_xxx*/
int ai_protocol; /*0 ou IPPROTO_xxx para IPv4 e IPv6*/
socklen_t ai_addrlen; /*tamanho do ai_addr*/
char *ai_canonname; /*ponteiro para o nome oficial do host*/
struct sockaddr *ai_addr; /*ponteiro para a estrutura de endereços*/
struct addrinfo *ai_next; /*ponteiro para a próxima estrutura da lista ligada*/
};
Os campos ai_family
, ai_socktype
, e ai_protocol
possuem a mesma função que os parâmetros utilizados pela chamada de sistema socket()
. O campo ai_family
indica a família do protocolo (não família de endereços), que será PF_INET6
para IPv6 ou PF_INET
para IPv4. O parâmetro ai_socktype
indica o tipo de serviço da camada de transporte (SOCK_STREAM
ou SOCK_DGRAM
). O campo ai_protocol
especifica o protocolo da camada de transporte.
O ai_addr
aponta para uma estrutura sockaddr
. Se o valor do campo ai_family
for PF_INET
, ele apontará para a estrutura sockaddr_in
, se for PF_INET6
ele apontará para a estrutura sockaddr_in6
. O campo ai_addrlen
armazena o tamanho do objeto apontado pelo campo ai_addr
. O campo ai_canonname
aponta para o nome canônico (oficial) do host. E o campo ai_next
aponta para a próxima estrutura da lista. A função do getnameinfo()
, que é independente de protocolo, recebe um endereço de soquete e retorna o nome do host e o tipo de serviço.
Seu primeiro parâmetro aponta para a estrutura que contém o protocolo de endereço, e o segundo parâmetro é o tamanho dessa estrutura. O parâmetro host aponta para um buffer onde o nome do host será armazenado, e o parâmetro hostlen
indica o tamanho desse buffer. De forma semelhante, o parâmetro service
aponta para um buffer onde o tipo de serviço (ou número da porta) será armazenado, e o servicelen
é o tamanho desse buffer.
O parâmetro flag pode ser utilizado para modificar o comportamento da função. Por exemplo: se a constante NI_DGRAM
for marcada, o serviço será especificado como não orientado a conexão; a constante NI_NAMEREQD
indica que um erro será retornado se o nome do host não poduder ser localizado; se o flag NI_NOFQDN
estiver definido, apenas o nome a parte FQDN
do nome do host será retornada (ex. se o nome do host for aix.nic.br, será retornado apenas aix); NI_NUMERICHOST
retorna uma string numérica como nome do host; NI_NUMERICSERV
retorna uma string numérica como tipo de serviço; e NI_NUMERICSCOPE
retorna uma string numérica como identificador do escopo de rede (se a estrutura de endereço apontada for IPv6 este flag será ignorado).
As funções gethostbyname(), gethostbyaddr()
e gethostbyname2()
, apresentam uma grande desvantagem em sua implementação. Elas apontam para uma estrutura de dados estática, isto é, se em um programa você tiver duas chamadas a esta estrutura, os dados da segunda chamada serão sobrepostos aos dados da primeira. Este problema não ocorre com as funções getaddrinfo()
e getnameinfo()
devido ao fato delas utilizarem uma lista ligada que armazena estruturas de endereço. Assim, é possível armazenar diversos endereços diferentes. É pertinente ressaltar que algumas plataformas que suportam threads, disponibilizam versões dessas três primeiras funções citadas, com o sufixo _r
adicionado a seus nomes, que permitem a reentrada de dados na estrutura.