Skip to content

Instantly share code, notes, and snippets.

@aojea
Last active May 31, 2022 09:43
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aojea/94f6f483173641647c731f582e52f0b0 to your computer and use it in GitHub Desktop.
Save aojea/94f6f483173641647c731f582e52f0b0 to your computer and use it in GitHub Desktop.
localhost resolution in go
GODEBUG=netdns=cgo+2 go run listen.go
GOOS: linux
go package net: using cgo DNS resolver
go package net: hostLookupOrder(localhost) = cgo
net.LookupHost addrs: [::1 127.0.0.1] err: <nil>
go package net: hostLookupOrder(localhost) = cgo
Listener address: 127.0.0.1:8080
go package net: hostLookupOrder(localhost) = cgo
Dial remote address: 127.0.0.1:8080
GODEBUG=netdns=go+2 go run listen.go
GOOS: linux
go package net: GODEBUG setting forcing use of Go's resolver
go package net: hostLookupOrder(localhost) = files,dns
net.LookupHost addrs: [127.0.0.1 ::1] err: <nil>
go package net: hostLookupOrder(localhost) = files,dns
Listener address: 127.0.0.1:8080
go package net: hostLookupOrder(localhost) = files,dns
Dial remote address: 127.0.0.1:8080

Hostnames can have different IP addresses, however the go net.Listen() function

will create a listener for at most one of the host's IP addresses.

https://github.com/golang/go/blob/master/src/net/dial.go#L692-694

This is a problem if we want to create a listener and make our application dual stack, because we'll need to listen on both IPv4 and IPv6 addresses.

A common situation is creating a listener on localhost, that use to resolve to 127.0.0.1 and ::1 on a dual stack system. Typically, those entries are hardcoded in the /etc/hosts file.

It can happen that it resolves preferently one or other family depending on several factors, creating an inconsistency that can cause portability problems or incompatibilities between versions and/or dependencies.

Golang solve this giving preference to IPv4, the functions Dial and Listen first resolve all the host name ip addresses and then pick the first IPv4 address.

https://github.com/golang/go/blob/master/src/net/dial.go#L631

https://github.com/golang/go/blob/master/src/net/dial.go#L661

https://github.com/golang/go/blob/master/src/net/dial.go#L414-L415

We can see with the above code that the cgo resolver returns first the IPv6 address:

GODEBUG=netdns=cgo+2 go run listen.go 
GOOS: linux
go package net: using cgo DNS resolver
go package net: hostLookupOrder(localhost) = cgo
net.LookupHost addrs: [::1 127.0.0.1] err: <nil>
go package net: hostLookupOrder(localhost) = cgo
Listener address:  127.0.0.1:8080
go package net: hostLookupOrder(localhost) = cgo
Dial remote address:  127.0.0.1:8080

and the go resolver returns first the IPv4 address:

GODEBUG=netdns=go+2 go run listen.go 
GOOS: linux
go package net: GODEBUG setting forcing use of Go's resolver
go package net: hostLookupOrder(localhost) = files,dns
net.LookupHost addrs: [127.0.0.1 ::1] err: <nil>
go package net: hostLookupOrder(localhost) = files,dns
Listener address:  127.0.0.1:8080
go package net: hostLookupOrder(localhost) = files,dns
Dial remote address:  127.0.0.1:8080

However, the functions Dial and Listen always use the IPv4 address.

The common practice for opening a listener on a dual stack system seems to open one socket per protocol, but that's something that requires more investigation.

An example of this approach is the client-go mplementationg of the PortForwarder server

https://github.com/kubernetes/client-go/blob/master/tools/portforward/portforward.go#L125-L140

Ref:

golang/go#9334

https://groups.google.com/forum/#!topic/golang-nuts/Wapy7nWX408

package main
import (
"fmt"
"net"
"runtime"
)
func main() {
fmt.Println("GOOS:", runtime.GOOS)
// Resolver
addrs, err := net.LookupHost("localhost")
fmt.Println("net.LookupHost addrs:", addrs, "err:", err)
// Listener
listener, err := net.Listen("tcp", "localhost:8080")
if err != nil {
// handle err
}
listenerAddress := listener.Addr().String()
fmt.Println("Listener: ", listenerAddress)
// Dial
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
// handle error
}
fmt.Println("Connecting to: ", conn.RemoteAddr().String())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment