diff options
Diffstat (limited to 'network')
26 files changed, 2326 insertions, 0 deletions
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; +}; |