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


IPv6 nos softwares

Apesar do IPv6 tentar ser transparente visto da camada de aplicação, alguns problemas ainda não estão completamente resolvidos. O problema sobre qual conexão preferir caso o host possua tanto IPv6 quanto IPv4 é um deles. Por padrão os sistemas operacionais costumam dar uma preferência unânime para o IPv6, o que é bom, pois incentiva o uso do novo protocolo. No entanto, por se tratar de um protocolo ainda em seus estados iniciais de implantação, existem muitos problemas de conectividade na rede IPv6. Isso implica em um problema grave, pois nessa situação um usuário que possua uma conexão com IPv6 e IPv4 pode sofrer uma pior experiência de uso do que um usuário com apenas IPv4. Para resolver esse problema, foi desenvolvido uma técnica chamada Happy Eyeballs.

Happy Eyeballs

Definida na RFC 8305, Happy Eyeballs é uma forma de tentar corrigir o problema de decisão sobre qual conexão preferir, caso as duas pontas possuam IPv4 e IPv6. Seu funcionamento consiste em tentar se conectar às duas conexões simultaneamente e utilizar aquela que é estabelecida mais rapidamente, dando uma leve preferência para a conexões IPv6. Browsers como Google Chrome e Mozilla Firefox em suas versões atuais já implementam o Happy Eyeballs e o utilizam por padrão. Em sua definição mais básica, na hora de acessar um domínio, são verificados seus IPv4 e IPv6 disponíveis para se conectar. O funcionamento padrão da maioria dos sistemas operacionais faz com que primeiro seja tentado a conexão via IPv6 e em caso de falha (timeout) é tentado a conexão via IPv4. Essa implementação apresenta um problema grave, pois caso a conexão IPv6 apresente problemas, o usuário com IPv6 sofrerá um grande delay para acessar o site, enquanto um usuário comum que apresente apenas IPv4 não sofrerá disso. Esse problema é uma das principais barreiras para os provedores de conteúdos, pois o simples fato de se implementar o IPv6 pode acarretar em um prejuízo para o provedor. Uma solução proposta para isso foi a de tentar se conectar tanto via IPv6 como via IPv4 simultaneamente. Assim o primeiro a responder seria aceito e a outra conexão seria descartada. No entanto essa solução possui 2 problemas:
  1. Seriam desperdiçados muitos sockets para as tentativas de conexões, pois independente da qualidade do serviço sempre seriam feitas pelo menos 2 tentativas de conexão simultaneas. Além disso o excesso de tentativas poderia fazer com que regras mais rígidas de controle de acesso bloqueassem o usuário por excesso de conexões;
  2. Mesmo que o IPv6 possuísse uma qualidade aceitável, o simples fato dele ser um pouco mais lento que o IPv4 faria com que ele nunca fosse utilizado. A princípio isso não é um problema, porém acabaria gerando uma grande barreira para o avanço do IPv6, pois na situação atual é comum que as implementações de IPv6 não sejam tão eficientes quanto as IPv4 já existentes, o que faria com que tais conexões nunca fossem utilizadas, desestimulando assim seu uso.
Diante de tal problema, o algoritmo do Happy Eyeballs procura incentivar o uso do IPv6, porém sem prejudicar a experiência de uso do usuário. Isso é feito partindo do princípio de que a conexão IPv6 está OK, e caso a conexão não seja realizada após um intervalo de tempo (estimado suficiente para se estabelecer a conexão via IPv6) é tentando a conexão via IPv4. Assim mesmo que a conexão IPv6 falhe, o intervalo de tempo perdido para se estabelecer a conexão via IPv4 (da ordem de milisegundos) é praticamente imperceptível ao usuário, ao mesmo tempo em que se evita o excesso de conexões sofrido na solução anterior. Além disso, o algoritmo do Happy Eyeballs deve guardar em um cache a decisão sobre qual conexão foi preferida (IPv4 ou IPv6). Isso ajuda a evitar tráfego desnecessário na rede, pois caso o IPv6 esteja com problemas, toda conexão feita seria feita primeiramente em IPv6 (que irá falhar) e depois em IPv4. Com o cache esse processo seria feito apenas uma vez e as demais conexões seriam feitas diretamente via IPv4. A RFC 8305 especifica que o cache não deve ser guardado por mais de 10 minutos para evitar vícios na rede. Outro ponto a ser notado é no caso do host possuir mais de um IPv6 ou mais de um IPv4. Neste caso continua se utilizando o procedimento recomendado pelo Happy Eyeballs: tentar se conectar primeiro a um (único) IPv6 e após um pequeno intervalo de tempo t (da ordem de 100 - 300ms) tentar se conectar a um (único) IPv4. Se passar outro intervalo de tempo t após tentar se conectar via IPv4, isso significa que provavelmente o problema não se deve ao protocolo em si, mas sim a outro problema (específico do IP em questão). Assim a RFC 8305 recomenda que tente se conectar com os demais IPs, independente se for IPv6 ou IPv4, sempre esperando um intervalo de tempo t entre as tentativas. Abaixo segue o exemplo do algoritmo básico do Happy Eyeballs (sem cache e admitindo um único IPv6 e um único IPv4):
        conectar (endereço) {
                encontra IPs do endereço
                tenta se conectar a o endereço IPv6
                enquanto nenhuma conexão se estabelece e timeout não estoura {
                        se passou tempo de vantagem dado ao IPv6 {
                                tenta se conectar a um endereço IPv4 (uma única vez)
                        }
                }
                se conseguiu conectar {
                        mantém a conexão conectada
                        fecha as demais conexões (sockets abertos)
                } caso não conseguiu conectar (timeout) {
                        fecha todas as conexões (sockets abertos)
                }
        }
	
Este é apenas o algortimo básico, portanto não deve ser utilizado, pois faltam recursos importantes como o cache e o tratamento caso existam mais IPs. Porém ele serve para mostrar alguns pontos importantes:
  1. Ao encontrar os IPs do endereço deve-se tomar certo cuidado, pois alguns métodos de se encontrar os IPs (como gethostbyname) retornan apenas IPv4;
  2. Sempre lembrar de fechar as conexões que não foram estabelecidas (e limpar a memória em linguagens sem garbage collector).
Abaixo o algoritmo completo do Happy Eyeballs:
        conectar (endereço) {
                encontra IPs do endereço
                verifica cache (endereço)
                se não existir cache {
                        tenta se conectar a um endereço IPv6
                        enquanto nenhuma conexão se estabelece e timeout não estoura {
                                se passou tempo de vantagem dado ao IPv6 {
                                        tenta se conectar a um endereço IPv4 (uma única vez)
                                }
                                se passou tempo de vantagem e não se conectou nem via IPv6 nem via IPv4 {
                                        tenta se conectar a qualquer outro endereço disponível
                                }
                        }
                } se estiver no cache {
                        tenta se conectar a um endereço com a versão definida no cache
                }
                se conseguiu conectar {
                        mantém a conexão conectada
                        fecha as demais conexões (sockets abertos)
                } caso não conseguiu conectar (timeout) {
                        fecha todas as conexões (sockets abertos)
                }
        }
        
        verifica cache (endereço) {
                verifica se existe o cache para endereço
                se cache existe {
                        se o cache está ativo há mais de 10 minutos {
                                limpa o cache
                        } caso contrario {
                                retorna o cache
        }
                }
        }
	

Implementando o Happy Eyeballs

Como a própria RFC 8305 sugere, o ideal é que as próprias aplicações implementem o Happy Eyeballs. Para isso alguns conceitos importantes são necessários.

Sockets

Basicamente um socket é um par composto por um IP e uma porta que criam uma interface de comunicação com outro socket, estabelecendo assim uma conexão entre as duas interfaces (rfc793#page-84). Sockets podem se comportar diferentemente entre as diversas linguagens de programação, além disso é importante verificar se os sockets da linguagem utilizada possuem suporte a IPv6. É importante notar que a implementação de um socket pode variar de acordo com o sistema operacional, portanto sempre devemos conferir se o resultado realmente bate com o esperado (especialmente na hora de setar as flags de blocking do socket).

Blocking vs Non-blocking Sockets

Por padrão, sockets são implementados em modo blocking. Modo blocking significa que quando tentamos conectar a um servidor através da função connect() do socket a aplicação fica em standby esperando a conexão se estabelecer. Pela definição do Happy Eyeballs a aplicação deve continuar a trabalhar mesmo após tentar se conectar, por isso alguma alternativa deve ser tomada. Uma das soluções mais simples envolve tornar o socket non-blocking. Ao se tornar non-blocking, o programa continua sua execução mesmo após um connect() ou select(), possibilitando assim a implementação do Happy Eyeballs.

Suporte a IPv6

É importante verificar dois  requisitos com relação ao suporte a IPv6:
  1. Verificar se o sistema operacional utilizado possui suporte a IPv6 e se possuir verificar se ele está habilitado pelo sistema (principalmente em sistemas mais antigos, que possuem suporte a IPv6, porém este vem desabilitado por padrão);
  2. Verificar se a linguagem utilizada possui suporte a IPv6, pois muitas vezes versões antigas não possuem suporte, ou possuem muitos bugs. Além disso pode ser necessário a instalação de pacotes adicionais para o funcionamento correto do programa.
Outro ponto importante é com relação a conectividade IPv6 com a internet, pois por se tratar de um protocolo razoavelmente novo, é muito comum realizar sua configuração de forma errada, o que pode gerar problemas, como mesmo possuindo IPv6 na máquina, ela não possua conectividade com o mundo externo.

C/C++

Em C, utiliza-se muito a função gethostbyname, da bilioteca netdb.h, para realizar consultas DNS:
        struct hostent *gethostbyname(const char *name);
         
        struct  hostent {
                char        *h_name;            /* official name of host */
                char        **h_aliases;        /* alias list */
                int         h_addrtype;         /* host address type */
                int         h_length;           /* length of address */
                char        **h_addr_list;  /* list of addresses from name server */
        };
        
        Porém essa função, assim como outras da biblioteca netdb.h, só possui suporte a IPv4. Para corrigir essa falha, foi criado a função gethostbyname2:
        
        struct hostent *gethostbyname2(const char *name, int af);
	
Como pode ser observado, foi adicionado um novo parâmetro na função chamado af (Address Family), que pode ser do tipo AF_INET para resultados exclusivamente IPv4, AF_INET6 para resultados exclusivamente IPv6 ou AF_UNSPEC para poder retornar qualquer tipo de endereço. No entanto essa função apresenta problemas caso o host em questão possua mais de 1 único IP anunciado em seu DNS. Mais informações sobre as funções gethostbyname e gethostbyname2 podem ser encontradas em:
  1. https://pubs.opengroup.org/onlinepubs/009695399/functions/gethostbyname.html
  2. https://tools.ietf.org/html/rfc3493
Com isso a função gethostbyname2 foi depreciada e subtituída pela função getaddrinfo:
        
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
struct addrinfo {
        int              ai_flags;     // AI_PASSIVE, AI_CANONNAME, etc.         int              ai_family;    // AF_INET, AF_INET6, AF_UNSPEC         int              ai_socktype;  // SOCK_STREAM, SOCK_DGRAM         int              ai_protocol;  // use 0 for "any"         size_t           ai_addrlen;   // size of ai_addr in bytes         struct sockaddr *ai_addr;      // struct sockaddr_in or _in6         char                *ai_canonname; // full canonical hostname         struct addrinfo        *ai_next;      // linked list, next node };
Nota-se que a função é bem mais complexa que as anteriores, isso ocorre porque o getaddrinfo possui outras funcionalidades além das consultas DNS. O parâmetro node é o host ou IP no qual queremos nos conectar. O parâmetro service indica o tipo de serviço a ser conectado, o que pode ser uma porta (como “80” ou “22”) ou o nome do serviço (“http”, “ftp”, “telnet”, etc.). O parâmetro hints é uma estrutura de dados na qual só nos interessa o campo ai_famliy (que é o mesmo campo af da função gethostbyname2) e o campo ai_socktype, que em geral será preenchido com a constante SOCK_STREAM. O parâmetro res é a estrutura de dados na qual será recebida a resposta da consulta. Abaixo um exemplo de como utilizar a função getaddrinfo para realizar uma consulta dns:
        int main(int argc, char *argv[])
        {
                struct addrinfo hints, *res, *p;
                int status;
                char ipstr[INET6_ADDRSTRLEN];
         
                if (argc != 2) {
                        fprintf(stderr, "Usage: %s hostname\n", argv[0]);
                        return 1;
                }
         
                memset(&hints, 0, sizeof hints);
                hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
                hints.ai_socktype = SOCK_STREAM;
         
                if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
                        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
                        return 2;
                }
         
                for(p = res;p != NULL; p = p->ai_next) {
         
                        void *addr;
                         
                        if (p->ai_family == AF_INET) {
                                return 1;
                         
                        } else {
                         
                                struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
                                addr = &(ipv6->sin6_addr);
                                 
                                /* convert the IP to a string and print it: */
                                inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
                                 
                                printf("Hostname: %s\n", argv[1]);
                                printf("IP Address: %s\n", ipstr);
                                 
                        }
                }
         
                freeaddrinfo(res); // free the linked list
                 
                return 0;
        }
	
Mais informções sobre a função getaddrinfo podem ser encontradas em:
  1. https://pubs.opengroup.org/onlinepubs/009695399/functions/getaddrinfo.html
  2. https://pubs.opengroup.org/onlinepubs/009619199/getad.htm
Os sockets em C estão definidos na biblioteca sys/socket.h. Para se criar um socket é utilizado a seguinte função:
        int socket(int domain, int type, int protocol);
	
Apesar de parecer simples, essa função é um pouco confusa, pois todos os seus parâmetros de entrada são constantes pré-definidas. O parâmetro domain é o mesmo parâmetro af da função gethostbyname e o parâmetro ai_family da função getaddrinfo, ou seja, indica o tipo de endereço utilizado pelo socket. O parâmetro type é o mesmo parâmetro ai_socktype da função getaddrinfo, que indica o tipo de socket a ser utilizado. O parâmetro protocol indica o tipo de protocolo a ser utilizado na conexão pelo socket (como tcp, udp, etc.). Note que esse parâmetro é parecido com o parâmetro service da função getaddrinfo, no entanto service é uma string, enquanto protocol é um número do tipo int. Normalmente no parâmetro protocol é utilizado o valor IPPROTO_TCP, para indicar que a conexão deverá ser via TCP, porém se deixado seu valor como 0, a própria função ficará encarregada de decidir qual o melhor protocolo a ser utilizado para o tipo endereço e socket utilizado. É importante observar que essa função não retorna o socket em si, mas um número que representa o id do socket criado. Em quase todas as funções de socket o procedimento é o mesmo, não há contato direto com o socket, apenas são passados os ids dos sockets dentro das funções que irão realizar as operações. Para se conectar a um host utilizando o socket criado devemos utilizar a função connect:
        
        int connect(int socket, const struct sockaddr *address, socklen_t address_len);
        
        O parâmetro socket é o mesmo socket id que foi gerado na função socket. O parâmetro address é uma struct retornada pela função getaddrinfo, dentro da struct addrinfo->ai_addr O parâmetro address_len também é um parâmetro retornado pela função getaddrinfo, dentro da struct addrinfo->ai_addrlen. A struct sockaddr pode ser de vários  tipos:
        
        struct sockaddr {
                unsigned short    sa_family;    // address family, AF_xxx
                char              sa_data[14];  // 14 bytes of protocol address
        };
        
        
        // IPv4 AF_INET sockets:
        
        struct sockaddr_in {
                short            sin_family;   // e.g. AF_INET, AF_INET6
                unsigned short   sin_port;     // e.g. htons(3490)
                struct in_addr   sin_addr;     // see struct in_addr, below
                char             sin_zero[8];  // zero this if you want to
        };
        
        struct in_addr {
                unsigned long s_addr;          // load with inet_pton()
        };
        
        
        // IPv6 AF_INET6 sockets:
        
        struct sockaddr_in6 {
                u_int16_t       sin6_family;   // address family, AF_INET6
                u_int16_t       sin6_port;     // port number, Network Byte Order
                u_int32_t       sin6_flowinfo; // IPv6 flow information
                struct in6_addr sin6_addr;     // IPv6 address
                u_int32_t       sin6_scope_id; // Scope ID
        };
        
        struct in6_addr {
                unsigned char   s6_addr[16];   // load with inet_pton()
        };
        
        
        // General socket address holding structure, big enough to hold either
        // struct sockaddr_in or struct sockaddr_in6 data:
        
        struct sockaddr_storage {
                sa_family_t  ss_family;     // address family
        
                // all this is padding, implementation specific, ignore it:
                char      __ss_pad1[_SS_PAD1SIZE];
                int64_t   __ss_align;
                char      __ss_pad2[_SS_PAD2SIZE];
        };
        
        Abaixo um exemplo do uso da função connect:
        
        int sd;
        int rc;
        struct addrinfo ai_hints, *ai;
        
        /* create hints for getaddrinfo for stream socket */
        memset(&ai_hints, 0, sizeof(ai_hints));
        ai_hints.ai_family = AF_INET6;
        ai_hints.ai_socktype = SOCK_STREAM;
        ai_hints.ai_protocol = IPPROTO_TCP;
        /* get list of addresses for nodename from DNS */
        rc = getaddrinfo(szNodename, szService, &ai_hints, &ai);
        if (rc != 0)
        {
        strcpy(szError, "getaddrinfo() failed: ");
                strcat(szError, gai_strerror(rc));
                return -1;
        }
        /* create socket for this address */
        sd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
        if (sd < 0)
        {
        /* fail */
        strcpy(szError, "socket() failed, ");
        strcat(szError, strerror(errno));
        return -1;
        }
        rc = connect(sd, ai->ai_addr, ai->ai_addrlen);
        
	
É importante lembrar que ao iniciar a função connect o programa irá travar sua execução  até que a conexão seja estabelecida. Para a execução do Happy Eyeballs é necessário que o programa continue sua execução mesmo após a chamada da função connect. A forma mais simples de se fazer isso é tornando o socket non-blocking. Dessa forma o programa continuará sua execução mesmo após um connect. Mais informações sobre sockets e suas funções podem ser encontradas em:
  1. https://pubs.opengroup.org/onlinepubs/009695399/functions/socket.html
  2. https://pubs.opengroup.org/onlinepubs/009695399/functions/connect.html
  3. https://pubs.opengroup.org/onlinepubs/009695399/basedefs/sys/socket.h.html
Para tornar o socket  non-blocking pode-se utilizar a função ioctl, da biblioteca stropts.h:
        int ioctl(int fildes, int request, ... /* arg */);
        
        Note que essa é uma função mais avançada, portanto deve ser utilizado com cuidado. O parâmetro fildes (File Descriptor) indica o id do arquivo a ser modificado (no nosso caso é o socket, portanto seu id). O parâmetro request indica o comando que deverá ser executado, no nosso caso seria a constante FIONBIO. O parâmetro arg são configurações ou informações adicionais específicos da operação e do tipo de fildes utilizado. Um exemplo de como utilizar a função ioctl:
        
        int sd;
        int opt = 1;
        sd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
        ioctl(sd, FIONBIO, &opt);
        
        Note que nesse caso foi utilizado um parâmetro chamado opt de valor 1. A função ioctl, quando utilizada com o parâmetro FIONBIO, torna o socket non-blocking caso o parâmetro arg seja diferente de zero. Portanto no exemplo a função está tornando o socket criado em um socket non-blocking. Para retornar o socket em modo blocking basta retirar a flag setada anteriormente, utilizando o parâmetro arg com valor igual a zero:
        
        int sd;
        int opt = 1;
        sd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
        // Seta socket non-blocking
        ioctl(sd, FIONBIO, &opt);
        // Seta socket blocking
        opt = 0;
        ioctl(sd, FIONBIO, &opt);
	
Mais informações sobre a função ioctl podem ser encontradas em:
  1. https://pubs.opengroup.org/onlinepubs/009695399/functions/ioctl.html
Outra forma de se tornar o socket non-blocking é através da função fcntl, da biblioteca fcntl.h:
        int fcntl(int fildes, int cmd, ... /* arg */ );
	
Novamente, essa funçao deve ser utilizada com cuidado, pois mexe nas propriedades  do arquivo que queremos modificar. O parâmetro fildes é o mesmo da função ioctl, ou seja, o identificador do arquivo alvo (socket). O parâmetro cmd é similar ao parâmetro request da função ioctl, porém com constantes diferentes, o mesmo se aplica ao parâmetro arg. Abaixo um exemplo de como utilizar a função fcntl para tornar o socket non-blocking:
        int sd;
        sd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
        int flags = fcntl(sd, F_GETFL, 0);
        fcntl(sd, F_SETFL, flags | O_NONBLOCK);
	
Note que sua utilização é um pouco diferente da ioctl, pois é necessário executar a função duas vezes: uma para adquirir o parêmetro de flags do socket e outro para setar a flag indicando que o socket será non-blocking. Mais informações sobre a função fcntl podem ser acessadas em:
  1. https://pubs.opengroup.org/onlinepubs/009695399/functions/fcntl.html
Quando o socket está em modo non-blocking, é necessário verificar de tempos em tempos se a conexão foi estabelecida. Existem várias formas de se verificar isso, porém para o Happy Eyeballs uma função interessante é a getpeername, da biblioteca socket.h:
        int getpeername(int socket, struct sockaddr *address, socklen_t *address_len);
	
Como é possível notar, esta função possui os mesmos parâmetros de entrada que a função connect, ou seja, podemos utilizar as mesmas entradas utilizadas na função connect na função getpeername, como no exemplo abaixo:
        /* Faz as consultas DNS */
        
        sd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
        rc = connect(sd, ai->ai_addr, ai->ai_addrlen);
        // Espera estabelecer a conexão
        while (!connected) {
        rc = getpeername(sd, ai->ai_addr, ai->ai_addrlen);
        if (rc < 0) {
                connected = TRUE;
        }
        }
        // Conexão estabelecida
	
Mais informações sobre a função getpeername podem ser encontradas em:
  1. https://pubs.opengroup.org/onlinepubs/009695399/functions/getpeername.html
  2. https://pubs.opengroup.org/onlinepubs/7908799/xns/getpeername.html

Java

No java devemos tomar certo cuidado, pois o tratamento de IPs é dada pela classe InetAddress (java.net.InetAddress). Esta classe possui 2 implementações diretas: Inet4Adress para IPv4 e Inet6Address para IPv6. A documentação oficial das classes pode ser encontrada em:
  1. https://docs.oracle.com/javase/7/docs/api/java/net/InetAddress.html
  2. https://docs.oracle.com/javase/7/docs/api/java/net/Inet4Address.html
  3. https://docs.oracle.com/javase/7/docs/api/java/net/Inet6Address.html
Basicamente, a classe InetAddress é a responsável por armazenar e tratar os endereços IP. Além disso ela disponibiliza métodos de análises de escopo do IP, que estão listados a seguir:
  1. public boolean isAnyLocalAddress()
  2. public boolean isLoopbackAddress()
  3. public boolean isLinkLocalAddress()
  4. public boolean isSiteLocalAddress()
  5. public boolean isMCGlobal()
  6. public boolean isMCNodeLocal()
  7. public boolean isMCLinkLocal()
  8. public boolean isMCSiteLocal()
  9. public boolean isMCOrgLocal()
Esses métodos são reponsáveis por determinar o escopo do IP em questão. Eles são úteis se for necessário uma verificação prévia do IP, principalmente IPv6, onde são criados endereços link-locais automaticamente. Abaixo um exemplo de como adquirir um IP através de um hostname:
        InetAddress address = InetAddress.getByName(“hostname”);
        System.out.println(address.getHostAddress);
        
        Neste caso o java irá buscar um IP retornado na consulta DNS . Porém ele irá retornar apenas um dos IPs disponíveis. Portanto se quisermos utilizar o Happy Eyeballs precisamos obter todos os IPs retornados pela consulta. Isso pode ser feito da seguinte forma:
        
        InetAddress[] addressArray = InetAddress.getAllByName(“hostname”);
        for (InetAddress address : addressArray) {
                System.out.println(address.getHostAddress);
        }
	
Dessa forma todos os IPs são armazenados no array addressArray. Outra observacão importante é com relação aos Sockets, principalmente no quesito blocking/non-blocking. Apesar do java implementar a classe Socket (java.net.Socket), para utilizar sockets non-blocking devemos usar a classe SocketChannel (java.nio.channels.SocketChannel) e setar sua propriedade interna configureBlocking para false. A documentação oficial dos sockets pode ser encontrada em:
  1. https://docs.oracle.com/javase/1.4.2/docs/api/java/net/Socket.html
  2. https://docs.oracle.com/javase/1.4.2/docs/api/java/nio/channels/SocketChannel.html
Abaixo um exemplo de como criar um socket simples para tentar se conectar a algum host:
        Socket socket = new Socket(“hostname”, “porta”);
        socket.connect();
        // Faz o que precisa com o socket
        socket.close();
	
Esse exemplo mostra dois pontos importantes. O primeiro é que neste caso os IPs são resolvidos de forma transparente, ou seja, em nenhum momento foi necessário definir um IP para se conectar ao host. O segundo ponto implica justamente no problema que isso pode gerar quando estamos lidando com IPv6, pois se o método connect do socket não implementar o Happy Eyeballs teremos o problema descrito neste artigo. Então para o Happy Eyeballs funcionar no java temos de utilizar um IP para conectar ao socket:
        InetAddress address = InetAddress.getByName(“hostname”);
        Socket socket = new Socket(address, “porta”);
        socket.connect();
        // Faz o que precisa com o socket
        socket.close();
	
Porém, como já foi dito, a classe Socket não tem suporte a sockets non-blocking (diretamente). Para corrigir esse problema utilizamos a classe SocketChannel:
        InetAddress address = InetAddress.getByName(“hostname”);
        SocketChannel channel = SocketChannel.open();
        channel.configureBlocking(false);
        channel.connect(new InetSocketAddress(address, 80));
	
Desta forma será criado um socket non-blocking. Um detalhe muito importante a ser notado é que o programa continuará sua execução após a chamada do método connect, ou seja, ele não esperará a conexão ser estabelecida para continuar sua execução (pois o socket está configurado como non-blocking). Assim é necessário fazer uma verificação de conectividade antes de começar a trabalhar em cima do socket:
        InetAddress address = InetAddress.getByName(“hostname”);
        SocketChannel channel = SocketChannel.open();
        channel.configureBlocking(false);
        channel.connect(new InetSocketAddress(address, 80));
        // Espera se conectar
        while (!channel.finishConnect()) {
                // Espera um tempo
        }
        // Faz o que precisa com o socket
        socket.close();
	
É extamente isso que queremos para o Happy Eyeballs, pois enquanto esperamos uma conexão ser realizada, podemos tentar se conectar aos demais IPs disponíveis. Mas ao mesmo tempo precisamos de um IPv6 e um IPv4. Para isso utilizamos o método InetAddress.getAllByName para obter tanto as entradas IPv6 quanto IPv4:
        // Pega todos os IPs disponíveis para o hostname em específico
        InetAddress[] addressArray = InetAddress.getAllByName(“hostname”);
        for (InetAddress address : addressArray) {
        // Faz alguma coisa com o IP
        }
	
Como devemos tentar conectar primeiro a um IPv6, devemos distinguir quais IPs retornados são IPv6 e quais são IPv4. Para confirmar se o endereço é um IPv6 ou IPv4, utilizamos as classes Inet6Address e Inet4Address e fazemos uma comparação com instanceof:
        // Pega todos os IPs disponíveis para o hostname em específico
        InetAddress[] addressArray = InetAddress.getAllByName(“hostname”);
        for (InetAddress address : addressArray) {
        if (address instanceof Inet6Address) {
                        // O endereço é IPv6
        }
        if (address instanceof Inet4Address) {
                // O endereço é IPv4
        }
        }
Parte 2

Compartilhe

Busca