Ir para o conteúdo
Logo NIC.br Logo CGI.br

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 com sockaddr_ 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 é chamada sockaddr_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 é chamada sockaddr_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 estrutura sockaddr (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.

Compartilhe

Busca