Happy Eyeballs - Parte 2
php
O php não possui a função getaddrinfo, porém ele possui a função gethostbyname:
string gethostbyname ( string $hostname );
Além disso, ele também possui outra função chamada gethostbynamel:
array gethostbynamel ( string $hostname );
A diferença básica entre os dois é que na função gethostbyname é retornado apenas um único endereço IP. Já na funçao gethostbynamel é retornado um array contendo todos os endereços encontrados. Porém em ambos os casos só são retornados endereços IPv4. Mais informações sobre estas funções podem ser encontradas em:
- https://www.php.net/manual/en/function.gethostbyname.php
- https://www.php.net/manual/en/function.gethostbynamel.php
Por isso para obter todos os endereços IP, incluindo os endereços IPv6, devemos utilizar a função dns_get_record, que funciona como a função getaddrinfo:
array dns_get_record ( string $hostname [, int $type = DNS_ANY [, array &$authns [, array &$addtl ]]] );
Apesar de parecer complicada, para a implementação do Happy Eyeballs não é necessário adicionar muitos parâmetros a função, somente o tipo de consulta dns a ser feita, que são do tipo DNS_A (para endereços IPv4) e DNS_AAAA (para endereços IPv6):
$host = 'www.teste.com.br'; $Arecord = dns_get_record($host, DNS_A); $AAAArecord = dns_get_record($host, DNS_AAAA);
A função dns_get_record retorna um array com várias informações, porém apenas 2 nos interessam:
- type: pode ser de vários tipos, mas nos interessam apenas os tipo A e AAAA para poder identificar o que é IPv6 e o que é IPv4;
- ip ou ipv6: dado que vem atrelado ao type quando sua resposta é A ou AAAA. Dentro dele contém qual o IP retornado.
Mais informações sobre a função dns_get_record podem ser encontradas em:
Para se criar um socket em php, utiliza-se a função socket_create:
resource socket_create ( int $domain , int $type , int $protocol );
Note que todos os parâmetros de entradas são constantes de configuração pré definidas. O parâmetro domain indica qual o protocolo do endreço utilizado (Address Family), podendo ser AF_INET no caso de IPv4 ou AF_INET6 no caso de IPv6. O parâmetro type indica o tipo de protocolo utilizado, que normalmente é do tipo SOCK_STREAM ou SOCK_RAW. O parâmetro protocol indica o tipo de protocolo IP a ser utilizado, como tcp, udp ou icmp. Abaixo temos um exemplo de como criar um socket:
$socket = socket_create(AF_INET6, SOCK_STREAM, SOL_TCP);
Para se conectar a um host utilizando o socket é utilizado a função socket_connect:
bool socket_connect ( resource $socket , string $address [, int $port = 0 ] );
O parâmetro socket é o socket que foi criado na função socket_create. O parâmetro address é o endereço no qual queremos nos conectar. O parâmetro port é a porta na qual o socket irá se conectar. Abaixo utilizaremos o socket criado para se conectar a um IP:
$socket = socket_create(AF_INET6, SOCK_STREAM, SOL_TCP); socket_connect($socket, ‘2001:12ff:0:4::6’, 80)
Para setar o socket php para non-blocking basta utilizar a função socket_set_nonblock:
bool socket_set_nonblock ( resource $socket );
Onde o parâmetro socket é o socket no qual queremos tornar non-blocking. Para voltar o socket a forma blocking, utilizamos a função socket_set_block:
bool socket_set_block ( resource $socket );
Abaixo um exemplo de como utilizar um socket non-blocking:
$socket = socket_create(AF_INET6, SOCK_STREAM, SOL_TCP); socket_set_nonblock($socket); while (!($connected = @socket_connect($socket,‘2001:12ff:0:4::6’, 80))) { // Faz alguma coisa } // Conectou-se ao servidor socket_set_block($socket);
Mais informações sobre sockets em php podem ser encontrados em:
- https://php.net/manual/en/book.sockets.php
- https://www.php.net/manual/en/function.socket-create.php
- https://www.php.net/manual/en/function.socket-connect.php
- https://www.php.net/manual/en/function.socket-set-block.php
- https://www.php.net/manual/en/function.socket-set-nonblock.php
python
Em python, tanto a consulta de DNS quanto as operações de sockets estão dentro da biblioteca socket. Além disso podemos verificar se o sistema utilizado possui suporte a IPv6, através da propriedade:
socket.has_ipv6
Para realizar consultas DNS, temos a função gethostbyname:
socket.gethostbyname(hostname)
Temos também uma versão mais detalhada, chamada gethostbyname_ex:
socket.gethostbyname_ex(hostname)
A diferença básica entre as duas funções são as informações retornadas. Enquanto na função gethostbyname é retornado apenas o endereço IP na forma de string, na função gethostbyname_ex também são retornados a lista de nomes e a lista de IPs disponíveis. No entando nenhuma das duas funções tem suporte a IPv6, portanto não servem para implementar o algoritmo do Happy Eyeballs.
Para realizar consultas DNS que incluem o IPv6, usamos a função getaddrinfo:
socket.getaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0)
Seu funcionamento é o mesmo da linguagem C, portanto as constantes são as mesmas, com o detalhe de que essas constantes estão dentro da biblioteca socket do python. Um exemplo abaixo de como utilizar a função getaddrinfo:
socket.getaddrinfo(“ipv6.br", 80, socket.AF_INET6, socket.SOCK_STREAM, socket.SOL_TCP)
A função acima irá listar os IPv6 disponíveis para uma possível conexão tcp via stream socket na porta 80 do site ipv6.br. O retorno da função getaddrinfo é do tipo:
(family, socktype, proto, canonname, sockaddr)
Para o Happy Eyeballs precisamos apenas do campo sockaddr, que indicam o endereço IP, sendo do tipo (address, port) no caso do IPv4 (AF_INET) e (address, port, flow info, scope id) no caso do IPv6 (AF_INET6). Em ambos os casos utilizaremos apenas o campo address retornado, que é o que realmente precisamos. Veja o exemplo abaixo:
>>> socket.getaddrinfo("ipv6.br", 80, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.SOL_TCP) [(10, 1, 6, '', ('2001:12ff:0:4::22', 80, 0, 0)), (2, 1, 6, '', ('200.160.4.22', 80))]
Nesse caso a consulta retorna tanto os endereços IPv4 quanto os endereços IPv6, pois o parâmetro family está setado como AF_UNSPEC, o que faz com que ele aceite qualquer tipo de protocolo como resposta. Mais informações sobre a função getaddrinfo podem ser encontradas em:
Para criar um socket em python basta utilizar a função socket da biblioteca socket:
socket.socket([family[, type[, proto]]])
Sua utilização é bem simples:
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
Nesse caso o socket criado é atribuído a variável s. É importante observar que pela definição do socket, faltam duas informações essenciais: o endereço IP e a porta na qual tentaremos conectar. Esses parâmetros são indicados quando utilizamos a função connect:
socket.connect(address)
Para setar o socket como non-blocking basta setar sua propriedade setblocking(valor) para 0.
socket.setblocking(flag)
perl
O perl possui um problema grave com relação ao IPv6, pois sua biblioteca padrão de sockets (IO::Socket::INET) não possui suporte a IPv6. Além disso, o perl utiliza um sistema de módulos para serem adicionados e incorporados a ele, porém não existe uma padronização oficial para isso, o que leva a existirem vários módulos diferentes que implementam uma mesma funcionalidade. É o que acontece no caso do módulo que suporta sockets IPv6. Nesta artigo vamos apenas abordar o módulo indicado no próprio site do perl (https://www.perl.org/about/whitepapers/perl-ipv6.html), a nova biblioteca de sockets (IO::Socket::IP), que suporta tanto IPv4 quanto IPv6.
Javascript
Javascript é um caso especial, pois não é possível realizar consultas DNS diretamente utilizando apenas javascript. Uma solução possível é criar um servidor que faça a consulta em seu lugar e tentar se conectar segundo o algoritmo do Happy Eyeballs. Porém essa prática não é recomendada, pois além de necessitar fazer uma conexão extra fora do escopo, ele fica dependente dessa resposta, além de necessitar de um servidor exclusivo para isso.
Links Úteis
- Exemplo de Happy Eyeballs em C: https://www.ipv6forum.com/ipv6_enabled/DNS.php
- Happy Eyeballs no squid: https://squidproxy.wordpress.com/2012/07/14/happy-eyeballs/
- Hampering Eyeballs: https://labs.ripe.net/Members/emileaben/hampered-eyeballs