From 11f17fef40dd9e08f56ce5cabfec811094914182 Mon Sep 17 00:00:00 2001 From: Gioacchino Mazzurco Date: Fri, 12 Nov 2021 16:02:53 +0100 Subject: [PATCH] libretroshare Android Qt network dependency optional One of the reason libretroshare dependend on Qt on Android and in particular in networking module is the lack of `getifaddrs` with API level < 24, we included Android Gingerbread internal implementation so this dependency can be avoided at compile time. The code depending on Qt has been placed under `#if` and can be enabled at compile time by appending `DEFINES+=LIBRETROSHARE_ANDROID_IFADDRS_QT` to `qmake` command line. --- .../src/android_ifaddrs/LocalArray.h | 75 ++++++ libretroshare/src/android_ifaddrs/README.adoc | 45 ++++ libretroshare/src/android_ifaddrs/ScopedFd.h | 46 ++++ .../src/android_ifaddrs/ifaddrs-android.h | 228 ++++++++++++++++++ libretroshare/src/libretroshare.pro | 13 +- libretroshare/src/pqi/pqinetwork.cc | 17 +- libretroshare/src/use_libretroshare.pri | 11 +- 7 files changed, 426 insertions(+), 9 deletions(-) create mode 100644 libretroshare/src/android_ifaddrs/LocalArray.h create mode 100644 libretroshare/src/android_ifaddrs/README.adoc create mode 100644 libretroshare/src/android_ifaddrs/ScopedFd.h create mode 100644 libretroshare/src/android_ifaddrs/ifaddrs-android.h diff --git a/libretroshare/src/android_ifaddrs/LocalArray.h b/libretroshare/src/android_ifaddrs/LocalArray.h new file mode 100644 index 000000000..2ab708aff --- /dev/null +++ b/libretroshare/src/android_ifaddrs/LocalArray.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LOCAL_ARRAY_H_included +#define LOCAL_ARRAY_H_included + +#include +#include + +/** + * A fixed-size array with a size hint. That number of bytes will be allocated + * on the stack, and used if possible, but if more bytes are requested at + * construction time, a buffer will be allocated on the heap (and deallocated + * by the destructor). + * + * The API is intended to be a compatible subset of C++0x's std::array. + */ +template +class LocalArray { +public: + /** + * Allocates a new fixed-size array of the given size. If this size is + * less than or equal to the template parameter STACK_BYTE_COUNT, an + * internal on-stack buffer will be used. Otherwise a heap buffer will + * be allocated. + */ + LocalArray(size_t desiredByteCount) : mSize(desiredByteCount) { + if (desiredByteCount > STACK_BYTE_COUNT) { + mPtr = new char[mSize]; + } else { + mPtr = &mOnStackBuffer[0]; + } + } + + /** + * Frees the heap-allocated buffer, if there was one. + */ + ~LocalArray() { + if (mPtr != &mOnStackBuffer[0]) { + delete[] mPtr; + } + } + + // Capacity. + size_t size() { return mSize; } + bool empty() { return mSize == 0; } + + // Element access. + char& operator[](size_t n) { return mPtr[n]; } + const char& operator[](size_t n) const { return mPtr[n]; } + +private: + char mOnStackBuffer[STACK_BYTE_COUNT]; + char* mPtr; + size_t mSize; + + // Disallow copy and assignment. + LocalArray(const LocalArray&); + void operator=(const LocalArray&); +}; + +#endif // LOCAL_ARRAY_H_included diff --git a/libretroshare/src/android_ifaddrs/README.adoc b/libretroshare/src/android_ifaddrs/README.adoc new file mode 100644 index 000000000..ba0355df7 --- /dev/null +++ b/libretroshare/src/android_ifaddrs/README.adoc @@ -0,0 +1,45 @@ += README Android ifaddrs + +Android API level < 24 doesn't provide `getifaddrs` and related functions, we +have tested multiple ways to overcome this issue. + + +== Non Weorking alternative implementations + +https://github.com/kmackay/android-ifaddrs +https://github.com/morristech/android-ifaddrs +https://www.openhub.net/p/android-ifaddrs/ + +Compiles but then segfault at runtime. + + +== Qt implementation + +Using `QNetworkInterface::allAddresses()` provided by Qt works but at time of +writing (Q4 2021) on newer Android the log is flooded by warnings as we reported +here +https://bugreports.qt.io/browse/QTBUG-78659 +plus depending on Qt networking module just for this is frustrating. + +Update: the warning flood seems have been fixed in later Qt versions +https://bugreports.qt.io/browse/QTBUG-86394 + +This solution is the first working we implemented in our code it is disabled by +default but can be enabled passing `DEFINES+=LIBRETROSHARE_ANDROID_IFADDRS_QT` +when running `qmake` command. + + +== Code copied from Android Gingerbread release + +As explained here +https://stackoverflow.com/a/57112520 + +even older Android have `getifaddrs` implementations but doesn't make them +accessible in the API, in particular the one included in Android Gingerbread + +https://android.googlesource.com/platform/libcore/+/refs/heads/gingerbread-release/luni/src/main/native/ifaddrs-android.h +https://android.googlesource.com/platform/libcore/+/refs/heads/gingerbread-release/ + +is particularly easy to include in our code base and compile. + +This solution seems the best fitting and doesn't introduce dependency on Qt. diff --git a/libretroshare/src/android_ifaddrs/ScopedFd.h b/libretroshare/src/android_ifaddrs/ScopedFd.h new file mode 100644 index 000000000..d2b7935fa --- /dev/null +++ b/libretroshare/src/android_ifaddrs/ScopedFd.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCOPED_FD_H_included +#define SCOPED_FD_H_included + +#include + +// A smart pointer that closes the given fd on going out of scope. +// Use this when the fd is incidental to the purpose of your function, +// but needs to be cleaned up on exit. +class ScopedFd { +public: + explicit ScopedFd(int fd) : fd(fd) { + } + + ~ScopedFd() { + close(fd); + } + + int get() const { + return fd; + } + +private: + int fd; + + // Disallow copy and assignment. + ScopedFd(const ScopedFd&); + void operator=(const ScopedFd&); +}; + +#endif // SCOPED_FD_H_included diff --git a/libretroshare/src/android_ifaddrs/ifaddrs-android.h b/libretroshare/src/android_ifaddrs/ifaddrs-android.h new file mode 100644 index 000000000..446d8d2be --- /dev/null +++ b/libretroshare/src/android_ifaddrs/ifaddrs-android.h @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IFADDRS_ANDROID_H_included +#define IFADDRS_ANDROID_H_included + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "LocalArray.h" +#include "ScopedFd.h" + +// Android (bionic) doesn't have getifaddrs(3)/freeifaddrs(3). +// We fake it here, so java_net_NetworkInterface.cpp can use that API +// with all the non-portable code being in this file. + +// Source-compatible subset of the BSD struct. +struct ifaddrs { + // Pointer to next struct in list, or NULL at end. + ifaddrs* ifa_next; + + // Interface name. + char* ifa_name; + + // Interface flags. + unsigned int ifa_flags; + + // Interface network address. + sockaddr* ifa_addr; + + // Interface netmask. + sockaddr* ifa_netmask; + + ifaddrs(ifaddrs* next) + : ifa_next(next), ifa_name(NULL), ifa_flags(0), ifa_addr(NULL), ifa_netmask(NULL) + { + } + + ~ifaddrs() { + delete ifa_next; + delete[] ifa_name; + delete ifa_addr; + delete ifa_netmask; + } + + // Sadly, we can't keep the interface index for portability with BSD. + // We'll have to keep the name instead, and re-query the index when + // we need it later. + bool setNameAndFlagsByIndex(int interfaceIndex) { + // Get the name. + char buf[IFNAMSIZ]; + char* name = if_indextoname(interfaceIndex, buf); + if (name == NULL) { + return false; + } + ifa_name = new char[strlen(name) + 1]; + strcpy(ifa_name, name); + + // Get the flags. + ScopedFd fd(socket(AF_INET, SOCK_DGRAM, 0)); + if (fd.get() == -1) { + return false; + } + ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, name); + int rc = ioctl(fd.get(), SIOCGIFFLAGS, &ifr); + if (rc == -1) { + return false; + } + ifa_flags = ifr.ifr_flags; + return true; + } + + // Netlink gives us the address family in the header, and the + // sockaddr_in or sockaddr_in6 bytes as the payload. We need to + // stitch the two bits together into the sockaddr that's part of + // our portable interface. + void setAddress(int family, void* data, size_t byteCount) { + // Set the address proper... + sockaddr_storage* ss = new sockaddr_storage; + memset(ss, 0, sizeof(*ss)); + ifa_addr = reinterpret_cast(ss); + ss->ss_family = family; + uint8_t* dst = sockaddrBytes(family, ss); + memcpy(dst, data, byteCount); + } + + // Netlink gives us the prefix length as a bit count. We need to turn + // that into a BSD-compatible netmask represented by a sockaddr*. + void setNetmask(int family, size_t prefixLength) { + // ...and work out the netmask from the prefix length. + sockaddr_storage* ss = new sockaddr_storage; + memset(ss, 0, sizeof(*ss)); + ifa_netmask = reinterpret_cast(ss); + ss->ss_family = family; + uint8_t* dst = sockaddrBytes(family, ss); + memset(dst, 0xff, prefixLength / 8); + if ((prefixLength % 8) != 0) { + dst[prefixLength/8] = (0xff << (8 - (prefixLength % 8))); + } + } + + // Returns a pointer to the first byte in the address data (which is + // stored in network byte order). + uint8_t* sockaddrBytes(int family, sockaddr_storage* ss) { + if (family == AF_INET) { + sockaddr_in* ss4 = reinterpret_cast(ss); + return reinterpret_cast(&ss4->sin_addr); + } else if (family == AF_INET6) { + sockaddr_in6* ss6 = reinterpret_cast(ss); + return reinterpret_cast(&ss6->sin6_addr); + } + return NULL; + } + +private: + // Disallow copy and assignment. + ifaddrs(const ifaddrs&); + void operator=(const ifaddrs&); +}; + +// FIXME: use iovec instead. +struct addrReq_struct { + nlmsghdr netlinkHeader; + ifaddrmsg msg; +}; + +inline bool sendNetlinkMessage(int fd, const void* data, size_t byteCount) { + ssize_t sentByteCount = TEMP_FAILURE_RETRY(send(fd, data, byteCount, 0)); + return (sentByteCount == static_cast(byteCount)); +} + +inline ssize_t recvNetlinkMessage(int fd, char* buf, size_t byteCount) { + return TEMP_FAILURE_RETRY(recv(fd, buf, byteCount, 0)); +} + +// Source-compatible with the BSD function. +inline int getifaddrs(ifaddrs** result) { + // Simplify cleanup for callers. + *result = NULL; + + // Create a netlink socket. + ScopedFd fd(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)); + if (fd.get() < 0) { + return -1; + } + + // Ask for the address information. + addrReq_struct addrRequest; + memset(&addrRequest, 0, sizeof(addrRequest)); + addrRequest.netlinkHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH; + addrRequest.netlinkHeader.nlmsg_type = RTM_GETADDR; + addrRequest.netlinkHeader.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(addrRequest))); + addrRequest.msg.ifa_family = AF_UNSPEC; // All families. + addrRequest.msg.ifa_index = 0; // All interfaces. + if (!sendNetlinkMessage(fd.get(), &addrRequest, addrRequest.netlinkHeader.nlmsg_len)) { + return -1; + } + + // Read the responses. + LocalArray<0> buf(65536); // We don't necessarily have std::vector. + ssize_t bytesRead; + while ((bytesRead = recvNetlinkMessage(fd.get(), &buf[0], buf.size())) > 0) { + nlmsghdr* hdr = reinterpret_cast(&buf[0]); + for (; NLMSG_OK(hdr, (size_t)bytesRead); hdr = NLMSG_NEXT(hdr, bytesRead)) { + switch (hdr->nlmsg_type) { + case NLMSG_DONE: + return 0; + case NLMSG_ERROR: + return -1; + case RTM_NEWADDR: + { + ifaddrmsg* address = reinterpret_cast(NLMSG_DATA(hdr)); + rtattr* rta = IFA_RTA(address); + size_t ifaPayloadLength = IFA_PAYLOAD(hdr); + while (RTA_OK(rta, ifaPayloadLength)) { + if (rta->rta_type == IFA_LOCAL) { + int family = address->ifa_family; + if (family == AF_INET || family == AF_INET6) { + *result = new ifaddrs(*result); + if (!(*result)->setNameAndFlagsByIndex(address->ifa_index)) { + return -1; + } + (*result)->setAddress(family, RTA_DATA(rta), RTA_PAYLOAD(rta)); + (*result)->setNetmask(family, address->ifa_prefixlen); + } + } + rta = RTA_NEXT(rta, ifaPayloadLength); + } + } + break; + } + } + } + // We only get here if recv fails before we see a NLMSG_DONE. + return -1; +} + +// Source-compatible with the BSD function. +inline void freeifaddrs(ifaddrs* addresses) { + delete addresses; +} + +#endif // IFADDRS_ANDROID_H_included diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index d42f68328..a1a257aac 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: (C) 2004-2019 Retroshare Team +# SPDX-FileCopyrightText: (C) 2004-2021 Retroshare Team # SPDX-License-Identifier: CC0-1.0 !include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri") @@ -1091,6 +1091,8 @@ test_bitdht { ################################# Android ##################################### android-* { + lessThan(ANDROID_API_VERSION, 24) { + ## TODO: This probably disable largefile support and maybe is not necessary with ## __ANDROID_API__ >= 24 hence should be made conditional or moved to a ## compatibility header @@ -1098,6 +1100,15 @@ android-* { DEFINES *= "fseeko64=fseeko" DEFINES *= "ftello64=ftello" + ## @See: android_ifaddrs/README.adoc + !contains(DEFINES, LIBRETROSHARE_ANDROID_IFADDRS_QT) { + HEADERS += \ + android_ifaddrs/ifaddrs-android.h \ + android_ifaddrs/LocalArray.h \ + android_ifaddrs/ScopedFd.h + } + } + ## Static library are very susceptible to order in command line sLibs = bz2 $$RS_UPNP_LIB $$RS_SQL_LIB ssl crypto diff --git a/libretroshare/src/pqi/pqinetwork.cc b/libretroshare/src/pqi/pqinetwork.cc index a5ca76035..b6c52fc27 100644 --- a/libretroshare/src/pqi/pqinetwork.cc +++ b/libretroshare/src/pqi/pqinetwork.cc @@ -27,6 +27,7 @@ # include #endif // WINDOWS_SYS +/// @See: android_ifaddrs/README.adoc #ifdef __ANDROID__ # include #endif // def __ANDROID__ @@ -276,11 +277,17 @@ int inet_aton(const char *name, struct in_addr *addr) # include # include # pragma comment(lib, "IPHLPAPI.lib") -#elif defined(__ANDROID__) && __ANDROID_API__ < 24 +#elif defined(__ANDROID__) && __ANDROID_API__ < 24 && \ + defined(LIBRETROSHARE_ANDROID_IFADDRS_QT) +/// @See: android_ifaddrs/README.adoc # include # include # include # include +#elif defined(__ANDROID__) && __ANDROID_API__ < 24 && \ + !defined(LIBRETROSHARE_ANDROID_IFADDRS_QT) +/// @See: android_ifaddrs/README.adoc +# include "android_ifaddrs/ifaddrs-android.h" #else // not __ANDROID__ nor WINDOWS => Linux and other unixes # include # include @@ -324,7 +331,9 @@ bool getLocalAddresses(std::vector& addrs) } } free(adapter_addresses); -#elif defined(__ANDROID__) && __ANDROID_API__ < 24 +#elif defined(__ANDROID__) && __ANDROID_API__ < 24 && \ + defined(LIBRETROSHARE_ANDROID_IFADDRS_QT) +/// @See: android_ifaddrs/README.adoc for(auto& qAddr: QNetworkInterface::allAddresses()) { sockaddr_storage tmpAddr; @@ -336,8 +345,8 @@ bool getLocalAddresses(std::vector& addrs) struct ifaddrs *ifsaddrs, *ifa; if(getifaddrs(&ifsaddrs) != 0) { - std::cerr << __PRETTY_FUNCTION__ << " FATAL ERROR: " << errno << " " - << strerror(errno) << std::endl; + RS_ERR( "getifaddrs failed with: ", errno, " ", + rs_errno_to_condition(errno) ); print_stacktrace(); return false; } diff --git a/libretroshare/src/use_libretroshare.pri b/libretroshare/src/use_libretroshare.pri index 0cca29150..a34951772 100644 --- a/libretroshare/src/use_libretroshare.pri +++ b/libretroshare/src/use_libretroshare.pri @@ -111,11 +111,14 @@ PRE_TARGETDEPS += $$pretargetStaticLibs(sLibs) LIBS += $$linkDynamicLibs(dLibs) android-* { -## ifaddrs is missing on Android to add them don't use the one from -## https://github.com/morristech/android-ifaddrs -## because it crash, use QNetworkInterface from Qt instead CONFIG *= qt - QT *= network + + lessThan(ANDROID_API_VERSION, 24) { + ## @See: android_ifaddrs/README.adoc + contains(DEFINES, LIBRETROSHARE_ANDROID_IFADDRS_QT) { + QT *= network + } + } } ################################### Pkg-Config Stuff #############################