summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjoern Johansson <bjoernj@google.com>2018-09-06 15:39:12 -0700
committerBjoern Johansson <bjoernj@google.com>2018-09-06 15:39:12 -0700
commit873eee6422fdd7237b9bf86edb37f2958b23829e (patch)
tree255549de864e4b32e3ef8dbce699768a4ef712cc
parentf52fc12be67cfb72a44bf79a77112d80e01f3cf3 (diff)
downloadandroid_device_generic_goldfish-873eee6422fdd7237b9bf86edb37f2958b23829e.tar.gz
android_device_generic_goldfish-873eee6422fdd7237b9bf86edb37f2958b23829e.tar.bz2
android_device_generic_goldfish-873eee6422fdd7237b9bf86edb37f2958b23829e.zip
Create a network manager for the emulator
Create a network manager for the emulator that will contain functionality related to network setup and management. Initial functionality includes address assignments to wifi interfaces as they come and go, and receiving and acting on wifi related commands over the qemu pipe. Currently the network manager only supports creation of access points and blocking/unblocking network traffic for an access point. In order to have a hostapd configuration file that can change the setup scripts will now copy the template configuration file to the vendor data partition which is writable and then modify it. BUG: 111997081 Test: Run CTS Verifier test Multinetwork connectivity Change-Id: I754e0c74cbaea9daa130915bed47ef3b2fba164e
-rw-r--r--init.ranchu.rc9
-rw-r--r--network/netmgr/Android.bp38
-rw-r--r--network/netmgr/address_assigner.cpp151
-rw-r--r--network/netmgr/address_assigner.h54
-rw-r--r--network/netmgr/commander.cpp153
-rw-r--r--network/netmgr/commander.h48
-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.h36
-rw-r--r--network/netmgr/main.cpp139
-rw-r--r--network/netmgr/monitor.cpp152
-rw-r--r--network/netmgr/monitor.h54
-rw-r--r--network/netmgr/pollable.h36
-rw-r--r--network/netmgr/poller.cpp132
-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--sepolicy/common/execns.te3
-rw-r--r--sepolicy/common/file_contexts1
-rw-r--r--sepolicy/common/goldfish_setup.te5
-rw-r--r--sepolicy/common/hostapd_nohidl.te4
-rw-r--r--sepolicy/common/netmgr.te21
-rw-r--r--vendor.mk1
-rwxr-xr-xwifi/init.wifi.sh13
31 files changed, 1735 insertions, 4 deletions
diff --git a/init.ranchu.rc b/init.ranchu.rc
index 5871766..d7a61d4 100644
--- a/init.ranchu.rc
+++ b/init.ranchu.rc
@@ -57,7 +57,7 @@ on property:dev.bootcomplete=1
service ranchu-net /vendor/bin/init.ranchu-net.sh
class late_start
user root
- group root wakelock
+ group root wakelock wifi
oneshot
service ipv6proxy /vendor/bin/execns router /vendor/bin/ipv6proxy -o eth0 -i wlan1,radio0-peer
@@ -65,7 +65,7 @@ service ipv6proxy /vendor/bin/execns router /vendor/bin/ipv6proxy -o eth0 -i wla
group root
disabled
-service emu_hostapd /vendor/bin/execns -u wifi -g wifi router /vendor/bin/hostapd_nohidl /vendor/etc/simulated_hostapd.conf
+service emu_hostapd /vendor/bin/execns -u wifi -g wifi router /vendor/bin/hostapd_nohidl /data/vendor/wifi/hostapd/hostapd.conf
user root
group root wifi net_raw net_admin
disabled
@@ -75,6 +75,11 @@ service dhcpserver /vendor/bin/execns router /vendor/bin/dhcpserver --exclude-in
group root
disabled
+service netmgr /vendor/bin/execns router /vendor/bin/netmgr --if-prefix wlan1_ --network 192.168.232.9/29
+ user root
+ group root wifi
+ disabled
+
service dhcpclient_rtr /vendor/bin/execns router /vendor/bin/dhcpclient -i eth0
user root
group root
diff --git a/network/netmgr/Android.bp b/network/netmgr/Android.bp
new file mode 100644
index 0000000..ab177e6
--- /dev/null
+++ b/network/netmgr/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_binary {
+ name: "netmgr",
+ vendor: true,
+ srcs: [
+ "address_assigner.cpp",
+ "commander.cpp",
+ "fork.cpp",
+ "interface_state.cpp",
+ "log.cpp",
+ "main.cpp",
+ "monitor.cpp",
+ "poller.cpp",
+ "commands/wifi_command.cpp",
+ ],
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ ],
+ header_libs: [
+ "goldfish_headers",
+ ],
+}
diff --git a/network/netmgr/address_assigner.cpp b/network/netmgr/address_assigner.cpp
new file mode 100644
index 0000000..11df81d
--- /dev/null
+++ b/network/netmgr/address_assigner.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "address_assigner.h"
+
+#include "log.h"
+
+#include <errno.h>
+#include <net/if.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+AddressAssigner::AddressAssigner(const char* interfacePrefix,
+ in_addr_t baseAddress,
+ uint32_t maskLength) :
+ mInterfacePrefix(interfacePrefix),
+ mPrefixLength(strlen(interfacePrefix)),
+ mBaseAddress(baseAddress),
+ mMaskLength(maskLength) {
+
+}
+
+void AddressAssigner::onInterfaceState(unsigned int /*index*/,
+ const char* name,
+ InterfaceState state) {
+ if (strncmp(name, mInterfacePrefix, mPrefixLength) != 0) {
+ // The interface does not match the prefix, ignore this change
+ return;
+ }
+
+ switch (state) {
+ case InterfaceState::Up:
+ assignAddress(name);
+ break;
+ case InterfaceState::Down:
+ freeAddress(name);
+ break;
+ }
+}
+
+void AddressAssigner::assignAddress(const char* interfaceName) {
+ if (mMaskLength > 30) {
+ // The mask length is too long, we can't assign enough IP addresses from
+ // this. A maximum of 30 bits is supported, leaving 4 remaining
+ // addresses, one is network, one is broadcast, one is gateway, one is
+ // client.
+ return;
+ }
+ // Each subnet will have an amount of bits available to it that equals
+ // 32-bits - <mask length>, so if mask length is 29 there will be 3
+ // remaining bits for each subnet. Then the distance between each subnet
+ // is 2 to the power of this number, in our example 2^3 = 8 so to get to the
+ // next subnet we add 8 to the network address.
+ in_addr_t increment = 1 << (32 - mMaskLength);
+
+ // Convert the address to host byte-order first so we can do math on it.
+ for (in_addr_t addr = ntohl(mBaseAddress); true; addr += increment) {
+ // Take the reference of this lookup, that way we can assign a name to
+ // it if needed.
+ auto& usedName = mUsedIpAddresses[addr];
+ if (usedName.empty()) {
+ // This address is not in use, let's use it
+ usedName = interfaceName;
+ // Make sure we convert back to network byte-order when setting it.
+ setIpAddress(interfaceName, htonl(addr));
+ break;
+ }
+ }
+}
+
+void AddressAssigner::setIpAddress(const char* interfaceName,
+ in_addr_t address) {
+ int sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock == -1) {
+ LOGE("AddressAssigner unable to open IP socket: %s", strerror(errno));
+ return;
+ }
+ if (!setAddress(sock, SIOCSIFADDR, interfaceName, address)) {
+ LOGE("AddressAssigner unable to set interface address: %s",
+ strerror(errno));
+ ::close(sock);
+ return;
+ }
+
+ // The netmask is the inverted maximum value of the lower bits. That is if
+ // the mask length is 29 then the the largest value of the 3 (32-29) lowest
+ // bits is 7 (2^3 - 1) (111 binary). Inverting this value gives the netmask
+ // because it excludes those three bits and sets every other bit.
+ in_addr_t netmask = htonl(~((1 << (32 - mMaskLength)) - 1));
+
+ if (!setAddress(sock, SIOCSIFNETMASK, interfaceName, netmask)) {
+ LOGE("AddressAssigner unable to set interface netmask: %s",
+ strerror(errno));
+ ::close(sock);
+ return;
+ }
+
+ // The broadcast address is just the assigned address with all bits outside
+ // of the netmask set to one.
+ in_addr_t broadcast = address | ~netmask;
+
+ if (!setAddress(sock, SIOCSIFBRDADDR, interfaceName, broadcast)) {
+ LOGE("AddressAssigner unable to set interface broadcast: %s",
+ strerror(errno));
+ ::close(sock);
+ return;
+ }
+ ::close(sock);
+}
+
+bool AddressAssigner::setAddress(int sock,
+ int type,
+ const char* interfaceName,
+ in_addr_t address) {
+ struct ifreq request;
+ memset(&request, 0, sizeof(request));
+ strlcpy(request.ifr_name, interfaceName, sizeof(request.ifr_name));
+ auto addr = reinterpret_cast<struct sockaddr_in*>(&request.ifr_addr);
+ addr->sin_family = AF_INET;
+ addr->sin_addr.s_addr = address;
+
+ if (::ioctl(sock, type, &request) != 0) {
+ return false;
+ }
+ return true;
+}
+
+void AddressAssigner::freeAddress(const char* interfaceName) {
+ for (auto& ipName : mUsedIpAddresses) {
+ if (ipName.second == interfaceName) {
+ // This is the one, free it up for future use
+ mUsedIpAddresses.erase(ipName.first);
+ }
+ }
+}
+
diff --git a/network/netmgr/address_assigner.h b/network/netmgr/address_assigner.h
new file mode 100644
index 0000000..dde42ee
--- /dev/null
+++ b/network/netmgr/address_assigner.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "interface_state.h"
+
+#include <string>
+#include <unordered_map>
+
+#include <netinet/in.h>
+#include <stdint.h>
+
+class AddressAssigner {
+public:
+ AddressAssigner(const char* interfacePrefix,
+ in_addr_t baseAddress,
+ uint32_t maskLength);
+
+ void onInterfaceState(unsigned int index,
+ const char* name,
+ InterfaceState state);
+
+private:
+ void assignAddress(const char* interfaceName);
+ void freeAddress(const char* interfaceName);
+
+ void setIpAddress(const char* interfaceName, in_addr_t address);
+ bool setAddress(int socket,
+ int type,
+ const char* interfaceName,
+ in_addr_t address);
+ void removeIpAddress(const char* interfaceName, in_addr_t address);
+
+ const char* mInterfacePrefix;
+ size_t mPrefixLength;
+ in_addr_t mBaseAddress;
+ uint32_t mMaskLength;
+ std::unordered_map<in_addr_t, std::string> mUsedIpAddresses;
+};
+
diff --git a/network/netmgr/commander.cpp b/network/netmgr/commander.cpp
new file mode 100644
index 0000000..82ed95e
--- /dev/null
+++ b/network/netmgr/commander.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "commander.h"
+
+#include "commands/command.h"
+#include "log.h"
+
+#include <errno.h>
+#include <qemu_pipe.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+static const char kQemuPipeName[] = "qemud:network";
+
+// How much space to use for each command received
+static const size_t kReceiveSpace = 1024;
+// The maximum amount of bytes to keep in the receive buffer for a single
+// command before dropping data.
+static const size_t kMaxReceiveBufferSize = 65536;
+
+Commander::Commander() : mPipeFd(-1) {
+}
+
+Commander::~Commander() {
+ closePipe();
+}
+
+Result Commander::init() {
+ if (mPipeFd != -1) {
+ return Result::error("Commander already initialized");
+ }
+
+ openPipe();
+
+ return Result::success();
+}
+
+void Commander::registerCommand(const char* commandStr, Command* command) {
+ mCommands[commandStr] = command;
+}
+
+Pollable::Data Commander::data() const {
+ return Data { mPipeFd, mDeadline };
+}
+
+void Commander::onReadAvailable() {
+
+ size_t offset = mReceiveBuffer.size();
+ mReceiveBuffer.resize(offset + kReceiveSpace);
+ if (mReceiveBuffer.size() > kMaxReceiveBufferSize) {
+ // We have buffered too much data, this should never happen but as a
+ // seurity measure let's just drop everything we have and keep
+ // receiving. Maybe the situation will improve.
+ mReceiveBuffer.resize(kReceiveSpace);
+ offset = 0;
+ }
+ while (true) {
+ int status = ::read(mPipeFd, &mReceiveBuffer[offset], kReceiveSpace);
+
+ if (status < 0) {
+ if (errno == EINTR) {
+ // We got an interrupt, try again
+ continue;
+ }
+ LOGE("Commander failed to receive on pipe: %s", strerror(errno));
+ return;
+ }
+ size_t length = static_cast<size_t>(status);
+ mReceiveBuffer.resize(offset + length);
+
+ while (true) {
+ auto endline = std::find(mReceiveBuffer.begin(),
+ mReceiveBuffer.end(),
+ '\n');
+ if (endline == mReceiveBuffer.end()) {
+ // No endline in sight, keep waiting and buffering
+ return;
+ }
+
+ *endline = '\0';
+
+ char* args = ::strchr(mReceiveBuffer.data(), ' ');
+ if (args) {
+ *args++ = '\0';
+ }
+ auto command = mCommands.find(mReceiveBuffer.data());
+
+ if (command != mCommands.end()) {
+ command->second->onCommand(mReceiveBuffer.data(), args);
+ }
+ // Now that we have processed this line let's remove it from the
+ // receive buffer
+ ++endline;
+ if (endline == mReceiveBuffer.end()) {
+ mReceiveBuffer.clear();
+ // There can be nothing left, just return
+ return;
+ } else {
+ mReceiveBuffer.erase(mReceiveBuffer.begin(), endline + 1);
+ // There may be another line in there so keep looping and look
+ // for more
+ }
+ }
+ return;
+ }
+}
+
+void Commander::onClose() {
+ // Pipe was closed from the other end, close it on our side and re-open
+ closePipe();
+ openPipe();
+}
+
+void Commander::onTimeout() {
+ if (mPipeFd == -1) {
+ openPipe();
+ }
+}
+
+void Commander::openPipe() {
+ mPipeFd = qemu_pipe_open(kQemuPipeName);
+ if (mPipeFd == -1) {
+ LOGE("Failed to open QEMU pipe '%s': %s",
+ kQemuPipeName,
+ strerror(errno));
+ // Try again in the future
+ mDeadline = Pollable::Clock::now() + std::chrono::minutes(1);
+ } else {
+ mDeadline = Pollable::Timestamp::max();
+ }
+}
+
+void Commander::closePipe() {
+ if (mPipeFd != -1) {
+ ::close(mPipeFd);
+ mPipeFd = -1;
+ }
+}
diff --git a/network/netmgr/commander.h b/network/netmgr/commander.h
new file mode 100644
index 0000000..9f33526
--- /dev/null
+++ b/network/netmgr/commander.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pollable.h"
+#include "result.h"
+
+#include <unordered_map>
+#include <vector>
+
+class Command;
+
+class Commander : public Pollable {
+public:
+ Commander();
+ ~Commander();
+
+ Result init();
+
+ void registerCommand(const char* commandStr, Command* command);
+
+ Pollable::Data data() const override;
+ void onReadAvailable() override;
+ void onClose() override;
+ void onTimeout() override;
+private:
+ void openPipe();
+ void closePipe();
+
+ int mPipeFd;
+ Pollable::Timestamp mDeadline;
+ std::vector<char> mReceiveBuffer;
+ std::unordered_map<std::string, Command*> mCommands;
+};
diff --git a/network/netmgr/commands/command.h b/network/netmgr/commands/command.h
new file mode 100644
index 0000000..0612cef
--- /dev/null
+++ b/network/netmgr/commands/command.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "../result.h"
+
+// An interface for commands coming through the pipe. Note that each instance
+// can handle any number of commands, all it has to do is differentiate based
+// on the incoming command string or arguments. It will have to be registered
+// multiple times, once for each command though.
+class Command {
+public:
+ virtual ~Command() = default;
+
+ // Work to perform when a command is received. The result will be used to
+ // report the result to the user. If the result indicates success the user
+ // will see an "OK" response, on failure the error message in the result
+ // will be presented to the user. This means that the result error string
+ // should be fairly user-friendly.
+ virtual Result onCommand(const char* command, const char* args) = 0;
+
+};
+
diff --git a/network/netmgr/commands/wifi_command.cpp b/network/netmgr/commands/wifi_command.cpp
new file mode 100644
index 0000000..0d0be19
--- /dev/null
+++ b/network/netmgr/commands/wifi_command.cpp
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wifi_command.h"
+
+#include "../fork.h"
+#include "log.h"
+
+#include <cutils/properties.h>
+#include <errno.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+static const char kHostApdStubFile[] = "/vendor/etc/simulated_hostapd.conf";
+static const char kHostApdConfFile[] = "/data/vendor/wifi/hostapd/hostapd.conf";
+
+static const char kControlRestartProperty[] = "ctl.restart";
+static const char kHostApdServiceName[] = "emu_hostapd";
+
+static const char kIfNamePrefix[] = "wlan1_";
+
+class File {
+public:
+ explicit File(FILE* file) : mFile(file) {}
+ ~File() {
+ if (mFile) {
+ fclose(mFile);
+ }
+ }
+
+ FILE* get() { return mFile; }
+
+ bool operator!() const { return mFile == nullptr; }
+private:
+ FILE* mFile;
+};
+
+class Fd {
+public:
+ explicit Fd(int fd) : mFd(fd) {}
+ ~Fd() {
+ if (mFd != -1) {
+ ::close(mFd);
+ mFd = -1;
+ }
+ }
+
+ int get() const { return mFd; }
+
+private:
+ int mFd;
+};
+
+std::vector<std::string> explode(const char* str) {
+ const char* cur = str;
+ const char* space = nullptr;
+ std::vector<std::string> result;
+ do {
+ space = ::strchr(cur, ' ');
+ if (space) {
+ result.emplace_back(cur, space);
+ cur = space + 1;
+ } else {
+ result.emplace_back(cur);
+ }
+ } while (space);
+
+ return result;
+}
+
+WifiCommand::WifiCommand() : mLowestInterfaceNumber(1) {
+ readConfig();
+}
+
+Result WifiCommand::onCommand(const char* command, const char* args) {
+ const char* divider = ::strchr(args, ' ');
+ if (divider == nullptr) {
+ // Unknown command, every command needs an argument
+ return Result::error("Invalid wifi command '%s'", args);
+ }
+
+ std::string subCommand(args, divider);
+ if (subCommand.empty()) {
+ return Result::error("Empty wifi command");
+ }
+
+ std::vector<std::string> subArgs = explode(divider + 1);
+ if (subArgs.empty()) {
+ // All of these commands require sub arguments
+ return Result::error("Missing argument to command '%s'",
+ subCommand.c_str());
+ }
+
+ if (subCommand == "add") {
+ return onAdd(subArgs);
+ } else if (subCommand == "block") {
+ return onBlock(subArgs);
+ } else if (subCommand == "unblock") {
+ return onUnblock(subArgs);
+ } else {
+ return Result::error("Unknown wifi command '%s'", subCommand.c_str());
+ }
+}
+
+void WifiCommand::readConfig() {
+}
+
+Result WifiCommand::writeConfig() {
+ File in(fopen(kHostApdStubFile, "r"));
+ if (!in) {
+ return Result::error("Config failure: could not open template: %s",
+ strerror(errno));
+ }
+
+ File out(fopen(kHostApdConfFile, "w"));
+ if (!out) {
+ return Result::error("Config failure: could not open target: %s",
+ strerror(errno));
+ }
+
+ char buffer[32768];
+ while (!feof(in.get())) {
+ size_t bytesRead = fread(buffer, 1, sizeof(buffer), in.get());
+ if (bytesRead != sizeof(buffer) && ferror(in.get())) {
+ return Result::error("Config failure: Error reading template: %s",
+ strerror(errno));
+ }
+
+ size_t bytesWritten = fwrite(buffer, 1, bytesRead, out.get());
+ if (bytesWritten != bytesRead) {
+ return Result::error("Config failure: Error writing target: %s",
+ strerror(errno));
+ }
+ }
+ fprintf(out.get(), "\n\n");
+
+ for (const auto& ap : mAccessPoints) {
+ fprintf(out.get(), "bss=%s\n", ap.second.ifName.c_str());
+ fprintf(out.get(), "ssid=%s\n", ap.second.ssid.c_str());
+ if (!ap.second.password.empty()) {
+ fprintf(out.get(), "wpa=2\n");
+ fprintf(out.get(), "wpa_key_mgmt=WPA-PSK\n");
+ fprintf(out.get(), "rsn_pairwise=CCMP\n");
+ fprintf(out.get(), "wpa_passphrase=%s\n", ap.second.password.c_str());
+ }
+ fprintf(out.get(), "\n");
+ }
+ return Result::success();
+}
+
+Result WifiCommand::triggerHostApd() {
+ property_set(kControlRestartProperty, kHostApdServiceName);
+ return Result::success();
+}
+
+static const char* sSetForwardRule[] = {"/system/bin/iptables",
+ "-w", // Wait for iptables lock if
+ "-W", // needed. This prevents
+ "50000", // spurious failures.
+ "<AddOrDelete>", // To be replaced
+ "FORWARD",
+ "-i",
+ "<InInterface>", // To be replaced
+ "-o",
+ "<OutInterface>", // To be replaced
+ "-j",
+ "DROP",
+ nullptr };
+
+static const char kIpTables[] = "/system/bin/iptables";
+static const char kIp6Tables[] = "/system/bin/ip6tables";
+static const char kAddRule[] = "-A";
+static const char kDeleteRule[] = "-D";
+static const size_t kIpTablesIndex = 0;
+static const size_t kActionIndex = 4;
+static const size_t kInInterfaceIndex = 7;
+static const size_t kOutInterfaceIndex = 9;
+
+
+Result WifiCommand::setBlocked(const char* ifName, bool blocked) {
+ // Blocking means adding block rules, unblocking means removing them
+ sSetForwardRule[kActionIndex] = blocked ? kAddRule : kDeleteRule;
+
+ // Do this for both IPv4 and IPv6 to ensure all traffic is blocked/unblocked
+ for (const auto& iptables : { kIpTables, kIp6Tables }) {
+ // Block traffic coming in from the outside world to this wlan
+ sSetForwardRule[kIpTablesIndex] = iptables;
+ sSetForwardRule[kInInterfaceIndex] = "eth0";
+ sSetForwardRule[kOutInterfaceIndex] = ifName;
+ if (!forkAndExec(sSetForwardRule)) {
+ return Result::error("Internal error: Unable to %s network",
+ blocked ? "block" : "unblock");
+ }
+ // Block traffic going from the wlan to the outside world
+ sSetForwardRule[kInInterfaceIndex] = ifName;
+ sSetForwardRule[kOutInterfaceIndex] = "eth0";
+ if (!forkAndExec(sSetForwardRule)) {
+ return Result::error("Internal error: Unable to %s network",
+ blocked ? "block" : "unblock");
+ }
+ }
+ return Result::success();
+}
+
+Result WifiCommand::onAdd(const std::vector<std::string>& arguments) {
+ AccessPoint& ap = mAccessPoints[arguments[0]];
+ ap.ssid = arguments[0];
+ if (arguments.size() > 1) {
+ ap.password = arguments[1];
+ } else {
+ ap.password.clear();
+ }
+ if (ap.ifName.empty()) {
+ char buffer[sizeof(kIfNamePrefix) + 10];
+ while (true) {
+ snprintf(buffer, sizeof(buffer), "%s%d",
+ kIfNamePrefix, mLowestInterfaceNumber);
+ ap.ifName = buffer;
+ auto usedInterface = mUsedInterfaces.find(ap.ifName);
+ if (usedInterface == mUsedInterfaces.end()) {
+ // This interface is available, use it
+ ++mLowestInterfaceNumber;
+ mUsedInterfaces.insert(ap.ifName);
+ break;
+ }
+ // The interface name was alread in use, try the next one
+ ++mLowestInterfaceNumber;
+ }
+ }
+ Result res = writeConfig();
+ if (!res) {
+ return res;
+ }
+ return triggerHostApd();
+}
+
+Result WifiCommand::onBlock(const std::vector<std::string>& arguments) {
+ auto interface = mAccessPoints.find(arguments[0]);
+ if (interface == mAccessPoints.end()) {
+ return Result::error("Unknown SSID '%s", arguments[0].c_str());
+ }
+ interface->second.blocked = true;
+ return setBlocked(interface->second.ifName.c_str(), true);
+}
+
+Result WifiCommand::onUnblock(const std::vector<std::string>& arguments) {
+ auto interface = mAccessPoints.find(arguments[0]);
+ if (interface == mAccessPoints.end()) {
+ return Result::error("Unknown SSID '%s", arguments[0].c_str());
+ }
+ interface->second.blocked = false;
+ return setBlocked(interface->second.ifName.c_str(), false);
+}
+
diff --git a/network/netmgr/commands/wifi_command.h b/network/netmgr/commands/wifi_command.h
new file mode 100644
index 0000000..f9ce96c
--- /dev/null
+++ b/network/netmgr/commands/wifi_command.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "command.h"
+#include "result.h"
+
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+class WifiCommand : public Command {
+public:
+ WifiCommand();
+ virtual ~WifiCommand() = default;
+
+ Result onCommand(const char* command, const char* args) override;
+private:
+ void readConfig();
+ Result writeConfig();
+ Result triggerHostApd();
+ Result setBlocked(const char* ifName, bool blocked);
+
+ Result onAdd(const std::vector<std::string>& args);
+ Result onBlock(const std::vector<std::string>& args);
+ Result onUnblock(const std::vector<std::string>& args);
+
+ struct AccessPoint {
+ AccessPoint() : blocked(false) {}
+ std::string ifName;
+ std::string ssid;
+ std::string password;
+ bool blocked;
+ };
+
+ std::unordered_map<std::string, AccessPoint> mAccessPoints;
+ std::unordered_set<std::string> mUsedInterfaces;
+ int mLowestInterfaceNumber;
+};
+
diff --git a/network/netmgr/fork.cpp b/network/netmgr/fork.cpp
new file mode 100644
index 0000000..49c4c61
--- /dev/null
+++ b/network/netmgr/fork.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fork.h"
+
+#include "log.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+bool forkAndExec(const char* argv[]) {
+ pid_t pid = ::fork();
+ if (pid < 0) {
+ // Failed to fork
+ LOGE("fork() failed: %s", strerror(errno));
+ return false;
+ } else if (pid == 0) {
+ // Child
+ char buffer[32768];
+ size_t offset = 0;
+ for (size_t i = 0; argv[i]; ++i) {
+ offset += snprintf(buffer + offset, sizeof(buffer) - offset,
+ "%s ", argv[i]);
+ }
+ LOGE("Running '%s'", buffer);
+ execvp(argv[0], const_cast<char* const*>(argv));
+ LOGE("Failed to run '%s': %s", argv[0], strerror(errno));
+ _exit(1);
+ } else {
+ // Parent
+ int status = 0;
+ do {
+ ::waitpid(pid, &status, 0);
+ if (WIFEXITED(status)) {
+ int exitStatus = WEXITSTATUS(status);
+ if (exitStatus == 0) {
+ return true;
+ }
+ LOGE("Error: '%s' exited with code: %d", argv[0], exitStatus);
+ } else if (WIFSIGNALED(status)) {
+ LOGE("Error: '%s' terminated with signal: %d",
+ argv[0], WTERMSIG(status));
+ }
+ // Other possibilities include being stopped and continued as part
+ // of a trace but we don't really care about that. The important
+ // part is that unless the process explicitly exited or was killed
+ // by a signal we have to keep waiting.
+ } while (!WIFEXITED(status) && !WIFSIGNALED(status));
+ return false;
+ }
+}
diff --git a/network/netmgr/fork.h b/network/netmgr/fork.h
new file mode 100644
index 0000000..a7cfd48
--- /dev/null
+++ b/network/netmgr/fork.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// Fork and run the provided program with arguments and wait until the program
+// exits. The list of arguments in |argv| has to be terminated by a NULL
+// pointer (e.g. { "ls", "-l", "/", nullptr } to run 'ls -l /'
+// Returns true if the process exits normally with a return code of 0. Returns
+// false if the process is terminated by a signal or if it exits with a return
+// code that is not 0.
+bool forkAndExec(const char* argv[]);
+
diff --git a/network/netmgr/interface_state.cpp b/network/netmgr/interface_state.cpp
new file mode 100644
index 0000000..57e169c
--- /dev/null
+++ b/network/netmgr/interface_state.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "interface_state.h"
+
+const char* interfaceStateToStr(InterfaceState state) {
+ switch (state) {
+ case InterfaceState::Up:
+ return "Up";
+ case InterfaceState::Down:
+ return "Down";
+ }
+}
+
diff --git a/network/netmgr/interface_state.h b/network/netmgr/interface_state.h
new file mode 100644
index 0000000..5315b0f
--- /dev/null
+++ b/network/netmgr/interface_state.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+enum class InterfaceState {
+ Up,
+ Down,
+};
+
diff --git a/network/netmgr/log.cpp b/network/netmgr/log.cpp
new file mode 100644
index 0000000..7256a3f
--- /dev/null
+++ b/network/netmgr/log.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "log.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+bool isTerminal;
+
+void initIsTerminal() {
+ isTerminal = isatty(STDOUT_FILENO) != 0;
+}
+
diff --git a/network/netmgr/log.h b/network/netmgr/log.h
new file mode 100644
index 0000000..e722091
--- /dev/null
+++ b/network/netmgr/log.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#define LOG_TAG "netmgr"
+#include <log/log.h>
+#include <stdio.h>
+
+extern bool isTerminal;
+void initIsTerminal();
+
+// Print errors to stderr if running from a terminal, otherwise print to logcat
+// This is useful for debugging from a terminal
+#define LOGE(...) do { \
+ if (isTerminal) { \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); \
+ } else { \
+ ALOGE(__VA_ARGS__); \
+ } \
+} while (0)
+
diff --git a/network/netmgr/main.cpp b/network/netmgr/main.cpp
new file mode 100644
index 0000000..c6e20bc
--- /dev/null
+++ b/network/netmgr/main.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "address_assigner.h"
+#include "commander.h"
+#include "commands/wifi_command.h"
+#include "log.h"
+#include "monitor.h"
+#include "poller.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <functional>
+
+static void usage(const char* name) {
+ LOGE("Usage: %s --if-prefix <prefix> --network <ip/mask>", name);
+ LOGE(" <prefix> indicates the name of network interfaces to configure.");
+ LOGE(" <ip/mask> is the base IP address to assign to the first interface");
+ LOGE(" and mask indicates the netmask and broadcast to set.");
+ LOGE(" Additionally mask is used to determine the address");
+ LOGE(" for the second interface by skipping ahead one subnet");
+ LOGE(" and the size of the subnet is indicated by <mask>");
+}
+
+static bool parseNetwork(const char* network,
+ in_addr_t* address,
+ uint32_t* mask) {
+ const char* divider = strchr(network, '/');
+ if (divider == nullptr) {
+ LOGE("Network specifier '%s' is missing netmask length", network);
+ return false;
+ }
+ if (divider - network >= INET_ADDRSTRLEN) {
+ LOGE("Network specifier '%s' contains an IP address that is too long",
+ network);
+ return false;
+ }
+
+ char buffer[INET_ADDRSTRLEN];
+ strlcpy(buffer, network, divider - network + 1);
+ struct in_addr addr;
+ if (!::inet_aton(buffer, &addr)) {
+ // String could not be converted to IP address
+ LOGE("Network specifier '%s' contains an invalid IP address '%s'",
+ network, buffer);
+ return false;
+ }
+
+ ++divider;
+
+ char dummy = 0;
+ if (sscanf(divider, "%u%c", mask, &dummy) != 1) {
+ LOGE("Netork specifier '%s' contains an invalid netmask length '%s'",
+ network, divider);
+ return false;
+ }
+
+ *address = addr.s_addr;
+ return true;
+}
+
+int main(int argc, char* argv[]) {
+ const char* interfacePrefix = nullptr;
+ const char* network = nullptr;
+
+ for (int i = 1; i < argc; ++i) {
+ if (strcmp(argv[i], "--if-prefix") == 0 && i + 1 < argc) {
+ interfacePrefix = argv[++i];
+ } else if (strcmp(argv[i], "--network") == 0 && i + 1 < argc) {
+ network = argv[++i];
+ } else {
+ LOGE("Unknown parameter '%s'", argv[i]);
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (interfacePrefix == nullptr) {
+ LOGE("Missing parameter --if-prefix");
+ }
+ if (network == nullptr) {
+ LOGE("Missing parameter --network");
+ }
+ if (network == nullptr || interfacePrefix == nullptr) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ in_addr_t address = 0;
+ uint32_t mask = 0;
+ if (!parseNetwork(network, &address, &mask)) {
+ return 1;
+ }
+
+ AddressAssigner assigner(interfacePrefix, address, mask);
+ Monitor monitor;
+
+ monitor.setOnInterfaceState(std::bind(&AddressAssigner::onInterfaceState,
+ &assigner,
+ std::placeholders::_1,
+ std::placeholders::_2,
+ std::placeholders::_3));
+
+ Result res = monitor.init();
+ if (!res) {
+ LOGE("%s", res.c_str());
+ return 1;
+ }
+
+ Commander commander;
+ res = commander.init();
+ if (!res) {
+ LOGE("%s", res.c_str());
+ return 1;
+ }
+
+ WifiCommand wifiCommand;
+ commander.registerCommand("wifi", &wifiCommand);
+
+ Poller poller;
+ poller.addPollable(&monitor);
+ poller.addPollable(&commander);
+ return poller.run();
+}
+
diff --git a/network/netmgr/monitor.cpp b/network/netmgr/monitor.cpp
new file mode 100644
index 0000000..492e7c6
--- /dev/null
+++ b/network/netmgr/monitor.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "monitor.h"
+
+#include "log.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+Monitor::Monitor() : mSocketFd(-1) {
+
+}
+
+Monitor::~Monitor() {
+ closeSocket();
+}
+
+Result Monitor::init() {
+ return openSocket();
+}
+
+void Monitor::setOnInterfaceState(OnInterfaceStateCallback callback) {
+ mOnInterfaceStateCallback = callback;
+}
+
+void Monitor::onReadAvailable() {
+ char buffer[32768];
+ struct sockaddr_storage storage;
+
+ while (true) {
+ socklen_t addrSize = sizeof(storage);
+ int status = ::recvfrom(mSocketFd,
+ buffer,
+ sizeof(buffer),
+ MSG_DONTWAIT,
+ reinterpret_cast<struct sockaddr*>(&storage),
+ &addrSize);
+ if (status < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ // Nothing to receive, everything is fine
+ return;
+ } else if (status < 0) {
+ LOGE("Monitor receive failed: %s", strerror(errno));
+ return;
+ } else if (addrSize < 0 ||
+ static_cast<size_t>(addrSize) != sizeof(struct sockaddr_nl)) {
+ LOGE("Monitor received invalid address size");
+ return;
+ }
+
+ size_t length = static_cast<size_t>(status);
+
+ auto hdr = reinterpret_cast<struct nlmsghdr*>(buffer);
+ while (NLMSG_OK(hdr, length) && hdr->nlmsg_type != NLMSG_DONE) {
+ switch (hdr->nlmsg_type) {
+ case RTM_NEWLINK:
+ handleNewLink(hdr);
+ break;
+ default:
+ break;
+ }
+ NLMSG_NEXT(hdr, length);
+ }
+ }
+}
+
+void Monitor::onClose() {
+ // Socket was closed from the other end, close it from our end and re-open
+ closeSocket();
+ Result res = openSocket();
+ if (!res) {
+ LOGE("%s", res.c_str());
+ }
+}
+
+void Monitor::onTimeout() {
+}
+
+Pollable::Data Monitor::data() const {
+ return Pollable::Data{ mSocketFd, Pollable::Timestamp::max() };
+}
+
+Result Monitor::openSocket() {
+ if (mSocketFd != -1) {
+ return Result::error("Monitor already initialized");
+ }
+
+ mSocketFd = ::socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE);
+ if (mSocketFd == -1) {
+ return Result::error("Monitor failed to open socket: %s",
+ strerror(errno));
+ }
+
+ struct sockaddr_nl addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = RTNLGRP_LINK | RTNLGRP_IPV4_IFADDR | RTNLGRP_IPV6_IFADDR;
+
+ struct sockaddr* sa = reinterpret_cast<struct sockaddr*>(&addr);
+ if (::bind(mSocketFd, sa, sizeof(addr)) != 0) {
+ return Result::error("Monitor failed to bind socket: %s",
+ strerror(errno));
+ }
+
+ return Result::success();
+}
+
+void Monitor::closeSocket() {
+ if (mSocketFd != -1) {
+ ::close(mSocketFd);
+ mSocketFd = -1;
+ }
+}
+
+void Monitor::handleNewLink(const struct nlmsghdr* hdr) {
+ if (!mOnInterfaceStateCallback) {
+ return;
+ }
+
+ auto msg = reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr));
+
+ if (msg->ifi_change & IFF_UP) {
+ // The interface up/down flag changed, send a notification
+ char name[IF_NAMESIZE + 1] = { 0 };
+ if_indextoname(msg->ifi_index, name);
+
+ InterfaceState state = (msg->ifi_flags & IFF_UP) ? InterfaceState::Up :
+ InterfaceState::Down;
+ mOnInterfaceStateCallback(msg->ifi_index, name, state);
+ }
+}
+
diff --git a/network/netmgr/monitor.h b/network/netmgr/monitor.h
new file mode 100644
index 0000000..d75bac7
--- /dev/null
+++ b/network/netmgr/monitor.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "interface_state.h"
+#include "pollable.h"
+#include "result.h"
+
+const char* interfaceStateToStr(InterfaceState state);
+
+/** Monitor network interfaces and provide notifications of changes to those
+ * interfaces.
+ */
+class Monitor : public Pollable {
+public:
+ using OnInterfaceStateCallback = std::function<void (unsigned int index,
+ const char* name,
+ InterfaceState state)>;
+ Monitor();
+ ~Monitor();
+
+ Result init();
+
+ void setOnInterfaceState(OnInterfaceStateCallback callback);
+
+ // Pollable interface
+ void onReadAvailable() override;
+ void onClose() override;
+ void onTimeout() override;
+ Pollable::Data data() const override;
+
+private:
+ Result openSocket();
+ void closeSocket();
+ void handleNewLink(const struct nlmsghdr* hdr);
+
+ int mSocketFd;
+ OnInterfaceStateCallback mOnInterfaceStateCallback;
+};
+
diff --git a/network/netmgr/pollable.h b/network/netmgr/pollable.h
new file mode 100644
index 0000000..fe87a41
--- /dev/null
+++ b/network/netmgr/pollable.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+
+class Pollable {
+public:
+ using Clock = std::chrono::steady_clock;
+ using Timestamp = Clock::time_point;
+ struct Data {
+ int fd;
+ Timestamp deadline;
+ };
+ virtual ~Pollable() = default;
+
+ virtual Data data() const = 0;
+ virtual void onReadAvailable() = 0;
+ virtual void onClose() = 0;
+ virtual void onTimeout() = 0;
+};
+
diff --git a/network/netmgr/poller.cpp b/network/netmgr/poller.cpp
new file mode 100644
index 0000000..20c822d
--- /dev/null
+++ b/network/netmgr/poller.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "poller.h"
+
+#include "log.h"
+
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <vector>
+
+using std::chrono::duration_cast;
+
+static struct timespec* getTimeout(Pollable::Timestamp deadline,
+ struct timespec* ts) {
+ if (deadline < Pollable::Timestamp::max()) {
+ auto timeout = deadline - Pollable::Clock::now();
+ // Convert and round down to seconds
+ auto seconds = duration_cast<std::chrono::seconds>(timeout);
+ // Then subtract the seconds from the timeout and convert the remainder
+ auto nanos = duration_cast<std::chrono::nanoseconds>(timeout - seconds);
+
+ ts->tv_sec = seconds.count();
+ ts->tv_nsec = nanos.count();
+
+ return ts;
+ }
+ return nullptr;
+}
+
+Poller::Poller() {
+}
+
+void Poller::addPollable(Pollable* pollable) {
+ mPollables.push_back(pollable);
+}
+
+int Poller::run() {
+ // Block all signals while we're running. This way we don't have to deal
+ // with things like EINTR. We then uses ppoll to set the original mask while
+ // polling. This way polling can be interrupted but socket writing, reading
+ // and ioctl remain interrupt free. If a signal arrives while we're blocking
+ // it it will be placed in the signal queue and handled once ppoll sets the
+ // original mask. This way no signals are lost.
+ sigset_t blockMask, mask;
+ int status = ::sigfillset(&blockMask);
+ if (status != 0) {
+ LOGE("Unable to fill signal set: %s", strerror(errno));
+ return errno;
+ }
+ status = ::sigprocmask(SIG_SETMASK, &blockMask, &mask);
+ if (status != 0) {
+ LOGE("Unable to set signal mask: %s", strerror(errno));
+ return errno;
+ }
+
+ std::vector<struct pollfd> fds;
+ while (true) {
+ fds.clear();
+ Pollable::Timestamp deadline = Pollable::Timestamp::max();
+ for (const auto& pollable : mPollables) {
+ int fd = pollable->data().fd;
+ if (fd != -1) {
+ fds.push_back(pollfd{});
+ fds.back().fd = fd;
+ fds.back().events = POLLIN;
+ }
+ if (pollable->data().deadline < deadline) {
+ deadline = pollable->data().deadline;
+ }
+ }
+
+ struct timespec ts = { 0, 0 };
+ struct timespec* tsPtr = getTimeout(deadline, &ts);
+ status = ::ppoll(fds.data(), fds.size(), tsPtr, &mask);
+ if (status < 0) {
+ if (errno == EINTR) {
+ // Interrupted, just keep going
+ continue;
+ }
+ // Actual error, time to quit
+ LOGE("Polling failed: %s", strerror(errno));
+ return errno;
+ }
+ // Check for timeouts
+ Pollable::Timestamp now = Pollable::Clock::now();
+ for (auto& pollable : mPollables) {
+ // Since we're going to have a very low number of pollables it's
+ // probably faster to just loop through fds here instead of
+ // constructing a map from fd to pollable every polling loop.
+ for (const auto& fd : fds) {
+ if (fd.fd == pollable->data().fd) {
+ if (fd.revents & POLLIN) {
+ // This pollable has data available for reading
+ pollable->onReadAvailable();
+ }
+ if (fd.revents & POLLHUP) {
+ // The fd was closed from the other end
+ pollable->onClose();
+ }
+ }
+ }
+ // Potentially trigger both read and timeout for pollables that have
+ // different logic for these two cases. By checking the timeout
+ // after the read we allow the pollable to update the deadline after
+ // the read to prevent this from happening.
+ if (now > pollable->data().deadline) {
+ // This pollable has reached its deadline
+ pollable->onTimeout();
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/network/netmgr/poller.h b/network/netmgr/poller.h
new file mode 100644
index 0000000..9794f4d
--- /dev/null
+++ b/network/netmgr/poller.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include "pollable.h"
+
+class Poller {
+public:
+ Poller();
+
+ void addPollable(Pollable* pollable);
+
+ int run();
+
+private:
+ std::vector<Pollable*> mPollables;
+};
+
diff --git a/network/netmgr/result.h b/network/netmgr/result.h
new file mode 100644
index 0000000..5087e14
--- /dev/null
+++ b/network/netmgr/result.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <string>
+
+class Result {
+public:
+ static Result success() {
+ return Result(true);
+ }
+ // Construct a result indicating an error.
+ static Result error(std::string message) {
+ return Result(message);
+ }
+ static Result error(const char* format, ...) {
+ char buffer[1024];
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buffer, sizeof(buffer), format, args);
+ va_end(args);
+ buffer[sizeof(buffer) - 1] = '\0';
+ return Result(std::string(buffer));
+ }
+
+ bool isSuccess() const { return mSuccess; }
+ bool operator!() const { return !mSuccess; }
+
+ const char* c_str() const { return mMessage.c_str(); }
+private:
+ explicit Result(bool success) : mSuccess(success) { }
+ explicit Result(std::string message)
+ : mMessage(message), mSuccess(false) {
+ }
+ std::string mMessage;
+ bool mSuccess;
+};
+
diff --git a/network/netmgr/timestamp.cpp b/network/netmgr/timestamp.cpp
new file mode 100644
index 0000000..36c79ee
--- /dev/null
+++ b/network/netmgr/timestamp.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "timestamp.h"
+
+
+Timestamp::Timestamp() {
+ memset(&mTime, 0, sizeof(mTime));
+}
+
+static Timestamp Timestamp::now() {
+ Timestamp t;
+ clock_gettime(CLOCK_MONOTONIC, &t.mTime);
+ return t;
+}
+
+bool Timestamp::operator==(const Timestamp& other) const {
+}
+bool Timestamp::operator<(const Timestamp& other) const {
+}
diff --git a/network/netmgr/timestamp.h b/network/netmgr/timestamp.h
new file mode 100644
index 0000000..8ad7bf8
--- /dev/null
+++ b/network/netmgr/timestamp.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <time.h>
+
+class Timestamp {
+public:
+ Timestamp();
+
+ static Timestamp now();
+
+ bool operator==(const Timestamp& other) const;
+ bool operator<(const Timestamp& other) const;
+
+private:
+ struct timespec mTime;
+};
+
diff --git a/sepolicy/common/execns.te b/sepolicy/common/execns.te
index dc6c424..24eee41 100644
--- a/sepolicy/common/execns.te
+++ b/sepolicy/common/execns.te
@@ -21,6 +21,9 @@ domain_auto_trans(execns, dhcpserver_exec, dhcpserver);
# Allow hostapd_nohidl to be run by execns in its own domain
domain_auto_trans(execns, hostapd_nohidl_exec, hostapd_nohidl);
+# Allow netmgr to be run by execns in its own domain
+domain_auto_trans(execns, netmgr_exec, netmgr);
+
# Allow execns to read createns proc file to get the namespace file
allow execns createns:file read;
allow execns createns:dir search;
diff --git a/sepolicy/common/file_contexts b/sepolicy/common/file_contexts
index 3c9df34..f429ccb 100644
--- a/sepolicy/common/file_contexts
+++ b/sepolicy/common/file_contexts
@@ -25,6 +25,7 @@
/vendor/bin/dhcpclient u:object_r:dhcpclient_exec:s0
/vendor/bin/dhcpserver u:object_r:dhcpserver_exec:s0
/vendor/bin/hostapd_nohidl u:object_r:hostapd_nohidl_exec:s0
+/vendor/bin/netmgr u:object_r:netmgr_exec:s0
/vendor/bin/hw/android\.hardware\.drm@1\.0-service\.widevine u:object_r:hal_drm_widevine_exec:s0
/vendor/bin/hw/android\.hardware\.drm@1\.1-service\.widevine u:object_r:hal_drm_widevine_exec:s0
diff --git a/sepolicy/common/goldfish_setup.te b/sepolicy/common/goldfish_setup.te
index 3041436..0fdcd37 100644
--- a/sepolicy/common/goldfish_setup.te
+++ b/sepolicy/common/goldfish_setup.te
@@ -6,7 +6,7 @@ init_daemon_domain(goldfish_setup)
# TODO(b/79502552): Invalid property access from emulator vendor
#set_prop(goldfish_setup, debug_prop);
-allow goldfish_setup self:capability { net_admin net_raw };
+allow goldfish_setup self:capability { fowner chown net_admin net_raw };
allow goldfish_setup self:udp_socket { create ioctl };
allow goldfish_setup vendor_toolbox_exec:file execute_no_trans;
allowxperm goldfish_setup self:udp_socket ioctl priv_sock_ioctls;
@@ -45,3 +45,6 @@ allow goldfish_setup self:rawip_socket { create getopt setopt };
allow goldfish_setup createns:file { read };
allow goldfish_setup createns:dir { search };
allow goldfish_setup createns:lnk_file { read };
+# Allow goldfish_setup to copy the hostapd conf template to the vendor data dir
+allow goldfish_setup hostapd_data_file:file create_file_perms;
+allow goldfish_setup hostapd_data_file:dir rw_dir_perms;
diff --git a/sepolicy/common/hostapd_nohidl.te b/sepolicy/common/hostapd_nohidl.te
index add648a..badad45 100644
--- a/sepolicy/common/hostapd_nohidl.te
+++ b/sepolicy/common/hostapd_nohidl.te
@@ -6,10 +6,12 @@ net_domain(hostapd_nohidl)
allow hostapd_nohidl execns:fd use;
+allow hostapd_nohidl hostapd_data_file:file r_file_perms;
+allow hostapd_nohidl hostapd_data_file:dir r_dir_perms;
allow hostapd_nohidl self:capability { net_admin net_raw };
allow hostapd_nohidl self:netlink_generic_socket { bind create getattr read setopt write };
allow hostapd_nohidl self:netlink_route_socket nlmsg_write;
-allow hostapd_nohidl self:packet_socket { create setopt };
+allow hostapd_nohidl self:packet_socket { create setopt read write };
allowxperm hostapd_nohidl self:udp_socket ioctl priv_sock_ioctls;
# hostapd will attempt to search sysfs but it's not needed and will spam the log
diff --git a/sepolicy/common/netmgr.te b/sepolicy/common/netmgr.te
new file mode 100644
index 0000000..20eb00c
--- /dev/null
+++ b/sepolicy/common/netmgr.te
@@ -0,0 +1,21 @@
+# Wifi manager
+type netmgr, domain;
+type netmgr_exec, exec_type, vendor_file_type, file_type;
+
+init_daemon_domain(netmgr)
+net_domain(netmgr)
+
+allow netmgr execns:fd use;
+
+# Set ctrl.restart property to restart hostapd when config changes
+set_prop(netmgr, ctl_default_prop);
+# Modify hostapd config file
+allow netmgr hostapd_data_file:file rw_file_perms;
+allow netmgr hostapd_data_file:dir rw_dir_perms;
+# Assign addresses to new interfaces as hostapd brings them up
+allow netmgr self:capability { net_raw net_admin };
+allow netmgr self:udp_socket { ioctl };
+allowxperm netmgr self:udp_socket ioctl { SIOCSIFADDR SIOCSIFNETMASK SIOCSIFBRDADDR };
+# Allow netmgr to run iptables to block and unblock network traffic
+allow netmgr system_file:file execute_no_trans;
+allow netmgr system_file:file lock;
diff --git a/vendor.mk b/vendor.mk
index 37b170a..aa2db85 100644
--- a/vendor.mk
+++ b/vendor.mk
@@ -123,6 +123,7 @@ PRODUCT_PACKAGES += \
hostapd \
hostapd_nohidl \
ipv6proxy \
+ netmgr \
wpa_supplicant \
PRODUCT_COPY_FILES += \
diff --git a/wifi/init.wifi.sh b/wifi/init.wifi.sh
index 9c08654..eb84fd0 100755
--- a/wifi/init.wifi.sh
+++ b/wifi/init.wifi.sh
@@ -50,6 +50,19 @@ rm -rf /data/vendor/var/run/netns/${NAMESPACE}.pid
/system/bin/ip link set dev wlan0 address 02:00:00:44:55:66
createns ${NAMESPACE}
+
+# If this is a clean boot we need to copy the hostapd configuration file to the
+# data partition where netmgr can change it if needed. If it already exists we
+# need to preserve the existing settings.
+if [ ! -f /data/vendor/wifi/hostapd/hostapd.conf ]; then
+ cp /vendor/etc/simulated_hostapd.conf /data/vendor/wifi/hostapd/hostapd.conf
+ chown wifi:wifi /data/vendor/wifi/hostapd/hostapd.conf
+ chmod 660 /data/vendor/wifi/hostapd/hostapd.conf
+fi
+# Start the network manager as soon as possible after the namespace is available.
+# This ensures that anything that follows is properly managed and monitored.
+setprop ctl.start netmgr
+
# createns will have created a file that contains the process id (pid) of a
# process running in the network namespace. This pid is needed for some commands
# to access the namespace.