Comitê Gestor da Internet no Brasil Seu IP: 38.107.191.117 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


6. Exemplos de código Sockets BSD para IPv6

Segue uma lista de exemplos de código funcional Sockets BSD para IPv6, em linguagem C++. Eles foram testados testados no sistema operacional Linux, kernel 2.4.19.

Os programas são padrão POSIX e devem funcionar em outros sistemas operacionais POSIX que suportem IPv6. Pode, no entanto, haver pequenas diferenças e incompatibilidades, visto que IPv6 evoluiu muito nos últimos anos e ainda evolui, portanto há mudanças na API que versões antigas de determinado sistema não implementavam (vide 4.1, campo sin6_scope_id).

Os exemplos, embora tenham sido retirados de programas funcionais, não executam tarefas úteis. Para torná-los funcionais, é necessário acrescentar a lógica da camada de aplicação.

Foi utilizado C++ particularmente na manipulação de strings; tais manipulações podem ser feitas em C porém são mais práticas e claramente demonstráveis em C++.

6.1. Comunicação TCP básica

6.1.1. Cliente

#define PORTA 1000
#define SERVIDOR "fe80::1234:4568"
#define INTERFACE "eth0"
sockaddr_in6 end_servidor;
bzero(&end_servidor, sizeof(end_servidor));

#ifdef SIN6_LEN   // necessário em BSD, desnecessário em Linux
 	end_servidor.sin6_len = SIN6_LEN;
#endif

end_servidor.sin6_family = AF_INET6;
end_servidor.sin6_port = htons(PORTA);
inet_pton(AF_INET6, SERVIDOR, &end_servidor.sin6_addr); //se o endereço não fosse
                                                    //link-local, bastaria preencher
                                                    // sin6_scope_id com zero
end_servidor.sin6_scope_id = if_nametoindex(INTERFACE);

// descritor de arquivo
int fd;

if((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
 	// soquete inválido (IPv6 não suportado na máquina)
 	exit(1);
}

if (connect(fd, (sockaddr*) &end_servidor, sizeof(end_servidor))) {
 	// conexão falhou
 	exit(2);
}

Daqui para diante, absolutamente nada muda seja qual for o protocolo de rede; as funções de comunicação como read(), write(), send(), recv(), writev(), readv(), shutdown() e close() funcionam da mesma forma.

6.1.2. Servidor

#define PORTA 1000

// criamos um endereço IPv6 "coringa" (0000::0000), para atrelar
// todas as interfaces com bind()

sockaddr_in6 ia6_any;
bzero(&ia6_any, sizeof(ia6_any));

#ifdef SIN6_LEN
 	// necessário em BSD, desnecessário em Linux
 	ia6_any.sin6_len = SIN6_LEN;
#endif

ia6_any.sin6_family = AF_INET6;
ia6_any.sin6_port = htons(PORTA);
ia6_any.sin6_addr = in6addr_any;
ia6_any.sin6_scope_id = 0;

// descritor de arquivo
int fd;

if((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
 	// soquete inválido (IPv6 não suportado na máquina)
 	exit(1);
}

if (bind(fd, (sockaddr*) &ia6_any, sizeof(ia6_any))) {
 	// bind() falhou, provavelmente outro já está bind()ado à porta
 	exit(1);
}

listen(fd, 5);

for(;;) {
 	sockaddr_in6 end_cliente;
 	socklen_t len_cliente = sizeof(end_cliente);
 	int fd_conexao = accept(fd, (sockaddr*) &end_cliente, &len_cliente);
 	 
 	// comunica-se com o cliente
 	close(fd_conexao);
}

Assim como no código de exemplo para cliente TCP, a comunicação em si faz uso das mesmas diretivas (send(), recv() etc.) seja qual for o protocolo de rede.

6.2. Comunicação UDP básica

6.2.1. Criação do soquete e definições gerais

Em UDP, a criação do soquete é idêntica seja ele utilizado como cliente ou servidor, pois do ponto de vista do sistema operacional não existem conexões UDP.

#define PORTA 1000

// criamos um endereço IPv6 "coringa" (0000::0000), para atrelar
// todas as interfaces com bind()

sockaddr_in6 ia6_any;
bzero(&ia6_any, sizeof(ia6_any));

#ifdef SIN6_LEN
 	// necessário em BSD, desnecessário em Linux
 	ia6_any.sin6_len = SIN6_LEN;
#endif

ia6_any.sin6_family = AF_INET6;
ia6_any.sin6_port = htons(PORTA);
ia6_any.sin6_addr = in6addr_any;
ia6_any.sin6_scope_id = 0;

// descritor de arquivo
int fd;

if((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
 	// soquete inválido (IPv6 não suportado na máquina)
 	exit(1);
}

// Este segmento torna o soquete não bloqueante, ou seja, recv(), recvfrom()
// e recvmsg() retornam imediamente com erro se não houver dados a receber.
//
// A maioria dos programas usa select() ao invés de soquetes não bloqueantes.
//
int opcoes = fcntl(fd, F_GETFL, 0);
if (fcntl(fd, F_SETFL, opcoes | O_NONBLOCK) == -1) {
 	// soquetes não bloqueantes não são suportados
 	exit(1);
}

if (bind(fd, (sockaddr*) &ia6_any, sizeof(ia6_any))) {
 	// bind() falhou, provavelmente outro já está bind()ado à porta
 	exit(1);
}

6.2.2. Envio

#define INTERFACE "eth0"
#define DESTINO "fe80::1234:5678"

char buffer[1500]; // contém a mensagem
int len_buffer; // contém o comprimento da mensagem

sockaddr_in6 end_destino;

bzero(&end_destino, sizeof(end_destino));

#ifdef SIN6_LEN
 	// necessário em BSD, desnecessário em Linux
 	end_destino.sin6_len = SIN6_LEN;
#endif

end_destino.sin6_family = AF_INET6;

end_destino.sin6_port = htons(PORTA);

inet_pton(AF_INET6, DESTINO, &end_destino.sin6_addr);

// necessário se utilizarmos endereços link-local, como o exemplo.
// Do contrário, deve-se passar zero
end_destino.sin6_scope_id = if_nametoindex(INTERFACE);

int vol = sendto(fd, buffer, len_buffer, 0, (sockaddr*) &end_destino, 
                 sizeof(end_destino));

if (vol < 0) {
 	// falha
	 
 	if (errno !=
	EAGAIN && errno != EINTR && errno != EWOULDBLOCK) {
 	 	// erro fatal
 	 	exit(1);
 	} else {
	 
 	 	// erro não fatal
 	}	 
}

De acordo com a especificação do Sockets BSD e do POSIX, uma remessa ou recebimento de datagrama nunca deve falhar (exceto por EWOULDBLOCK, se o soquete for não-bloqueante), Mas é possível que haja alguma implementação imperfeita que não respeite essa regra; portanto é prudente prevermos a situação de falha mesmo em protocolos orientados a datagrama.

6.2.3. Recebimento

char buffer[1500];
int len_buffer;

sockaddr_in6 end_origem;
u_int socklen = sizeof(end_origem);

len_buffer = recvfrom(fd, buffer, sizeof(buffer), 0, (sockaddr*) &end_origem,
                      &socklen);

if (len_buffer < 0) {
 	// falha
	 
 	if(errno !=
	EAGAIN && errno != EINTR && errno != EWOULDBLOCK) {
 	 	// erro fatal
 	 	exit(1);
 	} else {
	 
 	 	// erro não fatal, pode tentar novamente mais tarde
 	}	 
}

6.2.4. Coleta de dados auxiliares

Possivelmente o dado auxiliar mais comumente coletado é o endereço de destino de um pacote UDP, e é o que será utilizado como exemplo neste trabalho. A coleta de outros dados auxiliares é análoga.

A chamada recvmsg() entra em substituição a recvfrom(). O código a seguir substitui o exemplo 6.2.3.

char buffer[1500];
int len_buffer;

sockaddr_in6 end_origem;

// cria estrutura iovec, que deve apontar para um buffer que
// receberá o conteúdo do pacote em si
iovec iov;
iov.iov_base = buffer;
iov.iov_len = sizeof(buffer);

// buffer para receber o endereço de destino
char buf_end_destino[CMSG_SPACE(sizeof(in6_pktinfo))];

// finalmente cria a estrutura msghdr e relaciona seus componentes
// aos elementos criados mais acima
msghdr msg;

msg.msg_name = &end_origem;
msg.msg_namelen = sizeof(end_origem);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = buf_end_destino;
msg.msg_controllen = sizeof(buf_end_destino);
msg.msg_flags = 0;

// configuramos o descritor de arquivo para fornecer o endereço de
// destino do pacote

// Linux *não* suporta mais as opções IP(V6)_RECVDSTADDR e IP(V6)_RECVIF
// porém suporta a opção IP(V6)_PKTINFO que fornece as duas informações
// ao mesmo tempo

int ligado = 1;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_PKTINFO, &ligado, sizeof(ligado)) < 0)
{
 	// setsockopt() falhou (opção não suportada pela implementação?)
 	exit(1);
}

len_buffer = recvmsg(fd, &msg, 0);

if (len_buffer < 0) {
 	// falha
	 
 	if(errno !=
	EAGAIN && errno != EINTR && errno != EWOULDBLOCK) {
 	 	// erro fatal
 	 	exit(1);
 	} else {
	 
 	 	// erro não fatal, pode tentar novamente mais tarde
 	 	exit(1);
 	}
	 
}

// buffer[] conterá o payload do pacote, através de msg.msg_iov
// end_origem conterá o endereço de origem, através de msg.msg_name
// resta obter o endereço de destino e a interface de onde veio o pacote

in6_pktinfo info;
in6_addr end_destino;
int interface;

// obtém o dado auxiliar requisitado
for(cmsghdr* cmptr = CMSG_FIRSTHDR(&msg); cmptr != 0;
       cmptr = CMSG_NXTHDR(&msg, cmptr)) {
       if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == IPV6_PKTINFO) {
       memcpy(&info, CMSG_DATA(cmptr), sizeof(info));
       }
	 
}

end_destino = info.ipi6_addr;
interface = info.ipi6_ifindex;

Como se pode notar por este código-fonte, o recebimento de dados auxiliares utiliza muitos ponteiros e é fácil cometer erros ou esquecer alguma atribuição.

6.3. Comunicação com nós IPv4

Valem as regras explanadas em 4.15. Em resumo:

a) Servidores IPv6 recebem conexões e pacotes IPv4 sem necessidade de qualquer previsão especial no código. O endereço de origem será um endereço IPv4 mapeado em IPv6 (::FFFF:0:0/96);

b) Se o servidor precisar discriminar clientes IPv4 de IPv6, pode usar a macro IN6_IS_ADDR_V4MAPPED() para testar se o endereço de origem é IPv4;

c) Clientes IPv6 devem utilizar os endereços IPv4 mapeados em IPv6 para conectar-se a servidores IPv4.

6.4. Multicast

A comunicação multicast só funciona com protocolos de transporte orientados a datagrama. Tipicamente é utilizado o UDP, e que também será usado no código de exemplo.

No Linux até a versão 2.4.19, os endereços de multicast provisórios (prefixo FF1x::/24) não funcionam; a função sendto() falha e o soquete é inutilizado após este erro.

O problema está no arquivo net/ipv6/addrconf.c do kernel, mais especificamente na função ipv6_addr_type(), que falha em identificar o escopo do endereço se o flag for diferente de zero.

Felizmente, os endereços FF0x::/24 funcionam a contento.

6.4.1. Criação do soquete

Note que a parte inicial é virtualmente idêntica a 6.2.1. Neste exemplo, a aplicação entra em dois grupos de multicast: um predefinido (FF02::1 – todos os nós da rede local) e outro criado pela aplicação (FF02::1234:1234). Ambos têm escopo de rede local.

#define PORTA 1000
#define MULTICAST_LAN "FF02::1"
#define INTERFACE “eth0”
#define MULTICAST_PARTICULAR "FF02::1234:1234"

// criamos um endereço IPv6 "coringa" (0000::0000), para atrelar
// todas as interfaces com bind()

sockaddr_in6 ia6_any;
bzero(&ia6_any, sizeof(ia6_any));
#ifdef SIN6_LEN
 	// necessário em BSD, desnecessário em Linux
 	ia6_any.sin6_len = SIN6_LEN;
#endif
ia6_any.sin6_family = AF_INET6;
ia6_any.sin6_port = htons(PORTA);
ia6_any.sin6_addr = in6addr_any;
ia6_any.sin6_scope_id = 0;

// descritor de arquivo
int fd;

if((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
 	// soquete inválido (IPv6 não suportado na máquina)
 	exit(1);
}

// Este segmento torna o soquete não bloqueante, ou seja, recv(), recvfrom()
// e recvmsg() retornam imediamente com erro se não houver dados a receber.
//
// A maioria dos programas usa select() ao invés de soquetes não bloqueantes.
//
int opcoes = fcntl(fd, F_GETFL, 0);
if (fcntl(fd, F_SETFL, opcoes | O_NONBLOCK) == -1) {
 	// soquetes não bloqueantes não são suportados
 	exit(1);
}

// Passar o endereço de multicast ao bind() é uma forma simples
// de evitar que pacotes "normais" sejam recebidos. Infelizmente
// algumas implementações falham quando isso é feito; e mesmo que
// funcione, continua sendo necessário chamar setsockopt(IPV6_ADD_MEMBERSHIP)
//
// Assim, o procedimento portável é passar o endereço in6_addrany para
// o bind().

if (bind(fd, (sockaddr*) &ia6_any, sizeof(ia6_any))) {
 	// bind() falhou, provavelmente outro já está bind()ado à porta
 	exit(1);
}

// este segmento desliga o loopback de pacotes multicast, e costuma ser
// utilizada. Dificilmente um aplicativo tem interesse em ouvir pacotes
// multicast emitidos por ele mesmo.
u_int mcast_loop = 0;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &mcast_loop, 
    sizeof(mcast_loop)))
{
 	exit(1);
}

in6_addr mcast_geral, mcast_particular;

inet_pton(AF_INET6, MULTICAST_LAN, &mcast_geral);
inet_pton(AF_INET6, MULTICAST_PARTICULAR, &mcast_particular);

// entra no grupo multicast "todas as máquinas da LAN" (FF02::1)

ipv6_mreq mcast_req;

mcast_req.ipv6mr_multiaddr = mcast_geral;
mcast_req.ipv6mr_interface = if_nametoindex(INTERFACE);
if (setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mcast_req, sizeof(mcast_req)))
{

	// não conseguiu entrar no grupo
 	exit(1);
}
// entra num outro grupo de multicast arbitrariamente definido pela aplicação
mcast_req.ipv6mr_multiaddr = mcast_particular;
mcast_req.ipv6mr_interface = if_nametoindex(INTERFACE);

if (setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mcast_req, sizeof(mcast_req)))
{
 	// não conseguiu entrar no grupo
 	exit(1);
}

// entra num outro grupo de multicast arbitrariamente definido pela aplicação

mcast_req.ipv6mr_multiaddr = mcast_particular;
mcast_req.ipv6mr_interface = if_nametoindex(INTERFACE);

if (setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mcast_req, sizeof(mcast_req)))
{
 	// não conseguiu entrar no grupo
 	exit(1);
}

6.4.2. Envio

Idêntico a 6.2.2, basta passar um endereço de multicast como endereço de destino.

O endereço de origem não pode ser um endereço de multicast. Se a atribuição do endereço de origem for deixada a cargo do sistema operacional (como tipicamente o é) será utilizado o endereço da interface de saída.

6.4.3. Recebimento

Se o aplicativo não precisa distinguir pacotes normais de pacotes multicast, vide item 6.2.3.

Se o aplicativo quiser discriminar pacotes de multicast, vide item 6.2.4. Para descobrir se um pacote é normal ou multicast, basta testar o endereço de destino com a macro IN6_IS_ADDR_MULTICAST:

if (IN6_IS_ADDR_MULTICAST(&end_origem.sin6_addr))) {
// multicast
...
}

6.5. Obtenção dos endereços IPv6 da máquina

Infelizmente, não há uma função padrão para obter os endereços IPv6 da máquina. Deve-se utilizar métodos diretos (de baixo nível e dependentes de implementação).

6.5.1. Via ioctl()

Segundo USAGI, já mencionado neste trabalho, não é possível obter os endereços IPv6 via ioctl(SIOCGIFADDR), como seria possível em IPv4, pois uma interface IPv6 pode ter múltiplos endereços.

Em IPv4, a sintaxe interface: número-de-alias (e.g. eth0:2), pode ser usada para especificar qual o endereço alternativo desejado na chamada a ioctl(SIOCGIFADDR).

Tal sintaxe não é suportada no IPv6 do Linux, portanto realmente não há meio de utilizarmos ioctl() para obter os diversos endereços IPv6 de uma interface.

6.5.2. Via /proc, no Linux

static std::string HEXDIGITS = "0123456789abcdef";

int hex2bin(const std::string& s)
{
 	int ret = 0;

 	for(unsigned int i = 0; i < s.size(); ++i) {
 	 	ret <<= 4;
 	 	ret |= (unsigned char) HEXDIGITS.find(tolower(s[i]));
 	}
	 
 	return ret;
	 
}

int get_ifv6_info(const char *if_nome, in6_addr *addr)
{
 	std::ifstream arq("/proc/net/if_inet6");
 	std::string _bruto, _endereco, _indice, _netmask, _escopo, _estado, _nome;
 	std::string endereco;
 	int indice;

 	// formato: endereço (32 digitos hexa), indice, comprimento da mascara,
 	// escopo (0x20 = link-local)
 	// estado (0x80=on), nome da interface

 	while(getline(arq, _bruto, '\n') && _bruto.size() >= 53) {
 	 	_endereco = _bruto.substr(0, 32);
 	 	_indice = _bruto.substr(33, 2);
 	 	_netmask = _bruto.substr(36, 2);
 	 	_escopo = _bruto.substr(39, 2);
 	 	_estado = _bruto.substr(42, 2);
 	 	_nome = _bruto.substr(49, 4);
 	 	if (if_nome == _nome) {
 	 	 	for(int n = 0; n < 32; n += 4) {
 	 	 	 	endereco += _endereco.substr(n, 4)+":";
 	 	 	}
 	 	 	endereco.erase(endereco.size()-1, 1);
 	 	 	inet_pton(AF_INET6, endereco.c_str(), addr);
 	 	 	if (IN6_IS_ADDR_LINKLOCAL(addr)) {
 	 	 	 	indice = hex2bin(_indice);
 	 	 	 	return indice;
 	 	 	}
	 
 	 	}
	 	 
 	}
	 	 	 
 	return -1;
}

Para obter o endereço IPv6, a função deve ser chamada da seguinte forma:

int iface_nr;
const char* iface_nome = "eth0";
sockaddr_in6 ia6_local;

iface_nr = get_ifv6_info(iface_nome, &ia6_local.sin6_addr);

A função retorna diretamente o número seqüencial da interface (que também poderia ser obtido pela função padrão if_nametoindex()) e indiretamente o endereço IPv6, preenchido em ia6_local.sin6_addr que foi passado como parâmetro.

6.6. Configuração de endereço IPv6 adicional

Para o administrador, a forma mais simples é utilizar o comando ifconfig, como no exemplo:

# ifconfig eth0 add 2001::1/64

Conforme pode ser visto em NETTOOLS, internamente o ifconfig preenche uma estrutura in6_ifreq com os dados da interface e do endereço, e utiliza a chamada ioctl(fd, SIOCSIFADDR, in6_ifreq*) para adicionar um endereço e ioctl(fd, SIOCDIFADDR, in6_ifreq*) para remover um endereço.

6.7. Resolução de nomes IPv6

6.7.1. Simples conversão de endereço textual para binário e vice-versa

// texto para endereço
const char endereco_texto = "FE80::1234:5678";
sockaddr_in6 endereco;
inet_pton(AF_INET6, endereco_texto, &endereco.sin6_addr);

// endereço para texto
char endereco_texto_2[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &endereco.sin6_addr,
endereco_texto_2, sizeof(endereco_texto_2));

6.7.2. Resolução de nomes

const char *nome = "www.dominio.com.br";

char buffer[INET6_ADDRSTRLEN];

hostent* h = gethostbyname2(nome, AF_INET6);
if (!h) {
 	// falha na resolução do nome
 	exit(1);
}
for(char** ppc = h->h_addr_list; (*ppc) != NULL; ++ppc) {
 	inet_ntop(h->h_addrtype, *ppc, buffer, sizeof(buffer));
 	printf("Endereço IPv6: %s\n", buffer);
}

hostent* h = gethostbyname2(nome, AF_INET);
if (!h) {
 	// falha na resolução do nome
 	exit(1);
}
for(char** ppc = h->h_addr_list; (*ppc) != NULL; ++ppc) {
 	inet_ntop(h->h_addrtype, *ppc, buffer, sizeof(buffer));
 	printf("Endereço IPv4: %s\n", buffer);
}

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

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