summaryrefslogtreecommitdiffstats
path: root/network
diff options
context:
space:
mode:
Diffstat (limited to 'network')
-rw-r--r--network/netmgr/Android.bp44
-rw-r--r--network/netmgr/address_assigner.cpp151
-rw-r--r--network/netmgr/address_assigner.h54
-rw-r--r--network/netmgr/commander.cpp164
-rw-r--r--network/netmgr/commander.h49
-rw-r--r--network/netmgr/commands/command.h37
-rw-r--r--network/netmgr/commands/wifi_command.cpp273
-rw-r--r--network/netmgr/commands/wifi_command.h55
-rw-r--r--network/netmgr/fork.cpp67
-rw-r--r--network/netmgr/fork.h26
-rw-r--r--network/netmgr/interface_state.cpp27
-rw-r--r--network/netmgr/interface_state.h23
-rw-r--r--network/netmgr/log.cpp27
-rw-r--r--network/netmgr/log.h45
-rw-r--r--network/netmgr/macaddress.h53
-rw-r--r--network/netmgr/main.cpp150
-rw-r--r--network/netmgr/monitor.cpp167
-rw-r--r--network/netmgr/monitor.h55
-rw-r--r--network/netmgr/pollable.h62
-rw-r--r--network/netmgr/poller.cpp158
-rw-r--r--network/netmgr/poller.h34
-rw-r--r--network/netmgr/result.h54
-rw-r--r--network/netmgr/timestamp.cpp33
-rw-r--r--network/netmgr/timestamp.h33
-rw-r--r--network/netmgr/wifi_forwarder.cpp432
-rw-r--r--network/netmgr/wifi_forwarder.h53
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;
+};