From 0945ed5cc5921243724fed4465d20881f4891a8d Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Sat, 19 Dec 2015 14:39:10 -0800 Subject: [PATCH] Implement getifaddrs(3)/freeifaddrs(3). Time to dust off the old libcore implementation from gingerbread and add it to bionic. Unlike the original, this actually looks at both RTM_NEWLINK and RTM_NEWADDR. Bug: http://b/26238832 Change-Id: I7bb4b432deb766065b66b9c9ff36ed68249aba82 --- libc/Android.mk | 1 + libc/bionic/ifaddrs.cpp | 235 ++++++++++++++++++++++++++++++++++++++++++++++ libc/include/ifaddrs.h | 59 ++++++++++++ libc/libc.arm.brillo.map | 2 + libc/libc.arm.map | 2 + libc/libc.arm64.map | 2 + libc/libc.map.txt | 2 + libc/libc.mips.brillo.map | 2 + libc/libc.mips.map | 2 + libc/libc.mips64.map | 2 + libc/libc.x86.brillo.map | 2 + libc/libc.x86.map | 2 + libc/libc.x86_64.map | 2 + tests/Android.mk | 1 + tests/ifaddrs_test.cpp | 38 ++++++++ 15 files changed, 354 insertions(+) create mode 100644 libc/bionic/ifaddrs.cpp create mode 100644 libc/include/ifaddrs.h create mode 100644 tests/ifaddrs_test.cpp diff --git a/libc/Android.mk b/libc/Android.mk index a399b8988..83e1fb505 100644 --- a/libc/Android.mk +++ b/libc/Android.mk @@ -149,6 +149,7 @@ libc_bionic_ndk_src_files := \ bionic/getpid.cpp \ bionic/gettid.cpp \ bionic/__gnu_basename.cpp \ + bionic/ifaddrs.cpp \ bionic/inotify_init.cpp \ bionic/ioctl.cpp \ bionic/lchown.cpp \ diff --git a/libc/bionic/ifaddrs.cpp b/libc/bionic/ifaddrs.cpp new file mode 100644 index 000000000..5da6bcc19 --- /dev/null +++ b/libc/bionic/ifaddrs.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// The public ifaddrs struct is full of pointers. Rather than track several +// different allocations, we use a maximally-sized structure with the public +// part at offset 0, and pointers into its hidden tail. +struct ifaddrs_storage { + // Must come first, so that `ifaddrs_storage` is-a `ifaddrs`. + ifaddrs ifa; + + // The interface index, so we can match RTM_NEWADDR messages with + // earlier RTM_NEWLINK messages (to copy the interface flags). + int interface_index; + + // Storage for the pointers in `ifa`. + sockaddr_storage addr; + sockaddr_storage netmask; + sockaddr_storage ifa_ifu; + char name[IFNAMSIZ + 1]; + + ifaddrs_storage(ifaddrs** list) { + memset(this, 0, sizeof(*this)); + + // push_front onto `list`. + ifa.ifa_next = *list; + *list = reinterpret_cast(this); + } + + // 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, const void* data, size_t byteCount) { + addr.ss_family = family; + memcpy(SockaddrBytes(family, &addr), data, byteCount); + ifa.ifa_addr = reinterpret_cast(&addr); + } + + void SetBroadcastAddress(int family, const void* data, size_t byteCount) { + ifa_ifu.ss_family = family; + memcpy(SockaddrBytes(family, &ifa_ifu), data, byteCount); + ifa.ifa_dstaddr = reinterpret_cast(&ifa_ifu); + } + + // 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 prefix_length) { + // ...and work out the netmask from the prefix length. + netmask.ss_family = family; + uint8_t* dst = SockaddrBytes(family, &netmask); + memset(dst, 0xff, prefix_length / 8); + if ((prefix_length % 8) != 0) { + dst[prefix_length/8] = (0xff << (8 - (prefix_length % 8))); + } + ifa.ifa_netmask = reinterpret_cast(&netmask); + } + + private: + // 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 nullptr; + } +}; + +static void __handle_netlink_response(ifaddrs** out, nlmsghdr* hdr) { + if (hdr->nlmsg_type == RTM_NEWLINK) { + ifinfomsg* ifi = reinterpret_cast(NLMSG_DATA(hdr)); + + // Create a new ifaddr entry, and set the interface index and flags. + ifaddrs_storage* new_addr = new ifaddrs_storage(out); + new_addr->interface_index = ifi->ifi_index; + new_addr->ifa.ifa_flags = ifi->ifi_flags; + + // Go through the various bits of information and find the name. + rtattr* rta = IFLA_RTA(ifi); + size_t rta_len = IFLA_PAYLOAD(hdr); + while (RTA_OK(rta, rta_len)) { + if (rta->rta_type == IFLA_IFNAME) { + if (RTA_PAYLOAD(rta) < sizeof(new_addr->name)) { + memcpy(new_addr->name, RTA_DATA(rta), RTA_PAYLOAD(rta)); + new_addr->ifa.ifa_name = new_addr->name; + } + } + rta = RTA_NEXT(rta, rta_len); + } + } else if (hdr->nlmsg_type == RTM_NEWADDR) { + ifaddrmsg* msg = reinterpret_cast(NLMSG_DATA(hdr)); + + // We should already know about this from an RTM_NEWLINK message. + ifaddrs_storage* addr = reinterpret_cast(*out); + while (addr != nullptr && addr->interface_index != static_cast(msg->ifa_index)) { + addr = reinterpret_cast(addr->ifa.ifa_next); + } + // If this is an unknown interface, ignore whatever we're being told about it. + if (addr == nullptr) return; + + // Create a new ifaddr entry and copy what we already know. + ifaddrs_storage* new_addr = new ifaddrs_storage(out); + // We can just copy the name rather than look for IFA_LABEL. + strcpy(new_addr->name, addr->name); + new_addr->ifa.ifa_name = new_addr->name; + new_addr->ifa.ifa_flags = addr->ifa.ifa_flags; + new_addr->interface_index = addr->interface_index; + + // Go through the various bits of information and find the address + // and any broadcast/destination address. + rtattr* rta = IFA_RTA(msg); + size_t rta_len = IFA_PAYLOAD(hdr); + while (RTA_OK(rta, rta_len)) { + if (rta->rta_type == IFA_ADDRESS) { + if (msg->ifa_family == AF_INET || msg->ifa_family == AF_INET6) { + addr->SetAddress(msg->ifa_family, RTA_DATA(rta), RTA_PAYLOAD(rta)); + addr->SetNetmask(msg->ifa_family, msg->ifa_prefixlen); + } + } else if (rta->rta_type == IFA_BROADCAST) { + if (msg->ifa_family == AF_INET || msg->ifa_family == AF_INET6) { + addr->SetBroadcastAddress(msg->ifa_family, RTA_DATA(rta), RTA_PAYLOAD(rta)); + } + } + rta = RTA_NEXT(rta, rta_len); + } + } +} + +static bool __send_netlink_request(int fd, int type) { + struct NetlinkMessage { + nlmsghdr hdr; + rtgenmsg msg; + } request; + memset(&request, 0, sizeof(request)); + request.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; + request.hdr.nlmsg_type = type; + request.hdr.nlmsg_len = sizeof(request); + request.msg.rtgen_family = AF_UNSPEC; // All families. + return (TEMP_FAILURE_RETRY(send(fd, &request, sizeof(request), 0)) == sizeof(request)); +} + +static bool __read_netlink_responses(int fd, ifaddrs** out, char* buf, size_t buf_len) { + ssize_t bytes_read; + // Read through all the responses, handing interesting ones to __handle_netlink_response. + while ((bytes_read = TEMP_FAILURE_RETRY(recv(fd, buf, buf_len, 0))) > 0) { + nlmsghdr* hdr = reinterpret_cast(buf); + for (; NLMSG_OK(hdr, static_cast(bytes_read)); hdr = NLMSG_NEXT(hdr, bytes_read)) { + if (hdr->nlmsg_type == NLMSG_DONE) return true; + if (hdr->nlmsg_type == NLMSG_ERROR) return false; + __handle_netlink_response(out, hdr); + } + } + // We only get here if recv fails before we see a NLMSG_DONE. + return false; +} + +int getifaddrs(ifaddrs** out) { + // Make cleanup easy. + *out = nullptr; + + // The kernel keeps packets under 8KiB (NLMSG_GOODSIZE), + // but that's a bit too large to go on the stack. + size_t buf_len = 8192; + char* buf = new char[buf_len]; + if (buf == nullptr) return -1; + + // Open the netlink socket and ask for all the links and addresses. + int fd = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); + bool okay = fd != -1 && + __send_netlink_request(fd, RTM_GETLINK) && __read_netlink_responses(fd, out, buf, buf_len) && + __send_netlink_request(fd, RTM_GETADDR) && __read_netlink_responses(fd, out, buf, buf_len); + + if (!okay) { + freeifaddrs(*out); + // Ensure that callers crash if they forget to check for success. + *out = nullptr; + } + { + int saved_errno = errno; + close(fd); + delete[] buf; + errno = saved_errno; + } + return okay ? 0 : -1; +} + +void freeifaddrs(ifaddrs* list) { + while (list != nullptr) { + ifaddrs* current = list; + list = list->ifa_next; + free(current); + } +} diff --git a/libc/include/ifaddrs.h b/libc/include/ifaddrs.h new file mode 100644 index 000000000..54a5a2c59 --- /dev/null +++ b/libc/include/ifaddrs.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _IFADDRS_H_ +#define _IFADDRS_H_ + +#include +#include +#include + +__BEGIN_DECLS + +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; +}; + +#define ifa_broadaddr ifa_ifu.ifu_broadaddr +#define ifa_dstaddr ifa_ifu.ifu_dstaddr + +void freeifaddrs(struct ifaddrs*); +int getifaddrs(struct ifaddrs**); + +__END_DECLS + +#endif diff --git a/libc/libc.arm.brillo.map b/libc/libc.arm.brillo.map index 4dd448146..e09960bb6 100644 --- a/libc/libc.arm.brillo.map +++ b/libc/libc.arm.brillo.map @@ -1277,8 +1277,10 @@ LIBC_N { __pwrite64_chk; __write_chk; fileno_unlocked; + freeifaddrs; getgrgid_r; getgrnam_r; + getifaddrs; preadv; preadv64; prlimit; # arm mips x86 diff --git a/libc/libc.arm.map b/libc/libc.arm.map index 1a666f484..f24a867e9 100644 --- a/libc/libc.arm.map +++ b/libc/libc.arm.map @@ -1311,8 +1311,10 @@ LIBC_N { __pwrite64_chk; __write_chk; fileno_unlocked; + freeifaddrs; getgrgid_r; getgrnam_r; + getifaddrs; preadv; preadv64; prlimit; # arm mips x86 diff --git a/libc/libc.arm64.map b/libc/libc.arm64.map index 8fa46a84c..0eb0275c9 100644 --- a/libc/libc.arm64.map +++ b/libc/libc.arm64.map @@ -1156,8 +1156,10 @@ LIBC_N { __pwrite64_chk; __write_chk; fileno_unlocked; + freeifaddrs; getgrgid_r; getgrnam_r; + getifaddrs; preadv; preadv64; pthread_barrierattr_destroy; diff --git a/libc/libc.map.txt b/libc/libc.map.txt index 9eb8d01d6..232f85980 100644 --- a/libc/libc.map.txt +++ b/libc/libc.map.txt @@ -1338,8 +1338,10 @@ LIBC_N { __pwrite64_chk; __write_chk; fileno_unlocked; + freeifaddrs; getgrgid_r; getgrnam_r; + getifaddrs; preadv; preadv64; prlimit; # arm mips x86 diff --git a/libc/libc.mips.brillo.map b/libc/libc.mips.brillo.map index d4a17eaf4..5b7216bc2 100644 --- a/libc/libc.mips.brillo.map +++ b/libc/libc.mips.brillo.map @@ -1240,8 +1240,10 @@ LIBC_N { __pwrite64_chk; __write_chk; fileno_unlocked; + freeifaddrs; getgrgid_r; getgrnam_r; + getifaddrs; preadv; preadv64; prlimit; # arm mips x86 diff --git a/libc/libc.mips.map b/libc/libc.mips.map index 5bee67eb4..78b224b16 100644 --- a/libc/libc.mips.map +++ b/libc/libc.mips.map @@ -1274,8 +1274,10 @@ LIBC_N { __pwrite64_chk; __write_chk; fileno_unlocked; + freeifaddrs; getgrgid_r; getgrnam_r; + getifaddrs; preadv; preadv64; prlimit; # arm mips x86 diff --git a/libc/libc.mips64.map b/libc/libc.mips64.map index 8fa46a84c..0eb0275c9 100644 --- a/libc/libc.mips64.map +++ b/libc/libc.mips64.map @@ -1156,8 +1156,10 @@ LIBC_N { __pwrite64_chk; __write_chk; fileno_unlocked; + freeifaddrs; getgrgid_r; getgrnam_r; + getifaddrs; preadv; preadv64; pthread_barrierattr_destroy; diff --git a/libc/libc.x86.brillo.map b/libc/libc.x86.brillo.map index f66f21ba8..27a7778e5 100644 --- a/libc/libc.x86.brillo.map +++ b/libc/libc.x86.brillo.map @@ -1238,8 +1238,10 @@ LIBC_N { __pwrite64_chk; __write_chk; fileno_unlocked; + freeifaddrs; getgrgid_r; getgrnam_r; + getifaddrs; preadv; preadv64; prlimit; # arm mips x86 diff --git a/libc/libc.x86.map b/libc/libc.x86.map index 7120e7ab4..a4b4494a5 100644 --- a/libc/libc.x86.map +++ b/libc/libc.x86.map @@ -1272,8 +1272,10 @@ LIBC_N { __pwrite64_chk; __write_chk; fileno_unlocked; + freeifaddrs; getgrgid_r; getgrnam_r; + getifaddrs; preadv; preadv64; prlimit; # arm mips x86 diff --git a/libc/libc.x86_64.map b/libc/libc.x86_64.map index 8fa46a84c..0eb0275c9 100644 --- a/libc/libc.x86_64.map +++ b/libc/libc.x86_64.map @@ -1156,8 +1156,10 @@ LIBC_N { __pwrite64_chk; __write_chk; fileno_unlocked; + freeifaddrs; getgrgid_r; getgrnam_r; + getifaddrs; preadv; preadv64; pthread_barrierattr_destroy; diff --git a/tests/Android.mk b/tests/Android.mk index 86c141a6b..ff9e96a9b 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -61,6 +61,7 @@ libBionicStandardTests_src_files := \ ftw_test.cpp \ getauxval_test.cpp \ getcwd_test.cpp \ + ifaddrs_test.cpp \ inttypes_test.cpp \ libc_logging_test.cpp \ libgen_basename_test.cpp \ diff --git a/tests/ifaddrs_test.cpp b/tests/ifaddrs_test.cpp new file mode 100644 index 000000000..67857cbb9 --- /dev/null +++ b/tests/ifaddrs_test.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 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. + */ + +#include + +#include + +TEST(ifaddrs, freeifaddrs_null) { + freeifaddrs(nullptr); +} + +TEST(ifaddrs, getifaddrs_smoke) { + ifaddrs* addrs = nullptr; + + ASSERT_EQ(0, getifaddrs(&addrs)); + ASSERT_TRUE(addrs != nullptr); + + bool saw_loopback = false; + for (ifaddrs* addr = addrs; addr != nullptr; addr = addr->ifa_next) { + if (addr->ifa_name && strcmp(addr->ifa_name, "lo") == 0) saw_loopback = true; + } + ASSERT_TRUE(saw_loopback); + + freeifaddrs(addrs); +} -- 2.11.0