Recentemente eu esbarrei com um problema interessante, eu precisava subir um pequeno serviço REST para gerenciar alguns hardwares, tudo sem contato humano e também com várias interfaces de rede.
Em um determinado momento meu sistema tem que subir um serviço auxiliar e informar o cliente em que ip e porta ele deveria acessar. Normalmente isso seria feito com um arquivo de configuração em algum lugar, mas um requisito é que eu não podia presumir meu ip porque ele poderia vir de qualquer uma das interfaces de rede e deixar tudo isso parametrizado ficaria complexo demais.
A solução foi automatizar todo o processo dentro do meu próprio código e para isso eu precisava saber tanto meu ip como o do cliente, assim eu saberia qual rede responder e etc.
Como o Go tem uma biblioteca net muito boa eu também já queria dar suporte para ipv6, isso não é difícil, basicamente é só tomar cuidado para usar as coisas que o Go fornece e não presumir o formato do ip e porta.
Tratando IP
Existem duas funções do pacote net que não são exportadas então eu copiei elas para meu código para ajudar a parsear o resultado.
A função last serve para extrair a ultima parte de uma string dado um separador, no caso estamos usando para extrair a zona em endereços ipv6, a zona geralmente é o nome da interface de rede.
func last(s string, b byte) int {
i := len(s)
for i--; i >= 0; i-- {
if s[i] == b {
break
}
}
return i
}
A função splitHostZone retorna o host e a zona separados se não tiver zona ela sera uma string vazia, eu preciso disso porque quando eu juntar o ip com a porta novamente eu não quero incluir a zona.
func splitHostZone(s string) (host, zone string) {
// The ipv6 scoped addressing zone identifier starts after the
// last percent sign.
if i := last(s, '%'); i > 0 {
host, zone = s[:i], s[i+1:]
return
}
host = s
return
}
Pegando o ip do servidor
Para descobrir o ip do servidor tem varias formas mas a mais simples e no “estilo go” que eu encontrei foi pegando o endereço que é colocado no contexto da requisição.
adr := r.Context().Value(http.LocalAddrContextKey)
serverAddr, serverPort, err := net.SplitHostPort(
fmt.Sprintf("%v", adr))
if err != nil {
fmt.Println(err)
return
}
serverIP, serverZone := splitHostZone(serverAddr)
Para pegar o ip do cliente é mais tranquilo porque isso já vem na requisição então é só tratar o retorno como fizemos antes.
clientAddr, clientPort, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
fmt.Println(err)
return
}
clientIP, clientZone := splitHostZone(clientAddr)
Aqui tem um gist com o código do exemplo.