From 1de4a5ca073da24211fc4d427b2224da2212f372 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Tue, 12 Jul 2022 19:57:52 +0200 Subject: [PATCH] [Android] Port getifaddrs implementation from Xamarin.Android (#71943) In recent Android versions the data returned by getifaddrs is invalid and doesn't allow us to implement System.Net.NetworkInformation.GetAllNetworkInterfaces(). It's possible to reimplement getifaddrs using Netlink and there's already an existing implementation in Xamarin.Android. Fixes xamarin/xamarin-android#6973 Ref #62780 Ref #51303 --- src/mono/cmake/config.h.in | 3 - src/native/libs/Common/pal_config.h.in | 1 - src/native/libs/System.Native/CMakeLists.txt | 7 + src/native/libs/System.Native/pal_ifaddrs.c | 760 ++++++++++++++++++ src/native/libs/System.Native/pal_ifaddrs.h | 43 + .../System.Native/pal_interfaceaddresses.c | 74 +- src/native/libs/configure.cmake | 12 - 7 files changed, 815 insertions(+), 85 deletions(-) create mode 100644 src/native/libs/System.Native/pal_ifaddrs.c create mode 100644 src/native/libs/System.Native/pal_ifaddrs.h diff --git a/src/mono/cmake/config.h.in b/src/mono/cmake/config.h.in index 1c99ed73089d7..81ff6697c2a27 100644 --- a/src/mono/cmake/config.h.in +++ b/src/mono/cmake/config.h.in @@ -517,9 +517,6 @@ /* Have getifaddrs */ #cmakedefine HAVE_GETIFADDRS 1 -/* Have struct ifaddrs */ -#cmakedefine HAVE_IFADDRS 1 - /* Have access */ #cmakedefine HAVE_ACCESS 1 diff --git a/src/native/libs/Common/pal_config.h.in b/src/native/libs/Common/pal_config.h.in index 3f5b8c3627edc..6243761d8600d 100644 --- a/src/native/libs/Common/pal_config.h.in +++ b/src/native/libs/Common/pal_config.h.in @@ -11,7 +11,6 @@ #cmakedefine01 HAVE_F_FULLFSYNC #cmakedefine01 HAVE_O_CLOEXEC #cmakedefine01 HAVE_GETIFADDRS -#cmakedefine01 HAVE_IFADDRS #cmakedefine01 HAVE_UTSNAME_DOMAINNAME #cmakedefine01 HAVE_STAT64 #cmakedefine01 HAVE_FORK diff --git a/src/native/libs/System.Native/CMakeLists.txt b/src/native/libs/System.Native/CMakeLists.txt index 19466c7572dbd..b4d9703982431 100644 --- a/src/native/libs/System.Native/CMakeLists.txt +++ b/src/native/libs/System.Native/CMakeLists.txt @@ -83,6 +83,13 @@ include(${CMAKE_CURRENT_LIST_DIR}/extra_libs.cmake) set(NATIVE_LIBS_EXTRA) append_extra_system_libs(NATIVE_LIBS_EXTRA) +if (CLR_CMAKE_TARGET_ANDROID) + add_compile_options(-Wno-gnu-zero-variadic-macro-arguments) + + list (APPEND NATIVE_LIBS_EXTRA -llog) + list (APPEND NATIVE_SOURCES pal_ifaddrs.c) +endif () + if (GEN_SHARED_LIB) add_library(System.Native SHARED diff --git a/src/native/libs/System.Native/pal_ifaddrs.c b/src/native/libs/System.Native/pal_ifaddrs.c new file mode 100644 index 0000000000000..9ef4ca3abda4e --- /dev/null +++ b/src/native/libs/System.Native/pal_ifaddrs.c @@ -0,0 +1,760 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_ifaddrs.h" +#include "pal_safecrt.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#define LOG(level, ...) __android_log_print(level, "DOTNET_NETLINK", ## __VA_ARGS__) +#define LOG_INFO(...) LOG(ANDROID_LOG_INFO, ## __VA_ARGS__) +#define LOG_WARN(...) LOG(ANDROID_LOG_WARN, ## __VA_ARGS__) +#ifdef DEBUG +#define LOG_DEBUG(...) LOG(ANDROID_LOG_DEBUG, ## __VA_ARGS__) +#else +#define LOG_DEBUG(...) do {} while (0) +#endif + +/* Maximum interface address label size, should be more than enough */ +#define MAX_IFA_LABEL_SIZE 1024 + +/* This is the message we send to the kernel */ +struct netlink_request { + struct nlmsghdr header; + struct rtgenmsg message; +}; + +struct netlink_session { + int sock_fd; + int seq; + struct sockaddr_nl them; /* kernel end */ + struct sockaddr_nl us; /* our end */ + struct msghdr message_header; /* for use with sendmsg */ + struct iovec payload_vector; /* Used to send netlink_request */ +}; + +struct sockaddr_ll_extended { + unsigned short int sll_family; + unsigned short int sll_protocol; + int sll_ifindex; + unsigned short int sll_hatype; + unsigned char sll_pkttype; + unsigned char sll_halen; + unsigned char sll_addr[24]; +}; + +static void free_single_ifaddrs(struct ifaddrs **ifap) +{ + struct ifaddrs *ifa = ifap ? *ifap : NULL; + if (!ifa) + return; + + if (ifa->ifa_name) + free(ifa->ifa_name); + + if (ifa->ifa_addr) + free(ifa->ifa_addr); + + if (ifa->ifa_netmask) + free(ifa->ifa_netmask); + + if (ifa->ifa_broadaddr) + free(ifa->ifa_broadaddr); + + if (ifa->ifa_data) + free(ifa->ifa_data); + + free(ifa); + *ifap = NULL; +} + +static int open_netlink_session(struct netlink_session *session) +{ + abort_if_invalid_pointer_argument(session); + + memset(session, 0, sizeof(*session)); + session->sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (session->sock_fd == -1) { + LOG_WARN("Failed to create a netlink socket. %s\n", strerror(errno)); + return -1; + } + + /* Fill out addresses */ + session->us.nl_family = AF_NETLINK; + + /* We have previously used `getpid()` here but it turns out that WebView/Chromium does the same + and there can only be one session with the same PID. Setting it to 0 will cause the kernel to + assign some PID that's unique and valid instead. + See: https://bugzilla.xamarin.com/show_bug.cgi?id=41860 + */ + session->us.nl_pid = 0; + session->us.nl_groups = 0; + + session->them.nl_family = AF_NETLINK; + + if (bind (session->sock_fd, (struct sockaddr *)&session->us, sizeof(session->us)) < 0) { + LOG_WARN("Failed to bind to the netlink socket. %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +static int send_netlink_dump_request(struct netlink_session *session, int type) +{ + struct netlink_request request; + + memset(&request, 0, sizeof(request)); + request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); + /* Flags (from netlink.h): + NLM_F_REQUEST - it's a request message + NLM_F_DUMP - gives us the root of the link tree and returns all links matching our requested + AF, which in our case means all of them (AF_PACKET) + */ + request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_MATCH; + request.header.nlmsg_seq = (uint32_t)++session->seq; + request.header.nlmsg_pid = session->us.nl_pid; + request.header.nlmsg_type = (uint16_t)type; + + /* AF_PACKET means we want to see everything */ + request.message.rtgen_family = AF_PACKET; + + memset(&session->payload_vector, 0, sizeof(session->payload_vector)); + session->payload_vector.iov_len = request.header.nlmsg_len; + session->payload_vector.iov_base = &request; + + memset(&session->message_header, 0, sizeof(session->message_header)); + session->message_header.msg_namelen = sizeof(session->them); + session->message_header.msg_name = &session->them; + session->message_header.msg_iovlen = 1; + session->message_header.msg_iov = &session->payload_vector; + + if (sendmsg(session->sock_fd, (const struct msghdr*)&session->message_header, 0) < 0) { + LOG_WARN("Failed to send netlink message. %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +static int append_ifaddr(struct ifaddrs *addr, struct ifaddrs **ifaddrs_head, struct ifaddrs **last_ifaddr) +{ + abort_if_invalid_pointer_argument(addr); + abort_if_invalid_pointer_argument(ifaddrs_head); + abort_if_invalid_pointer_argument(last_ifaddr); + + if (addr->ifa_name == NULL) { + LOG_WARN("ifa_name is NULL -- skipping"); + return 0; // skip this addr + } + + if (!*ifaddrs_head) { + *ifaddrs_head = *last_ifaddr = addr; + if (!*ifaddrs_head) + return -1; + } else if (!*last_ifaddr) { + struct ifaddrs *last = *ifaddrs_head; + + while (last->ifa_next) + last = last->ifa_next; + *last_ifaddr = last; + } + + addr->ifa_next = NULL; + if (addr == *last_ifaddr) + return 0; + + (*last_ifaddr)->ifa_next = addr; + *last_ifaddr = addr; + + return 0; +} + +static int fill_sa_address(struct sockaddr **sa, struct ifaddrmsg *net_address, void *rta_data, size_t rta_payload_length) +{ + abort_if_invalid_pointer_argument(sa); + abort_if_invalid_pointer_argument(net_address); + abort_if_invalid_pointer_argument(rta_data); + + switch (net_address->ifa_family) { + case AF_INET: { + struct sockaddr_in *sa4; + if (rta_payload_length != 4) /* IPv4 address length */ { + LOG_WARN("Unexpected IPv4 address payload length %zu", rta_payload_length); + return -1; + } + sa4 = (struct sockaddr_in*)calloc(1, sizeof(*sa4)); + if (sa4 == NULL) + return -1; + + sa4->sin_family = AF_INET; + memcpy(&sa4->sin_addr, rta_data, rta_payload_length); + *sa = (struct sockaddr*)sa4; + break; + } + + case AF_INET6: { + struct sockaddr_in6 *sa6; + if (rta_payload_length != 16) /* IPv6 address length */ { + LOG_WARN("Unexpected IPv6 address payload length %zu", rta_payload_length); + return -1; + } + sa6 = (struct sockaddr_in6*)calloc(1, sizeof(*sa6)); + if (sa6 == NULL) + return -1; + + sa6->sin6_family = AF_INET6; + memcpy(&sa6->sin6_addr, rta_data, rta_payload_length); + if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr) || IN6_IS_ADDR_MC_LINKLOCAL (&sa6->sin6_addr)) + sa6->sin6_scope_id = net_address->ifa_index; + *sa = (struct sockaddr*)sa6; + break; + } + + default: { + struct sockaddr *sagen; + if (rta_payload_length > sizeof(sagen->sa_data)) { + LOG_WARN("Unexpected RTA payload length %zu (wanted at most %zu)", rta_payload_length, sizeof(sagen->sa_data)); + return -1; + } + + *sa = sagen = (struct sockaddr*)calloc(1, sizeof(*sagen)); + if (!sagen) + return -1; + + sagen->sa_family = net_address->ifa_family; + memcpy(&sagen->sa_data, rta_data, rta_payload_length); + break; + } + } + + return 0; +} + +static int fill_ll_address(struct sockaddr_ll_extended **sa, struct ifinfomsg *net_interface, void *rta_data, size_t rta_payload_length) +{ + abort_if_invalid_pointer_argument(sa); + abort_if_invalid_pointer_argument(net_interface); + + /* Always allocate, do not free - caller may reuse the same variable */ + *sa = (struct sockaddr_ll_extended*)calloc(1, sizeof(**sa)); + if (!*sa) + return -1; + + (*sa)->sll_family = AF_PACKET; /* Always for physical links */ + + /* The assert can only fail for Iniband links, which are quite unlikely to be found + * in any mobile devices + */ + LOG_DEBUG("rta_payload_length == %zu; sizeof sll_addr == %zu; hw type == 0x%X\n", rta_payload_length, sizeof((*sa)->sll_addr), net_interface->ifi_type); + if ((size_t)(rta_payload_length) > sizeof((*sa)->sll_addr)) { + LOG_INFO("Address is too long to place in sockaddr_ll (%zu > %zu)", rta_payload_length, sizeof((*sa)->sll_addr)); + free(*sa); + *sa = NULL; + return -1; + } + + if (rta_payload_length > UCHAR_MAX) { + LOG_INFO("Payload length too big to fit in the address structure"); + free(*sa); + *sa = NULL; + return -1; + } + + (*sa)->sll_ifindex = net_interface->ifi_index; + (*sa)->sll_hatype = net_interface->ifi_type; + (*sa)->sll_halen = (unsigned char)(rta_payload_length); + memcpy((*sa)->sll_addr, rta_data, rta_payload_length); + + return 0; +} + + +static struct ifaddrs *get_link_info(struct nlmsghdr *message) +{ + ssize_t length; + struct rtattr *attribute; + struct ifinfomsg *net_interface; + struct ifaddrs *ifa = NULL; + struct sockaddr_ll_extended *sa = NULL; + + abort_if_invalid_pointer_argument(message); + net_interface = (struct ifinfomsg*)(NLMSG_DATA(message)); + length = (ssize_t)(message->nlmsg_len - NLMSG_LENGTH(sizeof(*net_interface))); + if (length <= 0) { + goto error; + } + + ifa = (struct ifaddrs*)calloc(1, sizeof(*ifa)); + if (!ifa) { + goto error; + } + + ifa->ifa_flags = net_interface->ifi_flags; + attribute = IFLA_RTA(net_interface); + while (RTA_OK(attribute, length)) { + switch (attribute->rta_type) { + case IFLA_IFNAME: + ifa->ifa_name = strdup((const char*)(RTA_DATA(attribute))); + if (!ifa->ifa_name) { + goto error; + } + LOG_DEBUG(" interface name (payload length: %zu; string length: %zu)\n", RTA_PAYLOAD(attribute), strlen(ifa->ifa_name)); + LOG_DEBUG(" %s\n", ifa->ifa_name); + break; + + case IFLA_BROADCAST: + LOG_DEBUG(" interface broadcast (%lu 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)); + if (fill_ll_address(&sa, net_interface, RTA_DATA(attribute), RTA_PAYLOAD(attribute)) < 0) { + goto error; + } + ifa->ifa_addr = (struct sockaddr*)sa; + break; + + default: + break; + } + + attribute = RTA_NEXT (attribute, length); + } + + LOG_DEBUG("link flags: 0x%X", ifa->ifa_flags); + return ifa; + + error: + if (sa) + free(sa); + free_single_ifaddrs(&ifa); + + return NULL; +} + +static struct ifaddrs *find_interface_by_index(int index, struct ifaddrs **ifaddrs_head) +{ + struct ifaddrs *cur; + if (!ifaddrs_head || !*ifaddrs_head) + 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 + */ + cur = *ifaddrs_head; + while (cur) { + if (cur->ifa_addr && cur->ifa_addr->sa_family == AF_PACKET && ((struct sockaddr_ll_extended*)cur->ifa_addr)->sll_ifindex == index) + return cur; + if (cur == cur->ifa_next) + break; + cur = cur->ifa_next; + } + + return NULL; +} + +static char *get_interface_name_by_index(int index, struct ifaddrs **ifaddrs_head) +{ + struct ifaddrs *iface = find_interface_by_index(index, ifaddrs_head); + if (!iface || !iface->ifa_name) + return NULL; + + return iface->ifa_name; +} + +static int get_interface_flags_by_index(int index, struct ifaddrs **ifaddrs_head) +{ + struct ifaddrs *iface = find_interface_by_index(index, ifaddrs_head); + if (!iface) + return 0; + + return (int)(iface->ifa_flags); +} + +static int calculate_address_netmask(struct ifaddrs *ifa, struct ifaddrmsg *net_address) +{ + if (ifa->ifa_addr && ifa->ifa_addr->sa_family != AF_UNSPEC && ifa->ifa_addr->sa_family != AF_PACKET) { + uint32_t prefix_length = 0; + uint32_t data_length = 0; + unsigned char *netmask_data = NULL; + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: { + struct sockaddr_in *sa = (struct sockaddr_in*)calloc(1, sizeof(struct sockaddr_in)); + if (!sa) + return -1; + + ifa->ifa_netmask = (struct sockaddr*)sa; + prefix_length = net_address->ifa_prefixlen; + if (prefix_length > 32) + prefix_length = 32; + data_length = sizeof(sa->sin_addr); + netmask_data = (unsigned char*)&sa->sin_addr; + break; + } + + case AF_INET6: { + struct sockaddr_in6 *sa = (struct sockaddr_in6*)calloc(1, sizeof(struct sockaddr_in6)); + if (!sa) + return -1; + + ifa->ifa_netmask = (struct sockaddr*)sa; + prefix_length = net_address->ifa_prefixlen; + if (prefix_length > 128) + prefix_length = 128; + data_length = sizeof(sa->sin6_addr); + netmask_data = (unsigned char*)&sa->sin6_addr; + break; + } + } + + if (ifa->ifa_netmask && netmask_data) { + /* Fill the first X bytes with 255 */ + uint32_t prefix_bytes = prefix_length / 8; + uint32_t postfix_bytes; + + if (prefix_bytes > data_length) { + errno = EINVAL; + return -1; + } + postfix_bytes = data_length - prefix_bytes; + memset(netmask_data, 0xFF, prefix_bytes); + if (postfix_bytes > 0) + memset(netmask_data + prefix_bytes + 1, 0x00, postfix_bytes); + LOG_DEBUG(" calculating netmask, prefix length is %u bits (%u bytes), data length is %u bytes\n", prefix_length, prefix_bytes, data_length); + if (prefix_bytes + 2 < data_length) + /* Set the rest of the mask bits in the byte following the last 0xFF value */ + netmask_data[prefix_bytes + 1] = (unsigned char)(0xff << (8 - (prefix_length % 8))); + } + } + + return 0; +} + + +static struct ifaddrs *get_link_address(struct nlmsghdr *message, struct ifaddrs **ifaddrs_head) +{ + ssize_t length = 0; + struct rtattr *attribute; + struct ifaddrmsg *net_address; + struct ifaddrs *ifa = NULL; + struct sockaddr **sa; + size_t payload_size; + + abort_if_invalid_pointer_argument(message); + net_address = (struct ifaddrmsg*)(NLMSG_DATA(message)); + length = (ssize_t)(IFA_PAYLOAD(message)); + LOG_DEBUG(" address data length: %zu", length); + if (length <= 0) { + goto error; + } + + ifa = (struct ifaddrs*)(calloc(1, sizeof(*ifa))); + if (!ifa) { + goto error; + } + + // values < 0 are never returned, the cast is safe + ifa->ifa_flags = (unsigned int)(get_interface_flags_by_index((int)(net_address->ifa_index), ifaddrs_head)); + + attribute = IFA_RTA(net_address); + LOG_DEBUG(" reading attributes"); + while (RTA_OK(attribute, length)) { + payload_size = RTA_PAYLOAD(attribute); + LOG_DEBUG(" attribute payload_size == %zu\n", payload_size); + sa = NULL; + + switch (attribute->rta_type) { + case IFA_LABEL: { + size_t room_for_trailing_null = 0; + + LOG_DEBUG(" attribute type: LABEL"); + if (payload_size > MAX_IFA_LABEL_SIZE) { + payload_size = MAX_IFA_LABEL_SIZE; + room_for_trailing_null = 1; + } + + if (payload_size > 0) { + size_t alloc_size; + if (!multiply_s(payload_size, room_for_trailing_null, &alloc_size)) { + goto error; + } + + ifa->ifa_name = (char*)malloc(alloc_size); + if (!ifa->ifa_name) { + goto error; + } + + memcpy(ifa->ifa_name, RTA_DATA (attribute), payload_size); + if (room_for_trailing_null) + ifa->ifa_name[payload_size] = '\0'; + } + break; + } + + case IFA_LOCAL: + 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 + * is indeed the P2P destination one. + */ + ifa->ifa_dstaddr = ifa->ifa_addr; + ifa->ifa_addr = 0; + } + sa = &ifa->ifa_addr; + break; + + case IFA_BROADCAST: + LOG_DEBUG(" attribute type: BROADCAST"); + if (ifa->ifa_dstaddr) { + /* IFA_LOCAL happened earlier, undo its effect here */ + free(ifa->ifa_dstaddr); + ifa->ifa_dstaddr = NULL; + } + sa = &ifa->ifa_broadaddr; + break; + + case IFA_ADDRESS: + LOG_DEBUG(" attribute type: ADDRESS"); + if (ifa->ifa_addr) { + /* Apparently IFA_LOCAL occured earlier and we have a P2P connection + * here. IFA_LOCAL carries the destination address, move it there + */ + ifa->ifa_dstaddr = ifa->ifa_addr; + ifa->ifa_addr = NULL; + } + sa = &ifa->ifa_addr; + break; + + case IFA_UNSPEC: + LOG_DEBUG(" attribute type: UNSPEC"); + break; + + case IFA_ANYCAST: + LOG_DEBUG(" attribute type: ANYCAST"); + break; + + case IFA_CACHEINFO: + LOG_DEBUG(" attribute type: CACHEINFO"); + break; + + case IFA_MULTICAST: + LOG_DEBUG(" attribute type: MULTICAST"); + break; + + default: + LOG_DEBUG(" attribute type: %u", attribute->rta_type); + break; + } + + if (sa) { + if (fill_sa_address(sa, net_address, RTA_DATA(attribute), RTA_PAYLOAD(attribute)) < 0) { + goto error; + } + } + + attribute = RTA_NEXT(attribute, length); + } + + /* glibc stores the associated interface name in the address if IFA_LABEL never occured */ + 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"); + ifa->ifa_name = name ? strdup(name) : NULL; + } + LOG_DEBUG(" address label: %s\n", ifa->ifa_name); + + if (calculate_address_netmask(ifa, net_address) < 0) { + goto error; + } + + return ifa; + + error: + { + /* errno may be modified by free, or any other call inside the free_single_xamarin_ifaddrs + * function. We don't care about errors in there since it is more important to know how we + * failed to obtain the link address and not that we went OOM. Save and restore the value + * after the resources are freed. + */ + int errno_save = errno; + free_single_ifaddrs (&ifa); + errno = errno_save; + return NULL; + } + +} + +static int parse_netlink_reply(struct netlink_session *session, struct ifaddrs **ifaddrs_head, struct ifaddrs **last_ifaddr) +{ + struct msghdr netlink_reply; + struct iovec reply_vector; + struct nlmsghdr *current_message; + struct ifaddrs *addr; + int ret = -1; + unsigned char *response = NULL; + + abort_if_invalid_pointer_argument(session); + abort_if_invalid_pointer_argument(ifaddrs_head); + abort_if_invalid_pointer_argument(last_ifaddr); + + size_t buf_size = (size_t)(getpagesize()); + LOG_DEBUG("receive buffer size == %zu", buf_size); + + size_t alloc_size; + if (!multiply_s(sizeof(*response), buf_size, &alloc_size)) { + goto cleanup; + } + + response = (unsigned char*)malloc(alloc_size); + ssize_t length = 0; + if (!response) { + goto cleanup; + } + + while (1) { + memset(response, 0, buf_size); + memset(&reply_vector, 0, sizeof(reply_vector)); + reply_vector.iov_len = buf_size; + reply_vector.iov_base = response; + + memset(&netlink_reply, 0, sizeof(netlink_reply)); + netlink_reply.msg_namelen = sizeof(&session->them); + netlink_reply.msg_name = &session->them; + netlink_reply.msg_iovlen = 1; + netlink_reply.msg_iov = &reply_vector; + + length = recvmsg(session->sock_fd, &netlink_reply, 0); + LOG_DEBUG(" length == %d\n", (int)length); + + if (length < 0) { + LOG_DEBUG("Failed to receive reply from netlink. %s\n", strerror(errno)); + goto cleanup; + } + +#ifdef DEBUG + LOG_DEBUG("response flags:"); + if (netlink_reply.msg_flags == 0) + LOG_DEBUG(" [NONE]"); + else { + if (netlink_reply.msg_flags & MSG_EOR) + LOG_DEBUG(" MSG_EOR"); + if (netlink_reply.msg_flags & MSG_TRUNC) + LOG_DEBUG(" MSG_TRUNC"); + if (netlink_reply.msg_flags & MSG_CTRUNC) + LOG_DEBUG(" MSG_CTRUNC"); + if (netlink_reply.msg_flags & MSG_OOB) + LOG_DEBUG(" MSG_OOB"); + if (netlink_reply.msg_flags & MSG_ERRQUEUE) + LOG_DEBUG(" MSG_ERRQUEUE"); + } +#endif + + if (length == 0) + break; + + for (current_message = (struct nlmsghdr*)response; current_message && NLMSG_OK(current_message, (size_t)length); current_message = NLMSG_NEXT(current_message, length)) { + LOG_DEBUG("next message... (type: %u)\n", current_message->nlmsg_type); + switch (current_message->nlmsg_type) { + /* See rtnetlink.h */ + case RTM_NEWLINK: + LOG_DEBUG(" dumping link...\n"); + addr = get_link_info(current_message); + if (!addr || append_ifaddr(addr, ifaddrs_head, last_ifaddr) < 0) { + ret = -1; + goto cleanup; + } + LOG_DEBUG(" done\n"); + break; + + case RTM_NEWADDR: + LOG_DEBUG(" got an address\n"); + addr = get_link_address(current_message, ifaddrs_head); + if (!addr || append_ifaddr(addr, ifaddrs_head, last_ifaddr) < 0) { + ret = -1; + goto cleanup; + } + break; + + case NLMSG_DONE: + LOG_DEBUG(" message done\n"); + ret = 0; + goto cleanup; + + default: + LOG_DEBUG(" message type: %u", current_message->nlmsg_type); + break; + } + } + } + + cleanup: + if (response) + free(response); + return ret; +} + +int getifaddrs(struct ifaddrs **ifap) +{ + int ret = -1; + + *ifap = NULL; + struct ifaddrs *ifaddrs_head = 0; + struct ifaddrs *last_ifaddr = 0; + struct netlink_session session; + + if (open_netlink_session(&session) < 0) { + goto cleanup; + } + + /* Request information about the specified link. In our case it will be all of them since we + request the root of the link tree below + */ + if ((send_netlink_dump_request(&session, RTM_GETLINK) < 0) || + (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); + goto cleanup; + } + + ret = 0; + *ifap = ifaddrs_head; + +cleanup: + if (session.sock_fd >= 0) { + close(session.sock_fd); + session.sock_fd = -1; + } + + return ret; +} + +void freeifaddrs(struct ifaddrs *ifa) +{ + struct ifaddrs *cur, *next; + + if (!ifa) + return; + + cur = ifa; + while (cur) { + next = cur->ifa_next; + free_single_ifaddrs (&cur); + cur = next; + } +} diff --git a/src/native/libs/System.Native/pal_ifaddrs.h b/src/native/libs/System.Native/pal_ifaddrs.h new file mode 100644 index 0000000000000..e52a392b13d9d --- /dev/null +++ b/src/native/libs/System.Native/pal_ifaddrs.h @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#ifndef TARGET_ANDROID +#error The pal_ifaddrs.h shim is intended only for Android +#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. + +#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 + +int getifaddrs (struct ifaddrs **ifap); +void 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 a876e7f172843..fa05d575ef7ad 100644 --- a/src/native/libs/System.Native/pal_interfaceaddresses.c +++ b/src/native/libs/System.Native/pal_interfaceaddresses.c @@ -11,12 +11,11 @@ #include #include #include -#if HAVE_IFADDRS || HAVE_GETIFADDRS +#if HAVE_GETIFADDRS && !defined(TARGET_ANDROID) #include #endif -#if !HAVE_GETIFADDRS && TARGET_ANDROID -#include -#include +#ifdef TARGET_ANDROID +#include "pal_ifaddrs.h" #endif #include #include @@ -101,66 +100,12 @@ static inline uint8_t mask2prefix(uint8_t* mask, int length) return len; } -#if !HAVE_IFADDRS && TARGET_ANDROID -// This structure is exactly the same as struct ifaddrs defined in ifaddrs.h but since the header -// might not be available (e.g., in bionics used in Android before API 24) we need to mirror it here -// so that we can dynamically load the getifaddrs function and use it. -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; -}; -#endif - -#if !HAVE_GETIFADDRS && TARGET_ANDROID -// Try to load the getifaddrs and freeifaddrs functions manually. -// This workaround is necessary on Android prior to API 24 and it can be removed once -// we drop support for earlier Android versions. -static int (*getifaddrs)(struct ifaddrs**) = NULL; -static void (*freeifaddrs)(struct ifaddrs*) = NULL; - -static void try_loading_getifaddrs() -{ - void *libc = dlopen("libc.so", RTLD_NOW); - if (libc) - { - getifaddrs = (int (*)(struct ifaddrs**)) dlsym(libc, "getifaddrs"); - freeifaddrs = (void (*)(struct ifaddrs*)) dlsym(libc, "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 && TARGET_ANDROID - // Workaround for Android API < 24 - if (!ensure_getifaddrs_is_loaded()) - { - errno = ENOTSUP; - return -1; - } -#endif - -#if HAVE_GETIFADDRS || TARGET_ANDROID +#if HAVE_GETIFADDRS || defined(TARGET_ANDROID) struct ifaddrs* headAddr; if (getifaddrs(&headAddr) == -1) { @@ -305,16 +250,7 @@ int32_t SystemNative_EnumerateInterfaceAddresses(void* context, int32_t SystemNative_GetNetworkInterfaces(int32_t * interfaceCount, NetworkInterfaceInfo **interfaceList, int32_t * addressCount, IpAddressInfo **addressList ) { -#if !HAVE_GETIFADDRS && TARGET_ANDROID - // Workaround for Android API < 24 - if (!ensure_getifaddrs_is_loaded()) - { - errno = ENOTSUP; - return -1; - } -#endif - -#if HAVE_GETIFADDRS || TARGET_ANDROID +#if HAVE_GETIFADDRS || defined(TARGET_ANDROID) struct ifaddrs* head; // Pointer to block allocated by getifaddrs(). struct ifaddrs* ifaddrsEntry; IpAddressInfo *ai; diff --git a/src/native/libs/configure.cmake b/src/native/libs/configure.cmake index 4a8b7940ce5ef..96bd6ed9d9cc8 100644 --- a/src/native/libs/configure.cmake +++ b/src/native/libs/configure.cmake @@ -121,18 +121,6 @@ check_c_source_compiles( " HAVE_FLOCK64) -check_c_source_compiles( - " - #include - #include - int main(void) - { - struct ifaddrs ia; - return 0; - } - " - HAVE_IFADDRS) - check_symbol_exists( O_CLOEXEC fcntl.h