Comitê Gestor da Internet no Brasil Seu IP: 38.107.191.119 CGI.br Registro CERT.br

Sítio web não compatível com IPv6 Este sítio web funciona com IPv6. Se o globo estiver girando, você também já usa IPv6!


Últimas Atualizações...

25 May 2010 - 18:40:
Relato sobre o Treinamento IPv6 do RIPE

11 Jan 2010 - 17:49:
Introdução ao IPv6 para o CCNA

11 Dec 2009 - 11:14:
Videos sobre cases IPv6 fora do Brasil

09 Jun 2009 - 18:59:
Curso de Introdução ao IPv6

30 Apr 2009 - 17:58:
Construindo um firewall Linux com suporte a IPv6



Artigo

Migração para IPv6 de aplicações usuárias da interface de programação Sockets BSD


Escrito por Elvis Pfützenreuter


  

Índice do Artigo

  

Migração para IPv6 de aplicações usuárias da interface de programação Sockets BSD
Introdução
IPv6: principais mudanças em relação a IPv4
Convivência entre IPv4 e IPv6
Usos práticos do IPv6, hoje
Interface Sockets BSD para programação em IPv6
A interface /proc do Linux
Exemplos de código Sockets BSD para IPv6
Conclusão
Bibliografia


4. Interface Sockets BSD para programação em IPv6

Este capítulo lista as principais extensões da interface Sockets BSD para programação IPv6. São os elementos que devem ser revistos na migração de um programa de IPv4 para IPv6.

Boa parte dos elementos não sofre qualquer alteração. Exemplos: funções como read(), write(), bind(), select(), poll(). Eles não serão abordados neste trabalho.

4.1. Estruturas de endereçamento e soquetes

struct in6_addr {
 	union {
	 
 	 	uint8_t u6_addr8[16];
 	 	uint16_t u6_addr16[8];
 	 	uint32_t u6_addr32[4];
 	} in6_u;	 

#define
	s6_addr
	in6_u.u6_addr8
#define	s6_addr16
	in6_u.u6_addr16
#define
	s6_addr32
	in6_u.u6_addr32

};

Esta estrutura é análoga a in_addr do IPv4, e contém o endereço IPv6 de 128 bits. Graças aos artifícios union e #define, é possível lidar com o endereço de diversas formas: como 16 bytes, 8 palavras ou 4 palavras duplas.

Unions e macros são artifícios próprios da linguagem C; outras linguagens como C++ permitem uma manipulação mais civilizada do endereço IPv6. Do ponto de vista do kernel, in6_addr é uma seqüência de 128 bits de comprimento, sem qualquer estrutura interna.

Conforme OPENGROUP, o membro s6_addr é obrigatoriamente definido; portanto é o mais portável.

A variável in6addr_any contém o endereço IPv6 "indefinido" ou nulo (completamente zerado). Esta constante é uma variável, não uma macro. O sistema encarrega-se de inicializá-la.

A variável in6addr_loopback contém o endereço IPv6 ::1, local da máquina, equivalente ao endereço 127.0.0.1 do IPv4.

Também é definida a macro IN6ADDR_ANY_INIT, que pode ser usada para inicializar uma estrutura in6_addr com o endereço nulo.

Assim como em IPv4, o endereço nulo IPv6 é passado ao bind() em programas de servidor que desejem ouvir todas as interfaces existentes.

struct sockaddr_in6 {
 	uint8_t
	sin6_len;
 	sa_family_t	sin6_family;
 	in_port_t
	sin6_port;
 	uint32_t sin6_flowinfo;
 	struct in6_addr sin6_addr;
 	uint32_t sin6_scope_id;
};

Estrutura análoga ao sockaddr_in do IPv4. Há dois membros completamente novos na estrutura:

a) sin6_flowinfo: os primeiros 4 bits são reservados, os 4 bits seguintes são os bits de prioridade, os 24 bits finais correspondem aos bits da etiqueta de fluxo do cabeçalho IPv6.

Estes campos servem, em tese, para controle de prioridade e garantias de fluxo, e provavelmente vai interagir com protocolos de reserva de banda e.g. RSVP no futuro. Conforme 1.3.11, ainda é assunto em discussão.

b) sin6_scope_id: Identifica a interface pela qual deve trafegar o pacote. As interfaces são identificadas por um número inteiro positivo; o número zero delega a escolha ao algoritmo de roteamento.

Este campo ainda não existia à época da obra de STEVENS (1998), nem tampouco é ali mencionado. É mandatório especificar a interface se endereço de destino é stateless de rede local (FE80::/10), pois todas as redes LAN diretamente conectadas possuem esse mesmo prefixo de rede, e não há como o algoritmo de roteamento decidir por uma delas.

Interessante notar que esse campo é obrigatório para endereços locais mesmo que haja uma única interface de rede. Exemplo prático:

# ping6 fe80::2e0:18ff:fe1c:62b0
connect: Invalid argument

# ping6 -I eth0 fe80::2e0:18ff:fe1c:62b0
PING fe80::2e0:18ff:fe1c:62b0(fe80::2e0:18ff:fe1c:62b0) from ::1 eth0: 56 data bytes
64 bytes from fe80::2e0:18ff:fe1c:62b0: icmp_seq=1 ttl=64 time=59 usec
64 bytes from fe80::2e0:18ff:fe1c:62b0: icmp_seq=2 ttl=64 time=58 usec

O comando ping6 funciona apenas quando especificamos a inteface de rede. Isto é um resultado direto da existência do campo sin6_scope_id. As implementações mais antigas (sem sin6_scope_id) optavam pela primeira interface de rede não-local, o que via de regra funcionava (pois a maioria dos computadores têm apenas uma placa de rede) mas cria incerteza em computadores multihomed. É possível que alguma implementação moderna ainda siga esta regra se o campo sin6_scope_id for passado como zero.

Segundo USAGI, em versões futuras o utilitário ping6 para Linux pretende suportar a sintaxe endereço%interface, como no exemplo:

# ping6 fe80::2e0:18ff:fe1c:62b0%eth0

Junto com o endereço stateless, codifica-se a interface de saída. Porém essa sintaxe não é suportada hoje, e também não há garantia que seja portável no futuro.

Segundo o documento de OPENGROUP, pode haver mais campos dependentes de plataforma nas estruturas de endereço e soquete IPv6. O programador deve sempre preencher inteiramente as estruturas com zero antes de utilizá-las, pois zerar cada campo individualmente pode deixar algum campo não-padrão com conteúdo aleatório.

Analogamente a IPv4, o campo sin6_family deve ser preenchido pelo programador com a constante AF_INET6. O campo sin6_len, se existir, deve ser preenchido com a constante SIN6_LEN.

A existência do campo sin6_len pode ser determinada em C pela presença das macros HAVE_SA_LEN ou SIN6_LEN. A título de exemplo, as diversas versões de BSD apresentam esse campo, porém o Linux não o apresenta. Todo programa com pretensões de portabilidade deve fazer o teste de macro, e, caso este resulte positivo, preencher o campo, como no exemplo a seguir:

struct sockaddr_in6 endereco;
#ifdef HAVE_SA_LEN
 	endereco.sin6_len = SIN6_LEN;
#endif

O mesmo vale para IPv4, em relação ao campo sin_len e sua constante SIN_LEN.

O número de porta continua com 16 bits, como em IPv4, pois os protocolos TCP e UDP não mudaram.

Todos os campos apresentam os bytes em network byte order, ou seja, o primeiro byte (com endereço mais baixo) é o mais significativo.

A título de exemplo: na arquitetura Intel 80386 e no Alpha, o primeiro byte de um inteiro é o menos significativo. Nas arquiteturas PowerPC e Sun Sparc, o primeiro byte é o mais significativo, exatamente como na network byte order. Nestas últimas, as funções htons() etc. são inócuas. Os campos sin6_len e sin6_family têm apenas um byte, logo a ordem dos bytes é irrelevante para eles. O padrão POSIX diz que os valores atribuídos a eles não devem ser processados pelas funções htons() etc.

Também é interessante observar que, ao contrário dos endereços e portas, esses valores nunca são transcritos para o pacote IP, portanto não há porque serem expressos em network byte order. Mesmo que esses campos venham a ter tamanho maior no futuro, devem continuar expressos na ordem de bytes nativa da máquina.

As funções para conversão de/para a ordem nativa da máquina continuam as mesmas: htons(), htonl(), ntohs(), ntohl().

As funções para tratamento de números de 64 e 128 bits v.g. htonll(), ntohll(), htonllll() e ntohllll(), embora ainda não estejam disponíveis hoje (Fevereiro/2003) já aparecem em documentações on-line e devem materializar-se num futuro próximo.

No comando socket(), o primeiro parâmetro é o protocolo. A constante AF_INET6 deve ser passada para comunicação IPv6, ao invés do tradicional AF_INET utilizado em IPv4.

4.2. Codificação/decodificação de endereços

As funções inet_aton(), inet_ntoa() e inet_addr() tratam apenas endereços IPv4 e são consideradas obsoletas.

As novas funções, que lidam tanto com IPv4 e IPv6, são:

a) inet_pton

int inet_pton(int familia, const char *origem, void *destino)

Converte um endereço em forma de texto (origem) para uma estrutura de endereçamento (apontada por destino). A família deve ser AF_INET ou AF_INET6, conforme o tipo de endereço desejado. A estrutura passada em destino deve ser compatível com a família (in_addr ou in6_addr).

Esta função retorna um valor negativo se a família for inválida, zero se o endereço for inválido, e positivo se a conversão foi bem-sucedida.

b) inet_ntop

const char* inet_ntop(int familia, const void* origem, char* destino, 
                      size_t tamanho);

Converte uma estrutura de endereço para um endereço em forma textual. O tamanho do buffer de destino deve ser passado por tamanho. O parâmetro origem deve ser um ponteiro para uma estrutura compatível com a familia (in_addr ou in6_addr).

O buffer de destino deve no mínimo comportar o maior endereço textual IPv4 ou IPv6 possível (de acordo com a família com que está-se a lidar). Ao invés de estimar o tamanho, o programador deve alocar o buffer nos tamanhos INET_ADDRSTRLEN ou INET6_ADDRSTRLEN.

Esta função retorna o endereço de destino em caso de sucesso, ou NULL se a conversão não foi bem-sucedida.

Note que as funções descritas aceitam ponteiros void* como ponteiros para estruturas de endereço, o que economiza uma coerção em ANSI C.

4.3. TCP e UDP básicos

A utilização de soquetes TCP e UDP sobre IPv6 é exatamente igual a IPv4. As constantes SOCK_STREAM e SOCK_DGRAM continuam sendo usadas como o segundo argumento de socket().

As demais funções, como bind(), listen(), connect() etc. também continuam em uso da forma usual.

4.4. Cabeçalhos opcionais IPv6

Como foi dito, o IPv6 permite a adição de cabeçalhos adicionais em número e tamanho arbitrários. A descrição dos cabeçalhos presentemente definidos bem como as estruturas C correspondentes podem ser encontradas em STEVENS & THOMAS (1998), e as extensões de segurança podem ser encontradas em ATKINSON (1995) e ATKINSON (1995a).

Para remeter cabeçalhos adicionais, basta criá-los como dados auxiliares e remetê-los via sendmsg(). Por outro lado, para habilitar a recepção dos mesmos, deve-se habilitar as opções de soquete IPV6_HOPOPTS e IPV6_DSTOPTS, conforme se deseje receber cabeçalhos hop-by-hop e de destino, respectivamente. Tais opções também são abordadas brevemente na seção 4.11.

A mecânica de coleta de dados auxiliares é abordada na seção 4.7.

4.5. Conversão entre nomes e endereços IP

Estas funções permitem ao aplicativo determinar o endereço IP a partir de um nome e vice-versa. Têm como principal objetivo a simplicidade, e oferecer a aplicativos comuns a funcionalidade de conversão de nomes.

O aplicativo não conhece e não controla quais bancos de dados são consultados na conversão do nome. Num sistema POSIX, os bancos típicos serão o arquivo /etc/hosts, o NIS e o DNS. No Windows, o WINS toma o lugar do NIS.

Em alguns sistemas POSIX, é possível ao administrador configurar quais bancos de dados serão consultados através do arquivo de configuração /etc/nsswitch.conf, bem como adicionar plug-ins de resolução de nomes.

Devido a complexidade da tarefa, a conversão de nomes é implementada fora do kernel, tipicamente como uma biblioteca.

Não há suporte a buscas mais avançadas, e.g. busca de registros MX do DNS que apenas servidores de e-mail utilizam. Tais buscas têm de ser implementadas pelo próprio aplicativo ou acessadas através de funções dependentes de plataforma.

A estrutura hostent, utilizada pelas funções de resolução de nomes, tem o seguinte formato:

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;   	// comprimento do endereço do host 
                                // (4 para IPv4, 16 para IPv6)
 	char **h_addr_list; 	// endereços do host, em forma binária
}
#define h_addr h_addr_list[0]   // primeiro endereço do host

Dada sua flexibilidade e inexistência de tamanhos fixos, seu formato não precisou ser alterado para suportar IPv6.

4.5.1. Conversão básica de nome para endereço

A função mais comumente usada, gethostbyname(), resolve apenas endereços IPv4. A função gethostbyname2() resolve tanto endereços IPv4 como IPv6 e deve ser utilizada em lugar da anterior por qualquer aplicativo novo.

struct hostent *gethostbyname2(const char *name, int af);

onde o segundo parâmetro af especifica a família de endereços desejada: AF_INET ou AF_INET6. Conforme a escolha, os registros DNS A ou AAAA serão consultados respectivamente, e a estrutura hostent será preenchida com o tipo de endereço desejado.

De acordo com GILLIGAN (1997), ao chamar gethostbyname2(), o aplicativo precisa saber de antemão (mas nem sempre tem como saber) se o endereço procurado é IPv4 ou IPv6. A procura simultânea por endereços IPv4 e IPv6 pode ser ativada por um flag.

Se for passado um endereço IP em forma de texto para gethostbyname2(), a chamada não falhará, mas simplesmente devolverá esse endereço como resultado.

Portanto, o aplicativo não precisa prever explicitamente essa situação (e.g. se o usuário informa um endereço ao invés de um nome).

4.5.2. Conversão com busca simultânea a endereços IPv6 e IPv4

res_init();
_res.options |= RES_USE_INET6;

Com a ativação desse flag, a função gethostbyname() primeiro procurará por endereços IPv6; se não encontrar, procura por endereços IPv4.

Já a função gethostbyname2() não muda seu comportamento, mesmo com o flag ativado; procura apenas pela família de endereços solicitada através do segundo parâmetro.

Com o flag RES_USE_INET6 ativado, ambas as funções retornam apenas endereços IPv6 em hostent; os endereços IPv4 são retornados mapeados em IPv6 (vide 2.1.1). O aplicativo deve estar preparado para isso.

4.5.3. Conversão de endereços IP para nomes

A função gethostbyaddr() sempre solicitou a família de endereços como argumento de chamada, portanto seu protótipo continua válido para IPv6:

struct hostent *gethostbyaddr(const char * src, int len, int af);

Segundo GILLIGAN (1997) "uma possível fonte de confusão é a manipulação de endereços IPv4 mapeados em IPv6, e endereços IPv4 compatíveis com IPv6." O mesmo documento indica as seguintes regras, avaliadas na ordem a seguir, para eliminar a confusão:

a) Se af = AF_INET6 e len = 16 (tamanho do endereço IPv6 na memória); e o endereço IPv6 é um endereço IPv4 mapeado ou compatível com IPv6: faça a procura como IPv4.

b) Se af = AF_INET, faça a procura como IPv4.

c) Se af = AF_INET6, faça a procura como IPv6.

d) Se a função retornou sucesso, e af = AF_INET, e o flag RES_USE_INET6 estiver ativado (vide 4.5.2) então o endereço retornado em hostent será IPv6 (IPv4 mapeado em IPv6) e o membro h_length será modificado para 16.

4.5.4. Funções de resolução de nomes de protocolos e serviços

Como IPv4 e IPv6 têm as mesmas camadas de transporte, as demais funções e.g. getservbyname() permanecem inalteradas.

4.6. Macros de identificação de classes de endereçamento

Pelo menos as macros a seguir devem existir em um sistema compatível com soquetes IPv6. Todas têm sintaxe de função, aceitam o parâmetro= const struct in6_addr *=, e retornam um valor booleano.

IN6_IS_ADDR_UNSPECIFIED         Não especificado (totalmente zero)
IN6_IS_ADDR_LOOPBACK	        Endereço ::1 (loopback)
IN6_IS_ADDR_MULTICAST	        Endereço de multicast
IN6_IS_ADDR_LINKLOCAL	        Endereço de rede local (stateless)
IN6_IS_ADDR_SITELOCAL           Endereço do sítio local
IN6_IS_ADDR_V4MAPPED   	        Endereço IPv4 mapeado em IPv6 (::FFFF:0:0/96)
IN6_IS_ADDR_V4COMPAT            Endereço IPv4 compatível com IPv6 (::/96)
IN6_IS_ADDR_MC_NODELOCAL	Multicast e local ao nó (simultaneamente)
IN6_IS_ADDR_MC_LINKLOCAL	Multicast e de rede local
IN6_IS_ADDR_MC_SITELOCAL   	Multicast e de sítio local
IN6_IS_ADDR_MC_ORGLOCAL	        Multicast e de organização local
IN6_IS_ADDR_MC_GLOBAL    	Multicast global

4.7. Remessa e coleta de dados auxiliares

A interface de dados auxiliares permite remeter e coletar inúmeras informações que não cabem no paradigma padrão do Sockets BSD. Devido a sua flexibilidade, esta interface está progressivamente tomando o lugar de comandos ioctl() e soquetes crus.

O exemplo clássico é a obtenção do endereço de destino de uma conexão TCP ou UDP. Para a esmagadora maioria dos aplicativos, interessa apenas o endereço de origem, portanto comandos como accept() e recvfrom() apenas dão conhecimento deste último. Porém, quando o aplicativo precisa saber o endereço de destino do pacote, pode:

a) Utilizar um soquete de acesso direto à rede e analisar os pacotes de chegada por conta própria. Tem as sérias desvantagens da complexidade e de impor uma carga pesada ao computador;

b) Atrelar (bind()) um descritor de arquivo a cada interface de rede; se o pacote chegou por determinada interface, o endereço de destino era o endereço da interface. Esse raciocínio não funciona em interfaces que respondem por vários endereços IP (situação comum tanto em IPv4 como em IPv6).

c) Utilizar a interface de dados auxiliares, que entregará ao aplicativo apenas os dados que interessam, e apenas ao soquete interessado.

Em IPv6, essa interface tem ainda mais importância devido à presença dos cabeçalhos opcionais (brevemente abordados neste trabalho).

A mecânica da remessa e coleta de dados auxiliares é a mesma para todos os protocolos, inclusive IPv6. Revisando brevemente, o segundo parâmetro passado a recvmsg() e sendmsg() é um ponteiro para a estrutura msghdr:

struct msghdr {
 	void *msg_name;
 	socklen_t msg_namelen;
 	struct iovec* msg_iov;
 	SIze_t msg_iovlen;
 	void *msg_control;
 	socklen_t msg_controllen;
 	int msg_flags;
};

O campo msg_name deve apontar para um buffer capaz de conter uma estrutura de endereço como sockaddr_in6; o campo msg_namelen deve conter o tamanho desse buffer.

O campo msg_iov deve apontar para uma matriz de estruturas iovec (descritas mais adiante); o campo msg_iovlen deve conter o número de elementos na matriz.

O campo msg_control deve apontar para um buffer que receberá uma ou mais estruturas cmsghdr (descrita mais adiante); o campo msg_controllen deve conter o tamanho desse buffer. Deve haver espaço suficiente para receber todos os dados auxiliares que o aplicativo solicitou via s=etsockopt()=.

A estrutura iovec tem o seguinte formato:

struct iovec {
 	void* iov_base;
 	int iov_len;
};

A estrutura iovec contém ou conterá o payload do pacote IP transmitido ou recebido, tal qual os buffers utilizados em read(), write() etc. A grande diferença é que pode-se definir várias estruturas iovec, cada uma contendo uma fração do payload. Tipicamente, apenas um iovec grande é utilizado.

O valor de iov_len deve conter o tamanho do buffer. Esse valor não é alterado quando o sistema preenche o buffer; o programa deve obter o número de octetos recebidos pelo valor de retorno de recvmsg() (tal qual read(), recv() e recvfrom()).

Finalmente, a estrutura cmsghdr:

struct cmsghdr {
 	int cmsg_len;
 	int cmsg_level;
 	int cmsg_type;
 	char data[];
};

A estrutura cmsghdr recebe os dados auxiliares. A parte inicial fixa é seguida por um número variável de bytes, representados em C pela matriz indefinida data[]. Portanto, o tamanho total da estrutura é variável.

O campo cmsg_len contém o tamanho total da estrutura. Os campos cmsg_level e cmsg_type contém o tipo de dado auxiliar.

Adjacente a este cabeçalho fixo, estão contidos os dados auxiliares propriamente ditos, cujo tamanho e formato interno são dependentes do tipo de dado auxiliar.

Diversas estruturas cmsghdr podem estar encadeadas no buffer apontado por msghdr.msg_control; o aplicativo deve deduzir o início da próxima estrutura a partir do tamanho da anterior e das características de alinhamento da máquina. Para facilitar esta tarefa, as seguintes macros são definidas:

cmsghdr* CMSG_FIRSTHDR(msghdr *);

Determina a primeira estrutura cmsghdr* contida em msghdr.

cmsghdr* CMSG_NXTHDR(msghdr*, cmsghdr*);

Determina a próxima estrutura cmsghdr*. Retorna NULL se não há mais nenhuma.

unsigned char* CMSG_DATA(cmsghdr*);

Obtém o dado auxiliar contido em cmsghdr(), ou seja, cmsghdr.data.

unsigned int CMSG_SPACE(unsigned int);

Calcula o tamanho do buffer necessário para conter uma estrutura cmsghdr, levando em conta o alinhamento etc. Informa-se o tamanho do dado auxiliar passado ou esperado. Por exemplo, para obter o endereço de destino, o tamanho será sizeof(in_addr) ou sizeof(in6_addr) dependendo do protocolo.

Para calcular o tamanho total de um buffer que vá receber diversos dados auxiliares, deve-se calcular assim:

CMSG_SPACE(sizeof(estrutura1)) + CMSG_SPACE(sizeof(estrutura2))

e não assim:

CMSG_SPACE(sizeof(estrutura1)+sizeof(estrutura2))

pois cada dado auxiliar tem de ser alinhado e completado com bytes de enchimento.

unsigned int CMSG_LEN(unsigned int);

Idem a CMSG_SPACE() porém retorna apenas o comprimento útil, sem os bytes de enchimento ao final.

O uso das três primeiras macros é revelado facilmente pelo seu protótipo. Quanto às duas últimas, servem para calcular o tamanho do buffer que receberá a estrutura cmsghdr, tamanho do dado auxiliar em questão.

As seguintes constantes são definidas para dados auxiliares IPV6:

cmsg_level  	cmsg_type
IPPROTO_IPV6 	IPV6_DSTOPTS
        	IPV6_HOPLIMIT
        	IPV6_HOPOPTS
        	IPV6_NEXTHOP
        	IPV6_PKTINFO
        	IPV6_RECVDSTADDR (obsoleta, não suportada em Linux)
        	IPV6_RECVIF (obsoleta, não suportada em Linux)
          	IPV6_RTHDR

O formato interno do cabeçalho de extensão IPv6 permite identificar seu subtipo. As extensões definidas presentemente e seus códigos podem ser encontrados em STEVENS & THOMAS (1998).

4.8. Operações ioctl()

A chamada ioctl() é utilizada para operações que não cabem na metáfora abrir/ler/gravar/fechar arquivo, nem possuem uma chamada especial. (fcntl e setsockopt são exemplos de chamadas especiais, que originalmente eram supridas por ioctl()).

A tendência do padrão POSIX e dos sistemas operacionais é desencorajar o uso de ioctl() e oferecer cada vez mais chamadas especiais e expandir a interface /proc, porém ioctl() continua sendo útil - e muitas vezes as "chamadas especiais" são meras macros de acesso facilitado a ioctl().

A maioria das operações ioctl() de rede atua apenas sobre o soquete passado como parâmetro. As operações de manipulação de interfaces e rotas, que atuam em características do sistema como um todo, usam um soquete qualquer como "trampolim", tradicionalmente UDP e da família de endereços sobre a qual se deseja atuar (e.g. família AF_INET6 para operações IPv6).

4.8.1. Operações sobre soquetes

Estas operações podem ser usadas com qualquer protocolo de rede:

SIOCSPGRP - configura o processo que deve receber sinais SIGIO e

SIGURG.
FIONBIO - liga/desliga o flag de I/O não-bloqueante.

4.8.2. ARP

Como o IPv6 não implementa o protocolo ARP, não existem as chamadas para manipulação da tabela ARP.

4.8.3. Manipulação de interfaces

Algumas operações disponíveis em IPv4 também estão disponíveis em IPv6. Conforme dito em 4.8, deve-se usar um soquete-trampolim da família AF_INET6.

Operação Comportamento em IPv6
SIOCGIFADDR Não suportada em IPv6
SIOCSIFADDR Adiciona, ao invés de substituir, um endereço
SIOCDIFADDR (nova) Elimina um endereço da interface
SIOCGIFCONF Não suportada
outras Não suportadas

Em IPv6, as chamadas utilizam a estrutura in6_ifreq ao invés de ifreq. A interface é identificada por um número positivo (in6_ifreq.ifr6_ifindex) ao invés de um nome como em IPv4 (ifreq.ifr_name).

Infelizmente, a chamada SIOCGIFCONF, que serve para obter a lista de todas as interfaces, não existe em IPv6. A tendência é oferecer esta informação via interface /proc.

Operações de broadcast são inválidas em IPv6 pois este não suporta broadcast.

4.8.4. Manipulação de rotas

As mesmas operações SIOCADDRT e SIOCDELRT são válidas para IPv4 e IPv6; o que define se a operação é IPv4 ou IPv6 é a família do soquete-trampolim utlizado.

Previsivelmente, as estruturas passadas via ioctl() são diferentes conforme a família. Ambas estão no arquivo de inclusão <net/route.h>.

Protocolo Estrutura
IPv4 rtentry
IPv6 in6_rtmsg

Um exemplo prático da manipulação de rotas via programa é o próprio utilitário ip do Linu (referência: IPROUTE). Segundo STEVENS (1998) e TORVALDS et al. (2002) não existe operação ioctl() para consulta da tabela de roteamento. A consulta tem de ser feita via interface /proc.

4.8.5. Conversões entre nome de interface e número de índice

A função if_nametoindex(const char*) executa esta tarefa. Verifique a seção 4.11.1 para mais detalhes.

4.9. Soquetes de roteamento

Soquetes de roteamento são criados com a família AF_ROUTE. São uma alternativa aos comandos ioctl() para manipulação da tabela de roteamento.
No Linux 2.4, a família AF_ROUTE é apresentada como sinônimo de AF_DATALINK. Embora esta última seja suportada pelo Linux, não são suportadas as funções de manipulação de rotas.

4.10. Sysctl

A função sysctl() permite acesso aos mesmos recursos da árvore /proc/sys, de forma mais apropriada a um programa em C, ou seja, sem necessidade de interprertar textos ASCII.

Internamente, esta função utiliza soquetes de roteamento ou netlink para comunicar-se com o kernel.

Os mesmos recursos disponíveis através dos arquivos em /proc/sys também são acessíveis e modificáveis via sysctl(). Porém, o identificador de cada recurso é uma constante inteira, e não um nome de arquivo. As constantes podem ser consultadas em <sys/sysctl.h> e <linux/sysctl.h>. Em outros sistemas operacionais, consulte <sys/sysctl.h>, que por sua vez inclui arquivos dependentes de plataforma.

A utilização dessa interface tem duas peculiaridades:

a) A interface /proc é ligeiramente diferente em cada sistema operacional, muito embora os Unixes tentem ser semelhantes nas operações acessíveis via sysctl(). Usar /proc ou sysctl() pode trazer problemas de portabilidade;

b) Em Linux, apenas números inteiros (e não strings, nem estruturas) podem ser acessados via sysctl(), portanto apenas parâmetros numéricos presentes em /proc/sys podem ser manipulados via sysctl(). Portanto, toda a discussão em STEVENS (1998) sobre o acesso às tabelas de roteamento via sysctl() é inválida para o Linux.

Esta é uma limitação e uma decisão de design específica do Linux. Ponderou TSO (2000) que prever manipulação de estruturas via sysctl() duplicaria esforços de programação sem que isso considerável vantagem aos aplicativos-usuários.

4.11. Multicast

Assim como em IPv4, o ingresso e saída de grupos multicast é feita através da função setsockopt().

As macros de comando são todas análogas a IPv4:

IPV6_ADD_MEMBERSHIP ou IPV6_JOIN_GROUP
IPV6_DROP_MEMBERSHIP ou IPV6_LEAVE_GROUP
IPV6_MULTICAST_IF
IPV6_MULTICAST_HOPS (correspondente a IP_MULTICAST_TTL)
IPV6_MULTICAST_LOOP

As macros IP*_MEMBERSHIP são consideradas obsoletas.

A principal mudança é na forma de especificar uma interface. Em IPv4, a interface multicast é especificada através de um endereço IP. Em IPv6, a interface é specificada através de um número inteiro.

Estrutura de requisição de multicast, utilizada pelos comandos IPV6_*_GROUP:

struct ipv6_mreq {
 	struct in6_addr ipv6mr_multiaddr;
 	unsigned int ipv6mr_interface;
};

Em algumas implementações mais antigas, bem como na implementação de TORVALDS et al. (2002), o segundo campo é apresentado com o nome Ipv6mr_ifindex.

O comando IPV6_MULTICAST_IF recebe um parâmetro u_int (número inteiro) ao invés de um endereço IP.

Os comandos IPV6_MULTICAST_HOPS e IPV6_MULTICAST_LOOP recebem, respectivamente, int e u_int como parâmetros, ao invés de u_char; na verdade, é uma simples mudança no tamanho do número.

4.11.1. Número da interface

Em IPv6, a interface é especificada pelo número, pois seria pouco prático especificar a interface pelo seu endereço IP stateless (que pode mudar caso seja trocada a placa de rede).

Se este número de interface é passado como zero, a tabela de roteamento decidirá qual a interface de saída. No caso de endereços stateless FE80::/10, a passagem do número de interface é obrigatório (vide 4.1 item b).

Em geral, o programador dispõe do nome da interface por onde deve ser emitido o multicast, e precisa apenas descobrir o número. A função if_nametoindex(const char*) foi definida para facilitar esta tarefa.

4.11.1.1. Métodos diretos de descoberta do número da interface

Os métodos pelos quais a função if_nametoindex() descobre o número da interface são dependentes de implementação. No Linux, são dois:

a) Consultar o arquivo /proc/net/if_inet6;

b) Utilizar a chamada ioctl(fd_trampolim, SIOGIFINDEX, &ifreq).

4.12. Opções de soquete

Segue a lista das opções disponíveis para IPv6, exceto as opções de multicast que já foram abordadas. As opções sem paralelo em IPv4 estão marcadas como "(nova)".

IPV6_ADDRFORM Permite que um soquete seja convertido de/para IPv4. (nova)
IPV6_CHECKSUM Especifica a localização do checksum dentro de um pacote IPv6 "cru". Para outros protocolos, inclusive ICMPv6, o kernel sempre encarrega-se de calcular o checksum. (nova)
IPV6_DSTOPTS Cabeçalhos opcionais para o destino devem ser recebidos como dados auxiliares via recvmsg(). (nova)
IPV6_HOPLIMIT Recebe o campo hop count como dado auxiliar. (nova)
IPV6_HOPOPTS Cabeçalhos opcionais hop-by-hop devem ser passados como dados auxiliares. (nova)
IPV6_NEXTHOP Permite especificar o endereço next hop via sendmsg() (nova)
IPV6_PKTINFO Recebe endereço de destino e número da interface de chegada como dados auxiliares
IPV6_RECVDSTADDR Recebe endereço de destino como dado auxiliar (obsoleto, não mais suportado pelo Linux)
IPV6_RECVIF Recebe número da interface de chegada como dado auxiliar (obsoleto, não mais suportado em Linux)
IPV6_PKTOPTIONS Permite que TCP troque dados auxiliares. Normalmente, dados auxiliares são tratados via sendmsg() e recvmsg() o que pressupõe comunicação em datagramas (UDP ou soquete "cru").
IPV6_RTHDR Cabeçalho de roteamento IPv6 será recebido como dado auxiliar. (nova)
IPV6_UNICAST_HOPS Permite especificar o limite de saltos (hop limit) (análoga a IP_TTL em IPv4)

As opções a seguir são mais recentes que STEVENS (1998):

IPV6_AUTHHDR Cabeçalho IPSEC AH (será obsoletado)
IPV6_ROUTER_ALERT Repassa todos os soquetes contendo um alerta de roteamento para o soquete
IPV6_MTU_DISCOVER Controla o recurso de descoberta de MTU (path MTU discovery) no soquete.
IPV6_MTU Obtém ou configura o MTU para o soquete. Limitado ao MTU da interface.
IPV6_RECVERR Controla recebimento de erros assíncronos como dados auxiliares.

4.13. Soquetes crus (raw)

Soquetes crus permitem enviar e receber pacotes IP, sem imposição de um protocolo de quarta camada como UDP ou TCP. Por outro lado, a responsabilidade de criar pacotes válidos recai totalmente sobre o aplicativo.

A principal aplicação dos soquetes crus é a comunicação ICMP. Qualquer protocolo de transporte não suportado diretamente pelo kernel pode ser implementado diretamente pela aplicação através de soquetes crus.

Os soquetes crus diferem dos soquetes de acesso à camada de enlace pois enviam/recebem apenas pacotes IP, e recebem apenas pacotes que não puderam ser aproveitados por outra conexão. Por exemplo, os pacotes de uma conexão TCP em andamento não são ouvidos por nenhum soquete cru, portanto ele não se presta para sniffing.

Por outro lado, todos os pacotes IP que não tenham sido aproveitados por conexões TCP ou UDP serão repassados a todos os soquetes crus. Cada aplicativo usuário de soquetes crus deve, de alguma maneira, discriminar os pacotes desejados. (No protocolo ICMP, os campos identificador e número seqüencial prestam-se a isso). O uso excessivo de soquetes crus por muitos processos ao mesmo tempo pode sobrecarregar a máquina, pois o número de pacotes repassados aumenta exponencialmente.

Quando um soquete cru é criado, deve-se passar através do terceiro parâmetro de socket() uma constante IPPROTO_x onde x é o protocolo de quarta camada em que o programa está interessado.

As diferenças entre soquetes crus IPv4 e IPv6 são:

a) O soquete é criado com a família AF_INET6 ao invés de AF_INET;

b) Os valores de IPPROTO_ICMP e IPPROTO_ICMPV6 são diferentes, pois são protocolos diferentes (vide 1.3.8);

c) Em IPv4, o programa pode utilizar a opção IP_HDRINCL, que delega ao programa a geração e acesso ao cabeçalho de terceira camada (IP). Esta opção não é válida em IPv6. Programas que precisem lidar com o cabeçalho IP devem usar soquetes de acesso à camada de enlace;

d) Caso haja necessidade de ler/gravar dados do cabeçalho IP, inclusive cabeçalhos auxiliares, isso deve ser feito via opções IP ou acesso a dados auxiliares (vide respectivos tópicos);

e) Todos os campos nos cabeçalhos de protocolo apresentam-se em network byte order;

f) Como o checksum dos protocolos de quarta camada inclui dados da terceira camada em IPv6, o kernel oferece a comodidade em fazê-lo em lugar do programa;

Para os soquetes crus ICMPv6, esse cálculo sempre é feito pelo kernel. Para os demais protocolos, a opção setsockopt() IPV6_CHECKSUM permite comutar o recurso.

g) Como o protocolo ICMPv6 acumula muito mais funções que ICMPv4, existe uma opção de soquete que permite filtrar os pacotes por subtipo. A estrutura de filtragem é icmp6_filter. As macros que manipulam esta estrutura são

ICMP6_FILTER_SETPASSALL(struct icmp6_filter* filtro)
ICMP6_FILTER_SETBLOCKALL(struct icmp6_filter* filtro)
ICMP6_FILTER_SETPASS(int subtipo, struct icmp6_filter* filtro)
ICMP6_FILTER_SETBLOCK(int subtipo, struct icmp6_filter* filtro)
int ICMP6_FILTER_WILLPASS(int subtipo, const struct icmp6_filter *filtro)
int ICMP6_FILTER_WILLBLOCK(int subtipo, const struct icmp6_filter *filtro)

Os valores aceitáveis para o subtipo podem ser consultados em <netinet/icmp6.h>. A estrutura é passada para o kernel via comando

setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, filtro, sizeof(icmp6_filter))

4.14. Acesso à camada de enlace

A interface de acesso à camada de enlace não é um padrão do Sockets BSD; existem pelo menos três implementações bem difundidas, muito embora, segundo STEVENS (1998) a biblioteca libpcap seja o padrão de fato para ocultar essa multiplicidade. A rigor o acesso à camada de enlace não sofre qualquer alteração pela inclusão do IPv6, que está numa camada superior. As possíveis mudanças num aplicativo que use esse recurso são:

a) Se o aplicativo está interessado em pacotes IPv6, deve utilizar a opção de filtragem adequada à interface.

b) Pacotes IPv6 devem ser enquadrados na estrutura ip6_hdr contida no arquivo de inclusão <netinet/ip6.h>.

4.15. Interoperabilidade entre IPv4 e IPv6

4.15.1. Convivência de aplicativos servidores IPv4 e IPv6

Um aplicativo pode permanecer na escuta de uma porta do protocolo de transporte em todas as interfaces, ou em apenas uma interface, ou ainda em algumas delas.

Quando o aplicativo deseja ouvir em todas as interfaces, sinaliza isso ao sistema operacional passando um pseudo-endereço a bind() (INADDR_ANY em IPv4, in6addr_any em IPv6). Apenas uma chamada a bind() é necessária.

É permitido que vários aplicativos ouçam a mesma porta, desde que cada um esteja atrelado a uma interface diferente. Não pode haver dois aplicativos ouvindo na mesma porta e na mesma interface. E naturalmente, se um aplicativo está ouvindo em todas as interfaces, nenhum outro aplicativo poderá atrelar-se à mesma porta.

Isso tudo é verdadeiro tanto para IPv4 quanto para IPv6, considerados isoladamente. Porém, IPv4 e IPv6 alimentam as mesmas camadas de transporte, e pode haver conversão entre endereços IPv4 e IPv6; portanto, é necessário estabelecer uma regra adicional de convivência.

Para o caso de dois aplicativos, um IPv4 e outro IPv6, ambos ouvindo na mesma porta e na(s) mesma(s) interface(s), a implementação pode escolher uma das regras a seguir:

a) Os servidores convivem; conexões IPv6 vão para o servidor IPv6, conexões IPv4 para o servidor IPv4; ou

b) Os servidores não podem conviver. O primeiro a ser iniciado tomará conta da porta, os próximos falharão ao tentar usar a mesma porta.

A maioria das implementações, Linux inclusive, segue o critério B por não ser ambíguo, conforme o item 4.15.2.

4.15.2 Mapeamento automático de IPv4 em IPv6

Os endereços IPv4 podem ser mapeados em IPv6 (::FFFF:0:0/96), e os protocolos de transporte são os mesmos para IPv4 e IPv6.

Graças a isso, um aplicativo-servidor IPv6 pode receber conexões IPv4 sem qualquer previsão ou tratamento especial. Basta que o sistema operacional subjacente remapeie as conexões IPv4 (esse é realmente o comportamento que se espera de qualquer implementação).

Se dois servidores IPv4 e IPv6 puderem conviver ouvindo a mesma porta, isso cria incerteza, pois uma conexão IPv4 poderia ser aceita por qualquer dos servidores.

Para eliminar essa ambigüidade, a maioria das implementações não permite que dois servidores, um IPv4 e outro IPv6, ouçam a mesma porta do protocolo de transporte (item 4.15.1 opção B).

O aplicativo pode optar em não receber conexões IPv4; basta testar se o endereço remoto é IPv4 mapeado em IPv6, com a macro IN6_IS_ADDR_V4MAPPED().

4.15.3 Passagem de descritores de arquivo

Em programação POSIX, é bastante comum passar descritores de arquivo abertos para processos-filhos. Também é possível, embora menos comum, passar descritores entre processos não aparentados.

Normalmente o processo-escravo não se importa com o que o descritor realmente representa, e não precisa mesmo se importar se utilizar apenas as operações normais de arquivo, como read(), write() etc. sobre o descritor.

No entanto, há um caso especial criado pela convivência entre IPv4 e IPv6. Se um processo-mestre com descritor IPv6 aberto repassar esse descritor a um processo-escravo que espera um soquete IPv4, e este último tentar executar alguma chamada como setsockopt() que se aplique apenas a IPv4, tal chamada falhará.

A solução definitiva é consertar o programa do processo-escravo; mas nem sempre isso é possível. No caso de soquetes IPv6 que utilizem exclusivamente endereços IPv4 mapeados em IPv6 (::FFFF:0:0/96), a comunicação subjacente é IPv4, e esse soquete pode ser convertido para IPv4 durante seu uso. Se temos como modificar o programa-mestre mas não o programa-escravo, esta é a saída.

Para converter um soquete IPv6 para IPv4, GILLIGAN (1997) prescreve o seguinte comando setsockopt():

int addrform = PF_INET;
setsockopt(descritor, IPPROTO_IPV6, IPV6_ADDRFORM, (char*) 
           &addrform, sizeof(addrform));

A opção IPV6_ADDRFORM do comando setsockopt() presta-se a conversão de soquetes. O parâmetro passado (no exemplo, através da variável addrform) indica para que protocolo se quer converter o soquete - no caso, PF_INET (IPv4).

Uma vez convertido, o soquete pode sofrer qualquer operação IPv4 sem problemas. Isso permite inclusive que ele seja passado a outros processos que esperem exclusivamente soquetes IPv4.

É prudente frisar que essa conversão não faz nenhum milagre, e de forma nenhuma deve ser entendida como um proxy IPv4-IPv6. Um soquete IPv6 que utilize endereços nativos IPv6 não pode ser convertido. Soquetes crus IPv6 também não o podem, pois seu modus operandi é muito especializado.

Não se pode "pingar" um endereço IPv4 da seguinte forma:

# ping6 ::FFFF:200.215.89.83

pois não existe conversão subjacente de IPv6 para IPv4 para soquetes crus.

Por outro lado, para converter um soquete IPv4 para IPv6 (se o processo-escravo espera um descritor de arquivo IPv6), o comando é:

int addrform = PF_INET6;
setsockopt(descritor, IPPROTO_IP, IPV6_ADDRFORM, (char*) &addrform, 
           sizeof(addrform));

Última atualização 02/09/2008 18h34

Comentários     +  

Seu nome: (max. 35 letras)


Comentário: (max. 2500 caracteres)


Verificação: (se estiver ilegível, clique na imagem)


   


   Licença:  Creative Commons Atribuição 2.5 Brasil (salvo seja especificada outra)     Válido:  XHTML 1.0 -  CSS 3