Skip to content

Commit

Permalink
[release/6.0][Android] Backport fix NetworkInterface.GetAllNetworkInt…
Browse files Browse the repository at this point in the history
…erfaces (#77260)

* [Android] Fix NetworkInterface.GetAllNetworkInterfaces (#76370)

* Revert "[Android] Port getifaddrs implementation from Xamarin.Android (#71943)"

This reverts commit 1de4a5c.

* Fix allocating memory block for interfaces and addresses on recent Android SDKs

* Detect loopback interface on Android

* Add comment with explanation

* Simplify the changes to be closer to the original code

* Fix build

* Fix typos

Co-authored-by: Alexander Köplinger <alex.koeplinger@outlook.com>

* Improve comment

* Indent using spaces instead of tabs

* Remove check for ifaddrs.h

* Add ANDROID_GETIFADDRS_WORKAROUND

* Update comment

Co-authored-by: Alexander Köplinger <alex.koeplinger@outlook.com>

* [Android] Fix NetworkInterface.GetAllNetworkInterfaces on API 21-23 (#76541)

* Bring back pal_ifaddrs

* Update the header file

Co-authored-by: Alexander Köplinger <alex.koeplinger@outlook.com>
  • Loading branch information
simonrozsival and akoeplinger authored Nov 3, 2022
1 parent 526eafa commit 6376e01
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 48 deletions.
3 changes: 2 additions & 1 deletion src/libraries/Native/Unix/System.Native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,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)
Expand Down
18 changes: 9 additions & 9 deletions src/libraries/Native/Unix/System.Native/pal_ifaddrs.c
Original file line number Diff line number Diff line change
Expand Up @@ -312,15 +312,15 @@ static struct ifaddrs *get_link_info(struct nlmsghdr *message)
break;

case IFLA_BROADCAST:
LOG_DEBUG(" interface broadcast (%lu bytes)\n", RTA_PAYLOAD(attribute));
LOG_DEBUG(" interface broadcast (%zu bytes)\n", RTA_PAYLOAD(attribute));
if (fill_ll_address(&sa, net_interface, RTA_DATA(attribute), RTA_PAYLOAD(attribute)) < 0) {
goto error;
}
ifa->ifa_broadaddr = (struct sockaddr*)sa;
break;

case IFLA_ADDRESS:
LOG_DEBUG(" interface address (%lu bytes)\n", RTA_PAYLOAD(attribute));
LOG_DEBUG(" interface address (%zu bytes)\n", RTA_PAYLOAD(attribute));
if (fill_ll_address(&sa, net_interface, RTA_DATA(attribute), RTA_PAYLOAD(attribute)) < 0) {
goto error;
}
Expand Down Expand Up @@ -352,7 +352,7 @@ static struct ifaddrs *find_interface_by_index(int index, struct ifaddrs **ifadd
return NULL;

/* Normally expensive, but with the small amount of links in the chain we'll deal with it's not
* worth the extra houskeeping and memory overhead
* worth the extra housekeeping and memory overhead
*/
cur = *ifaddrs_head;
while (cur) {
Expand Down Expand Up @@ -509,7 +509,7 @@ static struct ifaddrs *get_link_address(struct nlmsghdr *message, struct ifaddrs
LOG_DEBUG(" attribute type: LOCAL");
if (ifa->ifa_addr) {
/* P2P protocol, set the dst/broadcast address union from the original address.
* Since ifa_addr is set it means IFA_ADDRESS occured earlier and that address
* Since ifa_addr is set it means IFA_ADDRESS occurred earlier and that address
* is indeed the P2P destination one.
*/
ifa->ifa_dstaddr = ifa->ifa_addr;
Expand All @@ -531,7 +531,7 @@ static struct ifaddrs *get_link_address(struct nlmsghdr *message, struct ifaddrs
case IFA_ADDRESS:
LOG_DEBUG(" attribute type: ADDRESS");
if (ifa->ifa_addr) {
/* Apparently IFA_LOCAL occured earlier and we have a P2P connection
/* Apparently IFA_LOCAL occurred earlier and we have a P2P connection
* here. IFA_LOCAL carries the destination address, move it there
*/
ifa->ifa_dstaddr = ifa->ifa_addr;
Expand Down Expand Up @@ -570,7 +570,7 @@ static struct ifaddrs *get_link_address(struct nlmsghdr *message, struct ifaddrs
attribute = RTA_NEXT(attribute, length);
}

/* glibc stores the associated interface name in the address if IFA_LABEL never occured */
/* glibc stores the associated interface name in the address if IFA_LABEL never occurred */
if (!ifa->ifa_name) {
char *name = get_interface_name_by_index((int)(net_address->ifa_index), ifaddrs_head);
LOG_DEBUG(" address has no name/label, getting one from interface\n");
Expand Down Expand Up @@ -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;

Expand All @@ -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;
}

Expand All @@ -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;

Expand Down
38 changes: 8 additions & 30 deletions src/libraries/Native/Unix/System.Native/pal_ifaddrs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <sys/cdefs.h>
#include <netinet/in.h>
#include <sys/socket.h>

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 <ifaddrs.h>

int getifaddrs (struct ifaddrs **ifap);
void freeifaddrs (struct ifaddrs *ifap);
int _netlink_getifaddrs (struct ifaddrs **ifap);
void _netlink_freeifaddrs (struct ifaddrs *ifap);
79 changes: 71 additions & 8 deletions src/libraries/Native/Unix/System.Native/pal_interfaceaddresses.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
#include <stdlib.h>
#include <sys/types.h>
#include <assert.h>
#if HAVE_GETIFADDRS && !defined(TARGET_ANDROID)
#if HAVE_GETIFADDRS || defined(ANDROID_GETIFADDRS_WORKAROUND)
#include <ifaddrs.h>
#endif
#ifdef TARGET_ANDROID
#include "pal_ifaddrs.h"
#ifdef ANDROID_GETIFADDRS_WORKAROUND
#include <dlfcn.h>
#include <pthread.h>
#include "pal_ifaddrs.h" // fallback for Android API 21-23
#endif
#include <net/if.h>
#include <netinet/in.h>
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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)
{
Expand All @@ -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)
Expand Down

0 comments on commit 6376e01

Please sign in to comment.