ooni-probe-cli/internal/netxlite/getaddrinfo_linux.go

162 lines
6.3 KiB
Go
Raw Normal View History

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build cgo && linux
package netxlite
/*
// Both glibc and musl expose the EAI_NODATA error if we
// ask them to expose it through this define. See below for
// more details on how each of the supported libcs hides
// (or does not hide) the EAI_NODATA define.
#cgo CFLAGS: -D_GNU_SOURCE
#include <netdb.h>
*/
import "C"
import (
"runtime"
"syscall"
)
// Implementation note: the original Go codebase separated linux and android
// but we want them to be in the same file, so we can implement tests for both
// operating system and increase our confidence that the behavior will be the
// one we'd like to see on Android systems.
var getaddrinfoAIFlags = getaddrinfoGetPlatformSpecificAIFlags(runtime.GOOS)
// This function returns the platforms-specific AI flags that go1.17.6
// used to set when we merged resolver's code into ooni/probe-cli
//
// SPDX-License-Identifier: BSD-3-Clause.
func getaddrinfoGetPlatformSpecificAIFlags(goos string) C.int {
switch goos {
case "android":
return C.AI_CANONNAME
default:
// NOTE(rsc): In theory there are approximately balanced
// arguments for and against including AI_ADDRCONFIG
// in the flags (it includes IPv4 results only on IPv4 systems,
// and similarly for IPv6), but in practice setting it causes
// getaddrinfo to return the wrong canonical name on Linux.
// So definitely leave it out.
return C.AI_CANONNAME | C.AI_V4MAPPED | C.AI_ALL
}
}
// Making constants available to Go code so we can run tests (it seems
// it's not possible to import C directly in tests, sadly).
const (
aiCanonname = C.AI_CANONNAME
aiV4Mapped = C.AI_V4MAPPED
aiAll = C.AI_ALL
eaiSystem = C.EAI_SYSTEM
eaiNoName = C.EAI_NONAME
eaiBadFlags = C.EAI_BADFLAGS
eaiNoData = C.EAI_NODATA
)
// toError is the function that converts the return value from
// the getaddrinfo function into a proper Go error.
//
// This function is adapted from cgoLookupIPCNAME
// https://github.com/golang/go/blob/go1.17.6/src/net/cgo_unix.go#L145
//
// SPDX-License-Identifier: BSD-3-Clause.
func (state *getaddrinfoState) toError(code int64, err error, goos string) error {
switch code {
case C.EAI_SYSTEM:
if err == nil {
// err should not be nil, but sometimes getaddrinfo returns
// code == C.EAI_SYSTEM with err == nil on Linux.
// The report claims that it happens when we have too many
// open files, so use syscall.EMFILE (too many open files in system).
// Most system calls would return ENFILE (too many open files),
// so at the least EMFILE should be easy to recognize if this
// comes up again. golang.org/issue/6232.
err = syscall.EMFILE
}
return newErrGetaddrinfo(code, err)
case C.EAI_NONAME:
return newErrGetaddrinfo(code, ErrOODNSNoSuchHost)
case C.EAI_NODATA:
return state.toErrorNODATA(err, goos)
default:
return newErrGetaddrinfo(code, ErrOODNSMisbehaving)
}
}
// toErrorNODATA maps the EAI_NODATA value to the proper return value
// depending on the underlying operating system.
//
// As of 2022-05-28, this is the status of the major C libraries whose
// getaddrinfo return value we may end up processing here:
//
// 1. musl libc (statically linked Linux builds for official OONI
// Probe packages we build): EAI_NODATA is defined in netdb.h in a
// section guarded by _GNU_SOURCE and _BSD_SOURCE and the code
// does not otherwise ever use this definition.
//
// 2. GNU libc (which is what you would get if you compile OONI Probe
// for yourself in a GNU/Linux system): the codebase defines EAI_NODATA
// inside netdb.h protected by __USE_GNU, which is defined to 1 in
// include/features.h if the user defines _GNU_SOURCE. Additionally,
// the getaddrinfo implementation returns EAI_NODATA when a name
// exists but there's no associated address for such a name. There
// was a bug, fixed in glibc 2.27, were EAI_NONAME was returned
// when EAI_NODATA would actually have been more proper:
//
// https://sourceware.org/bugzilla/show_bug.cgi?id=21922
//
// 3. Android libc: EAI_NODATA is defined in netdb.h and is not
// protected by any feature flag. The getaddrinfo function (as
// of 4ebdeebef74) calls android_getaddrinfofornet, which in turns
// calls android_getaddrinfofornetcontext. This function will
// eventually call android_getaddrinfo_proxy. If this function
// returns any status code different from EAI_SYSTEM, then bionic
// will return its return value. Otherwise, the code ends up
// calling explore_fqdn, which in turn calls nsdispatch, which
// is what NetBSD is still doing today.
//
// So, android_getaddrinfo_proxy was introduced a long time
// ago on October 28, 2010 by this commit:
//
// https://github.com/aosp-mirror/platform_bionic/commit/a1dbf0b453801620565e5911f354f82706b0200d
//
// Then a subsequent commit changed android_getaddrinfo_proxy
// to basically default to EAI_NODATA on proxy errors:
//
// https://github.com/aosp-mirror/platform_bionic/commit/c63e59039d28c352e3053bb81319e960c392dbd4
//
// As of today and 4ebdeebef74, android_getaddrinfo_proxy returns
// one of the following possible return codes:
//
// a) 0 on success;
//
// b) EAI_SYSTEM if it cannot speak to the proxy (which causes the code
// to fall through to the original NetBSD implementation);
//
// c) EAI_NODATA in all the other cases.
//
// The above discussion about Android provides us with a theory that explains the
// https://github.com/ooni/probe/issues/2029 issue. That said, we are still missing
// some bits, e.g., why some Android 6 phones did not experience this problem.
//
// We originally proposed to handle the EAI_NODATA error on Android like it was a
// EAI_NONAME error. However, this mapping seems very inaccurate. Any error inside
// the DNS proxy could cause EAI_NODATA (_unless_ we're "lucky" for some reason
// and the original NetBSD code runs). Therefore, the sanest choice is to introduce
// a new OONI error describing this error condition `android_dns_cache_no_data`
// and handle this error as a special case when checking for NXDOMAIN.
func (state *getaddrinfoState) toErrorNODATA(err error, goos string) error {
switch goos {
case "android":
return newErrGetaddrinfo(C.EAI_NODATA, ErrAndroidDNSCacheNoData)
default:
return newErrGetaddrinfo(C.EAI_NODATA, ErrOODNSNoAnswer)
}
}