diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2018-10-02 22:15:52 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2018-10-02 22:15:52 +0000 |
commit | 20077d56e6f6dae98d2365d4d67e739bc49a5076 (patch) | |
tree | 255549de864e4b32e3ef8dbce699768a4ef712cc | |
parent | 225639a44dc0a02893f8cbd4ff02b58cd9454a35 (diff) | |
parent | 873eee6422fdd7237b9bf86edb37f2958b23829e (diff) | |
download | android_device_generic_goldfish-20077d56e6f6dae98d2365d4d67e739bc49a5076.tar.gz android_device_generic_goldfish-20077d56e6f6dae98d2365d4d67e739bc49a5076.tar.bz2 android_device_generic_goldfish-20077d56e6f6dae98d2365d4d67e739bc49a5076.zip |
Snap for 5044688 from 873eee6422fdd7237b9bf86edb37f2958b23829e to pi-qpr2-release
Change-Id: Icd99a18af2c325808b9183e2919b7c695136ecba
44 files changed, 2446 insertions, 153 deletions
diff --git a/dhcp/server/dhcpserver.cpp b/dhcp/server/dhcpserver.cpp index 33fb61a..d6d4a7b 100644 --- a/dhcp/server/dhcpserver.cpp +++ b/dhcp/server/dhcpserver.cpp @@ -35,16 +35,7 @@ static const int kMaxDnsServers = 4; -DhcpServer::DhcpServer(in_addr_t dhcpRangeStart, - in_addr_t dhcpRangeEnd, - in_addr_t netmask, - in_addr_t gateway, - unsigned int excludeInterface) : - mNextAddressOffset(0), - mDhcpRangeStart(dhcpRangeStart), - mDhcpRangeEnd(dhcpRangeEnd), - mNetmask(netmask), - mGateway(gateway), +DhcpServer::DhcpServer(unsigned int excludeInterface) : mExcludeInterface(excludeInterface) { } @@ -147,9 +138,13 @@ void DhcpServer::sendDhcpOffer(const Message& message, unsigned int interfaceIndex ) { updateDnsServers(); in_addr_t offerAddress; + in_addr_t netmask; + in_addr_t gateway; Result res = getOfferAddress(interfaceIndex, message.dhcpData.chaddr, - &offerAddress); + &offerAddress, + &netmask, + &gateway); if (!res) { ALOGE("Failed to get address for offer: %s", res.c_str()); return; @@ -165,8 +160,8 @@ void DhcpServer::sendDhcpOffer(const Message& message, Message offer = Message::offer(message, serverAddress, offerAddress, - mNetmask, - mGateway, + netmask, + gateway, mDnsServers.data(), mDnsServers.size()); res = sendMessage(interfaceIndex, serverAddress, offer); @@ -177,10 +172,15 @@ void DhcpServer::sendDhcpOffer(const Message& message, void DhcpServer::sendAck(const Message& message, unsigned int interfaceIndex) { updateDnsServers(); - in_addr_t offerAddress, serverAddress; + in_addr_t offerAddress; + in_addr_t netmask; + in_addr_t gateway; + in_addr_t serverAddress; Result res = getOfferAddress(interfaceIndex, message.dhcpData.chaddr, - &offerAddress); + &offerAddress, + &netmask, + &gateway); if (!res) { ALOGE("Failed to get address for offer: %s", res.c_str()); return; @@ -194,8 +194,8 @@ void DhcpServer::sendAck(const Message& message, unsigned int interfaceIndex) { Message ack = Message::ack(message, serverAddress, offerAddress, - mNetmask, - mGateway, + netmask, + gateway, mDnsServers.data(), mDnsServers.size()); res = sendMessage(interfaceIndex, serverAddress, ack); @@ -222,9 +222,13 @@ void DhcpServer::sendNack(const Message& message, unsigned int interfaceIndex) { bool DhcpServer::isValidDhcpRequest(const Message& message, unsigned int interfaceIndex) { in_addr_t offerAddress; + in_addr_t netmask; + in_addr_t gateway; Result res = getOfferAddress(interfaceIndex, message.dhcpData.chaddr, - &offerAddress); + &offerAddress, + &netmask, + &gateway); if (!res) { ALOGE("Failed to get address for offer: %s", res.c_str()); return false; @@ -251,32 +255,99 @@ void DhcpServer::updateDnsServers() { } } -Result DhcpServer::getInterfaceAddress(unsigned int interfaceIndex, - in_addr_t* address) { +Result DhcpServer::getInterfaceData(unsigned int interfaceIndex, + unsigned long type, + struct ifreq* response) { char interfaceName[IF_NAMESIZE + 1]; if (if_indextoname(interfaceIndex, interfaceName) == nullptr) { return Result::error("Failed to get interface name for index %u: %s", interfaceIndex, strerror(errno)); } - struct ifreq request; - memset(&request, 0, sizeof(request)); - request.ifr_addr.sa_family = AF_INET; - strncpy(request.ifr_name, interfaceName, IFNAMSIZ - 1); + memset(response, 0, sizeof(*response)); + response->ifr_addr.sa_family = AF_INET; + strncpy(response->ifr_name, interfaceName, IFNAMSIZ - 1); - if (::ioctl(mSocket.get(), SIOCGIFADDR, &request) == -1) { - return Result::error("Failed to get address for interface %s: %s", + if (::ioctl(mSocket.get(), type, response) == -1) { + return Result::error("Failed to get data for interface %s: %s", interfaceName, strerror(errno)); } - auto inAddr = reinterpret_cast<struct sockaddr_in*>(&request.ifr_addr); - *address = inAddr->sin_addr.s_addr; - return Result::success(); } +Result DhcpServer::getInterfaceAddress(unsigned int interfaceIndex, + in_addr_t* address) { + struct ifreq data; + Result res = getInterfaceData(interfaceIndex, SIOCGIFADDR, &data); + if (res.isSuccess()) { + auto inAddr = reinterpret_cast<struct sockaddr_in*>(&data.ifr_addr); + *address = inAddr->sin_addr.s_addr; + } + return res; +} + +Result DhcpServer::getInterfaceNetmask(unsigned int interfaceIndex, + in_addr_t* address) { + struct ifreq data; + Result res = getInterfaceData(interfaceIndex, SIOCGIFNETMASK, &data); + if (res.isSuccess()) { + auto inAddr = reinterpret_cast<struct sockaddr_in*>(&data.ifr_addr); + *address = inAddr->sin_addr.s_addr; + } + return res; +} + +static bool isValidHost(const in_addr_t address, + const in_addr_t interfaceAddress, + const in_addr_t netmask) { + // If the bits outside of the netmask are all zero it's a network address, + // don't use this. + bool isNetworkAddress = (address & ~netmask) == 0; + // If all bits outside of the netmask are set then it's a broadcast address, + // don't use this either. + bool isBroadcastAddress = (address & ~netmask) == ~netmask; + // Don't assign the interface address to a host + bool isInterfaceAddress = address == interfaceAddress; + + return !isNetworkAddress && !isBroadcastAddress && !isInterfaceAddress; +} + +static bool addressInRange(const in_addr_t address, + const in_addr_t interfaceAddress, + const in_addr_t netmask) { + if (address <= (interfaceAddress & netmask)) { + return false; + } + if (address >= (interfaceAddress | ~netmask)) { + return false; + } + return true; +} + Result DhcpServer::getOfferAddress(unsigned int interfaceIndex, const uint8_t* macAddress, - in_addr_t* address) { + in_addr_t* address, + in_addr_t* netmask, + in_addr_t* gateway) { + // The interface address will be the gateway and will be used to determine + // the range of valid addresses (along with the netmask) for the client. + in_addr_t interfaceAddress = 0; + Result res = getInterfaceAddress(interfaceIndex, &interfaceAddress); + if (!res) { + return res; + } + // The netmask of the interface will be the netmask for the client as well + // as used to determine network range. + in_addr_t mask = 0; + res = getInterfaceNetmask(interfaceIndex, &mask); + if (!res) { + return res; + } + + // Assign these values now before they are modified below + *gateway = interfaceAddress; + *netmask = mask; + Lease key(interfaceIndex, macAddress); // Find or create entry, if it's created it will be zero and we update it @@ -284,18 +355,30 @@ Result DhcpServer::getOfferAddress(unsigned int interfaceIndex, if (value == 0) { // Addresses are stored in network byte order so when doing math on them // they have to be converted to host byte order - in_addr_t nextAddress = ntohl(mDhcpRangeStart) + mNextAddressOffset; - uint8_t lastAddressByte = nextAddress & 0xFF; - while (lastAddressByte == 0xFF || lastAddressByte == 0) { - // The address ends in .255 or .0 which means it's a broadcast or - // network address respectively. Increase it further to avoid this. + interfaceAddress = ntohl(interfaceAddress); + mask = ntohl(mask); + // Get a reference to the offset so we can use it and increase it at the + // same time. If the entry does not exist it will be created with a + // value of zero. + in_addr_t& offset = mNextAddressOffsets[interfaceIndex]; + if (offset == 0) { + // Increase if zero to avoid assigning network address + ++offset; + } + // Start out at the first address in the range as determined by netmask + in_addr_t nextAddress = (interfaceAddress & mask) + offset; + + // Ensure the address is valid + while (!isValidHost(nextAddress, interfaceAddress, mask) && + addressInRange(nextAddress, interfaceAddress, mask)) { ++nextAddress; - ++mNextAddressOffset; + ++offset; } - if (nextAddress <= ntohl(mDhcpRangeEnd)) { - // And then converted back again + + if (addressInRange(nextAddress, interfaceAddress, mask)) { + // Convert back to network byte order value = htonl(nextAddress); - ++mNextAddressOffset; + ++offset; } else { // Ran out of addresses return Result::error("DHCP server is out of addresses"); diff --git a/dhcp/server/dhcpserver.h b/dhcp/server/dhcpserver.h index c5e1007..276cd5b 100644 --- a/dhcp/server/dhcpserver.h +++ b/dhcp/server/dhcpserver.h @@ -30,14 +30,9 @@ class Message; class DhcpServer { public: - // Construct a DHCP server with the given parameters. Ignore any requests - // and discoveries coming on the network interface identified by - // |excludeInterface|. - DhcpServer(in_addr_t dhcpRangeStart, - in_addr_t dhcpRangeEnd, - in_addr_t netmask, - in_addr_t gateway, - unsigned int excludeInterface); + // Construct a DHCP server. Ignore any requests and discoveries coming on + // the network interface identified by |excludeInterface|. + explicit DhcpServer(unsigned int excludeInterface); Result init(); Result run(); @@ -54,24 +49,27 @@ private: bool isValidDhcpRequest(const Message& message, unsigned int interfaceIndex); void updateDnsServers(); + Result getInterfaceData(unsigned int interfaceIndex, + unsigned long type, + struct ifreq* response); Result getInterfaceAddress(unsigned int interfaceIndex, in_addr_t* address); + Result getInterfaceNetmask(unsigned int interfaceIndex, + in_addr_t* netmask); Result getOfferAddress(unsigned int interfaceIndex, const uint8_t* macAddress, - in_addr_t* address); + in_addr_t* address, + in_addr_t* netmask, + in_addr_t* gateway); Socket mSocket; // This is the next address offset. This will be added to whatever the base // address of the DHCP address range is. For each new MAC address seen this // value will increase by one. - in_addr_t mNextAddressOffset; - in_addr_t mDhcpRangeStart; - in_addr_t mDhcpRangeEnd; - in_addr_t mNetmask; - in_addr_t mGateway; std::vector<in_addr_t> mDnsServers; // Map a lease to an IP address for that lease std::unordered_map<Lease, in_addr_t> mLeases; + std::unordered_map<unsigned int, in_addr_t> mNextAddressOffsets; unsigned int mExcludeInterface; }; diff --git a/dhcp/server/log.h b/dhcp/server/log.h index bb1094f..a0f21e0 100644 --- a/dhcp/server/log.h +++ b/dhcp/server/log.h @@ -16,5 +16,5 @@ #pragma once #define LOG_TAG "dhcpserver" -#include <cutils/log.h> +#include <log/log.h> diff --git a/dhcp/server/main.cpp b/dhcp/server/main.cpp index eecafc1..482ffd6 100644 --- a/dhcp/server/main.cpp +++ b/dhcp/server/main.cpp @@ -26,73 +26,10 @@ static void usage(const char* program) { } int main(int argc, char* argv[]) { - in_addr_t rangeStart = 0; - in_addr_t rangeEnd = 0; - in_addr_t gateway = 0; - in_addr_t netmask = 0; char* excludeInterfaceName = nullptr; unsigned int excludeInterfaceIndex = 0; for (int i = 1; i < argc; ++i) { - if (strcmp("--range", argv[i]) == 0) { - if (i + 1 >= argc) { - ALOGE("ERROR: Missing argument to --range parameter"); - usage(argv[0]); - return 1; - } - char* divider = strchr(argv[i + 1], ','); - if (divider != nullptr) { - *divider = '\0'; - struct in_addr address; - if (inet_pton(AF_INET, argv[i + 1], &address) > 0) { - rangeStart = address.s_addr; - } else { - ALOGE("ERROR: Invalid start address '%s'", argv[i + 1]); - usage(argv[0]); - return 1; - } - char* next = divider + 1; - if (inet_pton(AF_INET, next, &address) > 0) { - rangeEnd = address.s_addr; - } else { - ALOGE("ERROR: Invalid end address '%s'", next); - usage(argv[0]); - return 1; - } - } else { - ALOGE("ERROR: Invalid --range parameter '%s'", argv[i + 1]); - usage(argv[0]); - return 1; - } - ++i; - } else if (strcmp("--gateway", argv[i]) == 0) { - if (i + 1 >= argc) { - ALOGE("ERROR: Missing argument to --gateway parameter"); - usage(argv[0]); - return 1; - } - struct in_addr address; - if (inet_pton(AF_INET, argv[i + 1], &address) > 0) { - gateway = address.s_addr; - } else { - ALOGE("ERROR: Invalid gateway '%s'", argv[i + 1]); - usage(argv[0]); - return 1; - } - } else if (strcmp("--netmask", argv[i]) == 0) { - if (i + 1 >= argc) { - ALOGE("ERROR: Missing argument to --netmask parameter"); - usage(argv[0]); - return 1; - } - struct in_addr address; - if (inet_pton(AF_INET, argv[i + 1], &address) > 0) { - netmask = address.s_addr; - } else { - ALOGE("ERROR: Invalid netmask '%s'", argv[i + 1]); - usage(argv[0]); - return 1; - } - } else if (strcmp("--exclude-interface", argv[i]) == 0) { + if (strcmp("--exclude-interface", argv[i]) == 0) { if (i + 1 >= argc) { ALOGE("ERROR: Missing argument to " "--exclude-interfaces parameter"); @@ -110,27 +47,7 @@ int main(int argc, char* argv[]) { } } - if (rangeStart == 0 || rangeEnd == 0) { - ALOGE("ERROR: Missing or invalid --range argument"); - usage(argv[0]); - return 1; - } - if (gateway == 0) { - ALOGE("ERROR: Missing or invalid --gateway argument"); - usage(argv[0]); - return 1; - } - if (netmask == 0) { - ALOGE("ERROR: Missing or invalid --netmask argument"); - usage(argv[0]); - return 1; - } - - DhcpServer server(rangeStart, - rangeEnd, - netmask, - gateway, - excludeInterfaceIndex); + DhcpServer server(excludeInterfaceIndex); Result res = server.init(); if (!res) { ALOGE("Failed to initialize DHCP server: %s\n", res.c_str()); diff --git a/include/Android.bp b/include/Android.bp new file mode 100644 index 0000000..2f09676 --- /dev/null +++ b/include/Android.bp @@ -0,0 +1,5 @@ +cc_library_headers { + name: "goldfish_headers", + vendor_available: true, + export_include_dirs: ["."], +} diff --git a/init.ranchu.rc b/init.ranchu.rc index bf77184..d7a61d4 100644 --- a/init.ranchu.rc +++ b/init.ranchu.rc @@ -57,7 +57,7 @@ on property:dev.bootcomplete=1 service ranchu-net /vendor/bin/init.ranchu-net.sh class late_start user root - group root wakelock + group root wakelock wifi oneshot service ipv6proxy /vendor/bin/execns router /vendor/bin/ipv6proxy -o eth0 -i wlan1,radio0-peer @@ -65,16 +65,21 @@ service ipv6proxy /vendor/bin/execns router /vendor/bin/ipv6proxy -o eth0 -i wla group root disabled -service emu_hostapd /vendor/bin/execns -u wifi -g wifi router /vendor/bin/hostapd_nohidl /vendor/etc/simulated_hostapd.conf +service emu_hostapd /vendor/bin/execns -u wifi -g wifi router /vendor/bin/hostapd_nohidl /data/vendor/wifi/hostapd/hostapd.conf user root group root wifi net_raw net_admin disabled -service dhcpserver /vendor/bin/execns router /vendor/bin/dhcpserver --range 192.168.232.2,192.168.239.254 --gateway 192.168.232.1 --netmask 255.255.248.0 --exclude-interface eth0 +service dhcpserver /vendor/bin/execns router /vendor/bin/dhcpserver --exclude-interface eth0 user root group root disabled +service netmgr /vendor/bin/execns router /vendor/bin/netmgr --if-prefix wlan1_ --network 192.168.232.9/29 + user root + group root wifi + disabled + service dhcpclient_rtr /vendor/bin/execns router /vendor/bin/dhcpclient -i eth0 user root group root diff --git a/network/netmgr/Android.bp b/network/netmgr/Android.bp new file mode 100644 index 0000000..ab177e6 --- /dev/null +++ b/network/netmgr/Android.bp @@ -0,0 +1,38 @@ +// +// Copyright (C) 2018 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. +// + +cc_binary { + name: "netmgr", + vendor: true, + srcs: [ + "address_assigner.cpp", + "commander.cpp", + "fork.cpp", + "interface_state.cpp", + "log.cpp", + "main.cpp", + "monitor.cpp", + "poller.cpp", + "commands/wifi_command.cpp", + ], + shared_libs: [ + "libcutils", + "liblog", + ], + header_libs: [ + "goldfish_headers", + ], +} diff --git a/network/netmgr/address_assigner.cpp b/network/netmgr/address_assigner.cpp new file mode 100644 index 0000000..11df81d --- /dev/null +++ b/network/netmgr/address_assigner.cpp @@ -0,0 +1,151 @@ +/* + * Copyright 2018, 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 "address_assigner.h" + +#include "log.h" + +#include <errno.h> +#include <net/if.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +AddressAssigner::AddressAssigner(const char* interfacePrefix, + in_addr_t baseAddress, + uint32_t maskLength) : + mInterfacePrefix(interfacePrefix), + mPrefixLength(strlen(interfacePrefix)), + mBaseAddress(baseAddress), + mMaskLength(maskLength) { + +} + +void AddressAssigner::onInterfaceState(unsigned int /*index*/, + const char* name, + InterfaceState state) { + if (strncmp(name, mInterfacePrefix, mPrefixLength) != 0) { + // The interface does not match the prefix, ignore this change + return; + } + + switch (state) { + case InterfaceState::Up: + assignAddress(name); + break; + case InterfaceState::Down: + freeAddress(name); + break; + } +} + +void AddressAssigner::assignAddress(const char* interfaceName) { + if (mMaskLength > 30) { + // The mask length is too long, we can't assign enough IP addresses from + // this. A maximum of 30 bits is supported, leaving 4 remaining + // addresses, one is network, one is broadcast, one is gateway, one is + // client. + return; + } + // Each subnet will have an amount of bits available to it that equals + // 32-bits - <mask length>, so if mask length is 29 there will be 3 + // remaining bits for each subnet. Then the distance between each subnet + // is 2 to the power of this number, in our example 2^3 = 8 so to get to the + // next subnet we add 8 to the network address. + in_addr_t increment = 1 << (32 - mMaskLength); + + // Convert the address to host byte-order first so we can do math on it. + for (in_addr_t addr = ntohl(mBaseAddress); true; addr += increment) { + // Take the reference of this lookup, that way we can assign a name to + // it if needed. + auto& usedName = mUsedIpAddresses[addr]; + if (usedName.empty()) { + // This address is not in use, let's use it + usedName = interfaceName; + // Make sure we convert back to network byte-order when setting it. + setIpAddress(interfaceName, htonl(addr)); + break; + } + } +} + +void AddressAssigner::setIpAddress(const char* interfaceName, + in_addr_t address) { + int sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sock == -1) { + LOGE("AddressAssigner unable to open IP socket: %s", strerror(errno)); + return; + } + if (!setAddress(sock, SIOCSIFADDR, interfaceName, address)) { + LOGE("AddressAssigner unable to set interface address: %s", + strerror(errno)); + ::close(sock); + return; + } + + // The netmask is the inverted maximum value of the lower bits. That is if + // the mask length is 29 then the the largest value of the 3 (32-29) lowest + // bits is 7 (2^3 - 1) (111 binary). Inverting this value gives the netmask + // because it excludes those three bits and sets every other bit. + in_addr_t netmask = htonl(~((1 << (32 - mMaskLength)) - 1)); + + if (!setAddress(sock, SIOCSIFNETMASK, interfaceName, netmask)) { + LOGE("AddressAssigner unable to set interface netmask: %s", + strerror(errno)); + ::close(sock); + return; + } + + // The broadcast address is just the assigned address with all bits outside + // of the netmask set to one. + in_addr_t broadcast = address | ~netmask; + + if (!setAddress(sock, SIOCSIFBRDADDR, interfaceName, broadcast)) { + LOGE("AddressAssigner unable to set interface broadcast: %s", + strerror(errno)); + ::close(sock); + return; + } + ::close(sock); +} + +bool AddressAssigner::setAddress(int sock, + int type, + const char* interfaceName, + in_addr_t address) { + struct ifreq request; + memset(&request, 0, sizeof(request)); + strlcpy(request.ifr_name, interfaceName, sizeof(request.ifr_name)); + auto addr = reinterpret_cast<struct sockaddr_in*>(&request.ifr_addr); + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = address; + + if (::ioctl(sock, type, &request) != 0) { + return false; + } + return true; +} + +void AddressAssigner::freeAddress(const char* interfaceName) { + for (auto& ipName : mUsedIpAddresses) { + if (ipName.second == interfaceName) { + // This is the one, free it up for future use + mUsedIpAddresses.erase(ipName.first); + } + } +} + diff --git a/network/netmgr/address_assigner.h b/network/netmgr/address_assigner.h new file mode 100644 index 0000000..dde42ee --- /dev/null +++ b/network/netmgr/address_assigner.h @@ -0,0 +1,54 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +#include "interface_state.h" + +#include <string> +#include <unordered_map> + +#include <netinet/in.h> +#include <stdint.h> + +class AddressAssigner { +public: + AddressAssigner(const char* interfacePrefix, + in_addr_t baseAddress, + uint32_t maskLength); + + void onInterfaceState(unsigned int index, + const char* name, + InterfaceState state); + +private: + void assignAddress(const char* interfaceName); + void freeAddress(const char* interfaceName); + + void setIpAddress(const char* interfaceName, in_addr_t address); + bool setAddress(int socket, + int type, + const char* interfaceName, + in_addr_t address); + void removeIpAddress(const char* interfaceName, in_addr_t address); + + const char* mInterfacePrefix; + size_t mPrefixLength; + in_addr_t mBaseAddress; + uint32_t mMaskLength; + std::unordered_map<in_addr_t, std::string> mUsedIpAddresses; +}; + diff --git a/network/netmgr/commander.cpp b/network/netmgr/commander.cpp new file mode 100644 index 0000000..82ed95e --- /dev/null +++ b/network/netmgr/commander.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2018, 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 "commander.h" + +#include "commands/command.h" +#include "log.h" + +#include <errno.h> +#include <qemu_pipe.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> + +static const char kQemuPipeName[] = "qemud:network"; + +// How much space to use for each command received +static const size_t kReceiveSpace = 1024; +// The maximum amount of bytes to keep in the receive buffer for a single +// command before dropping data. +static const size_t kMaxReceiveBufferSize = 65536; + +Commander::Commander() : mPipeFd(-1) { +} + +Commander::~Commander() { + closePipe(); +} + +Result Commander::init() { + if (mPipeFd != -1) { + return Result::error("Commander already initialized"); + } + + openPipe(); + + return Result::success(); +} + +void Commander::registerCommand(const char* commandStr, Command* command) { + mCommands[commandStr] = command; +} + +Pollable::Data Commander::data() const { + return Data { mPipeFd, mDeadline }; +} + +void Commander::onReadAvailable() { + + size_t offset = mReceiveBuffer.size(); + mReceiveBuffer.resize(offset + kReceiveSpace); + if (mReceiveBuffer.size() > kMaxReceiveBufferSize) { + // We have buffered too much data, this should never happen but as a + // seurity measure let's just drop everything we have and keep + // receiving. Maybe the situation will improve. + mReceiveBuffer.resize(kReceiveSpace); + offset = 0; + } + while (true) { + int status = ::read(mPipeFd, &mReceiveBuffer[offset], kReceiveSpace); + + if (status < 0) { + if (errno == EINTR) { + // We got an interrupt, try again + continue; + } + LOGE("Commander failed to receive on pipe: %s", strerror(errno)); + return; + } + size_t length = static_cast<size_t>(status); + mReceiveBuffer.resize(offset + length); + + while (true) { + auto endline = std::find(mReceiveBuffer.begin(), + mReceiveBuffer.end(), + '\n'); + if (endline == mReceiveBuffer.end()) { + // No endline in sight, keep waiting and buffering + return; + } + + *endline = '\0'; + + char* args = ::strchr(mReceiveBuffer.data(), ' '); + if (args) { + *args++ = '\0'; + } + auto command = mCommands.find(mReceiveBuffer.data()); + + if (command != mCommands.end()) { + command->second->onCommand(mReceiveBuffer.data(), args); + } + // Now that we have processed this line let's remove it from the + // receive buffer + ++endline; + if (endline == mReceiveBuffer.end()) { + mReceiveBuffer.clear(); + // There can be nothing left, just return + return; + } else { + mReceiveBuffer.erase(mReceiveBuffer.begin(), endline + 1); + // There may be another line in there so keep looping and look + // for more + } + } + return; + } +} + +void Commander::onClose() { + // Pipe was closed from the other end, close it on our side and re-open + closePipe(); + openPipe(); +} + +void Commander::onTimeout() { + if (mPipeFd == -1) { + openPipe(); + } +} + +void Commander::openPipe() { + mPipeFd = qemu_pipe_open(kQemuPipeName); + if (mPipeFd == -1) { + LOGE("Failed to open QEMU pipe '%s': %s", + kQemuPipeName, + strerror(errno)); + // Try again in the future + mDeadline = Pollable::Clock::now() + std::chrono::minutes(1); + } else { + mDeadline = Pollable::Timestamp::max(); + } +} + +void Commander::closePipe() { + if (mPipeFd != -1) { + ::close(mPipeFd); + mPipeFd = -1; + } +} diff --git a/network/netmgr/commander.h b/network/netmgr/commander.h new file mode 100644 index 0000000..9f33526 --- /dev/null +++ b/network/netmgr/commander.h @@ -0,0 +1,48 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +#include "pollable.h" +#include "result.h" + +#include <unordered_map> +#include <vector> + +class Command; + +class Commander : public Pollable { +public: + Commander(); + ~Commander(); + + Result init(); + + void registerCommand(const char* commandStr, Command* command); + + Pollable::Data data() const override; + void onReadAvailable() override; + void onClose() override; + void onTimeout() override; +private: + void openPipe(); + void closePipe(); + + int mPipeFd; + Pollable::Timestamp mDeadline; + std::vector<char> mReceiveBuffer; + std::unordered_map<std::string, Command*> mCommands; +}; diff --git a/network/netmgr/commands/command.h b/network/netmgr/commands/command.h new file mode 100644 index 0000000..0612cef --- /dev/null +++ b/network/netmgr/commands/command.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +#include "../result.h" + +// An interface for commands coming through the pipe. Note that each instance +// can handle any number of commands, all it has to do is differentiate based +// on the incoming command string or arguments. It will have to be registered +// multiple times, once for each command though. +class Command { +public: + virtual ~Command() = default; + + // Work to perform when a command is received. The result will be used to + // report the result to the user. If the result indicates success the user + // will see an "OK" response, on failure the error message in the result + // will be presented to the user. This means that the result error string + // should be fairly user-friendly. + virtual Result onCommand(const char* command, const char* args) = 0; + +}; + diff --git a/network/netmgr/commands/wifi_command.cpp b/network/netmgr/commands/wifi_command.cpp new file mode 100644 index 0000000..0d0be19 --- /dev/null +++ b/network/netmgr/commands/wifi_command.cpp @@ -0,0 +1,273 @@ +/* + * Copyright 2018, 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 "wifi_command.h" + +#include "../fork.h" +#include "log.h" + +#include <cutils/properties.h> +#include <errno.h> +#include <net/if.h> +#include <netinet/in.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +static const char kHostApdStubFile[] = "/vendor/etc/simulated_hostapd.conf"; +static const char kHostApdConfFile[] = "/data/vendor/wifi/hostapd/hostapd.conf"; + +static const char kControlRestartProperty[] = "ctl.restart"; +static const char kHostApdServiceName[] = "emu_hostapd"; + +static const char kIfNamePrefix[] = "wlan1_"; + +class File { +public: + explicit File(FILE* file) : mFile(file) {} + ~File() { + if (mFile) { + fclose(mFile); + } + } + + FILE* get() { return mFile; } + + bool operator!() const { return mFile == nullptr; } +private: + FILE* mFile; +}; + +class Fd { +public: + explicit Fd(int fd) : mFd(fd) {} + ~Fd() { + if (mFd != -1) { + ::close(mFd); + mFd = -1; + } + } + + int get() const { return mFd; } + +private: + int mFd; +}; + +std::vector<std::string> explode(const char* str) { + const char* cur = str; + const char* space = nullptr; + std::vector<std::string> result; + do { + space = ::strchr(cur, ' '); + if (space) { + result.emplace_back(cur, space); + cur = space + 1; + } else { + result.emplace_back(cur); + } + } while (space); + + return result; +} + +WifiCommand::WifiCommand() : mLowestInterfaceNumber(1) { + readConfig(); +} + +Result WifiCommand::onCommand(const char* command, const char* args) { + const char* divider = ::strchr(args, ' '); + if (divider == nullptr) { + // Unknown command, every command needs an argument + return Result::error("Invalid wifi command '%s'", args); + } + + std::string subCommand(args, divider); + if (subCommand.empty()) { + return Result::error("Empty wifi command"); + } + + std::vector<std::string> subArgs = explode(divider + 1); + if (subArgs.empty()) { + // All of these commands require sub arguments + return Result::error("Missing argument to command '%s'", + subCommand.c_str()); + } + + if (subCommand == "add") { + return onAdd(subArgs); + } else if (subCommand == "block") { + return onBlock(subArgs); + } else if (subCommand == "unblock") { + return onUnblock(subArgs); + } else { + return Result::error("Unknown wifi command '%s'", subCommand.c_str()); + } +} + +void WifiCommand::readConfig() { +} + +Result WifiCommand::writeConfig() { + File in(fopen(kHostApdStubFile, "r")); + if (!in) { + return Result::error("Config failure: could not open template: %s", + strerror(errno)); + } + + File out(fopen(kHostApdConfFile, "w")); + if (!out) { + return Result::error("Config failure: could not open target: %s", + strerror(errno)); + } + + char buffer[32768]; + while (!feof(in.get())) { + size_t bytesRead = fread(buffer, 1, sizeof(buffer), in.get()); + if (bytesRead != sizeof(buffer) && ferror(in.get())) { + return Result::error("Config failure: Error reading template: %s", + strerror(errno)); + } + + size_t bytesWritten = fwrite(buffer, 1, bytesRead, out.get()); + if (bytesWritten != bytesRead) { + return Result::error("Config failure: Error writing target: %s", + strerror(errno)); + } + } + fprintf(out.get(), "\n\n"); + + for (const auto& ap : mAccessPoints) { + fprintf(out.get(), "bss=%s\n", ap.second.ifName.c_str()); + fprintf(out.get(), "ssid=%s\n", ap.second.ssid.c_str()); + if (!ap.second.password.empty()) { + fprintf(out.get(), "wpa=2\n"); + fprintf(out.get(), "wpa_key_mgmt=WPA-PSK\n"); + fprintf(out.get(), "rsn_pairwise=CCMP\n"); + fprintf(out.get(), "wpa_passphrase=%s\n", ap.second.password.c_str()); + } + fprintf(out.get(), "\n"); + } + return Result::success(); +} + +Result WifiCommand::triggerHostApd() { + property_set(kControlRestartProperty, kHostApdServiceName); + return Result::success(); +} + +static const char* sSetForwardRule[] = {"/system/bin/iptables", + "-w", // Wait for iptables lock if + "-W", // needed. This prevents + "50000", // spurious failures. + "<AddOrDelete>", // To be replaced + "FORWARD", + "-i", + "<InInterface>", // To be replaced + "-o", + "<OutInterface>", // To be replaced + "-j", + "DROP", + nullptr }; + +static const char kIpTables[] = "/system/bin/iptables"; +static const char kIp6Tables[] = "/system/bin/ip6tables"; +static const char kAddRule[] = "-A"; +static const char kDeleteRule[] = "-D"; +static const size_t kIpTablesIndex = 0; +static const size_t kActionIndex = 4; +static const size_t kInInterfaceIndex = 7; +static const size_t kOutInterfaceIndex = 9; + + +Result WifiCommand::setBlocked(const char* ifName, bool blocked) { + // Blocking means adding block rules, unblocking means removing them + sSetForwardRule[kActionIndex] = blocked ? kAddRule : kDeleteRule; + + // Do this for both IPv4 and IPv6 to ensure all traffic is blocked/unblocked + for (const auto& iptables : { kIpTables, kIp6Tables }) { + // Block traffic coming in from the outside world to this wlan + sSetForwardRule[kIpTablesIndex] = iptables; + sSetForwardRule[kInInterfaceIndex] = "eth0"; + sSetForwardRule[kOutInterfaceIndex] = ifName; + if (!forkAndExec(sSetForwardRule)) { + return Result::error("Internal error: Unable to %s network", + blocked ? "block" : "unblock"); + } + // Block traffic going from the wlan to the outside world + sSetForwardRule[kInInterfaceIndex] = ifName; + sSetForwardRule[kOutInterfaceIndex] = "eth0"; + if (!forkAndExec(sSetForwardRule)) { + return Result::error("Internal error: Unable to %s network", + blocked ? "block" : "unblock"); + } + } + return Result::success(); +} + +Result WifiCommand::onAdd(const std::vector<std::string>& arguments) { + AccessPoint& ap = mAccessPoints[arguments[0]]; + ap.ssid = arguments[0]; + if (arguments.size() > 1) { + ap.password = arguments[1]; + } else { + ap.password.clear(); + } + if (ap.ifName.empty()) { + char buffer[sizeof(kIfNamePrefix) + 10]; + while (true) { + snprintf(buffer, sizeof(buffer), "%s%d", + kIfNamePrefix, mLowestInterfaceNumber); + ap.ifName = buffer; + auto usedInterface = mUsedInterfaces.find(ap.ifName); + if (usedInterface == mUsedInterfaces.end()) { + // This interface is available, use it + ++mLowestInterfaceNumber; + mUsedInterfaces.insert(ap.ifName); + break; + } + // The interface name was alread in use, try the next one + ++mLowestInterfaceNumber; + } + } + Result res = writeConfig(); + if (!res) { + return res; + } + return triggerHostApd(); +} + +Result WifiCommand::onBlock(const std::vector<std::string>& arguments) { + auto interface = mAccessPoints.find(arguments[0]); + if (interface == mAccessPoints.end()) { + return Result::error("Unknown SSID '%s", arguments[0].c_str()); + } + interface->second.blocked = true; + return setBlocked(interface->second.ifName.c_str(), true); +} + +Result WifiCommand::onUnblock(const std::vector<std::string>& arguments) { + auto interface = mAccessPoints.find(arguments[0]); + if (interface == mAccessPoints.end()) { + return Result::error("Unknown SSID '%s", arguments[0].c_str()); + } + interface->second.blocked = false; + return setBlocked(interface->second.ifName.c_str(), false); +} + diff --git a/network/netmgr/commands/wifi_command.h b/network/netmgr/commands/wifi_command.h new file mode 100644 index 0000000..f9ce96c --- /dev/null +++ b/network/netmgr/commands/wifi_command.h @@ -0,0 +1,55 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +#include "command.h" +#include "result.h" + +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +class WifiCommand : public Command { +public: + WifiCommand(); + virtual ~WifiCommand() = default; + + Result onCommand(const char* command, const char* args) override; +private: + void readConfig(); + Result writeConfig(); + Result triggerHostApd(); + Result setBlocked(const char* ifName, bool blocked); + + Result onAdd(const std::vector<std::string>& args); + Result onBlock(const std::vector<std::string>& args); + Result onUnblock(const std::vector<std::string>& args); + + struct AccessPoint { + AccessPoint() : blocked(false) {} + std::string ifName; + std::string ssid; + std::string password; + bool blocked; + }; + + std::unordered_map<std::string, AccessPoint> mAccessPoints; + std::unordered_set<std::string> mUsedInterfaces; + int mLowestInterfaceNumber; +}; + diff --git a/network/netmgr/fork.cpp b/network/netmgr/fork.cpp new file mode 100644 index 0000000..49c4c61 --- /dev/null +++ b/network/netmgr/fork.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2018, 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 "fork.h" + +#include "log.h" + +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +bool forkAndExec(const char* argv[]) { + pid_t pid = ::fork(); + if (pid < 0) { + // Failed to fork + LOGE("fork() failed: %s", strerror(errno)); + return false; + } else if (pid == 0) { + // Child + char buffer[32768]; + size_t offset = 0; + for (size_t i = 0; argv[i]; ++i) { + offset += snprintf(buffer + offset, sizeof(buffer) - offset, + "%s ", argv[i]); + } + LOGE("Running '%s'", buffer); + execvp(argv[0], const_cast<char* const*>(argv)); + LOGE("Failed to run '%s': %s", argv[0], strerror(errno)); + _exit(1); + } else { + // Parent + int status = 0; + do { + ::waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + int exitStatus = WEXITSTATUS(status); + if (exitStatus == 0) { + return true; + } + LOGE("Error: '%s' exited with code: %d", argv[0], exitStatus); + } else if (WIFSIGNALED(status)) { + LOGE("Error: '%s' terminated with signal: %d", + argv[0], WTERMSIG(status)); + } + // Other possibilities include being stopped and continued as part + // of a trace but we don't really care about that. The important + // part is that unless the process explicitly exited or was killed + // by a signal we have to keep waiting. + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + return false; + } +} diff --git a/network/netmgr/fork.h b/network/netmgr/fork.h new file mode 100644 index 0000000..a7cfd48 --- /dev/null +++ b/network/netmgr/fork.h @@ -0,0 +1,26 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +// Fork and run the provided program with arguments and wait until the program +// exits. The list of arguments in |argv| has to be terminated by a NULL +// pointer (e.g. { "ls", "-l", "/", nullptr } to run 'ls -l /' +// Returns true if the process exits normally with a return code of 0. Returns +// false if the process is terminated by a signal or if it exits with a return +// code that is not 0. +bool forkAndExec(const char* argv[]); + diff --git a/network/netmgr/interface_state.cpp b/network/netmgr/interface_state.cpp new file mode 100644 index 0000000..57e169c --- /dev/null +++ b/network/netmgr/interface_state.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2018, 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 "interface_state.h" + +const char* interfaceStateToStr(InterfaceState state) { + switch (state) { + case InterfaceState::Up: + return "Up"; + case InterfaceState::Down: + return "Down"; + } +} + diff --git a/network/netmgr/interface_state.h b/network/netmgr/interface_state.h new file mode 100644 index 0000000..5315b0f --- /dev/null +++ b/network/netmgr/interface_state.h @@ -0,0 +1,23 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +enum class InterfaceState { + Up, + Down, +}; + diff --git a/network/netmgr/log.cpp b/network/netmgr/log.cpp new file mode 100644 index 0000000..7256a3f --- /dev/null +++ b/network/netmgr/log.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2018, 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 "log.h" + +#include <stdio.h> +#include <unistd.h> + +bool isTerminal; + +void initIsTerminal() { + isTerminal = isatty(STDOUT_FILENO) != 0; +} + diff --git a/network/netmgr/log.h b/network/netmgr/log.h new file mode 100644 index 0000000..e722091 --- /dev/null +++ b/network/netmgr/log.h @@ -0,0 +1,36 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +#define LOG_TAG "netmgr" +#include <log/log.h> +#include <stdio.h> + +extern bool isTerminal; +void initIsTerminal(); + +// Print errors to stderr if running from a terminal, otherwise print to logcat +// This is useful for debugging from a terminal +#define LOGE(...) do { \ + if (isTerminal) { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } else { \ + ALOGE(__VA_ARGS__); \ + } \ +} while (0) + diff --git a/network/netmgr/main.cpp b/network/netmgr/main.cpp new file mode 100644 index 0000000..c6e20bc --- /dev/null +++ b/network/netmgr/main.cpp @@ -0,0 +1,139 @@ +/* + * Copyright 2018, 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 "address_assigner.h" +#include "commander.h" +#include "commands/wifi_command.h" +#include "log.h" +#include "monitor.h" +#include "poller.h" + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <functional> + +static void usage(const char* name) { + LOGE("Usage: %s --if-prefix <prefix> --network <ip/mask>", name); + LOGE(" <prefix> indicates the name of network interfaces to configure."); + LOGE(" <ip/mask> is the base IP address to assign to the first interface"); + LOGE(" and mask indicates the netmask and broadcast to set."); + LOGE(" Additionally mask is used to determine the address"); + LOGE(" for the second interface by skipping ahead one subnet"); + LOGE(" and the size of the subnet is indicated by <mask>"); +} + +static bool parseNetwork(const char* network, + in_addr_t* address, + uint32_t* mask) { + const char* divider = strchr(network, '/'); + if (divider == nullptr) { + LOGE("Network specifier '%s' is missing netmask length", network); + return false; + } + if (divider - network >= INET_ADDRSTRLEN) { + LOGE("Network specifier '%s' contains an IP address that is too long", + network); + return false; + } + + char buffer[INET_ADDRSTRLEN]; + strlcpy(buffer, network, divider - network + 1); + struct in_addr addr; + if (!::inet_aton(buffer, &addr)) { + // String could not be converted to IP address + LOGE("Network specifier '%s' contains an invalid IP address '%s'", + network, buffer); + return false; + } + + ++divider; + + char dummy = 0; + if (sscanf(divider, "%u%c", mask, &dummy) != 1) { + LOGE("Netork specifier '%s' contains an invalid netmask length '%s'", + network, divider); + return false; + } + + *address = addr.s_addr; + return true; +} + +int main(int argc, char* argv[]) { + const char* interfacePrefix = nullptr; + const char* network = nullptr; + + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--if-prefix") == 0 && i + 1 < argc) { + interfacePrefix = argv[++i]; + } else if (strcmp(argv[i], "--network") == 0 && i + 1 < argc) { + network = argv[++i]; + } else { + LOGE("Unknown parameter '%s'", argv[i]); + usage(argv[0]); + return 1; + } + } + + if (interfacePrefix == nullptr) { + LOGE("Missing parameter --if-prefix"); + } + if (network == nullptr) { + LOGE("Missing parameter --network"); + } + if (network == nullptr || interfacePrefix == nullptr) { + usage(argv[0]); + return 1; + } + + in_addr_t address = 0; + uint32_t mask = 0; + if (!parseNetwork(network, &address, &mask)) { + return 1; + } + + AddressAssigner assigner(interfacePrefix, address, mask); + Monitor monitor; + + monitor.setOnInterfaceState(std::bind(&AddressAssigner::onInterfaceState, + &assigner, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + + Result res = monitor.init(); + if (!res) { + LOGE("%s", res.c_str()); + return 1; + } + + Commander commander; + res = commander.init(); + if (!res) { + LOGE("%s", res.c_str()); + return 1; + } + + WifiCommand wifiCommand; + commander.registerCommand("wifi", &wifiCommand); + + Poller poller; + poller.addPollable(&monitor); + poller.addPollable(&commander); + return poller.run(); +} + diff --git a/network/netmgr/monitor.cpp b/network/netmgr/monitor.cpp new file mode 100644 index 0000000..492e7c6 --- /dev/null +++ b/network/netmgr/monitor.cpp @@ -0,0 +1,152 @@ +/* + * Copyright 2018, 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 "monitor.h" + +#include "log.h" + +#include <arpa/inet.h> +#include <errno.h> +#include <linux/rtnetlink.h> +#include <net/if.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +Monitor::Monitor() : mSocketFd(-1) { + +} + +Monitor::~Monitor() { + closeSocket(); +} + +Result Monitor::init() { + return openSocket(); +} + +void Monitor::setOnInterfaceState(OnInterfaceStateCallback callback) { + mOnInterfaceStateCallback = callback; +} + +void Monitor::onReadAvailable() { + char buffer[32768]; + struct sockaddr_storage storage; + + while (true) { + socklen_t addrSize = sizeof(storage); + int status = ::recvfrom(mSocketFd, + buffer, + sizeof(buffer), + MSG_DONTWAIT, + reinterpret_cast<struct sockaddr*>(&storage), + &addrSize); + if (status < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + // Nothing to receive, everything is fine + return; + } else if (status < 0) { + LOGE("Monitor receive failed: %s", strerror(errno)); + return; + } else if (addrSize < 0 || + static_cast<size_t>(addrSize) != sizeof(struct sockaddr_nl)) { + LOGE("Monitor received invalid address size"); + return; + } + + size_t length = static_cast<size_t>(status); + + auto hdr = reinterpret_cast<struct nlmsghdr*>(buffer); + while (NLMSG_OK(hdr, length) && hdr->nlmsg_type != NLMSG_DONE) { + switch (hdr->nlmsg_type) { + case RTM_NEWLINK: + handleNewLink(hdr); + break; + default: + break; + } + NLMSG_NEXT(hdr, length); + } + } +} + +void Monitor::onClose() { + // Socket was closed from the other end, close it from our end and re-open + closeSocket(); + Result res = openSocket(); + if (!res) { + LOGE("%s", res.c_str()); + } +} + +void Monitor::onTimeout() { +} + +Pollable::Data Monitor::data() const { + return Pollable::Data{ mSocketFd, Pollable::Timestamp::max() }; +} + +Result Monitor::openSocket() { + if (mSocketFd != -1) { + return Result::error("Monitor already initialized"); + } + + mSocketFd = ::socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE); + if (mSocketFd == -1) { + return Result::error("Monitor failed to open socket: %s", + strerror(errno)); + } + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = RTNLGRP_LINK | RTNLGRP_IPV4_IFADDR | RTNLGRP_IPV6_IFADDR; + + struct sockaddr* sa = reinterpret_cast<struct sockaddr*>(&addr); + if (::bind(mSocketFd, sa, sizeof(addr)) != 0) { + return Result::error("Monitor failed to bind socket: %s", + strerror(errno)); + } + + return Result::success(); +} + +void Monitor::closeSocket() { + if (mSocketFd != -1) { + ::close(mSocketFd); + mSocketFd = -1; + } +} + +void Monitor::handleNewLink(const struct nlmsghdr* hdr) { + if (!mOnInterfaceStateCallback) { + return; + } + + auto msg = reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr)); + + if (msg->ifi_change & IFF_UP) { + // The interface up/down flag changed, send a notification + char name[IF_NAMESIZE + 1] = { 0 }; + if_indextoname(msg->ifi_index, name); + + InterfaceState state = (msg->ifi_flags & IFF_UP) ? InterfaceState::Up : + InterfaceState::Down; + mOnInterfaceStateCallback(msg->ifi_index, name, state); + } +} + diff --git a/network/netmgr/monitor.h b/network/netmgr/monitor.h new file mode 100644 index 0000000..d75bac7 --- /dev/null +++ b/network/netmgr/monitor.h @@ -0,0 +1,54 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +#include "interface_state.h" +#include "pollable.h" +#include "result.h" + +const char* interfaceStateToStr(InterfaceState state); + +/** Monitor network interfaces and provide notifications of changes to those + * interfaces. + */ +class Monitor : public Pollable { +public: + using OnInterfaceStateCallback = std::function<void (unsigned int index, + const char* name, + InterfaceState state)>; + Monitor(); + ~Monitor(); + + Result init(); + + void setOnInterfaceState(OnInterfaceStateCallback callback); + + // Pollable interface + void onReadAvailable() override; + void onClose() override; + void onTimeout() override; + Pollable::Data data() const override; + +private: + Result openSocket(); + void closeSocket(); + void handleNewLink(const struct nlmsghdr* hdr); + + int mSocketFd; + OnInterfaceStateCallback mOnInterfaceStateCallback; +}; + diff --git a/network/netmgr/pollable.h b/network/netmgr/pollable.h new file mode 100644 index 0000000..fe87a41 --- /dev/null +++ b/network/netmgr/pollable.h @@ -0,0 +1,36 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +#include <chrono> + +class Pollable { +public: + using Clock = std::chrono::steady_clock; + using Timestamp = Clock::time_point; + struct Data { + int fd; + Timestamp deadline; + }; + virtual ~Pollable() = default; + + virtual Data data() const = 0; + virtual void onReadAvailable() = 0; + virtual void onClose() = 0; + virtual void onTimeout() = 0; +}; + diff --git a/network/netmgr/poller.cpp b/network/netmgr/poller.cpp new file mode 100644 index 0000000..20c822d --- /dev/null +++ b/network/netmgr/poller.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2018, 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 "poller.h" + +#include "log.h" + +#include <errno.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> + +#include <vector> + +using std::chrono::duration_cast; + +static struct timespec* getTimeout(Pollable::Timestamp deadline, + struct timespec* ts) { + if (deadline < Pollable::Timestamp::max()) { + auto timeout = deadline - Pollable::Clock::now(); + // Convert and round down to seconds + auto seconds = duration_cast<std::chrono::seconds>(timeout); + // Then subtract the seconds from the timeout and convert the remainder + auto nanos = duration_cast<std::chrono::nanoseconds>(timeout - seconds); + + ts->tv_sec = seconds.count(); + ts->tv_nsec = nanos.count(); + + return ts; + } + return nullptr; +} + +Poller::Poller() { +} + +void Poller::addPollable(Pollable* pollable) { + mPollables.push_back(pollable); +} + +int Poller::run() { + // Block all signals while we're running. This way we don't have to deal + // with things like EINTR. We then uses ppoll to set the original mask while + // polling. This way polling can be interrupted but socket writing, reading + // and ioctl remain interrupt free. If a signal arrives while we're blocking + // it it will be placed in the signal queue and handled once ppoll sets the + // original mask. This way no signals are lost. + sigset_t blockMask, mask; + int status = ::sigfillset(&blockMask); + if (status != 0) { + LOGE("Unable to fill signal set: %s", strerror(errno)); + return errno; + } + status = ::sigprocmask(SIG_SETMASK, &blockMask, &mask); + if (status != 0) { + LOGE("Unable to set signal mask: %s", strerror(errno)); + return errno; + } + + std::vector<struct pollfd> fds; + while (true) { + fds.clear(); + Pollable::Timestamp deadline = Pollable::Timestamp::max(); + for (const auto& pollable : mPollables) { + int fd = pollable->data().fd; + if (fd != -1) { + fds.push_back(pollfd{}); + fds.back().fd = fd; + fds.back().events = POLLIN; + } + if (pollable->data().deadline < deadline) { + deadline = pollable->data().deadline; + } + } + + struct timespec ts = { 0, 0 }; + struct timespec* tsPtr = getTimeout(deadline, &ts); + status = ::ppoll(fds.data(), fds.size(), tsPtr, &mask); + if (status < 0) { + if (errno == EINTR) { + // Interrupted, just keep going + continue; + } + // Actual error, time to quit + LOGE("Polling failed: %s", strerror(errno)); + return errno; + } + // Check for timeouts + Pollable::Timestamp now = Pollable::Clock::now(); + for (auto& pollable : mPollables) { + // Since we're going to have a very low number of pollables it's + // probably faster to just loop through fds here instead of + // constructing a map from fd to pollable every polling loop. + for (const auto& fd : fds) { + if (fd.fd == pollable->data().fd) { + if (fd.revents & POLLIN) { + // This pollable has data available for reading + pollable->onReadAvailable(); + } + if (fd.revents & POLLHUP) { + // The fd was closed from the other end + pollable->onClose(); + } + } + } + // Potentially trigger both read and timeout for pollables that have + // different logic for these two cases. By checking the timeout + // after the read we allow the pollable to update the deadline after + // the read to prevent this from happening. + if (now > pollable->data().deadline) { + // This pollable has reached its deadline + pollable->onTimeout(); + } + } + } + + return 0; +} diff --git a/network/netmgr/poller.h b/network/netmgr/poller.h new file mode 100644 index 0000000..9794f4d --- /dev/null +++ b/network/netmgr/poller.h @@ -0,0 +1,34 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +#include <vector> + +#include "pollable.h" + +class Poller { +public: + Poller(); + + void addPollable(Pollable* pollable); + + int run(); + +private: + std::vector<Pollable*> mPollables; +}; + diff --git a/network/netmgr/result.h b/network/netmgr/result.h new file mode 100644 index 0000000..5087e14 --- /dev/null +++ b/network/netmgr/result.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 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. + */ +#pragma once + +#include <stdio.h> +#include <stdarg.h> + +#include <string> + +class Result { +public: + static Result success() { + return Result(true); + } + // Construct a result indicating an error. + static Result error(std::string message) { + return Result(message); + } + static Result error(const char* format, ...) { + char buffer[1024]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + buffer[sizeof(buffer) - 1] = '\0'; + return Result(std::string(buffer)); + } + + bool isSuccess() const { return mSuccess; } + bool operator!() const { return !mSuccess; } + + const char* c_str() const { return mMessage.c_str(); } +private: + explicit Result(bool success) : mSuccess(success) { } + explicit Result(std::string message) + : mMessage(message), mSuccess(false) { + } + std::string mMessage; + bool mSuccess; +}; + diff --git a/network/netmgr/timestamp.cpp b/network/netmgr/timestamp.cpp new file mode 100644 index 0000000..36c79ee --- /dev/null +++ b/network/netmgr/timestamp.cpp @@ -0,0 +1,33 @@ +/* + * Copyright 2018, 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 "timestamp.h" + + +Timestamp::Timestamp() { + memset(&mTime, 0, sizeof(mTime)); +} + +static Timestamp Timestamp::now() { + Timestamp t; + clock_gettime(CLOCK_MONOTONIC, &t.mTime); + return t; +} + +bool Timestamp::operator==(const Timestamp& other) const { +} +bool Timestamp::operator<(const Timestamp& other) const { +} diff --git a/network/netmgr/timestamp.h b/network/netmgr/timestamp.h new file mode 100644 index 0000000..8ad7bf8 --- /dev/null +++ b/network/netmgr/timestamp.h @@ -0,0 +1,33 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +#include <time.h> + +class Timestamp { +public: + Timestamp(); + + static Timestamp now(); + + bool operator==(const Timestamp& other) const; + bool operator<(const Timestamp& other) const; + +private: + struct timespec mTime; +}; + diff --git a/ril/Android.mk b/ril/Android.mk index 34bd227..cb5f31a 100644 --- a/ril/Android.mk +++ b/ril/Android.mk @@ -8,6 +8,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ reference-ril.c \ atchannel.c \ + if_monitor.cpp \ misc.c \ at_tok.c diff --git a/ril/atchannel.c b/ril/atchannel.c index 0041836..407a204 100644 --- a/ril/atchannel.c +++ b/ril/atchannel.c @@ -22,6 +22,7 @@ #include <string.h> #include <pthread.h> #include <ctype.h> +#include <poll.h> #include <stdlib.h> #include <errno.h> #include <fcntl.h> diff --git a/ril/if_monitor.cpp b/ril/if_monitor.cpp new file mode 100644 index 0000000..289477d --- /dev/null +++ b/ril/if_monitor.cpp @@ -0,0 +1,352 @@ +/* + * Copyright 2018, 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 "if_monitor.h" + +#include <errno.h> +#include <linux/rtnetlink.h> +#include <net/if.h> +#include <poll.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <memory> +#include <mutex> +#include <thread> +#include <unordered_map> +#include <vector> + +#define LOG_TAG "RIL-IFMON" +#include <utils/Log.h> + +static const size_t kReadBufferSize = 32768; + +static const size_t kControlServer = 0; +static const size_t kControlClient = 1; + +// A list of commands that can be sent to the monitor. These should be one +// character long as that is all that the monitor will read and process. +static const char kMonitorStopCommand[] = "\1"; +static const char kMonitorAckCommand[] = "\2"; + +static size_t addrLength(int addrFamily) { + switch (addrFamily) { + case AF_INET: + return 4; + case AF_INET6: + return 16; + default: + return 0; + } +} + +bool operator==(const struct ifAddress& left, const struct ifAddress& right) { + // The prefix length does not factor in to whether two addresses are the + // same or not. Only the family and the address data. This matches the + // kernel behavior when attempting to add the same address with different + // prefix lengths, those changes are rejected because the address already + // exists. + return left.family == right.family && + memcmp(&left.addr, &right.addr, addrLength(left.family)) == 0; +} + +class InterfaceMonitor { +public: + InterfaceMonitor() : mSocketFd(-1) { + mControlSocket[kControlServer] = -1; + mControlSocket[kControlClient] = -1; + } + + ~InterfaceMonitor() { + if (mControlSocket[kControlClient] != -1) { + ::close(mControlSocket[kControlClient]); + mControlSocket[kControlClient] = -1; + } + if (mControlSocket[kControlServer] != -1) { + ::close(mControlSocket[kControlServer]); + mControlSocket[kControlServer] = -1; + } + + if (mSocketFd != -1) { + ::close(mSocketFd); + mSocketFd = -1; + } + } + + bool init() { + if (mSocketFd != -1) { + RLOGE("InterfaceMonitor already initialized"); + return false; + } + + mSocketFd = ::socket(AF_NETLINK, + SOCK_DGRAM | SOCK_CLOEXEC, + NETLINK_ROUTE); + if (mSocketFd == -1) { + RLOGE("InterfaceMonitor failed to open socket: %s", strerror(errno)); + return false; + } + + if (::socketpair(AF_UNIX, SOCK_DGRAM, 0, mControlSocket) != 0) { + RLOGE("Unable to create control socket pair: %s", strerror(errno)); + return false; + } + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = (1 << (RTNLGRP_IPV4_IFADDR - 1)) | + (1 << (RTNLGRP_IPV6_IFADDR - 1)); + + struct sockaddr* sa = reinterpret_cast<struct sockaddr*>(&addr); + if (::bind(mSocketFd, sa, sizeof(addr)) != 0) { + RLOGE("InterfaceMonitor failed to bind socket: %s", + strerror(errno)); + return false; + } + + return true; + } + + void setCallback(ifMonitorCallback callback) { + mOnAddressChangeCallback = callback; + } + + void runAsync() { + std::unique_lock<std::mutex> lock(mThreadMutex); + mThread = std::make_unique<std::thread>([this]() { run(); }); + } + + void requestAddress() { + struct { + struct nlmsghdr hdr; + struct ifaddrmsg msg; + char padding[16]; + } request; + + memset(&request, 0, sizeof(request)); + request.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg)); + request.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT; + request.hdr.nlmsg_type = RTM_GETADDR; + + int status = ::send(mSocketFd, &request, request.hdr.nlmsg_len, 0); + if (status < 0 || + static_cast<unsigned int>(status) != request.hdr.nlmsg_len) { + if (status < 0) { + RLOGE("Failed to send netlink request: %s", strerror(errno)); + } else { + RLOGE("Short send only sent %d out of %d bytes", + status, (int)request.hdr.nlmsg_len); + } + } + } + + void run() { + requestAddress(); + + std::vector<struct pollfd> fds(2); + fds[0].events = POLLIN; + fds[0].fd = mControlSocket[kControlServer]; + fds[1].events = POLLIN; + fds[1].fd = mSocketFd; + while (true) { + int status = ::poll(fds.data(), fds.size(), -1); + if (status < 0) { + if (errno == EINTR) { + // Interrupted, just keep going + continue; + } + // Actual error, time to quit + RLOGE("Polling failed: %s", strerror(errno)); + break; + } else if (status == 0) { + // Timeout + continue; + } + + if (fds[0].revents & POLLIN) { + // Control message received + char command = -1; + if (::read(mControlSocket[kControlServer], + &command, + sizeof(command)) == 1) { + if (command == kMonitorStopCommand[0]) { + break; + } + } + } else if (fds[1].revents & POLLIN) { + onReadAvailable(); + } + } + ::write(mControlSocket[kControlServer], kMonitorAckCommand, 1); + } + + void stop() { + std::unique_lock<std::mutex> lock(mThreadMutex); + if (mThread) { + ::write(mControlSocket[kControlClient], kMonitorStopCommand, 1); + char ack = -1; + while (ack != kMonitorAckCommand[0]) { + ::read(mControlSocket[kControlClient], &ack, sizeof(ack)); + } + mThread->join(); + mThread.reset(); + } + } + +private: + void onReadAvailable() { + char buffer[kReadBufferSize]; + struct sockaddr_storage storage; + + while (true) { + socklen_t addrSize = sizeof(storage); + int status = ::recvfrom(mSocketFd, + buffer, + sizeof(buffer), + MSG_DONTWAIT, + reinterpret_cast<struct sockaddr*>(&storage), + &addrSize); + if (status < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + // Nothing to receive, everything is fine + return; + } else if (status < 0 && errno == EINTR) { + // Caught interrupt, try again + continue; + } else if (status < 0) { + RLOGE("InterfaceMonitor receive failed: %s", strerror(errno)); + return; + } else if (addrSize < 0 || + static_cast<size_t>(addrSize) != sizeof(struct sockaddr_nl)) { + RLOGE("InterfaceMonitor received invalid address size"); + return; + } + + size_t length = static_cast<size_t>(status); + + auto hdr = reinterpret_cast<struct nlmsghdr*>(buffer); + while (NLMSG_OK(hdr, length) && hdr->nlmsg_type != NLMSG_DONE) { + switch (hdr->nlmsg_type) { + case RTM_NEWADDR: + case RTM_DELADDR: + handleAddressChange(hdr); + break; + default: + RLOGE("Received message type %d", (int)hdr->nlmsg_type); + break; + } + NLMSG_NEXT(hdr, length); + } + } + } + + void handleAddressChange(const struct nlmsghdr* hdr) { + if (!mOnAddressChangeCallback) { + return; + } + + auto msg = reinterpret_cast<const struct ifaddrmsg*>(NLMSG_DATA(hdr)); + std::vector<ifAddress>& ifAddrs = mAddresses[msg->ifa_index]; + + auto attr = reinterpret_cast<const struct rtattr*>(IFA_RTA(msg)); + int attrLen = IFA_PAYLOAD(hdr); + + bool somethingChanged = false; + for (;attr && RTA_OK(attr, attrLen); attr = RTA_NEXT(attr, attrLen)) { + if (attr->rta_type != IFA_LOCAL && attr->rta_type != IFA_ADDRESS) { + continue; + } + + ifAddress addr; + memset(&addr, 0, sizeof(addr)); + + // Ensure that the payload matches the expected address length + if (RTA_PAYLOAD(attr) >= addrLength(msg->ifa_family)) { + addr.family = msg->ifa_family; + addr.prefix = msg->ifa_prefixlen; + memcpy(&addr.addr, RTA_DATA(attr), addrLength(addr.family)); + } else { + RLOGE("Invalid address family (%d) and size (%d) combination", + int(msg->ifa_family), int(RTA_PAYLOAD(attr))); + continue; + } + + auto it = std::find(ifAddrs.begin(), ifAddrs.end(), addr); + if (hdr->nlmsg_type == RTM_NEWADDR && it == ifAddrs.end()) { + // New address does not exist, add it + ifAddrs.push_back(addr); + somethingChanged = true; + } else if (hdr->nlmsg_type == RTM_DELADDR && it != ifAddrs.end()) { + // Address was removed and it exists, remove it + ifAddrs.erase(it); + somethingChanged = true; + } + } + + if (somethingChanged) { + mOnAddressChangeCallback(msg->ifa_index, + ifAddrs.data(), + ifAddrs.size()); + } + } + + ifMonitorCallback mOnAddressChangeCallback; + std::unordered_map<unsigned int, std::vector<ifAddress>> mAddresses; + std::unique_ptr<std::thread> mThread; + std::mutex mThreadMutex; + int mSocketFd; + int mControlSocket[2]; +}; + +extern "C" +struct ifMonitor* ifMonitorCreate() { + auto monitor = std::make_unique<InterfaceMonitor>(); + if (!monitor || !monitor->init()) { + return nullptr; + } + return reinterpret_cast<struct ifMonitor*>(monitor.release()); +} + +extern "C" +void ifMonitorFree(struct ifMonitor* ifMonitor) { + InterfaceMonitor* monitor = reinterpret_cast<InterfaceMonitor*>(ifMonitor); + delete monitor; +} + +extern "C" +void ifMonitorSetCallback(struct ifMonitor* ifMonitor, + ifMonitorCallback callback) { + InterfaceMonitor* monitor = reinterpret_cast<InterfaceMonitor*>(ifMonitor); + monitor->setCallback(callback); +} + +extern "C" +void ifMonitorRunAsync(struct ifMonitor* ifMonitor) { + InterfaceMonitor* monitor = reinterpret_cast<InterfaceMonitor*>(ifMonitor); + + monitor->runAsync(); +} + +extern "C" +void ifMonitorStop(struct ifMonitor* ifMonitor) { + InterfaceMonitor* monitor = reinterpret_cast<InterfaceMonitor*>(ifMonitor); + + monitor->stop(); +} + diff --git a/ril/if_monitor.h b/ril/if_monitor.h new file mode 100644 index 0000000..118bf88 --- /dev/null +++ b/ril/if_monitor.h @@ -0,0 +1,49 @@ +/* + * Copyright 2018, 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. + */ + +#pragma once + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct ifMonitor; + +struct ifAddress { + int family; + int prefix; + unsigned char addr[16]; +}; + +// A callback for when the addresses on an interface changes +typedef void (*ifMonitorCallback)(unsigned int /*interface index*/, + const struct ifAddress* /*addresses*/, + size_t /*number of addresses */); + +struct ifMonitor* ifMonitorCreate(); +void ifMonitorFree(struct ifMonitor* monitor); + +void ifMonitorSetCallback(struct ifMonitor* monitor, + ifMonitorCallback callback); +void ifMonitorRunAsync(struct ifMonitor* monitor); +void ifMonitorStop(struct ifMonitor* monitor); + +#ifdef __cplusplus +} // extern "C" +#endif + diff --git a/ril/reference-ril.c b/ril/reference-ril.c index 51f63ea..7e52e10 100644 --- a/ril/reference-ril.c +++ b/ril/reference-ril.c @@ -41,8 +41,10 @@ #include <sys/wait.h> #include <stdbool.h> #include <net/if.h> +#include <arpa/inet.h> #include <netinet/in.h> +#include "if_monitor.h" #include "ril.h" #define LOG_TAG "RIL" @@ -59,6 +61,11 @@ static void *noopRemoveWarning( void *a ) { return a; } // This is used if Wifi is supported to separate radio and wifi interface #define PPP_TTY_PATH_RADIO0 "radio0" +// This is the IP address to provide for radio0 when WiFi is enabled +// When WiFi is not enabled the RIL should provide the address given by +// the modem. +#define RADIO0_IPV4_ADDRESS "192.168.200.2/24" + // Default MTU value #define DEFAULT_MTU 1500 @@ -266,6 +273,10 @@ static int s_mnc = 0; static int s_lac = 0; static int s_cid = 0; +// A string containing all the IPv6 addresses of the radio interface +static char s_ipv6_addresses[8192]; +static pthread_mutex_t s_ipv6_addresses_mutex = PTHREAD_MUTEX_INITIALIZER; + static void pollSIMState (void *param); static void setRadioState(RIL_RadioState newState); static void setRadioTechnology(ModemInfo *mdm, int newtech); @@ -716,13 +727,32 @@ static void requestOrSendDataCallList(RIL_Token *t) responses[i].ifname = alloca(ifname_size); strlcpy(responses[i].ifname, radioInterfaceName, ifname_size); + // The next token is the IPv4 address provided by the emulator, only use + // it if WiFi is not enabled. When WiFi is enabled the network setup is + // specific to the system image and the emulator only provides the + // IP address for the external interface in the router namespace. err = at_tok_nextstr(&line, &out); if (err < 0) goto error; - int addresses_size = strlen(out) + 1; + pthread_mutex_lock(&s_ipv6_addresses_mutex); + + // Extra space for null terminator and separating space + int addresses_size = strlen(out) + strlen(s_ipv6_addresses) + 2; responses[i].addresses = alloca(addresses_size); - strlcpy(responses[i].addresses, out, addresses_size); + if (*s_ipv6_addresses) { + // IPv6 addresses exist, add them + snprintf(responses[i].addresses, addresses_size, + "%s %s", + hasWifi ? RADIO0_IPV4_ADDRESS : out, + s_ipv6_addresses); + } else { + // Only provide the IPv4 address + strlcpy(responses[i].addresses, + hasWifi ? RADIO0_IPV4_ADDRESS : out, + addresses_size); + } + pthread_mutex_unlock(&s_ipv6_addresses_mutex); if (isInEmulator()) { /* We are in the emulator - the dns servers are listed @@ -733,16 +763,16 @@ static void requestOrSendDataCallList(RIL_Token *t) * - net.eth0.dns3 * - net.eth0.dns4 */ - const int dnslist_sz = 128; + const int dnslist_sz = 256; char* dnslist = alloca(dnslist_sz); const char* separator = ""; int nn; + char propName[PROP_NAME_MAX]; + char propValue[PROP_VALUE_MAX]; dnslist[0] = 0; for (nn = 1; nn <= 4; nn++) { /* Probe net.eth0.dns<n> */ - char propName[PROP_NAME_MAX]; - char propValue[PROP_VALUE_MAX]; snprintf(propName, sizeof propName, "net.eth0.dns%d", nn); @@ -756,6 +786,18 @@ static void requestOrSendDataCallList(RIL_Token *t) strlcat(dnslist, propValue, dnslist_sz); separator = " "; } + for (nn = 1; nn <= 4; ++nn) { + /* Probe net.eth0.ipv6dns<n> for IPv6 DNS servers */ + snprintf(propName, sizeof propName, "net.eth0.ipv6dns%d", nn); + /* Ignore if undefined */ + if (property_get(propName, propValue, "") <= 0) { + continue; + } + strlcat(dnslist, separator, dnslist_sz); + strlcat(dnslist, propValue, dnslist_sz); + separator = " "; + } + responses[i].dnses = dnslist; /* There is only one gateway in the emulator. If WiFi is @@ -3883,16 +3925,79 @@ static void usage(char *s __unused) #endif } +static void onInterfaceAddressChange(unsigned int ifIndex, + const struct ifAddress* addresses, + size_t numAddresses) { + char ifName[IF_NAMESIZE]; + size_t i; + bool hasWifi = hasWifiCapability(); + const char* radioIfName = getRadioInterfaceName(hasWifi); + char* currentLoc; + size_t remaining; + + if (if_indextoname(ifIndex, ifName) == NULL) { + RLOGE("Unable to get interface name for interface %u", ifIndex); + return; + } + if (strcmp(radioIfName, ifName) != 0) { + // This is not for the radio interface, ignore it + return; + } + + pthread_mutex_lock(&s_ipv6_addresses_mutex); + // Clear out any existing addresses, we receive a full set of addresses + // that are going to replace the existing ones. + s_ipv6_addresses[0] = '\0'; + currentLoc = s_ipv6_addresses; + remaining = sizeof(s_ipv6_addresses); + for (i = 0; i < numAddresses; ++i) { + if (addresses[i].family != AF_INET6) { + // Only care about IPv6 addresses + continue; + } + char address[INET6_ADDRSTRLEN]; + if (inet_ntop(addresses[i].family, &addresses[i].addr, + address, sizeof(address))) { + int printed = 0; + if (s_ipv6_addresses[0]) { + // We've already printed something, separate them + if (remaining < 1) { + continue; + } + *currentLoc++ = ' '; + --remaining; + } + printed = snprintf(currentLoc, remaining, "%s/%d", + address, addresses[i].prefix); + if (printed > 0) { + remaining -= (size_t)printed; + currentLoc += printed; + } + } else { + RLOGE("Unable to convert address to string for if %s", ifName); + } + } + pthread_mutex_unlock(&s_ipv6_addresses_mutex); + + // Send unsolicited call list change to notify upper layers about the new + // addresses + requestOrSendDataCallList(NULL); +} + static void * mainLoop(void *param __unused) { int fd; int ret; + struct ifMonitor* monitor = ifMonitorCreate(); AT_DUMP("== ", "entering mainLoop()", -1 ); at_set_on_reader_closed(onATReaderClosed); at_set_on_timeout(onATTimeout); + ifMonitorSetCallback(monitor, &onInterfaceAddressChange); + ifMonitorRunAsync(monitor); + for (;;) { fd = -1; while (fd < 0) { @@ -3916,20 +4021,21 @@ mainLoop(void *param __unused) } if (fd < 0) { - perror ("opening AT interface. retrying..."); + RLOGE("Error opening AT interface, retrying..."); sleep(10); /* never returns */ } } s_closed = 0; - ret = at_open(fd, onUnsolicited); + ret = at_open(fd, onUnsolicited); if (ret < 0) { RLOGE ("AT error %d on at_open\n", ret); - return 0; + break; } + RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0); // Give initializeCallback a chance to dispatched, since @@ -3939,6 +4045,11 @@ mainLoop(void *param __unused) waitForClose(); RLOGI("Re-opening after close"); } + + ifMonitorStop(monitor); + ifMonitorFree(monitor); + + return NULL; } #ifdef RIL_SHLIB diff --git a/sepolicy/common/execns.te b/sepolicy/common/execns.te index dc6c424..24eee41 100644 --- a/sepolicy/common/execns.te +++ b/sepolicy/common/execns.te @@ -21,6 +21,9 @@ domain_auto_trans(execns, dhcpserver_exec, dhcpserver); # Allow hostapd_nohidl to be run by execns in its own domain domain_auto_trans(execns, hostapd_nohidl_exec, hostapd_nohidl); +# Allow netmgr to be run by execns in its own domain +domain_auto_trans(execns, netmgr_exec, netmgr); + # Allow execns to read createns proc file to get the namespace file allow execns createns:file read; allow execns createns:dir search; diff --git a/sepolicy/common/file_contexts b/sepolicy/common/file_contexts index 3c9df34..f429ccb 100644 --- a/sepolicy/common/file_contexts +++ b/sepolicy/common/file_contexts @@ -25,6 +25,7 @@ /vendor/bin/dhcpclient u:object_r:dhcpclient_exec:s0 /vendor/bin/dhcpserver u:object_r:dhcpserver_exec:s0 /vendor/bin/hostapd_nohidl u:object_r:hostapd_nohidl_exec:s0 +/vendor/bin/netmgr u:object_r:netmgr_exec:s0 /vendor/bin/hw/android\.hardware\.drm@1\.0-service\.widevine u:object_r:hal_drm_widevine_exec:s0 /vendor/bin/hw/android\.hardware\.drm@1\.1-service\.widevine u:object_r:hal_drm_widevine_exec:s0 diff --git a/sepolicy/common/goldfish_setup.te b/sepolicy/common/goldfish_setup.te index 3041436..0fdcd37 100644 --- a/sepolicy/common/goldfish_setup.te +++ b/sepolicy/common/goldfish_setup.te @@ -6,7 +6,7 @@ init_daemon_domain(goldfish_setup) # TODO(b/79502552): Invalid property access from emulator vendor #set_prop(goldfish_setup, debug_prop); -allow goldfish_setup self:capability { net_admin net_raw }; +allow goldfish_setup self:capability { fowner chown net_admin net_raw }; allow goldfish_setup self:udp_socket { create ioctl }; allow goldfish_setup vendor_toolbox_exec:file execute_no_trans; allowxperm goldfish_setup self:udp_socket ioctl priv_sock_ioctls; @@ -45,3 +45,6 @@ allow goldfish_setup self:rawip_socket { create getopt setopt }; allow goldfish_setup createns:file { read }; allow goldfish_setup createns:dir { search }; allow goldfish_setup createns:lnk_file { read }; +# Allow goldfish_setup to copy the hostapd conf template to the vendor data dir +allow goldfish_setup hostapd_data_file:file create_file_perms; +allow goldfish_setup hostapd_data_file:dir rw_dir_perms; diff --git a/sepolicy/common/hostapd_nohidl.te b/sepolicy/common/hostapd_nohidl.te index add648a..badad45 100644 --- a/sepolicy/common/hostapd_nohidl.te +++ b/sepolicy/common/hostapd_nohidl.te @@ -6,10 +6,12 @@ net_domain(hostapd_nohidl) allow hostapd_nohidl execns:fd use; +allow hostapd_nohidl hostapd_data_file:file r_file_perms; +allow hostapd_nohidl hostapd_data_file:dir r_dir_perms; allow hostapd_nohidl self:capability { net_admin net_raw }; allow hostapd_nohidl self:netlink_generic_socket { bind create getattr read setopt write }; allow hostapd_nohidl self:netlink_route_socket nlmsg_write; -allow hostapd_nohidl self:packet_socket { create setopt }; +allow hostapd_nohidl self:packet_socket { create setopt read write }; allowxperm hostapd_nohidl self:udp_socket ioctl priv_sock_ioctls; # hostapd will attempt to search sysfs but it's not needed and will spam the log diff --git a/sepolicy/common/ipv6proxy.te b/sepolicy/common/ipv6proxy.te index 22976fe..1defe10 100644 --- a/sepolicy/common/ipv6proxy.te +++ b/sepolicy/common/ipv6proxy.te @@ -9,6 +9,7 @@ net_domain(ipv6proxy) domain_auto_trans(execns, ipv6proxy_exec, ipv6proxy); allow ipv6proxy execns:fd use; +set_prop(ipv6proxy, net_eth0_prop); allow ipv6proxy self:capability { sys_admin sys_module net_admin net_raw }; allow ipv6proxy self:packet_socket { bind create read }; allow ipv6proxy self:netlink_route_socket nlmsg_write; diff --git a/sepolicy/common/netmgr.te b/sepolicy/common/netmgr.te new file mode 100644 index 0000000..20eb00c --- /dev/null +++ b/sepolicy/common/netmgr.te @@ -0,0 +1,21 @@ +# Wifi manager +type netmgr, domain; +type netmgr_exec, exec_type, vendor_file_type, file_type; + +init_daemon_domain(netmgr) +net_domain(netmgr) + +allow netmgr execns:fd use; + +# Set ctrl.restart property to restart hostapd when config changes +set_prop(netmgr, ctl_default_prop); +# Modify hostapd config file +allow netmgr hostapd_data_file:file rw_file_perms; +allow netmgr hostapd_data_file:dir rw_dir_perms; +# Assign addresses to new interfaces as hostapd brings them up +allow netmgr self:capability { net_raw net_admin }; +allow netmgr self:udp_socket { ioctl }; +allowxperm netmgr self:udp_socket ioctl { SIOCSIFADDR SIOCSIFNETMASK SIOCSIFBRDADDR }; +# Allow netmgr to run iptables to block and unblock network traffic +allow netmgr system_file:file execute_no_trans; +allow netmgr system_file:file lock; @@ -123,6 +123,7 @@ PRODUCT_PACKAGES += \ hostapd \ hostapd_nohidl \ ipv6proxy \ + netmgr \ wpa_supplicant \ PRODUCT_COPY_FILES += \ diff --git a/wifi/init.wifi.sh b/wifi/init.wifi.sh index 9c08654..eb84fd0 100755 --- a/wifi/init.wifi.sh +++ b/wifi/init.wifi.sh @@ -50,6 +50,19 @@ rm -rf /data/vendor/var/run/netns/${NAMESPACE}.pid /system/bin/ip link set dev wlan0 address 02:00:00:44:55:66 createns ${NAMESPACE} + +# If this is a clean boot we need to copy the hostapd configuration file to the +# data partition where netmgr can change it if needed. If it already exists we +# need to preserve the existing settings. +if [ ! -f /data/vendor/wifi/hostapd/hostapd.conf ]; then + cp /vendor/etc/simulated_hostapd.conf /data/vendor/wifi/hostapd/hostapd.conf + chown wifi:wifi /data/vendor/wifi/hostapd/hostapd.conf + chmod 660 /data/vendor/wifi/hostapd/hostapd.conf +fi +# Start the network manager as soon as possible after the namespace is available. +# This ensures that anything that follows is properly managed and monitored. +setprop ctl.start netmgr + # createns will have created a file that contains the process id (pid) of a # process running in the network namespace. This pid is needed for some commands # to access the namespace. diff --git a/wifi/ipv6proxy/log.h b/wifi/ipv6proxy/log.h index 527be29..53e8935 100644 --- a/wifi/ipv6proxy/log.h +++ b/wifi/ipv6proxy/log.h @@ -18,7 +18,7 @@ #ifdef ANDROID #define LOG_TAG "ipv6proxy" -#include <cutils/log.h> +#include <log/log.h> #define loge(...) ALOGE(__VA_ARGS__) #define logd(...) ALOGD(__VA_ARGS__) diff --git a/wifi/ipv6proxy/proxy.cpp b/wifi/ipv6proxy/proxy.cpp index 08fc0ed..cb19ff7 100644 --- a/wifi/ipv6proxy/proxy.cpp +++ b/wifi/ipv6proxy/proxy.cpp @@ -16,11 +16,14 @@ #include "proxy.h" +#include <arpa/inet.h> #include <errno.h> #include <linux/if_packet.h> #include <poll.h> #include <signal.h> +#include <cutils/properties.h> + #include "log.h" #include "message.h" #include "packet.h" @@ -29,6 +32,7 @@ // The prefix length for an address of a single unique node static const uint8_t kNodePrefixLength = 128; static const size_t kLinkAddressSize = 6; +static const size_t kRecursiveDnsOptHeaderSize = 8; // Rewrite the link address of a neighbor discovery option to the link address // of |interface|. This can be either a source or target link address as @@ -46,6 +50,45 @@ static void rewriteLinkAddressOption(Packet& packet, } } +static void extractRecursiveDnsServers(Packet& packet) { + for (nd_opt_hdr* opt = packet.firstOpt(); opt; opt = packet.nextOpt(opt)) { + if (opt->nd_opt_type != 25 || opt->nd_opt_len < 1) { + // Not a RNDSS option, skip it + continue; + } + size_t numEntries = (opt->nd_opt_len - 1) / 2; + //Found number of entries, dump each address + const char* option = reinterpret_cast<const char*>(opt); + option += kRecursiveDnsOptHeaderSize; + auto dnsServers = reinterpret_cast<const struct in6_addr*>(option); + + std::vector<std::string> validServers; + for (size_t i = 0; i < numEntries; ++i) { + char buffer[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, &dnsServers[i], buffer, sizeof(buffer))) { + validServers.push_back(buffer); + } else { + loge("Failed to convert RDNSS to string\n"); + } + } + + auto server = validServers.begin(); + char propName[PROP_NAME_MAX]; + char propValue[PROP_VALUE_MAX]; + for (int i = 1; i <= 4; ++i) { + snprintf(propName, sizeof(propName), "net.eth0.ipv6dns%d", i); + if (server != validServers.end()) { + property_set(propName, server->c_str()); + ++server; + } else { + // Clear the property if it's no longer a valid server, don't + // want to leave old servers around + property_set(propName, ""); + } + } + } +} + int Proxy::run() { sigset_t blockMask, originalMask; int status = ::sigfillset(&blockMask); @@ -128,6 +171,7 @@ void Proxy::handleOuterMessage(Message& message) { uint32_t options = kForwardOnly; switch (packet.type()) { case Packet::Type::RouterAdvertisement: + extractRecursiveDnsServers(packet); options = kRewriteSourceLink | kSetDefaultGateway; break; case Packet::Type::NeighborSolicitation: |