diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-02-11 15:31:23 -0800 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2019-02-11 15:31:23 -0800 |
commit | dd7bd20e319cefbafc69f0748740864f12657f6c (patch) | |
tree | 8ee3cb35321655ad38530cd30c15ae99386e7544 | |
parent | 4a61803e71e83d44f926048eb08ee9477779700c (diff) | |
parent | ae97ddcc37e8586a521106c4815fae588026c4c3 (diff) | |
download | android_device_generic_goldfish-dd7bd20e319cefbafc69f0748740864f12657f6c.tar.gz android_device_generic_goldfish-dd7bd20e319cefbafc69f0748740864f12657f6c.tar.bz2 android_device_generic_goldfish-dd7bd20e319cefbafc69f0748740864f12657f6c.zip |
Snap for 5180536 from b05b939d85b47ed8c2b53114f8116699b859df09 to pi-platform-release
am: ae97ddcc37
Change-Id: I04d4d15458ae1881f3d347ba78e548820e135c5d
48 files changed, 3116 insertions, 158 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..6686f98 --- /dev/null +++ b/network/netmgr/Android.bp @@ -0,0 +1,44 @@ +// +// 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, + cflags: [ + "-Wall", + "-Werror", + ], + srcs: [ + "address_assigner.cpp", + "commander.cpp", + "fork.cpp", + "interface_state.cpp", + "log.cpp", + "main.cpp", + "monitor.cpp", + "poller.cpp", + "wifi_forwarder.cpp", + "commands/wifi_command.cpp", + ], + shared_libs: [ + "libcutils", + "liblog", + "libpcap", + ], + 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..a2dc1aa --- /dev/null +++ b/network/netmgr/commander.cpp @@ -0,0 +1,164 @@ +/* + * 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> +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#include <qemu_pipe.h> +#pragma clang diagnostic pop +#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; +} + +void Commander::getPollData(std::vector<pollfd>* fds) const { + if (mPipeFd != -1) { + fds->push_back(pollfd{mPipeFd, POLLIN, 0}); + } +} + +Pollable::Timestamp Commander::getTimeout() const { + return mDeadline; +} + +bool Commander::onReadAvailable(int /*fd*/, int* /*status*/) { + 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)); + // Don't exit the looper because of this, keep trying + return true; + } + 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 true; + } + + *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 true; + } else { + mReceiveBuffer.erase(mReceiveBuffer.begin(), endline + 1); + // There may be another line in there so keep looping and look + // for more + } + } + return true; + } +} + +bool Commander::onClose(int /*fd*/, int* /*status*/) { + // Pipe was closed from the other end, close it on our side and re-open + closePipe(); + openPipe(); + return true; +} + +bool Commander::onTimeout(int* /*status*/) { + if (mPipeFd == -1) { + openPipe(); + } + return true; +} + +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..7f7fc7e --- /dev/null +++ b/network/netmgr/commander.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 "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); + + void getPollData(std::vector<pollfd>* fds) const override; + Timestamp getTimeout() const override; + bool onReadAvailable(int fd, int* status) override; + bool onClose(int fd, int* status) override; + bool onTimeout(int* status) 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..8f48a43 --- /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..1efe72a --- /dev/null +++ b/network/netmgr/log.h @@ -0,0 +1,45 @@ +/* + * 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) + +#define LOGW(...) do { \ + if (isTerminal) { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } else { \ + ALOGW(__VA_ARGS__); \ + } \ +} while (0) + diff --git a/network/netmgr/macaddress.h b/network/netmgr/macaddress.h new file mode 100644 index 0000000..9363859 --- /dev/null +++ b/network/netmgr/macaddress.h @@ -0,0 +1,53 @@ +/* + * 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. + * 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 <functional> + +#include <linux/if_ether.h> +#include <stdint.h> +#include <string.h> + +struct MacAddress { + uint8_t addr[ETH_ALEN]; + bool isBroadcast() const { + return memcmp(addr, "\xFF\xFF\xFF\xFF\xFF\xFF", ETH_ALEN) == 0; + } +} __attribute__((__packed__)); + +template<class T> +inline void hash_combine(size_t& seed, const T& value) { + std::hash<T> hasher; + seed ^= hasher(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +namespace std { +template<> struct hash<MacAddress> { + size_t operator()(const MacAddress& addr) const { + size_t seed = 0; + // Treat the first 4 bytes as an uint32_t to save some computation + hash_combine(seed, *reinterpret_cast<const uint32_t*>(addr.addr)); + // And the remaining 2 bytes as an uint16_t + hash_combine(seed, *reinterpret_cast<const uint16_t*>(addr.addr + 4)); + return seed; + } +}; +} + diff --git a/network/netmgr/main.cpp b/network/netmgr/main.cpp new file mode 100644 index 0000000..e4b43f6 --- /dev/null +++ b/network/netmgr/main.cpp @@ -0,0 +1,150 @@ +/* + * 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 "wifi_forwarder.h" + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <functional> + +static const char kWifiMonitorInterface[] = "hwsim0"; + +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); + + WifiForwarder forwarder(kWifiMonitorInterface); + res = forwarder.init(); + if (!res) { + LOGE("%s", res.c_str()); + return 1; + } + + Poller poller; + poller.addPollable(&monitor); + poller.addPollable(&commander); + poller.addPollable(&forwarder); + return poller.run(); +} + diff --git a/network/netmgr/monitor.cpp b/network/netmgr/monitor.cpp new file mode 100644 index 0000000..4ab111e --- /dev/null +++ b/network/netmgr/monitor.cpp @@ -0,0 +1,167 @@ +/* + * 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; +} + +bool Monitor::onReadAvailable(int /*fd*/, int* /*status*/) { + 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) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // Nothing to receive, everything is fine + return true; + } else if (errno == EINTR) { + continue; + } + LOGE("Monitor receive failed: %s", strerror(errno)); + // An error occurred but let's keep trying + return true; + } else if (addrSize < 0 || + static_cast<size_t>(addrSize) != sizeof(struct sockaddr_nl)) { + LOGE("Monitor received invalid address size"); + // It's an error but no need to exit, let's keep polling + return true; + } + + 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); + } + } +} + +bool Monitor::onClose(int /*fd*/, int* status) { + // 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()); + *status = 1; + return false; + } + return true; +} + +bool Monitor::onTimeout(int* /*status*/) { + return true; +} + +void Monitor::getPollData(std::vector<pollfd>* fds) const { + if (mSocketFd != -1) { + fds->push_back(pollfd{mSocketFd, POLLIN, 0}); + } +} + +Pollable::Timestamp Monitor::getTimeout() const { + return 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..3d2dc62 --- /dev/null +++ b/network/netmgr/monitor.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 "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 getPollData(std::vector<pollfd>* fds) const override; + Timestamp getTimeout() const override; + bool onReadAvailable(int fd, int* status) override; + bool onClose(int fd, int* status) override; + bool onTimeout(int* status) 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..fbf4baf --- /dev/null +++ b/network/netmgr/pollable.h @@ -0,0 +1,62 @@ +/* + * 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> +#include <vector> + +#include <poll.h> + +/* An interface for pollable classes. + */ +class Pollable { +public: + using Clock = std::chrono::steady_clock; + using Timestamp = Clock::time_point; + virtual ~Pollable() = default; + + /* Get the poll data for the next poll loop. The implementation can place + * as many fds as needed in |fds|. + */ + virtual void getPollData(std::vector<pollfd>* fds) const = 0; + /* Get the timeout for the next poll loop. This should be a timestamp + * indicating when the timeout should be triggered. Note that this may + * be called at any time and any number of times for a poll loop so the + * deadline should not be adjusted in this call, a set deadline should + * just be returned. Note specifically that if a call to onReadAvailable + * modifies the deadline the timeout for the previous timestamp might not + * fire as the poller will check the timestamp AFTER onReadAvailable is + * called. + */ + virtual Timestamp getTimeout() const = 0; + /* Called when there is data available to read on an fd associated with + * the pollable. |fd| indicates which fd to read from. If the call returns + * false the poller will exit its poll loop with a return code of |status|. + */ + virtual bool onReadAvailable(int fd, int* status) = 0; + /* Called when an fd associated with the pollable is closed. |fd| indicates + * which fd was closed. If the call returns false the poller will exit its + * poll loop with a return code of |status|. + */ + virtual bool onClose(int fd, int* status) = 0; + /* Called when the timeout returned by getPollData has been reached. If + * the call returns false the poller will exit its poll loop with a return + * code of |status|. + */ + virtual bool onTimeout(int* status) = 0; +}; + diff --git a/network/netmgr/poller.cpp b/network/netmgr/poller.cpp new file mode 100644 index 0000000..8dcbcab --- /dev/null +++ b/network/netmgr/poller.cpp @@ -0,0 +1,158 @@ +/* + * 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 <unordered_map> +#include <vector> + +using std::chrono::duration_cast; + +static struct timespec* calculateTimeout(Pollable::Timestamp deadline, + struct timespec* ts) { + Pollable::Timestamp now = Pollable::Clock::now(); + if (deadline < Pollable::Timestamp::max()) { + if (deadline <= now) { + LOGE("Poller found past due deadline, setting to zero"); + ts->tv_sec = 0; + ts->tv_nsec = 0; + return ts; + } + + auto timeout = deadline - 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; + std::unordered_map<int, Pollable*> pollables; + while (true) { + fds.clear(); + pollables.clear(); + Pollable::Timestamp deadline = Pollable::Timestamp::max(); + for (auto& pollable : mPollables) { + size_t start = fds.size(); + pollable->getPollData(&fds); + Pollable::Timestamp pollableDeadline = pollable->getTimeout(); + // Create a map from each fd to the pollable + for (size_t i = start; i < fds.size(); ++i) { + pollables[fds[i].fd] = pollable; + } + if (pollableDeadline < deadline) { + deadline = pollableDeadline; + } + } + + struct timespec ts = { 0, 0 }; + struct timespec* tsPtr = calculateTimeout(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; + } else if (status > 0) { + // Check for read or close events + for (const auto& fd : fds) { + if ((fd.revents & (POLLIN | POLLHUP)) == 0) { + // Neither POLLIN nor POLLHUP, not interested + continue; + } + auto pollable = pollables.find(fd.fd); + if (pollable == pollables.end()) { + // No matching fd, weird and unexpected + LOGE("Poller could not find fd matching %d", fd.fd); + continue; + } + if (fd.revents & POLLIN) { + // This pollable has data available for reading + int status = 0; + if (!pollable->second->onReadAvailable(fd.fd, &status)) { + // The onReadAvailable handler signaled an exit + return status; + } + } + if (fd.revents & POLLHUP) { + // The fd was closed from the other end + int status = 0; + if (!pollable->second->onClose(fd.fd, &status)) { + // The onClose handler signaled an exit + return status; + } + } + } + } + // Check for timeouts + Pollable::Timestamp now = Pollable::Clock::now(); + for (const auto& pollable : mPollables) { + if (pollable->getTimeout() <= now) { + int status = 0; + if (!pollable->onTimeout(&status)) { + // The onTimeout handler signaled an exit + return status; + } + } + } + } + + 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/network/netmgr/wifi_forwarder.cpp b/network/netmgr/wifi_forwarder.cpp new file mode 100644 index 0000000..e6997d4 --- /dev/null +++ b/network/netmgr/wifi_forwarder.cpp @@ -0,0 +1,432 @@ +/* + * 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_forwarder.h" + +#include "log.h" + +#include <inttypes.h> +#include <arpa/inet.h> +#include <errno.h> +#include <linux/if_packet.h> +#include <linux/kernel.h> +// Ignore warning about unused static qemu pipe function +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#include <qemu_pipe.h> +#pragma clang diagnostic pop +#include <string.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <pcap/pcap.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +static const char kQemuPipeName[] = "qemud:wififorward"; + +// The largest packet size to capture with pcap on the monitor interface +static const int kPcapSnapLength = 65536; + +static const size_t kForwardBufferIncrement = 32768; +static const size_t kForwardBufferMaxSize = 1 << 20; + +static const uint32_t kWifiForwardMagic = 0xD5C4B3A2; + +struct WifiForwardHeader { + WifiForwardHeader(uint32_t dataLength, uint32_t radioLength) + : magic(__cpu_to_le32(kWifiForwardMagic)) + , fullLength(__cpu_to_le32(dataLength + sizeof(WifiForwardHeader))) + , radioLength(__cpu_to_le32(radioLength)) { } + + uint32_t magic; + uint32_t fullLength; + uint32_t radioLength; +} __attribute__((__packed__)); + +struct RadioTapHeader { + uint8_t it_version; + uint8_t it_pad; + uint16_t it_len; + uint32_t it_present; +} __attribute__((__packed__)); + +enum class FrameType { + Management, + Control, + Data, + Extension +}; + +enum class ManagementType { + AssociationRequest, + AssociationResponse, + ReassociationRequest, + ReassociationResponse, + ProbeRequest, + ProbeResponse, + TimingAdvertisement, + Beacon, + Atim, + Disassociation, + Authentication, + Deauthentication, + Action, + ActionNoAck, +}; + +enum class ControlType { + BeamFormingReportPoll, + VhtNdpAnnouncement, + ControlFrameExtension, + ControlWrapper, + BlockAckReq, + BlockAck, + PsPoll, + Rts, + Cts, + Ack, + CfEnd, + CfEndCfAck +}; + +// Since the IEEE 802.11 header can vary in size depending on content we have +// to establish a minimum size that we need to be able to inspect and forward +// the frame. Every frame need to contain at least frame_control, duration_id, +// and addr1. +static const uint32_t kMinimumIeee80211Size = sizeof(uint16_t) + + sizeof(uint16_t) + + sizeof(MacAddress); + +WifiForwarder::WifiForwarder(const char* monitorInterfaceName) + : mInterfaceName(monitorInterfaceName), + mDeadline(Pollable::Timestamp::max()), + mMonitorPcap(nullptr), + mPipeFd(-1) { +} + +WifiForwarder::~WifiForwarder() { + cleanup(); +} + +Result WifiForwarder::init() { + if (mMonitorPcap || mPipeFd != -1) { + return Result::error("WifiForwarder already initialized"); + } + + mPipeFd = qemu_pipe_open(kQemuPipeName); + if (mPipeFd == -1) { + // It's OK if this fails, the emulator might not have been started with + // this feature enabled. If it's not enabled we'll try again later, in + // the meantime there is no point in opening the monitor socket either. + LOGE("WifiForwarder unable to open QEMU pipe: %s", strerror(errno)); + mDeadline = Pollable::Clock::now() + std::chrono::minutes(1); + return Result::success(); + } + + char errorMsg[PCAP_ERRBUF_SIZE]; + memset(errorMsg, 0, sizeof(errorMsg)); + mMonitorPcap = pcap_create(mInterfaceName.c_str(), errorMsg); + if (mMonitorPcap == nullptr) { + return Result::error("WifiForwarder cannot create pcap handle: %s", + errorMsg); + } + int result = pcap_set_snaplen(mMonitorPcap, kPcapSnapLength); + if (result != 0) { + return Result::error("WifiForwader cannot set pcap snap length: %s", + pcap_statustostr(result)); + } + + result = pcap_set_promisc(mMonitorPcap, 1); + if (result != 0) { + return Result::error("WifiForwader cannot set pcap promisc mode: %s", + pcap_statustostr(result)); + } + + result = pcap_set_immediate_mode(mMonitorPcap, 1); + if (result != 0) { + return Result::error("WifiForwader cannot set pcap immediate mode: %s", + pcap_statustostr(result)); + } + + result = pcap_activate(mMonitorPcap); + if (result > 0) { + // A warning, log it but keep going + LOGW("WifiForwader received warnings when activating pcap: %s", + pcap_statustostr(result)); + } else if (result < 0) { + // An error, return + return Result::error("WifiForwader unable to activate pcap: %s", + pcap_statustostr(result)); + } + + int datalinkType = pcap_datalink(mMonitorPcap); + if (datalinkType != DLT_IEEE802_11_RADIO) { + // Unexpected data link encapsulation, we don't support this + return Result::error("WifiForwarder detected incompatible data link " + "encapsulation: %d", datalinkType); + } + // All done + return Result::success(); +} + + +void WifiForwarder::getPollData(std::vector<pollfd>* fds) const { + if (mPipeFd == -1) { + return; + } + int pcapFd = pcap_get_selectable_fd(mMonitorPcap); + if (pcapFd != -1) { + fds->push_back(pollfd{pcapFd, POLLIN, 0}); + } else { + LOGE("WifiForwarder unable to get pcap fd"); + } + if (mPipeFd != -1) { + fds->push_back(pollfd{mPipeFd, POLLIN, 0}); + } +} + +Pollable::Timestamp WifiForwarder::getTimeout() const { + // If there is no pipe return the deadline, we're going to retry, otherwise + // use an infinite timeout. + return mPipeFd == -1 ? mDeadline : Pollable::Timestamp::max(); +} + +bool WifiForwarder::onReadAvailable(int fd, int* /*status*/) { + if (fd == mPipeFd) { + injectFromPipe(); + } else { + forwardFromPcap(); + } + return true; +} + +void WifiForwarder::forwardFromPcap() { + struct pcap_pkthdr* header = nullptr; + const u_char* data = nullptr; + int result = pcap_next_ex(mMonitorPcap, &header, &data); + if (result == 0) { + // Timeout, nothing to do + return; + } else if (result < 0) { + LOGE("WifiForwarder failed to read from pcap: %s", + pcap_geterr(mMonitorPcap)); + return; + } + if (header->caplen < header->len) { + LOGE("WifiForwarder received packet exceeding capture length: %u < %u", + header->caplen, header->len); + return; + } + + if (mPipeFd == -1) { + LOGE("WifiForwarder unable to forward data, pipe not open"); + return; + } + + if (header->caplen < sizeof(RadioTapHeader)) { + // This packet is too small to be a valid radiotap packet, drop it + LOGE("WifiForwarder captured packet that is too small: %u", + header->caplen); + return; + } + + auto radiotap = reinterpret_cast<const RadioTapHeader*>(data); + uint32_t radioLen = __le16_to_cpu(radiotap->it_len); + if (header->caplen < radioLen + kMinimumIeee80211Size) { + // This packet is too small to contain a valid IEEE 802.11 frame + LOGE("WifiForwarder captured packet that is too small: %u < %u", + header->caplen, radioLen + kMinimumIeee80211Size); + return; + } + + WifiForwardHeader forwardHeader(header->caplen, radioLen); + + if (!WriteFully(mPipeFd, &forwardHeader, sizeof(forwardHeader))) { + LOGE("WifiForwarder failed to write to pipe: %s", strerror(errno)); + return; + } + + if (!WriteFully(mPipeFd, data, header->caplen)) { + LOGE("WifiForwarder failed to write to pipe: %s", strerror(errno)); + return; + } +} + +void WifiForwarder::injectFromPipe() { + size_t start = mMonitorBuffer.size(); + size_t newSize = start + kForwardBufferIncrement; + if (newSize > kForwardBufferMaxSize) { + // We've exceeded the maximum allowed size, drop everything we have so + // far and start over. This is most likely caused by some delay in + // injection or the injection failing in which case keeping old data + // around isn't going to be very useful. + LOGE("WifiForwarder ran out of buffer space"); + newSize = kForwardBufferIncrement; + start = 0; + } + mMonitorBuffer.resize(newSize); + + while (true) { + int result = ::read(mPipeFd, + mMonitorBuffer.data() + start, + mMonitorBuffer.size() - start); + if (result < 0) { + if (errno == EINTR) { + continue; + } + LOGE("WifiForwarder failed to read to forward buffer: %s", + strerror(errno)); + // Return the buffer to its previous size + mMonitorBuffer.resize(start); + return; + } else if (result == 0) { + // Nothing received, nothing to write + // Return the buffer to its previous size + mMonitorBuffer.resize(start); + LOGE("WifiForwarder did not receive anything to inject"); + return; + } + // Adjust the buffer size to match everything we recieved + mMonitorBuffer.resize(start + static_cast<size_t>(result)); + break; + } + + while (mMonitorBuffer.size() >= + sizeof(WifiForwardHeader) + sizeof(RadioTapHeader)) { + auto fwd = reinterpret_cast<WifiForwardHeader*>(mMonitorBuffer.data()); + if (__le32_to_cpu(fwd->magic) != kWifiForwardMagic) { + // We are not properly aligned, this can happen for the first read + // if the client or server happens to send something that's in the + // middle of a stream. Attempt to find the next packet boundary. + LOGE("WifiForwarder found incorrect magic, finding next magic"); + uint32_t le32magic = __cpu_to_le32(kWifiForwardMagic); + auto next = reinterpret_cast<unsigned char*>( + ::memmem(mMonitorBuffer.data(), mMonitorBuffer.size(), + &le32magic, sizeof(le32magic))); + if (next) { + // We've found a possible candidate, erase everything before + size_t length = next - mMonitorBuffer.data(); + mMonitorBuffer.erase(mMonitorBuffer.begin(), + mMonitorBuffer.begin() + length); + continue; + } else { + // There is no possible candidate, drop everything except the + // last three bytes. The last three bytes could possibly be the + // start of the next magic without actually triggering the + // search above. + if (mMonitorBuffer.size() > 3) { + mMonitorBuffer.erase(mMonitorBuffer.begin(), + mMonitorBuffer.end() - 3); + } + // In this case there is nothing left to parse so just return + // right away. + return; + } + } + // The length according to the wifi forward header + const size_t fullLength = __le32_to_cpu(fwd->fullLength); + const size_t payloadLength = fullLength - sizeof(WifiForwardHeader); + const size_t radioLength = __le32_to_cpu(fwd->radioLength); + // Get the radio tap header, right after the wifi forward header + unsigned char* radioTapLocation = mMonitorBuffer.data() + sizeof(*fwd); + auto hdr = reinterpret_cast<RadioTapHeader*>(radioTapLocation); + const size_t radioHdrLength = __le16_to_cpu(hdr->it_len); + + if (radioLength != radioHdrLength) { + LOGE("WifiForwarder radiotap (%u), forwarder (%u) length mismatch", + (unsigned)(radioHdrLength), (unsigned)radioLength); + // The wifi forward header radio length does not match up with the + // radiotap header length. Either this was not an actual packet + // boundary or the packet is malformed. Remove a single byte from + // the buffer to trigger a new magic marker search. + mMonitorBuffer.erase(mMonitorBuffer.begin(), + mMonitorBuffer.begin() + 1); + continue; + } + // At this point we have verified that the magic marker is present and + // that the length in the wifi forward header matches the radiotap + // header length. We're now reasonably sure this is actually a valid + // packet that we can process. + + if (fullLength > mMonitorBuffer.size()) { + // We have not received enough data yet, wait for more to arrive. + return; + } + + if (hdr->it_version != 0) { + // Unknown header version, skip this packet because we don't know + // how to handle it. + LOGE("WifiForwarder encountered unknown radiotap version %u", + static_cast<unsigned>(hdr->it_version)); + mMonitorBuffer.erase(mMonitorBuffer.begin(), + mMonitorBuffer.begin() + fullLength); + continue; + } + + if (mMonitorPcap) { + // A sufficient amount of data has arrived, forward it. + int result = pcap_inject(mMonitorPcap, hdr, payloadLength); + if (result < 0) { + LOGE("WifiForwarder failed to inject %" PRIu64 " bytes: %s", + static_cast<uint64_t>(payloadLength), + pcap_geterr(mMonitorPcap)); + } else if (static_cast<size_t>(result) < payloadLength) { + LOGE("WifiForwarder only injected %d out of %" PRIu64 " bytes", + result, static_cast<uint64_t>(payloadLength)); + } + } else { + LOGE("WifiForwarder could not forward to monitor, pcap not set up"); + } + mMonitorBuffer.erase(mMonitorBuffer.begin(), + mMonitorBuffer.begin() + fullLength); + } + +} + +void WifiForwarder::cleanup() { + if (mMonitorPcap) { + pcap_close(mMonitorPcap); + mMonitorPcap = nullptr; + } + if (mPipeFd != -1) { + ::close(mPipeFd); + mPipeFd = -1; + } +} + +bool WifiForwarder::onClose(int /*fd*/, int* status) { + // Don't care which fd, just start all over again for simplicity + cleanup(); + Result res = init(); + if (!res) { + *status = 1; + return false; + } + return true; +} + +bool WifiForwarder::onTimeout(int* status) { + if (mPipeFd == -1 && mMonitorPcap == nullptr) { + Result res = init(); + if (!res) { + *status = 1; + return false; + } + } + return true; +} + diff --git a/network/netmgr/wifi_forwarder.h b/network/netmgr/wifi_forwarder.h new file mode 100644 index 0000000..5e9939e --- /dev/null +++ b/network/netmgr/wifi_forwarder.h @@ -0,0 +1,53 @@ +/* + * 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 "macaddress.h" +#include "pollable.h" +#include "result.h" + +#include <string> +#include <unordered_set> + +struct Ieee80211Header; +struct pcap; +typedef struct pcap pcap_t; + +class WifiForwarder : public Pollable { +public: + explicit WifiForwarder(const char* monitorInterfaceName); + ~WifiForwarder(); + + Result init(); + + // Pollable interface + void getPollData(std::vector<pollfd>* fds) const override; + Timestamp getTimeout() const override; + bool onReadAvailable(int fd, int* status) override; + bool onClose(int fd, int* status) override; + bool onTimeout(int* status) override; +private: + void forwardFromPcap(); + void injectFromPipe(); + void cleanup(); + + std::string mInterfaceName; + Pollable::Timestamp mDeadline; + std::vector<unsigned char> mMonitorBuffer; + pcap_t* mMonitorPcap; + int mPipeFd; +}; 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..fba9150 100644 --- a/sepolicy/common/goldfish_setup.te +++ b/sepolicy/common/goldfish_setup.te @@ -6,9 +6,10 @@ 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; +allow goldfish_setup vendor_file:file execute_no_trans; allowxperm goldfish_setup self:udp_socket ioctl priv_sock_ioctls; wakelock_use(goldfish_setup); allow goldfish_setup vendor_shell_exec:file { rx_file_perms }; @@ -45,3 +46,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..fec1f5e --- /dev/null +++ b/sepolicy/common/netmgr.te @@ -0,0 +1,29 @@ +# 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:socket { create ioctl }; +allow netmgr self:packet_socket { ioctl getopt }; +allow netmgr self:udp_socket { ioctl }; +allow netmgr proc_net:file { read getattr open }; +allowxperm netmgr self:socket ioctl { SIOCETHTOOL }; +allowxperm netmgr self:udp_socket ioctl { SIOCSIFADDR SIOCSIFNETMASK SIOCSIFBRDADDR }; +allowxperm netmgr self:packet_socket ioctl { SIOCGIFINDEX SIOCGIFHWADDR }; + +# 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; +# Packet socket for wifi forwarding +allow netmgr self:packet_socket { bind create read setopt write }; @@ -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..8ebe591 100755 --- a/wifi/init.wifi.sh +++ b/wifi/init.wifi.sh @@ -44,16 +44,40 @@ NAMESPACE="router" rm -rf /data/vendor/var/run/netns/${NAMESPACE} rm -rf /data/vendor/var/run/netns/${NAMESPACE}.pid -# We need to fake a mac address to pass CTS -# And the kernel only accept mac addresses with some special format -# (Like, begin with 02) -/system/bin/ip link set dev wlan0 address 02:00:00:44:55:66 +# Lower the MTU of the WiFi interface to prevent issues with packet injection. +# The MTU of the WiFi monitor interface cannot be higher than 1500 but injection +# requires extra space for injection headers which count against the MTU. So if +# a 1500 byte payload needs to be injected it will fail because with the +# additional headers the total amount of data will exceed 1500 bytes. This way +# the payload is restricted to a smaller size that should leave room for the +# injection headers. +/system/bin/ip link set wlan0 mtu 1400 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 + # 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. PID=$(cat /data/vendor/var/run/netns/${NAMESPACE}.pid) + +# Move the WiFi monitor interface to the other namespace and bring it up. This +# is what we use for injecting WiFi frames from the outside world. +/system/bin/ip link set hwsim0 netns ${PID} +execns ${NAMESPACE} /system/bin/ip link set hwsim0 up + +# 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 + /system/bin/ip link set eth0 netns ${PID} /system/bin/ip link add radio0 type veth peer name radio0-peer /system/bin/ip link set radio0-peer netns ${PID} @@ -73,7 +97,9 @@ setprop ctl.start dhcpclient_rtr execns ${NAMESPACE} /system/bin/iptables -w -W 50000 -t nat -A POSTROUTING -s 192.168.232.0/21 -o eth0 -j MASQUERADE execns ${NAMESPACE} /system/bin/iptables -w -W 50000 -t nat -A POSTROUTING -s 192.168.200.0/24 -o eth0 -j MASQUERADE /system/bin/iw phy phy1 set netns $PID + execns ${NAMESPACE} /system/bin/ip addr add 192.168.232.1/21 dev wlan1 +execns ${NAMESPACE} /system/bin/ip link set wlan1 mtu 1400 execns ${NAMESPACE} /system/bin/ip link set wlan1 up # Start the IPv6 proxy that will enable use of IPv6 in the main namespace setprop ctl.start ipv6proxy 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: diff --git a/wifi/wpa_supplicant.conf b/wifi/wpa_supplicant.conf index 391c970..5f1eb7b 100644 --- a/wifi/wpa_supplicant.conf +++ b/wifi/wpa_supplicant.conf @@ -1,4 +1,3 @@ disable_scan_offload=1 wowlan_triggers=any ap_scan=1 -p2p_no_group_iface=1 |