diff --git a/src/native/libs/System.Native/CMakeLists.txt b/src/native/libs/System.Native/CMakeLists.txt index b4d9703982431..e6ecf3514fdeb 100644 --- a/src/native/libs/System.Native/CMakeLists.txt +++ b/src/native/libs/System.Native/CMakeLists.txt @@ -83,7 +83,8 @@ include(${CMAKE_CURRENT_LIST_DIR}/extra_libs.cmake) set(NATIVE_LIBS_EXTRA) append_extra_system_libs(NATIVE_LIBS_EXTRA) -if (CLR_CMAKE_TARGET_ANDROID) +if (CLR_CMAKE_TARGET_ANDROID AND NOT HAVE_GETIFADDRS) + add_definitions(-DANDROID_GETIFADDRS_WORKAROUND) add_compile_options(-Wno-gnu-zero-variadic-macro-arguments) list (APPEND NATIVE_LIBS_EXTRA -llog) diff --git a/src/native/libs/System.Native/pal_ifaddrs.c b/src/native/libs/System.Native/pal_ifaddrs.c index 3e885d08114ee..b49edd513280e 100644 --- a/src/native/libs/System.Native/pal_ifaddrs.c +++ b/src/native/libs/System.Native/pal_ifaddrs.c @@ -708,7 +708,7 @@ static int parse_netlink_reply(struct netlink_session *session, struct ifaddrs * return ret; } -int getifaddrs(struct ifaddrs **ifap) +int _netlink_getifaddrs(struct ifaddrs **ifap) { int ret = -1; @@ -728,7 +728,7 @@ int getifaddrs(struct ifaddrs **ifap) (parse_netlink_reply(&session, &ifaddrs_head, &last_ifaddr) < 0) || (send_netlink_dump_request(&session, RTM_GETADDR) < 0) || (parse_netlink_reply(&session, &ifaddrs_head, &last_ifaddr) < 0)) { - freeifaddrs (ifaddrs_head); + _netlink_freeifaddrs (ifaddrs_head); goto cleanup; } @@ -744,7 +744,7 @@ int getifaddrs(struct ifaddrs **ifap) return ret; } -void freeifaddrs(struct ifaddrs *ifa) +void _netlink_freeifaddrs(struct ifaddrs *ifa) { struct ifaddrs *cur, *next; diff --git a/src/native/libs/System.Native/pal_ifaddrs.h b/src/native/libs/System.Native/pal_ifaddrs.h index e52a392b13d9d..0944eeb43eb9f 100644 --- a/src/native/libs/System.Native/pal_ifaddrs.h +++ b/src/native/libs/System.Native/pal_ifaddrs.h @@ -7,37 +7,15 @@ #error The pal_ifaddrs.h shim is intended only for Android #endif +#if __ANDROID_API__ >= 24 +#error The pal_ifaddrs.h shim is only necessary for Android API 21-23 and it should be removed now that the minimum supported API level is 24 or higher +#endif + // Android doesn't include the getifaddrs and freeifaddrs functions in older Bionic libc (pre API 24). -// In recent Android versions (Android 11+) the data returned by the getifaddrs function is not valid. // This shim is a port of Xamarin Android's implementation of getifaddrs using Netlink. +// https://github.com/xamarin/xamarin-android/blob/681887ebdbd192ce7ce1cd02221d4939599ba762/src/monodroid/jni/xamarin_getifaddrs.h -#include "pal_compiler.h" -#include "pal_config.h" -#include "pal_types.h" - -#include -#include -#include - -struct ifaddrs -{ - struct ifaddrs *ifa_next; - char *ifa_name; - unsigned int ifa_flags; - struct sockaddr *ifa_addr; - struct sockaddr *ifa_netmask; - union - { - struct sockaddr *ifu_broadaddr; - struct sockaddr *ifu_dstaddr; - } ifa_ifu; - void *ifa_data; -}; - -// Synonym for `ifa_ifu.ifu_broadaddr` in `struct ifaddrs`. -#define ifa_broadaddr ifa_ifu.ifu_broadaddr -// Synonym for `ifa_ifu.ifu_dstaddr` in `struct ifaddrs`. -#define ifa_dstaddr ifa_ifu.ifu_dstaddr +#include -int getifaddrs (struct ifaddrs **ifap); -void freeifaddrs (struct ifaddrs *ifap); +int _netlink_getifaddrs (struct ifaddrs **ifap); +void _netlink_freeifaddrs (struct ifaddrs *ifap); diff --git a/src/native/libs/System.Native/pal_interfaceaddresses.c b/src/native/libs/System.Native/pal_interfaceaddresses.c index fa05d575ef7ad..4af5d59ae96db 100644 --- a/src/native/libs/System.Native/pal_interfaceaddresses.c +++ b/src/native/libs/System.Native/pal_interfaceaddresses.c @@ -11,11 +11,13 @@ #include #include #include -#if HAVE_GETIFADDRS && !defined(TARGET_ANDROID) +#if HAVE_GETIFADDRS || defined(ANDROID_GETIFADDRS_WORKAROUND) #include #endif -#ifdef TARGET_ANDROID -#include "pal_ifaddrs.h" +#ifdef ANDROID_GETIFADDRS_WORKAROUND +#include +#include +#include "pal_ifaddrs.h" // fallback for Android API 21-23 #endif #include #include @@ -100,12 +102,56 @@ static inline uint8_t mask2prefix(uint8_t* mask, int length) return len; } +#ifdef ANDROID_GETIFADDRS_WORKAROUND +// This workaround is necessary as long as we support Android API 21-23 and it can be removed once +// we drop support for these old Android versions. +static int (*getifaddrs)(struct ifaddrs**) = NULL; +static void (*freeifaddrs)(struct ifaddrs*) = NULL; + +static void try_loading_getifaddrs() +{ + if (android_get_device_api_level() >= 24) + { + // Bionic on API 24+ contains the getifaddrs/freeifaddrs functions but the NDK doesn't expose those functions + // in ifaddrs.h when the minimum supported SDK is lower than 24 and therefore we need to load them manually + void *libc = dlopen("libc.so", RTLD_NOW); + if (libc) + { + getifaddrs = (int (*)(struct ifaddrs**)) dlsym(libc, "getifaddrs"); + freeifaddrs = (void (*)(struct ifaddrs*)) dlsym(libc, "freeifaddrs"); + } + } + else + { + // Bionic on API 21-23 doesn't contain the implementation of getifaddrs/freeifaddrs at all + // and we need to reimplement it using netlink (see pal_ifaddrs) + getifaddrs = _netlink_getifaddrs; + freeifaddrs = _netlink_freeifaddrs; + } +} + +static bool ensure_getifaddrs_is_loaded() +{ + static pthread_once_t getifaddrs_is_loaded = PTHREAD_ONCE_INIT; + pthread_once(&getifaddrs_is_loaded, try_loading_getifaddrs); + return getifaddrs != NULL && freeifaddrs != NULL; +} +#endif + int32_t SystemNative_EnumerateInterfaceAddresses(void* context, IPv4AddressFound onIpv4Found, IPv6AddressFound onIpv6Found, LinkLayerAddressFound onLinkLayerFound) { -#if HAVE_GETIFADDRS || defined(TARGET_ANDROID) +#ifdef ANDROID_GETIFADDRS_WORKAROUND + if (!ensure_getifaddrs_is_loaded()) + { + errno = ENOTSUP; + return -1; + } +#endif + +#if HAVE_GETIFADDRS || defined(ANDROID_GETIFADDRS_WORKAROUND) struct ifaddrs* headAddr; if (getifaddrs(&headAddr) == -1) { @@ -250,7 +296,15 @@ int32_t SystemNative_EnumerateInterfaceAddresses(void* context, int32_t SystemNative_GetNetworkInterfaces(int32_t * interfaceCount, NetworkInterfaceInfo **interfaceList, int32_t * addressCount, IpAddressInfo **addressList ) { -#if HAVE_GETIFADDRS || defined(TARGET_ANDROID) +#ifdef ANDROID_GETIFADDRS_WORKAROUND + if (!ensure_getifaddrs_is_loaded()) + { + errno = ENOTSUP; + return -1; + } +#endif + +#if HAVE_GETIFADDRS || defined(ANDROID_GETIFADDRS_WORKAROUND) struct ifaddrs* head; // Pointer to block allocated by getifaddrs(). struct ifaddrs* ifaddrsEntry; IpAddressInfo *ai; @@ -289,7 +343,16 @@ int32_t SystemNative_GetNetworkInterfaces(int32_t * interfaceCount, NetworkInter // To save allocation need for separate free() we will allocate one memory chunk // where we first write out NetworkInterfaceInfo entries immediately followed by // IpAddressInfo list. - void * memoryBlock = calloc((size_t)count, sizeof(NetworkInterfaceInfo)); +#ifdef TARGET_ANDROID + // Since Android API 30, getifaddrs returns only AF_INET and AF_INET6 addresses and we do not + // get any AF_PACKET addresses and so count == ip4count + ip6count. We need to make sure that + // the memoryBlock is large enough to hold all interfaces (up to `count` entries) and all + // addresses (ip4count + ip6count) without any overlap between interfaceList and addressList. + int entriesCount = count + ip4count + ip6count; +#else + int entriesCount = count; +#endif + void * memoryBlock = calloc((size_t)entriesCount, sizeof(NetworkInterfaceInfo)); if (memoryBlock == NULL) { errno = ENOMEM; @@ -300,7 +363,7 @@ int32_t SystemNative_GetNetworkInterfaces(int32_t * interfaceCount, NetworkInter ifaddrsEntry = head; *interfaceList = nii = (NetworkInterfaceInfo*)memoryBlock; // address of first IpAddressInfo after all NetworkInterfaceInfo entries. - *addressList = ai = (IpAddressInfo*)(nii + (count - ip4count - ip6count)); + *addressList = ai = (IpAddressInfo*)(nii + (entriesCount - ip4count - ip6count)); while (ifaddrsEntry != NULL) { @@ -324,7 +387,7 @@ int32_t SystemNative_GetNetworkInterfaces(int32_t * interfaceCount, NetworkInter memcpy(nii->Name, ifaddrsEntry->ifa_name, sizeof(nii->Name)); nii->InterfaceIndex = if_nametoindex(ifaddrsEntry->ifa_name); nii->Speed = -1; - nii->HardwareType = NetworkInterfaceType_Unknown; + nii->HardwareType = ((ifaddrsEntry->ifa_flags & IFF_LOOPBACK) == IFF_LOOPBACK) ? NetworkInterfaceType_Loopback : NetworkInterfaceType_Unknown; // Get operational state and multicast support. if ((ifaddrsEntry->ifa_flags & (IFF_MULTICAST|IFF_ALLMULTI)) != 0)