162 lines
6.3 KiB
Go
162 lines
6.3 KiB
Go
|
// 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)
|
||
|
}
|
||
|
}
|