Descobrindo ip com Golang

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 a 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.

Esse código funciona bem para o meu caso em que tudo acontece dentro de uma rede interna, mas ele não serve se houver um proxy no meio do caminho. Não seria muito difícil incluir o suporta a proxy usando os cabeçalhos como X-ProxyUser-Ip mas isso esta alem do que eu preciso.

Cesar Gimenes