When a Go program is built without cgo, what handles hostname and user lookups instead of libc?
answer
- two implementations, one API
- Go speaks DNS itself now
- the switch file is never read
- local files only, no directory service
- resolv.conf and passwd are the sources
basics
~10 sGo's own implementations take over. The pure-Go resolver parses /etc/resolv.conf and /etc/hosts and speaks DNS itself, and os/user parses /etc/passwd and /etc/group. Neither consults nsswitch.conf or the host's pluggable name-service modules.
solid answer
~50 sThe `net` and `os/user` packages each ship two implementations of the host lookups. Without cgo the pure-Go ones are linked in: the netgo resolver reads `/etc/resolv.conf` for nameservers and `/etc/hosts` for static names, then sends DNS queries itself, and `os/user` parses `/etc/passwd` and `/etc/group` line by line. Your source does not change — `net.LookupHost` and `user.Lookup` are the same calls — but the machinery underneath does. What you lose is everything that lives behind the host's name-service switch: `nsswitch.conf` ordering is ignored, pluggable modules such as mDNS or a directory-service backend are never consulted, and accounts provisioned only in LDAP are simply not found. You also inherit Go's own defaults: with no `/etc/resolv.conf` at all, the pure-Go resolver falls back to localhost nameservers, so on a bare filesystem every lookup fails even though the network is fine.
code
go · 12 linesfunc resolvePeer(host, account string) error {
addrs, err := net.LookupHost(host)
if err != nil {
return err
}
u, err := user.Lookup(account)
if err != nil {
return err
}
log.Printf("%s -> %v, running as uid %s", host, addrs, u.Uid)
return nil
}go deeper
Know that disabling cgo does not remove hostname or user lookups; Go has its own implementations. Be able to say which files they read: the resolver configuration, the hosts file, and the local account files.
Explain the swap mechanically: two implementations behind one API, chosen at build time, with Go parsing the resolver configuration and speaking DNS itself instead of calling the host C library.
Demonstrate that you check the behavioural fallout before shipping the change: name-service-switch ordering and loadable modules are gone, directory-supplied accounts vanish, and a missing resolver configuration makes every lookup fail.
Frame it as what your artefact is allowed to depend on in the runtime environment, and make sure whoever owns host naming has agreed that Go's smaller view of it is acceptable.
## Two implementations of the same call Go's standard library contains, for a handful of host-facing lookups, both a Go implementation and one that delegates to the platform's C library. Which one is compiled in is decided at build time by whether cgo is available. Your code is identical either way; only the implementation behind the call changes. The two that matter in practice on Linux are hostname/address resolution in **`net`** and account lookups in **`os/user`**. ## What the pure-Go resolver does The pure-Go path — commonly called the netgo resolver — reimplements what a C resolver does, in Go: - reads `/etc/resolv.conf` to learn the nameservers, the search domains and a subset of the options a C resolver understands; - reads `/etc/hosts` for statically mapped names; - builds and sends DNS queries over UDP, falling back to TCP for truncated answers, and parses the replies itself. Because it is ordinary Go code, a lookup is just another goroutine doing network I/O: it can be cancelled, it does not occupy an OS thread while it waits, and it needs no C library. What it deliberately does **not** do is go through the host's name-service switch. On a glibc system, `/etc/nsswitch.conf` decides the order of sources for hosts and passwd and can name loadable modules — multicast DNS, a container runtime's own name source, a directory-service client. Those modules are shared objects that only the C library knows how to load. Go's resolver never reads that file and never loads those modules, so a name that only such a module can answer becomes unresolvable in a cgo-free build. ## What the pure-Go os/user does The cgo-free `os/user` parses the local account databases directly: `/etc/passwd` for users, `/etc/group` for groups. `user.Lookup`, `user.LookupId` and `user.Current` all work — for accounts that appear in those files. The gap is the same one: accounts that exist only behind the name-service switch, typically supplied by a directory service, are invisible. A C-backed build resolves them because the C library loads the module that queries the directory; the Go implementation only ever sees the file. A related surprise is a root filesystem that has no `/etc/passwd` at all — `user.Current` then has nothing to read and returns an error rather than a user. ## The failure mode this produces The characteristic report is *not* a crash. It is a service that starts cleanly, resolves most things fine, and quietly fails a subset: - one class of hostnames — the ones the local network's name-service module supplies — stops resolving, while public DNS names keep working; - `user.Lookup` returns "unknown user" for accounts that `id` on the same host resolves without trouble; - with no `/etc/resolv.conf` present, *everything* fails, because Go's resolver falls back to localhost nameservers and nothing is listening there. All three are behavioural differences between two implementations of the same API, which is why they survive a code review of the diff: the diff is in the build, not in the source. ## Why the swap is usually still worth it The pure-Go path buys a binary with no run-time C library dependency, which is what makes one artefact runnable on any host of the right architecture. It also makes name resolution behave the same everywhere, which is a genuine operational virtue: the resolver you tested is the resolver that runs, rather than whatever the deployment host's C library and modules happen to do. The cost is fidelity to the host's configuration. If the environment's naming really is defined by name-service modules or its accounts really do live in a directory, then a cgo-free build is not "the same program, statically linked" — it is a program that sees a smaller world. ## How to work around the gap without going back to cgo When only a small part of the lost behaviour matters, there are usually cheaper options than re-linking the C library: - ship the entries that matter into the runtime filesystem — a `/etc/hosts` line, a `/etc/passwd` entry for the account the process runs as, a `/etc/resolv.conf` pointing at the real nameservers; - query the authoritative source yourself over its own protocol instead of through the name-service switch; - keep the numeric identity: if the code only needs a uid to compare against, it may not need `os/user` at all. Each of those keeps the portable artefact and pays for the one behaviour you actually depend on, which is nearly always a better trade than restoring a C library dependency for the whole binary.
- Your peer accounts come from a directory service via the name-service switch. What does user.Lookup do in a cgo-free build?It fails with an unknown-user error. The pure-Go implementation only parses `/etc/passwd` and `/etc/group`, and directory-supplied accounts never appear there — the C library reaches them by loading a name-service module, which Go does not do. Options are to query the directory yourself, to materialise the one account you need locally, or to accept a C-linked binary for that service.
- A cgo-free service resolves nothing at all on a bare root filesystem, yet the network is healthy. Why?There is no `/etc/resolv.conf` to read, so Go's resolver falls back to its default of localhost nameservers, where nothing is listening. Every lookup fails immediately rather than reaching the real DNS servers. Shipping a resolver configuration into the runtime filesystem fixes it.
- Does disabling cgo change how /etc/hosts is treated?No — the pure-Go resolver reads `/etc/hosts` and honours static mappings, which is why hosts-file entries remain the simplest escape hatch for a name a cgo-free build cannot otherwise resolve. What is dropped is `nsswitch.conf` ordering and the loadable modules it can name, not the hosts file itself.
saying these in an interview costs you the question
- Claims name resolution stops working entirely without cgo
- Thinks the pure-Go resolver reads nsswitch.conf
- Says os/user cannot function at all without cgo
- Assumes the two implementations behave identically
- Believes Go falls back to the C library when its own lookup fails