// 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 */ 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) } }