Merge "[MultiDisplay] Convert the Android.mk to Android.bp"
diff --git a/data/etc/x86/emulatorip b/data/etc/x86/emulatorip
new file mode 100755
index 0000000..72541a3
--- /dev/null
+++ b/data/etc/x86/emulatorip
Binary files differ
diff --git a/dhcp/client/dhcpclient.cpp b/dhcp/client/dhcpclient.cpp
index 20dd143..0897b4c 100644
--- a/dhcp/client/dhcpclient.cpp
+++ b/dhcp/client/dhcpclient.cpp
@@ -50,8 +50,9 @@
     return inet_ntop(AF_INET, &addr, buffer, sizeof(buffer));
 }
 
-DhcpClient::DhcpClient()
-    : mRandomEngine(std::random_device()()),
+DhcpClient::DhcpClient(uint32_t options)
+    : mOptions(options),
+      mRandomEngine(std::random_device()()),
       mRandomDistribution(-kTimeoutSpan, kTimeoutSpan),
       mState(State::Init),
       mNextTimeout(kInitialTimeout),
@@ -419,45 +420,50 @@
         }
     }
 
-    res = mInterface.setAddress(mDhcpInfo.offeredAddress);
-    if (!res) {
-        ALOGE("Could not configure DHCP: %s", res.c_str());
-        return false;
-    }
-
-    res = mInterface.setSubnetMask(mDhcpInfo.subnetMask);
-    if (!res) {
-        ALOGE("Could not configure DHCP: %s", res.c_str());
-        return false;
-    }
-
-    res = mRouter.setDefaultGateway(mDhcpInfo.gateway, mInterface.getIndex());
-    if (!res) {
-        ALOGE("Could not configure DHCP: %s", res.c_str());
-        return false;
-    }
     char propName[64];
     snprintf(propName, sizeof(propName), "net.%s.gw",
              mInterface.getName().c_str());
-    property_set(propName, addrToStr(mDhcpInfo.gateway).c_str());
+    if (property_set(propName, addrToStr(mDhcpInfo.gateway).c_str()) != 0) {
+        ALOGE("Failed to set %s: %s", propName, strerror(errno));
+    }
 
     int numDnsEntries = sizeof(mDhcpInfo.dns) / sizeof(mDhcpInfo.dns[0]);
     for (int i = 0; i < numDnsEntries; ++i) {
         snprintf(propName, sizeof(propName), "net.%s.dns%d",
                  mInterface.getName().c_str(), i + 1);
         if (mDhcpInfo.dns[i] != 0) {
-            property_set(propName, addrToStr(mDhcpInfo.dns[i]).c_str());
+            if (property_set(propName,
+                             addrToStr(mDhcpInfo.dns[i]).c_str()) != 0) {
+                ALOGE("Failed to set %s: %s", propName, strerror(errno));
+            }
         } else {
             // Clear out any previous value here in case it was set
-            property_set(propName, "");
+            if (property_set(propName, "") != 0) {
+                ALOGE("Failed to clear %s: %s", propName, strerror(errno));
+            }
         }
     }
 
+    res = mInterface.setAddress(mDhcpInfo.offeredAddress,
+                                mDhcpInfo.subnetMask);
+    if (!res) {
+        ALOGE("Could not configure DHCP: %s", res.c_str());
+        return false;
+    }
+
+    if ((mOptions & static_cast<uint32_t>(ClientOption::NoGateway)) == 0) {
+        res = mRouter.setDefaultGateway(mDhcpInfo.gateway,
+                                        mInterface.getIndex());
+        if (!res) {
+            ALOGE("Could not configure DHCP: %s", res.c_str());
+            return false;
+        }
+    }
     return true;
 }
 
 void DhcpClient::haltNetwork() {
-    Result res = mInterface.setAddress(0);
+    Result res = mInterface.setAddress(0, 0);
     if (!res) {
         ALOGE("Could not halt network: %s", res.c_str());
     }
diff --git a/dhcp/client/dhcpclient.h b/dhcp/client/dhcpclient.h
index 718f4c9..128d416 100644
--- a/dhcp/client/dhcpclient.h
+++ b/dhcp/client/dhcpclient.h
@@ -27,10 +27,16 @@
 
 #include <random>
 
+// Options to configure the behavior of the DHCP client.
+enum class ClientOption : uint32_t {
+    NoGateway = (1 << 0),   // Do not configure the system's default gateway
+};
 
 class DhcpClient {
 public:
-    DhcpClient();
+    // Create a DHCP client with the given |options|. These options are values
+    // from the ClientOption enum.
+    explicit DhcpClient(uint32_t options);
 
     // Initialize the DHCP client to listen on |interfaceName|.
     Result init(const char* interfaceName);
@@ -72,6 +78,7 @@
                 uint16_t sourcePort, uint16_t destinationPort,
                 const uint8_t* data, size_t size);
 
+    uint32_t mOptions;
     std::mt19937 mRandomEngine; // Mersenne Twister RNG
     std::uniform_int_distribution<int> mRandomDistribution;
 
diff --git a/dhcp/client/interface.cpp b/dhcp/client/interface.cpp
index 805fa52..a13af08 100644
--- a/dhcp/client/interface.cpp
+++ b/dhcp/client/interface.cpp
@@ -16,13 +16,26 @@
 
 #include "interface.h"
 
+#include "netlink.h"
+
 #include <errno.h>
 #include <linux/if.h>
 #include <linux/if_ether.h>
 #include <linux/route.h>
+#include <linux/rtnetlink.h>
 #include <string.h>
 #include <unistd.h>
 
+in_addr_t broadcastFromNetmask(in_addr_t address, in_addr_t netmask) {
+    // The broadcast address is the address with the bits excluded in the
+    // netmask set to 1. For example if address = 10.0.2.15 and netmask is
+    // 255.255.255.0 then the broadcast is 10.0.2.255. If instead netmask was
+    // 255.0.0.0.0 then the broadcast would be 10.255.255.255
+    //
+    // Simply set all the lower bits to 1 and that should do it.
+    return address | (~netmask);
+}
+
 Interface::Interface() : mSocketFd(-1) {
 }
 
@@ -40,7 +53,7 @@
         return Result::error("Interface initialized more than once");
     }
 
-    mSocketFd = ::socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_IP);
+    mSocketFd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
     if (mSocketFd == -1) {
         return Result::error("Failed to create interface socket for '%s': %s",
                              interfaceName, strerror(errno));
@@ -61,7 +74,7 @@
         return res;
     }
 
-    res = setAddress(0);
+    res = setAddress(0, 0);
     if (!res) {
         return res;
     }
@@ -93,37 +106,65 @@
     return Result::success();
 }
 
-Result Interface::setAddress(in_addr_t address) {
-    struct ifreq request = createRequest();
+Result Interface::setAddress(in_addr_t address, in_addr_t subnetMask) {
+    struct Request {
+        struct nlmsghdr hdr;
+        struct ifaddrmsg msg;
+        char buf[256];
+    } request;
 
-    auto requestAddr = reinterpret_cast<struct sockaddr_in*>(&request.ifr_addr);
-    requestAddr->sin_family = AF_INET;
-    requestAddr->sin_port = 0;
-    requestAddr->sin_addr.s_addr = address;
+    memset(&request, 0, sizeof(request));
 
-    int status = ::ioctl(mSocketFd, SIOCSIFADDR, &request);
-    if (status != 0) {
-        return Result::error("Failed to set interface address for '%s': %s",
-                             mInterfaceName.c_str(), strerror(errno));
+    request.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
+    request.hdr.nlmsg_type = RTM_NEWADDR;
+    request.hdr.nlmsg_flags = NLM_F_REQUEST |
+                              NLM_F_ACK |
+                              NLM_F_CREATE |
+                              NLM_F_REPLACE;
+
+    request.msg.ifa_family = AF_INET;
+    // Count the number of bits in the subnet mask, this is the length.
+    request.msg.ifa_prefixlen = __builtin_popcount(subnetMask);
+    request.msg.ifa_index = mIndex;
+
+    addRouterAttribute(request, IFA_ADDRESS, &address, sizeof(address));
+    addRouterAttribute(request, IFA_LOCAL, &address, sizeof(address));
+    in_addr_t broadcast = broadcastFromNetmask(address, subnetMask);
+    addRouterAttribute(request, IFA_BROADCAST, &broadcast, sizeof(broadcast));
+
+    struct sockaddr_nl nlAddr;
+    memset(&nlAddr, 0, sizeof(nlAddr));
+    nlAddr.nl_family = AF_NETLINK;
+
+    int status = ::sendto(mSocketFd, &request, request.hdr.nlmsg_len, 0,
+                          reinterpret_cast<sockaddr*>(&nlAddr),
+                          sizeof(nlAddr));
+    if (status == -1) {
+        return Result::error("Unable to set interface address: %s",
+                             strerror(errno));
     }
-
-    return Result::success();
-}
-
-Result Interface::setSubnetMask(in_addr_t subnetMask) {
-    struct ifreq request = createRequest();
-
-    auto addr = reinterpret_cast<struct sockaddr_in*>(&request.ifr_addr);
-    addr->sin_family = AF_INET;
-    addr->sin_port = 0;
-    addr->sin_addr.s_addr = subnetMask;
-
-    int status = ::ioctl(mSocketFd, SIOCSIFNETMASK, &request);
-    if (status != 0) {
-        return Result::error("Failed to set subnet mask for '%s': %s",
-                             mInterfaceName.c_str(), strerror(errno));
+    char buffer[8192];
+    status = ::recv(mSocketFd, buffer, sizeof(buffer), 0);
+    if (status < 0) {
+        return Result::error("Unable to read netlink response: %s",
+                             strerror(errno));
     }
-
+    size_t responseSize = static_cast<size_t>(status);
+    if (responseSize < sizeof(nlmsghdr)) {
+        return Result::error("Received incomplete response from netlink");
+    }
+    auto response = reinterpret_cast<const nlmsghdr*>(buffer);
+    if (response->nlmsg_type == NLMSG_ERROR) {
+        if (responseSize < NLMSG_HDRLEN + sizeof(nlmsgerr)) {
+            return Result::error("Recieved an error from netlink but the "
+                                 "response was incomplete");
+        }
+        auto err = reinterpret_cast<const nlmsgerr*>(NLMSG_DATA(response));
+        if (err->error) {
+            return Result::error("Could not set interface address: %s",
+                                 strerror(-err->error));
+        }
+    }
     return Result::success();
 }
 
diff --git a/dhcp/client/interface.h b/dhcp/client/interface.h
index b7f549a..ca9e9e5 100644
--- a/dhcp/client/interface.h
+++ b/dhcp/client/interface.h
@@ -40,8 +40,7 @@
     Result bringUp();
     Result bringDown();
     Result setMtu(uint16_t mtu);
-    Result setAddress(in_addr_t address);
-    Result setSubnetMask(in_addr_t subnetMask);
+    Result setAddress(in_addr_t address, in_addr_t subnetMask);
 
 private:
     struct ifreq createRequest() const;
diff --git a/dhcp/client/main.cpp b/dhcp/client/main.cpp
index f4a3ea9..70b854f 100644
--- a/dhcp/client/main.cpp
+++ b/dhcp/client/main.cpp
@@ -18,7 +18,9 @@
 #include "log.h"
 
 static void usage(const char* program) {
-    ALOGE("Usage: %s -i <interface>", program);
+    ALOGE("Usage: %s [--no-gateway] -i <interface>", program);
+    ALOGE("  If the optional parameter --no-gateway is specified the client");
+    ALOGE("  will not configure the default gateway of the system.");
 }
 
 int main(int argc, char* argv[]) {
@@ -27,6 +29,7 @@
         return 1;
     }
     const char* interfaceName = nullptr;
+    uint32_t options = 0;
 
     for (int i = 1; i < argc; ++i) {
         if (strcmp(argv[i], "-i") == 0) {
@@ -37,6 +40,8 @@
                 usage(argv[0]);
                 return 1;
             }
+        } else if (strcmp(argv[i], "--no-gateway") == 0) {
+            options |= static_cast<uint32_t>(ClientOption::NoGateway);
         } else {
             ALOGE("ERROR: unknown parameters %s", argv[i]);
             usage(argv[0]);
@@ -49,7 +54,7 @@
         return 1;
     }
 
-    DhcpClient client;
+    DhcpClient client(options);
     Result res = client.init(interfaceName);
     if (!res) {
         ALOGE("Failed to initialize DHCP client: %s\n", res.c_str());
diff --git a/dhcp/client/netlink.h b/dhcp/client/netlink.h
new file mode 100644
index 0000000..e0c916f
--- /dev/null
+++ b/dhcp/client/netlink.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <linux/rtnetlink.h>
+
+template<class Request>
+inline void addRouterAttribute(Request& r,
+                               int type,
+                               const void* data,
+                               size_t size) {
+    // Calculate the offset into the character buffer where the RTA data lives
+    // We use offsetof on the buffer to get it. This avoids undefined behavior
+    // by casting the buffer (which is safe because it's char) instead of the
+    // Request struct.(which is undefined because of aliasing)
+    size_t offset = NLMSG_ALIGN(r.hdr.nlmsg_len) - offsetof(Request, buf);
+    auto attr = reinterpret_cast<struct rtattr*>(r.buf + offset);
+    attr->rta_type = type;
+    attr->rta_len = RTA_LENGTH(size);
+    memcpy(RTA_DATA(attr), data, size);
+
+    // Update the message length to include the router attribute.
+    r.hdr.nlmsg_len = NLMSG_ALIGN(r.hdr.nlmsg_len) + RTA_ALIGN(attr->rta_len);
+}
diff --git a/dhcp/client/router.cpp b/dhcp/client/router.cpp
index 9b9f17d..7c87e2d 100644
--- a/dhcp/client/router.cpp
+++ b/dhcp/client/router.cpp
@@ -16,31 +16,14 @@
 
 #include "router.h"
 
+#include "netlink.h"
+
 #include <linux/rtnetlink.h>
 
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
 
-template<class Request>
-static void addRouterAttribute(Request& r,
-                               int type,
-                               const void* data,
-                               size_t size) {
-    // Calculate the offset into the character buffer where the RTA data lives
-    // We use offsetof on the buffer to get it. This avoids undefined behavior
-    // by casting the buffer (which is safe because it's char) instead of the
-    // Request struct.(which is undefined because of aliasing)
-    size_t offset = NLMSG_ALIGN(r.hdr.nlmsg_len) - offsetof(Request, buf);
-    auto attr = reinterpret_cast<struct rtattr*>(r.buf + offset);
-    attr->rta_type = type;
-    attr->rta_len = RTA_LENGTH(size);
-    memcpy(RTA_DATA(attr), data, size);
-
-    // Update the message length to include the router attribute.
-    r.hdr.nlmsg_len = NLMSG_ALIGN(r.hdr.nlmsg_len) + RTA_ALIGN(attr->rta_len);
-}
-
 Router::Router() : mSocketFd(-1) {
 }
 
diff --git a/dhcp/server/Android.mk b/dhcp/server/Android.mk
deleted file mode 100644
index 38234de..0000000
--- a/dhcp/server/Android.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-	dhcpserver.cpp \
-	main.cpp \
-	../common/message.cpp \
-	../common/socket.cpp \
-	../common/utils.cpp \
-
-
-LOCAL_CPPFLAGS += -Werror
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/../common
-LOCAL_SHARED_LIBRARIES := libcutils liblog
-LOCAL_PROPRIETARY_MODULE := true
-LOCAL_MODULE := dhcpserver
-
-LOCAL_MODULE_CLASS := EXECUTABLES
-
-include $(BUILD_EXECUTABLE)
-
diff --git a/dhcp/server/dhcpserver.cpp b/dhcp/server/dhcpserver.cpp
deleted file mode 100644
index d6d4a7b..0000000
--- a/dhcp/server/dhcpserver.cpp
+++ /dev/null
@@ -1,390 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#include "dhcpserver.h"
-
-#include "dhcp.h"
-#include "log.h"
-#include "message.h"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <linux/sockios.h>
-#include <net/if.h>
-#include <netinet/in.h>
-#include <poll.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include <cutils/properties.h>
-
-static const int kMaxDnsServers = 4;
-
-DhcpServer::DhcpServer(unsigned int excludeInterface) :
-    mExcludeInterface(excludeInterface)
-{
-}
-
-Result DhcpServer::init() {
-    Result res = mSocket.open(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
-    if (!res) {
-        return res;
-    }
-    res = mSocket.enableOption(SOL_IP, IP_PKTINFO);
-    if (!res) {
-        return res;
-    }
-    res = mSocket.enableOption(SOL_SOCKET, SO_BROADCAST);
-    if (!res) {
-        return res;
-    }
-
-    res = mSocket.bindIp(INADDR_ANY, PORT_BOOTP_SERVER);
-    if (!res) {
-        return res;
-    }
-
-    return Result::success();
-}
-
-Result DhcpServer::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, originalMask;
-    int status = ::sigfillset(&blockMask);
-    if (status != 0) {
-        return Result::error("Unable to fill signal set: %s", strerror(errno));
-    }
-    status = ::sigprocmask(SIG_SETMASK, &blockMask, &originalMask);
-    if (status != 0) {
-        return Result::error("Unable to set signal mask: %s", strerror(errno));
-    }
-
-    struct pollfd fds;
-    fds.fd = mSocket.get();
-    fds.events = POLLIN;
-    Message message;
-    while ((status = ::ppoll(&fds, 1, nullptr, &originalMask)) >= 0) {
-        if (status == 0) {
-            // Timeout
-            continue;
-        }
-
-        unsigned int interfaceIndex = 0;
-        Result res = mSocket.receiveFromInterface(&message,
-                                                  &interfaceIndex);
-        if (!res) {
-            ALOGE("Failed to recieve on socket: %s", res.c_str());
-            continue;
-        }
-        if (interfaceIndex == 0 || mExcludeInterface == interfaceIndex) {
-            // Received packet on unknown or unwanted interface, drop it
-            continue;
-        }
-        if (!message.isValidDhcpMessage(OP_BOOTREQUEST)) {
-            // Not a DHCP request, drop it
-            continue;
-        }
-        switch (message.type()) {
-            case DHCPDISCOVER:
-                // Someone is trying to find us, let them know we exist
-                sendDhcpOffer(message, interfaceIndex);
-                break;
-            case DHCPREQUEST:
-                // Someone wants a lease based on an offer
-                if (isValidDhcpRequest(message, interfaceIndex)) {
-                    // The request matches our offer, acknowledge it
-                    sendAck(message, interfaceIndex);
-                } else {
-                    // Request for something other than we offered, denied
-                    sendNack(message, interfaceIndex);
-                }
-                break;
-        }
-    }
-    // Polling failed, exit
-    return Result::error("Polling failed: %s", strerror(errno));
-}
-
-Result DhcpServer::sendMessage(unsigned int interfaceIndex,
-                               in_addr_t /*sourceAddress*/,
-                               const Message& message) {
-    return mSocket.sendOnInterface(interfaceIndex,
-                                   INADDR_BROADCAST,
-                                   PORT_BOOTP_CLIENT,
-                                   message);
-}
-
-void DhcpServer::sendDhcpOffer(const Message& message,
-                               unsigned int interfaceIndex ) {
-    updateDnsServers();
-    in_addr_t offerAddress;
-    in_addr_t netmask;
-    in_addr_t gateway;
-    Result res = getOfferAddress(interfaceIndex,
-                                 message.dhcpData.chaddr,
-                                 &offerAddress,
-                                 &netmask,
-                                 &gateway);
-    if (!res) {
-        ALOGE("Failed to get address for offer: %s", res.c_str());
-        return;
-    }
-    in_addr_t serverAddress;
-    res = getInterfaceAddress(interfaceIndex, &serverAddress);
-    if (!res) {
-        ALOGE("Failed to get address for interface %u: %s",
-              interfaceIndex, res.c_str());
-        return;
-    }
-
-    Message offer = Message::offer(message,
-                                   serverAddress,
-                                   offerAddress,
-                                   netmask,
-                                   gateway,
-                                   mDnsServers.data(),
-                                   mDnsServers.size());
-    res = sendMessage(interfaceIndex, serverAddress, offer);
-    if (!res) {
-        ALOGE("Failed to send DHCP offer: %s", res.c_str());
-    }
-}
-
-void DhcpServer::sendAck(const Message& message, unsigned int interfaceIndex) {
-    updateDnsServers();
-    in_addr_t offerAddress;
-    in_addr_t netmask;
-    in_addr_t gateway;
-    in_addr_t serverAddress;
-    Result res = getOfferAddress(interfaceIndex,
-                                 message.dhcpData.chaddr,
-                                 &offerAddress,
-                                 &netmask,
-                                 &gateway);
-    if (!res) {
-        ALOGE("Failed to get address for offer: %s", res.c_str());
-        return;
-    }
-    res = getInterfaceAddress(interfaceIndex, &serverAddress);
-    if (!res) {
-        ALOGE("Failed to get address for interface %u: %s",
-              interfaceIndex, res.c_str());
-        return;
-    }
-    Message ack = Message::ack(message,
-                               serverAddress,
-                               offerAddress,
-                               netmask,
-                               gateway,
-                               mDnsServers.data(),
-                               mDnsServers.size());
-    res = sendMessage(interfaceIndex, serverAddress, ack);
-    if (!res) {
-        ALOGE("Failed to send DHCP ack: %s", res.c_str());
-    }
-}
-
-void DhcpServer::sendNack(const Message& message, unsigned int interfaceIndex) {
-    in_addr_t serverAddress;
-    Result res = getInterfaceAddress(interfaceIndex, &serverAddress);
-    if (!res) {
-        ALOGE("Failed to get address for interface %u: %s",
-              interfaceIndex, res.c_str());
-        return;
-    }
-    Message nack = Message::nack(message, serverAddress);
-    res = sendMessage(interfaceIndex, serverAddress, nack);
-    if (!res) {
-        ALOGE("Failed to send DHCP nack: %s", res.c_str());
-    }
-}
-
-bool DhcpServer::isValidDhcpRequest(const Message& message,
-                                    unsigned int interfaceIndex) {
-    in_addr_t offerAddress;
-    in_addr_t netmask;
-    in_addr_t gateway;
-    Result res = getOfferAddress(interfaceIndex,
-                                 message.dhcpData.chaddr,
-                                 &offerAddress,
-                                 &netmask,
-                                 &gateway);
-    if (!res) {
-        ALOGE("Failed to get address for offer: %s", res.c_str());
-        return false;
-    }
-    if (message.requestedIp() != offerAddress) {
-        ALOGE("Client requested a different IP address from the offered one");
-        return false;
-    }
-    return true;
-}
-
-void DhcpServer::updateDnsServers() {
-    char key[64];
-    char value[PROPERTY_VALUE_MAX];
-    mDnsServers.clear();
-    for (int i = 1; i <= kMaxDnsServers; ++i) {
-        snprintf(key, sizeof(key), "net.eth0.dns%d", i);
-        if (property_get(key, value, nullptr) > 0) {
-            struct in_addr address;
-            if (::inet_pton(AF_INET, value, &address) > 0) {
-                mDnsServers.push_back(address.s_addr);
-            }
-        }
-    }
-}
-
-Result DhcpServer::getInterfaceData(unsigned int interfaceIndex,
-                                    unsigned long type,
-                                    struct ifreq* response) {
-    char interfaceName[IF_NAMESIZE + 1];
-    if (if_indextoname(interfaceIndex, interfaceName) == nullptr) {
-        return Result::error("Failed to get interface name for index %u: %s",
-                             interfaceIndex, strerror(errno));
-    }
-    memset(response, 0, sizeof(*response));
-    response->ifr_addr.sa_family = AF_INET;
-    strncpy(response->ifr_name, interfaceName, IFNAMSIZ - 1);
-
-    if (::ioctl(mSocket.get(), type, response) == -1) {
-        return Result::error("Failed to get data for interface %s: %s",
-                             interfaceName, strerror(errno));
-    }
-
-    return Result::success();
-}
-
-Result DhcpServer::getInterfaceAddress(unsigned int interfaceIndex,
-                                       in_addr_t* address) {
-    struct ifreq data;
-    Result res = getInterfaceData(interfaceIndex, SIOCGIFADDR, &data);
-    if (res.isSuccess()) {
-        auto inAddr = reinterpret_cast<struct sockaddr_in*>(&data.ifr_addr);
-        *address = inAddr->sin_addr.s_addr;
-    }
-    return res;
-}
-
-Result DhcpServer::getInterfaceNetmask(unsigned int interfaceIndex,
-                                       in_addr_t* address) {
-    struct ifreq data;
-    Result res = getInterfaceData(interfaceIndex, SIOCGIFNETMASK, &data);
-    if (res.isSuccess()) {
-        auto inAddr = reinterpret_cast<struct sockaddr_in*>(&data.ifr_addr);
-        *address = inAddr->sin_addr.s_addr;
-    }
-    return res;
-}
-
-static bool isValidHost(const in_addr_t address,
-                        const in_addr_t interfaceAddress,
-                        const in_addr_t netmask) {
-    // If the bits outside of the netmask are all zero it's a network address,
-    // don't use this.
-    bool isNetworkAddress = (address & ~netmask) == 0;
-    // If all bits outside of the netmask are set then it's a broadcast address,
-    // don't use this either.
-    bool isBroadcastAddress = (address & ~netmask) == ~netmask;
-    // Don't assign the interface address to a host
-    bool isInterfaceAddress = address == interfaceAddress;
-
-    return !isNetworkAddress && !isBroadcastAddress && !isInterfaceAddress;
-}
-
-static bool addressInRange(const in_addr_t address,
-                           const in_addr_t interfaceAddress,
-                           const in_addr_t netmask) {
-    if (address <= (interfaceAddress & netmask)) {
-        return false;
-    }
-    if (address >= (interfaceAddress | ~netmask)) {
-        return false;
-    }
-    return true;
-}
-
-Result DhcpServer::getOfferAddress(unsigned int interfaceIndex,
-                                   const uint8_t* macAddress,
-                                   in_addr_t* address,
-                                   in_addr_t* netmask,
-                                   in_addr_t* gateway) {
-    // The interface address will be the gateway and will be used to determine
-    // the range of valid addresses (along with the netmask) for the client.
-    in_addr_t interfaceAddress = 0;
-    Result res = getInterfaceAddress(interfaceIndex, &interfaceAddress);
-    if (!res) {
-        return res;
-    }
-    // The netmask of the interface will be the netmask for the client as well
-    // as used to determine network range.
-    in_addr_t mask = 0;
-    res = getInterfaceNetmask(interfaceIndex, &mask);
-    if (!res) {
-        return res;
-    }
-
-    // Assign these values now before they are modified below
-    *gateway = interfaceAddress;
-    *netmask = mask;
-
-    Lease key(interfaceIndex, macAddress);
-
-    // Find or create entry, if it's created it will be zero and we update it
-    in_addr_t& value = mLeases[key];
-    if (value == 0) {
-        // Addresses are stored in network byte order so when doing math on them
-        // they have to be converted to host byte order
-        interfaceAddress = ntohl(interfaceAddress);
-        mask = ntohl(mask);
-        // Get a reference to the offset so we can use it and increase it at the
-        // same time. If the entry does not exist it will be created with a
-        // value of zero.
-        in_addr_t& offset = mNextAddressOffsets[interfaceIndex];
-        if (offset == 0) {
-            // Increase if zero to avoid assigning network address
-            ++offset;
-        }
-        // Start out at the first address in the range as determined by netmask
-        in_addr_t nextAddress = (interfaceAddress & mask) + offset;
-
-        // Ensure the address is valid
-        while (!isValidHost(nextAddress, interfaceAddress, mask) &&
-               addressInRange(nextAddress, interfaceAddress, mask)) {
-            ++nextAddress;
-            ++offset;
-        }
-
-        if (addressInRange(nextAddress, interfaceAddress, mask)) {
-            // Convert back to network byte order
-            value = htonl(nextAddress);
-            ++offset;
-        } else {
-            // Ran out of addresses
-            return Result::error("DHCP server is out of addresses");
-        }
-    }
-    *address = value;
-    return Result::success();
-}
-
diff --git a/dhcp/server/dhcpserver.h b/dhcp/server/dhcpserver.h
deleted file mode 100644
index 276cd5b..0000000
--- a/dhcp/server/dhcpserver.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 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 "lease.h"
-#include "result.h"
-#include "socket.h"
-
-#include <netinet/in.h>
-#include <stdint.h>
-
-#include <unordered_map>
-#include <vector>
-
-class Message;
-
-class DhcpServer {
-public:
-    // Construct a DHCP server. Ignore any requests and discoveries coming on
-    // the network interface identified by |excludeInterface|.
-    explicit DhcpServer(unsigned int excludeInterface);
-
-    Result init();
-    Result run();
-
-private:
-    Result sendMessage(unsigned int interfaceIndex,
-                       in_addr_t sourceAddress,
-                       const Message& message);
-
-    void sendDhcpOffer(const Message& message, unsigned int interfaceIndex);
-    void sendAck(const Message& message, unsigned int interfaceIndex);
-    void sendNack(const Message& message, unsigned int interfaceIndex);
-
-    bool isValidDhcpRequest(const Message& message,
-                            unsigned int interfaceIndex);
-    void updateDnsServers();
-    Result getInterfaceData(unsigned int interfaceIndex,
-                            unsigned long type,
-                            struct ifreq* response);
-    Result getInterfaceAddress(unsigned int interfaceIndex,
-                               in_addr_t* address);
-    Result getInterfaceNetmask(unsigned int interfaceIndex,
-                               in_addr_t* netmask);
-    Result getOfferAddress(unsigned int interfaceIndex,
-                           const uint8_t* macAddress,
-                           in_addr_t* address,
-                           in_addr_t* netmask,
-                           in_addr_t* gateway);
-
-    Socket mSocket;
-    // This is the next address offset. This will be added to whatever the base
-    // address of the DHCP address range is. For each new MAC address seen this
-    // value will increase by one.
-    std::vector<in_addr_t> mDnsServers;
-    // Map a lease to an IP address for that lease
-    std::unordered_map<Lease, in_addr_t> mLeases;
-    std::unordered_map<unsigned int, in_addr_t> mNextAddressOffsets;
-    unsigned int mExcludeInterface;
-};
-
diff --git a/dhcp/server/lease.h b/dhcp/server/lease.h
deleted file mode 100644
index bad5614..0000000
--- a/dhcp/server/lease.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 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 <linux/if_ether.h>
-#include <stdint.h>
-
-#include <functional>
-
-// A lease consists of both the interface index and the MAC address. This
-// way the server can run on many different interfaces that have the same
-// client MAC address without giving out the same IP address. The reason
-// this is useful is because we might have several virtual interfaces, one
-// for each access point, that all have the same endpoint on the other side.
-// This endpoint would then have the same MAC address and always get the
-// same address. But for routing purposes it's useful to give it different
-// addresses depending on the server side interface. That way the routing
-// table can be set up so that packets are forwarded to the correct access
-// point interface based on IP address.
-struct Lease {
-    Lease(unsigned int interfaceIndex, const uint8_t* macAddress) {
-        InterfaceIndex = interfaceIndex;
-        memcpy(MacAddress, macAddress, sizeof(MacAddress));
-    }
-    unsigned int InterfaceIndex;
-    uint8_t MacAddress[ETH_ALEN];
-};
-
-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<Lease> {
-    size_t operator()(const Lease& lease) const {
-        size_t seed = 0;
-        hash_combine(seed, lease.InterfaceIndex);
-        // Treat the first 4 bytes as an uint32_t to save some computation
-        hash_combine(seed, *reinterpret_cast<const uint32_t*>(lease.MacAddress));
-        // And the remaining 2 bytes as an uint16_t
-        hash_combine(seed,
-                     *reinterpret_cast<const uint16_t*>(lease.MacAddress + 4));
-        return seed;
-    }
-};
-}
-
-inline bool operator==(const Lease& left, const Lease& right) {
-    return left.InterfaceIndex == right.InterfaceIndex &&
-        memcmp(left.MacAddress, right.MacAddress, sizeof(left.MacAddress)) == 0;
-}
diff --git a/dhcp/server/main.cpp b/dhcp/server/main.cpp
deleted file mode 100644
index 482ffd6..0000000
--- a/dhcp/server/main.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#include "dhcpserver.h"
-
-#include "log.h"
-
-#include <arpa/inet.h>
-#include <net/if.h>
-
-static void usage(const char* program) {
-    ALOGE("Usage: %s -i <interface> -r <", program);
-}
-
-int main(int argc, char* argv[]) {
-    char* excludeInterfaceName = nullptr;
-    unsigned int excludeInterfaceIndex = 0;
-    for (int i = 1; i < argc; ++i) {
-        if (strcmp("--exclude-interface", argv[i]) == 0) {
-            if (i + 1 >= argc) {
-                ALOGE("ERROR: Missing argument to "
-                     "--exclude-interfaces parameter");
-                usage(argv[0]);
-                return 1;
-            }
-            excludeInterfaceName = argv[i + 1];
-            excludeInterfaceIndex = if_nametoindex(excludeInterfaceName);
-            if (excludeInterfaceIndex == 0) {
-                ALOGE("ERROR: Invalid argument '%s' to --exclude-interface",
-                     argv[i + 1]);
-                usage(argv[0]);
-                return 1;
-            }
-        }
-    }
-
-    DhcpServer server(excludeInterfaceIndex);
-    Result res = server.init();
-    if (!res) {
-        ALOGE("Failed to initialize DHCP server: %s\n", res.c_str());
-        return 1;
-    }
-
-    res = server.run();
-    if (!res) {
-        ALOGE("DHCP server failed: %s\n", res.c_str());
-        return 1;
-    }
-    // This is weird and shouldn't happen, the server should run forever.
-    return 0;
-}
-
-
diff --git a/init.ranchu.rc b/init.ranchu.rc
index 36ae4d8..da0f0db 100644
--- a/init.ranchu.rc
+++ b/init.ranchu.rc
@@ -20,6 +20,7 @@
     mkdir /data/vendor/var 0755 root root
     mkdir /data/vendor/var/run 0755 root root
     mkdir /data/vendor/var/run/netns 0755 root root
+    start ranchu-net
 
 on zygote-start
     # Create the directories used by the Wireless subsystem
@@ -67,33 +68,32 @@
     user root
     group root wakelock wifi
     oneshot
-
-service ipv6proxy /vendor/bin/execns router /vendor/bin/ipv6proxy -o eth0 -i wlan1,radio0-peer
-    user root
-    group root
-    disabled
+    disabled    # Started on post-fs-data
 
 service emu_hostapd /vendor/bin/execns router /vendor/bin/hostapd_nohidl /data/vendor/wifi/hostapd/hostapd.conf
     user root
     group root wifi net_raw net_admin
     disabled
 
-service dhcpserver /vendor/bin/execns router /vendor/bin/dhcpserver --exclude-interface eth0
-    user root
-    group root
-    disabled
-
-service netmgr /vendor/bin/execns router /vendor/bin/netmgr --if-prefix wlan1_ --network 192.168.232.9/29
+service netmgr /vendor/bin/execns router /vendor/bin/netmgr --if-prefix wlan1 --bridge eth0,radio0-peer
     user root
     group root wifi
     disabled
 
-service dhcpclient_rtr /vendor/bin/execns router /vendor/bin/dhcpclient -i eth0
+service wifi_forwarder /vendor/bin/wifi_forwarder
+    user root
+    group root wifi
+    disabled
+
+service dhcpclient_rtr /vendor/bin/dhcpclient -i radio0 --no-gateway
     user root
     group root
     disabled
 
-service dhcpclient_def /vendor/bin/dhcpclient -i eth0
+on property:vendor.network.bridged=1
+    start dhcpclient_rtr
+
+service dhcpclient_def /vendor/bin/dhcpclient -i eth0 --no-gateway
     user root
     group root
     disabled
diff --git a/input/qwerty2.idc b/input/qwerty2.idc
index 711dea7..ca41665 100644
--- a/input/qwerty2.idc
+++ b/input/qwerty2.idc
@@ -15,9 +15,14 @@
 #
 # Emulator keyboard configuration file #2.
 #
+touch.deviceType = touchScreen
+touch.orientationAware = 1
 
 keyboard.layout = qwerty
 keyboard.characterMap = qwerty2
 keyboard.orientationAware = 0
 keyboard.builtIn = 1
 
+cursor.mode = navigation
+cursor.orientationAware = 1
+
diff --git a/network/netmgr/Android.bp b/network/netmgr/Android.bp
index 6686f98..7530c91 100644
--- a/network/netmgr/Android.bp
+++ b/network/netmgr/Android.bp
@@ -22,14 +22,15 @@
              "-Werror",
             ],
     srcs: [
-           "address_assigner.cpp",
+           "bridge.cpp",
+           "bridge_builder.cpp",
            "commander.cpp",
-           "fork.cpp",
            "interface_state.cpp",
            "log.cpp",
            "main.cpp",
            "monitor.cpp",
            "poller.cpp",
+           "utils.cpp",
            "wifi_forwarder.cpp",
            "commands/wifi_command.cpp",
           ],
diff --git a/network/netmgr/address_assigner.cpp b/network/netmgr/address_assigner.cpp
deleted file mode 100644
index 11df81d..0000000
--- a/network/netmgr/address_assigner.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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
deleted file mode 100644
index dde42ee..0000000
--- a/network/netmgr/address_assigner.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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/bridge.cpp b/network/netmgr/bridge.cpp
new file mode 100644
index 0000000..adbe21e
--- /dev/null
+++ b/network/netmgr/bridge.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2019, 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 "bridge.h"
+
+#include "log.h"
+
+#include <errno.h>
+#include <net/if.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+Bridge::Bridge(const std::string& bridgeName) : mBridgeName(bridgeName) {
+}
+
+Bridge::~Bridge() {
+    if (mSocketFd != -1) {
+        ::close(mSocketFd);
+        mSocketFd = -1;
+    }
+}
+
+Result Bridge::init() {
+    Result res = createSocket();
+    if (!res) {
+        return res;
+    }
+    res = createBridge();
+    if (!res) {
+        return res;
+    }
+    return Result::success();
+}
+
+Result Bridge::addInterface(const std::string& interfaceName) {
+    return doInterfaceOperation(interfaceName, SIOCBRADDIF, "add");
+}
+
+Result Bridge::removeInterface(const std::string& interfaceName) {
+    return doInterfaceOperation(interfaceName, SIOCBRDELIF, "remove");
+}
+
+Result Bridge::createSocket() {
+    if (mSocketFd != -1) {
+        return Result::error("Bridge already initialized");
+    }
+    mSocketFd = ::socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    if (mSocketFd < 0) {
+        return Result::error("Unable to create socket for bridge: %s",
+                             strerror(errno));
+    }
+    return Result::success();
+}
+
+Result Bridge::createBridge() {
+    int res = ::ioctl(mSocketFd, SIOCBRADDBR, mBridgeName.c_str());
+    if (res < 0) {
+        // If the bridge already exists we just keep going, that's fine.
+        // Otherwise something went wrong.
+        if (errno != EEXIST) {
+            return Result::error("Cannot create bridge %s: %s",
+                                 mBridgeName.c_str(), strerror(errno));
+        }
+    }
+
+    struct ifreq request;
+    memset(&request, 0, sizeof(request));
+    // Determine interface index of bridge
+    request.ifr_ifindex = if_nametoindex(mBridgeName.c_str());
+    if (request.ifr_ifindex == 0) {
+        return Result::error("Unable to get bridge %s interface index",
+                             mBridgeName.c_str());
+    }
+    // Get bridge interface flags
+    strlcpy(request.ifr_name, mBridgeName.c_str(), sizeof(request.ifr_name));
+    res = ::ioctl(mSocketFd, SIOCGIFFLAGS, &request);
+    if (res != 0) {
+        return Result::error("Unable to get interface flags for bridge %s: %s",
+                             mBridgeName.c_str(), strerror(errno));
+    }
+
+    if ((request.ifr_flags & IFF_UP) != 0) {
+        // Bridge is already up, it's ready to go
+        return Result::success();
+    }
+
+    // Bridge is not up, it needs to be up to work
+    request.ifr_flags |= IFF_UP;
+    res = ::ioctl(mSocketFd, SIOCSIFFLAGS, &request);
+    if (res != 0) {
+        return Result::error("Unable to set interface flags for bridge %s: %s",
+                             strerror(errno));
+    }
+
+    return Result::success();
+}
+
+Result Bridge::doInterfaceOperation(const std::string& interfaceName,
+                                    unsigned long operation,
+                                    const char* operationName) {
+    struct ifreq request;
+    memset(&request, 0, sizeof(request));
+
+    request.ifr_ifindex = if_nametoindex(interfaceName.c_str());
+    if (request.ifr_ifindex == 0) {
+        return Result::error("Bridge unable to %s interface '%s', no such "
+                             "interface", operationName, interfaceName.c_str());
+    }
+    strlcpy(request.ifr_name, mBridgeName.c_str(), sizeof(request.ifr_name));
+    int res = ::ioctl(mSocketFd, operation, &request);
+    // An errno of EBUSY most likely indicates that the interface is already
+    // part of the bridge. Ignore this.
+    if (res < 0 && errno != EBUSY) {
+        return Result::error("Bridge unable to %s interface '%s': %s",
+                             operationName, interfaceName.c_str(),
+                             strerror(errno));
+    }
+    return Result::success();
+}
diff --git a/network/netmgr/bridge.h b/network/netmgr/bridge.h
new file mode 100644
index 0000000..fc7ff51
--- /dev/null
+++ b/network/netmgr/bridge.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019, 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"
+
+#include <string>
+
+class Bridge {
+public:
+    explicit Bridge(const std::string& bridgeName);
+    ~Bridge();
+
+    Result init();
+
+    Result addInterface(const std::string& interfaceName);
+    Result removeInterface(const std::string& interfaceName);
+private:
+    Result createSocket();
+    Result createBridge();
+    Result doInterfaceOperation(const std::string& interfaceName,
+                                unsigned long operation,
+                                const char* operationName);
+
+    std::string mBridgeName;
+    int mSocketFd = -1;
+};
diff --git a/network/netmgr/bridge_builder.cpp b/network/netmgr/bridge_builder.cpp
new file mode 100644
index 0000000..d772eae
--- /dev/null
+++ b/network/netmgr/bridge_builder.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019, 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 "bridge_builder.h"
+
+#include "bridge.h"
+#include "log.h"
+#include "result.h"
+
+BridgeBuilder::BridgeBuilder(Bridge& bridge, const char* interfacePrefix) :
+    mBridge(bridge),
+    mInterfacePrefix(interfacePrefix),
+    mPrefixLength(strlen(interfacePrefix)) {
+}
+
+void BridgeBuilder::onInterfaceState(unsigned int /*index*/,
+                                     const char* name,
+                                     InterfaceState state) {
+    if (strncmp(name, mInterfacePrefix, mPrefixLength) != 0) {
+        // Does not match our prefix, ignore it
+        return;
+    }
+
+    if (state == InterfaceState::Up) {
+        Result res = mBridge.addInterface(name);
+        if (!res) {
+            LOGE("%s", res.c_str());
+        }
+    }
+}
diff --git a/network/netmgr/bridge_builder.h b/network/netmgr/bridge_builder.h
new file mode 100644
index 0000000..e3d9580
--- /dev/null
+++ b/network/netmgr/bridge_builder.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019, 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 <stddef.h>
+
+class Bridge;
+
+class BridgeBuilder {
+public:
+    // Construct a bridge builder that will add any interface that comes up to
+    // |bridge| if the interface name begins with |interfacePrefix|.
+    BridgeBuilder(Bridge& bridge, const char* interfacePrefix);
+
+    void onInterfaceState(unsigned int index,
+                          const char* name,
+                          InterfaceState state);
+
+private:
+    Bridge& mBridge;
+    const char* mInterfacePrefix;
+    size_t mPrefixLength;
+};
diff --git a/network/netmgr/commands/wifi_command.cpp b/network/netmgr/commands/wifi_command.cpp
index 8f48a43..27bf95a 100644
--- a/network/netmgr/commands/wifi_command.cpp
+++ b/network/netmgr/commands/wifi_command.cpp
@@ -16,18 +16,13 @@
 
 #include "wifi_command.h"
 
-#include "../fork.h"
-#include "log.h"
+#include "../bridge.h"
+#include "../utils.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";
@@ -70,24 +65,9 @@
     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) {
+WifiCommand::WifiCommand(Bridge& bridge)
+    : mBridge(bridge)
+    , mLowestInterfaceNumber(1) {
     readConfig();
 }
 
@@ -103,7 +83,7 @@
         return Result::error("Empty wifi command");
     }
 
-    std::vector<std::string> subArgs = explode(divider + 1);
+    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'",
@@ -172,55 +152,6 @@
     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];
@@ -256,18 +187,18 @@
 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());
+        return Result::error("Unknown SSID '%s'", arguments[0].c_str());
     }
     interface->second.blocked = true;
-    return setBlocked(interface->second.ifName.c_str(), true);
+    return mBridge.removeInterface(interface->second.ifName.c_str());
 }
 
 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());
+        return Result::error("Unknown SSID '%s'", arguments[0].c_str());
     }
     interface->second.blocked = false;
-    return setBlocked(interface->second.ifName.c_str(), false);
+    return mBridge.addInterface(interface->second.ifName.c_str());
 }
 
diff --git a/network/netmgr/commands/wifi_command.h b/network/netmgr/commands/wifi_command.h
index f9ce96c..b47edb8 100644
--- a/network/netmgr/commands/wifi_command.h
+++ b/network/netmgr/commands/wifi_command.h
@@ -24,9 +24,11 @@
 #include <unordered_set>
 #include <vector>
 
+class Bridge;
+
 class WifiCommand : public Command {
 public:
-    WifiCommand();
+    explicit WifiCommand(Bridge& bridge);
     virtual ~WifiCommand() = default;
 
     Result onCommand(const char* command, const char* args) override;
@@ -34,7 +36,6 @@
     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);
@@ -48,6 +49,7 @@
         bool blocked;
     };
 
+    Bridge& mBridge;
     std::unordered_map<std::string, AccessPoint> mAccessPoints;
     std::unordered_set<std::string> mUsedInterfaces;
     int mLowestInterfaceNumber;
diff --git a/network/netmgr/interface_state.h b/network/netmgr/interface_state.h
index 5315b0f..6d56aba 100644
--- a/network/netmgr/interface_state.h
+++ b/network/netmgr/interface_state.h
@@ -21,3 +21,4 @@
     Down,
 };
 
+const char* interfaceStateToStr(InterfaceState state);
diff --git a/network/netmgr/main.cpp b/network/netmgr/main.cpp
index e4b43f6..d83de39 100644
--- a/network/netmgr/main.cpp
+++ b/network/netmgr/main.cpp
@@ -14,23 +14,28 @@
  * limitations under the License.
  */
 
-#include "address_assigner.h"
+#include "bridge.h"
+#include "bridge_builder.h"
 #include "commander.h"
 #include "commands/wifi_command.h"
 #include "log.h"
 #include "monitor.h"
 #include "poller.h"
+#include "utils.h"
 #include "wifi_forwarder.h"
 
 #include <arpa/inet.h>
 #include <netinet/in.h>
 
+#include <cutils/properties.h>
+
 #include <functional>
 
-static const char kWifiMonitorInterface[] = "hwsim0";
+static const char kBridgeName[] = "br0";
+static const char kNetworkBridgedProperty[] = "vendor.network.bridged";
 
 static void usage(const char* name) {
-    LOGE("Usage: %s --if-prefix <prefix> --network <ip/mask>", name);
+    LOGE("Usage: %s --if-prefix <prefix> --bridge <if1,if2,...>", 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.");
@@ -39,52 +44,27 @@
     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;
-    }
+static Result addBridgeInterfaces(Bridge& bridge, const char* interfaces) {
+    std::vector<std::string> ifNames = explode(interfaces, ',');
 
-    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;
+    for (const auto& ifName : ifNames) {
+        Result res = bridge.addInterface(ifName);
+        if (!res) {
+            return res;
+        }
     }
-
-    ++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;
+    return Result::success();
 }
 
 int main(int argc, char* argv[]) {
     const char* interfacePrefix = nullptr;
-    const char* network = nullptr;
+    const char* bridgeInterfaces = 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 if (strcmp(argv[i], "--bridge") == 0 && i + 1 < argc) {
+            bridgeInterfaces = argv[++i];
         } else {
             LOGE("Unknown parameter '%s'", argv[i]);
             usage(argv[0]);
@@ -95,30 +75,39 @@
     if (interfacePrefix == nullptr) {
         LOGE("Missing parameter --if-prefix");
     }
-    if (network == nullptr) {
-        LOGE("Missing parameter --network");
+    if (bridgeInterfaces == nullptr) {
+        LOGE("Missing parameter --bridge");
     }
-    if (network == nullptr || interfacePrefix == nullptr) {
+    if (interfacePrefix == nullptr || bridgeInterfaces == nullptr) {
         usage(argv[0]);
         return 1;
     }
 
-    in_addr_t address = 0;
-    uint32_t mask = 0;
-    if (!parseNetwork(network, &address, &mask)) {
+    Bridge bridge(kBridgeName);
+    Result res = bridge.init();
+    if (!res) {
+        LOGE("%s", res.c_str());
+        return 1;
+    }
+    res = addBridgeInterfaces(bridge, bridgeInterfaces);
+    if (!res) {
+        LOGE("%s", res.c_str());
         return 1;
     }
 
-    AddressAssigner assigner(interfacePrefix, address, mask);
+    BridgeBuilder bridgeBuilder(bridge, interfacePrefix);
+
+    property_set(kNetworkBridgedProperty, "1");
+
     Monitor monitor;
 
-    monitor.setOnInterfaceState(std::bind(&AddressAssigner::onInterfaceState,
-                                          &assigner,
+    monitor.setOnInterfaceState(std::bind(&BridgeBuilder::onInterfaceState,
+                                          &bridgeBuilder,
                                           std::placeholders::_1,
                                           std::placeholders::_2,
                                           std::placeholders::_3));
 
-    Result res = monitor.init();
+    res = monitor.init();
     if (!res) {
         LOGE("%s", res.c_str());
         return 1;
@@ -131,20 +120,13 @@
         return 1;
     }
 
-    WifiCommand wifiCommand;
+    WifiCommand wifiCommand(bridge);
     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
index 4ab111e..a9d248a 100644
--- a/network/netmgr/monitor.cpp
+++ b/network/netmgr/monitor.cpp
@@ -37,7 +37,11 @@
 }
 
 Result Monitor::init() {
-    return openSocket();
+    Result res = openSocket();
+    if (!res) {
+        return res;
+    }
+    return requestInterfaces();
 }
 
 void Monitor::setOnInterfaceState(OnInterfaceStateCallback callback) {
@@ -84,7 +88,7 @@
                 default:
                     break;
             }
-            NLMSG_NEXT(hdr, length);
+            hdr = NLMSG_NEXT(hdr, length);
         }
     }
 }
@@ -140,6 +144,36 @@
     return Result::success();
 }
 
+Result Monitor::requestInterfaces() {
+    if (mSocketFd == -1) {
+        return Result::error("Monitor not initialized yet");
+    }
+
+    struct {
+        struct nlmsghdr hdr;
+        struct rtgenmsg gen;
+    } request;
+
+    memset(&request, 0, sizeof(request));
+
+    request.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(request.gen));
+    request.hdr.nlmsg_type = RTM_GETLINK;
+    request.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+    request.hdr.nlmsg_seq = 1;
+    request.hdr.nlmsg_pid = getpid();
+    request.gen.rtgen_family = AF_PACKET;
+
+    ssize_t result = TEMP_FAILURE_RETRY(::send(mSocketFd,
+                                               &request,
+                                               request.hdr.nlmsg_len,
+                                               0));
+    if (result < 0) {
+        return Result::error("Failed to request interfaces: %s",
+                             strerror(errno));
+    }
+    return Result::success();
+}
+
 void Monitor::closeSocket() {
     if (mSocketFd != -1) {
         ::close(mSocketFd);
@@ -154,14 +188,23 @@
 
     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);
+    char name[IF_NAMESIZE + 1] = { 0 };
+    const unsigned int ifIndex = msg->ifi_index;
+    if (if_indextoname(ifIndex, name) == nullptr) {
+        LOGE("Unable to get interface name for interface index %u", ifIndex);
+    }
 
-        InterfaceState state = (msg->ifi_flags & IFF_UP) ? InterfaceState::Up :
-                                                           InterfaceState::Down;
-        mOnInterfaceStateCallback(msg->ifi_index, name, state);
+    bool isUp = !!(msg->ifi_flags & IFF_UP);
+    auto iterator = mUpInterfaces.find(ifIndex);
+
+    if (iterator == mUpInterfaces.end() && isUp) {
+        // The interface was not known to be up but is up, known state changed
+        mUpInterfaces.insert(ifIndex);
+        mOnInterfaceStateCallback(ifIndex, name, InterfaceState::Up);
+    } else if (iterator != mUpInterfaces.end() && !isUp) {
+        // The interface was known to be up, now it's not, known state changed
+        mUpInterfaces.erase(iterator);
+        mOnInterfaceStateCallback(ifIndex, name, InterfaceState::Down);
     }
 }
 
diff --git a/network/netmgr/monitor.h b/network/netmgr/monitor.h
index 3d2dc62..00f525e 100644
--- a/network/netmgr/monitor.h
+++ b/network/netmgr/monitor.h
@@ -20,7 +20,7 @@
 #include "pollable.h"
 #include "result.h"
 
-const char* interfaceStateToStr(InterfaceState state);
+#include <unordered_set>
 
 /** Monitor network interfaces and provide notifications of changes to those
  *  interfaces.
@@ -46,10 +46,12 @@
 
 private:
     Result openSocket();
+    Result requestInterfaces();
     void closeSocket();
     void handleNewLink(const struct nlmsghdr* hdr);
 
     int mSocketFd;
     OnInterfaceStateCallback mOnInterfaceStateCallback;
+    std::unordered_set<unsigned int> mUpInterfaces;
 };
 
diff --git a/network/netmgr/timestamp.cpp b/network/netmgr/timestamp.cpp
deleted file mode 100644
index 36c79ee..0000000
--- a/network/netmgr/timestamp.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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/utils.cpp b/network/netmgr/utils.cpp
new file mode 100644
index 0000000..fa71c1e
--- /dev/null
+++ b/network/netmgr/utils.cpp
@@ -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.
+ */
+
+#include "utils.h"
+
+#include <string.h>
+
+std::vector<std::string> explode(const char* str, char divider) {
+    const char* cur = str;
+    const char* space = nullptr;
+    std::vector<std::string> result;
+    do {
+        space = ::strchr(cur, divider);
+        if (space) {
+            result.emplace_back(cur, space);
+            cur = space + 1;
+        } else {
+            result.emplace_back(cur);
+        }
+    } while (space);
+
+    return result;
+}
+
diff --git a/network/netmgr/timestamp.h b/network/netmgr/utils.h
similarity index 73%
copy from network/netmgr/timestamp.h
copy to network/netmgr/utils.h
index 8ad7bf8..7c05882 100644
--- a/network/netmgr/timestamp.h
+++ b/network/netmgr/utils.h
@@ -16,18 +16,7 @@
 
 #pragma once
 
-#include <time.h>
+#include <string>
+#include <vector>
 
-class Timestamp {
-public:
-    Timestamp();
-
-    static Timestamp now();
-
-    bool operator==(const Timestamp& other) const;
-    bool operator<(const Timestamp& other) const;
-
-private:
-    struct timespec mTime;
-};
-
+std::vector<std::string> explode(const char* str, char divider);
diff --git a/network/wifi_forwarder/Android.bp b/network/wifi_forwarder/Android.bp
new file mode 100644
index 0000000..6dbc9ca
--- /dev/null
+++ b/network/wifi_forwarder/Android.bp
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2019 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: "wifi_forwarder",
+    vendor: true,
+    cppflags: [
+             "-Wall",
+             "-Werror",
+             "--std=c++17",
+            ],
+    srcs: [
+           "frame.cpp",
+           "local_connection.cpp",
+           "main.cpp",
+           "netlink_message.cpp",
+           "netlink_socket.cpp",
+           "poller.cpp",
+           "remote_connection.cpp",
+           "wifi_forwarder.cpp",
+          ],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libnl",
+        "libpcap",
+        "libutils",
+        "libutilscallstack",
+    ],
+    header_libs: [
+        "goldfish_headers",
+    ],
+}
diff --git a/network/wifi_forwarder/cache.h b/network/wifi_forwarder/cache.h
new file mode 100644
index 0000000..bd64f1a
--- /dev/null
+++ b/network/wifi_forwarder/cache.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2019, 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 <inttypes.h>
+#include "log.h"
+
+#include <chrono>
+#include <unordered_map>
+
+// Default amount of time until a cache entry expires
+static constexpr auto kDefaultCacheTimeout = std::chrono::seconds(30);
+
+template<typename Key,
+         typename Value,
+         typename Timestamp = std::chrono::steady_clock::time_point>
+class Cache {
+public:
+    using TimedValue = std::pair<Timestamp, Value>;
+
+    using MapType = std::unordered_map<Key, TimedValue>;
+    using key_type = typename MapType::key_type;
+    using mapped_type = Value;
+
+    class ConstIterator {
+    public:
+        class IterPair {
+        public:
+            IterPair(const Key& key, const Value& value) 
+                : first(key), second(value) {
+            }
+
+            const IterPair* operator->() const { return this; }
+            const Key& first;
+            const Value& second;
+        private:
+        };
+
+        ConstIterator(typename MapType::const_iterator current)
+            : mCurrent(current) { }
+
+        IterPair operator->() const {
+            return IterPair(mCurrent->first, mCurrent->second.second);
+        }
+
+        IterPair operator*() const {
+            return IterPair(mCurrent->first, mCurrent->second.second);
+        }
+
+        bool operator==(const ConstIterator& other) const {
+            return mCurrent == other.mCurrent;
+        }
+
+        bool operator!=(const ConstIterator& other) const {
+            return mCurrent != other.mCurrent;
+        }
+
+        typename MapType::const_iterator internal() const { return mCurrent; }
+
+    private:
+        typename MapType::const_iterator mCurrent;
+    };
+    class Iterator {
+    public:
+        class IterPair {
+        public:
+            IterPair(const Key& key, Value& value) : first(key), second(value) { }
+
+            IterPair* operator->() { return this; }
+            const Key& first;
+            Value& second;
+        private:
+        };
+
+        Iterator(typename MapType::iterator current) : mCurrent(current) { }
+
+        IterPair operator->() {
+            return IterPair(mCurrent->first, mCurrent->second.second);
+        }
+
+        IterPair operator*() {
+            return IterPair(mCurrent->first, mCurrent->second.second);
+        }
+
+        bool operator==(const Iterator& other) const {
+            return mCurrent == other.mCurrent;
+        }
+
+        bool operator!=(const Iterator& other) const {
+            return mCurrent != other.mCurrent;
+        }
+
+        typename MapType::iterator internal() { return mCurrent; }
+
+    private:
+        typename MapType::iterator mCurrent;
+    };
+
+    using iterator = Iterator;
+    using const_iterator = ConstIterator;
+    using insert_return_type = std::pair<const_iterator, bool>;
+
+    Cache(std::chrono::milliseconds timeout = kDefaultCacheTimeout)
+        : mTimeout(timeout) {
+    }
+
+    template<typename M>
+    insert_return_type insert_or_assign(const key_type& key, M&& value) {
+        std::pair<typename MapType::iterator,bool> inserted =
+            mMap.insert_or_assign(key, TimedValue(mCurrentTime,
+                                                  std::move(value)));
+        return insert_return_type(inserted.first, inserted.second);
+    }
+
+    mapped_type& operator[](const key_type& key) {
+        TimedValue& v = mMap[key];
+        v.first = mCurrentTime;
+        return v.second;
+    }
+
+    iterator find(const key_type& key) {
+        return iterator(mMap.find(key));
+    }
+
+    const_iterator find(const key_type& key) const {
+        return const_iterator(mMap.find(key));
+    }
+
+    iterator erase(const_iterator pos) {
+        return iterator(mMap.erase(pos.internal()));
+    }
+
+    iterator erase(iterator pos) {
+        return iterator(mMap.erase(pos.internal()));
+    }
+
+    size_t erase(const key_type& key) {
+        return mMap.erase(key);
+    }
+
+    iterator begin() {
+        return iterator(mMap.begin());
+    }
+
+    iterator end() {
+        return iterator(mMap.end());
+    }
+
+    const_iterator begin() const {
+        return const_iterator(mMap.begin());
+    }
+
+    const_iterator end() const {
+        return const_iterator(mMap.end());
+    }
+
+    void setCurrentTime(Timestamp currentTime) {
+        mCurrentTime = currentTime;
+    }
+
+    void expireEntries() {
+        for (auto it = mMap.begin(); it != mMap.end(); ) {
+            const Timestamp timestamp = it->second.first;
+            if (mCurrentTime > timestamp &&
+                (mCurrentTime - timestamp) > mTimeout) {
+                // This entry has expired, remove it
+                it = mMap.erase(it);
+            } else {
+                ++it;
+            }
+        }
+    }
+private:
+    const std::chrono::milliseconds mTimeout;
+    Timestamp mCurrentTime;
+    MapType mMap;
+};
+
diff --git a/network/wifi_forwarder/frame.cpp b/network/wifi_forwarder/frame.cpp
new file mode 100644
index 0000000..3b20b4e
--- /dev/null
+++ b/network/wifi_forwarder/frame.cpp
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2019, 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 "frame.h"
+
+#include "hwsim.h"
+#include "ieee80211.h"
+
+#include <asm/byteorder.h>
+
+#include <sstream>
+
+static constexpr uint64_t kSlotTime = 9;
+
+static const AccessCategory kPriorityToAc[8] = {
+    AccessCategory::BestEffort,
+    AccessCategory::Background,
+    AccessCategory::Background,
+    AccessCategory::BestEffort,
+    AccessCategory::Video,
+    AccessCategory::Video,
+    AccessCategory::Voice,
+    AccessCategory::Voice,
+};
+
+static uint32_t calcContentionWindowMin(AccessCategory ac) {
+    switch (ac) {
+        case AccessCategory::Voice:
+            return 3;
+        case AccessCategory::Video:
+            return 7;
+        case AccessCategory::BestEffort:
+            return 15;
+        case AccessCategory::Background:
+            return 15;
+    }
+}
+
+static uint32_t calcContentionWindowMax(AccessCategory ac) {
+    switch (ac) {
+        case AccessCategory::Voice:
+            return 7;
+        case AccessCategory::Video:
+            return 15;
+        case AccessCategory::BestEffort:
+            return 1023;
+        case AccessCategory::Background:
+            return 1023;
+    }
+}
+
+FrameType frameTypeFromByte(uint8_t byte) {
+    if (byte == static_cast<uint8_t>(FrameType::Ack)) {
+        return FrameType::Ack;
+    } else if (byte == static_cast<uint8_t>(FrameType::Data)) {
+        return FrameType::Data;
+    }
+    return FrameType::Unknown;
+}
+
+FrameInfo::FrameInfo(const MacAddress& transmitter,
+                     uint64_t cookie,
+                     uint32_t flags,
+                     uint32_t channel,
+                     const hwsim_tx_rate* rates,
+                     size_t numRates)
+    : mTransmitter(transmitter)
+    , mCookie(cookie)
+    , mFlags(flags)
+    , mChannel(channel) {
+    size_t i = 0;
+    for (; i < numRates; ++i) {
+        mTxRates[i].count = 0;
+        mTxRates[i].idx = rates[i].idx;
+    }
+    for (; i < mTxRates.size(); ++i) {
+        mTxRates[i].count = 0;
+        mTxRates[i].idx = -1;
+    }
+}
+
+bool FrameInfo::shouldAck() const {
+    return !!(mFlags & HWSIM_TX_CTL_REQ_TX_STATUS);
+}
+
+Frame::Frame(const uint8_t* data, size_t size) : mData(data, data + size) {
+}
+
+Frame::Frame(const uint8_t* data, size_t size, const MacAddress& transmitter,
+             uint64_t cookie, uint32_t flags, uint32_t channel,
+             const hwsim_tx_rate* rates, size_t numRates)
+    : mData(data, data + size)
+    , mInfo(transmitter, cookie, flags, channel, rates, numRates)
+    , mContentionWindow(calcContentionWindowMin(getAc()))
+    , mContentionWindowMax(calcContentionWindowMax(getAc())) {
+    memcpy(mInitialTxRates.data(), rates, numRates * sizeof(*rates));
+}
+
+bool Frame::incrementAttempts() {
+    Rates& rates = mInfo.rates();
+    for (size_t i = 0; i < rates.size(); ++i) {
+        if (mInitialTxRates[i].idx == -1) {
+            // We've run out of attempts
+            break;
+        }
+        if (rates[i].count < mInitialTxRates[i].count) {
+            ++rates[i].count;
+            return true;
+        }
+    }
+    return false;
+}
+
+bool Frame::hasRemainingAttempts() {
+    Rates& rates = mInfo.rates();
+    for (size_t i = 0; i < rates.size(); ++i) {
+        if (mInitialTxRates[i].idx == -1) {
+            break;
+        }
+        if (rates[i].count < mInitialTxRates[i].count) {
+            return true;
+        }
+    }
+    return false;
+}
+
+const MacAddress& Frame::source() const {
+    auto hdr = reinterpret_cast<const struct ieee80211_hdr*>(mData.data());
+    return *reinterpret_cast<const MacAddress*>(hdr->addr2);
+}
+
+const MacAddress& Frame::destination() const {
+    auto hdr = reinterpret_cast<const struct ieee80211_hdr*>(mData.data());
+    return *reinterpret_cast<const MacAddress*>(hdr->addr1);
+}
+
+std::string Frame::str() const {
+    uint8_t type = (mData[0] >> 2) & 0x3;
+    uint8_t subType = (mData[0] >> 4) & 0x0F;
+
+    std::stringstream ss;
+    ss << "[ Ck: " << cookie() << " Ch: " << channel() << " ] ";
+    switch (type) {
+        case 0:
+            // Management
+            ss << "Management (";
+            switch (subType) {
+                case 0:
+                    ss << "Association Request";
+                    break;
+                case 1:
+                    ss << "Association Response";
+                    break;
+                case 2:
+                    ss << "Reassociation Request";
+                    break;
+                case 3:
+                    ss << "Reassociation Response";
+                    break;
+                case 4:
+                    ss << "Probe Request";
+                    break;
+                case 5:
+                    ss << "Probe Response";
+                    break;
+                case 6:
+                    ss << "Timing Advertisement";
+                    break;
+                case 8:
+                    ss << "Beacon";
+                    break;
+                case 9:
+                    ss << "ATIM";
+                    break;
+                case 10:
+                    ss << "Disassociation";
+                    break;
+                case 11:
+                    ss << "Authentication";
+                    break;
+                case 12:
+                    ss << "Deauthentication";
+                    break;
+                case 13:
+                    ss << "Action";
+                    break;
+                case 14:
+                    ss << "Action No Ack";
+                    break;
+                default:
+                    ss << subType;
+                    break;
+            }
+            ss << ')';
+            break;
+        case 1:
+            // Control
+            ss << "Control (";
+            switch (subType) {
+                case 4:
+                    ss << "Beamforming Report Poll";
+                    break;
+                case 5:
+                    ss << "VHT NDP Announcement";
+                    break;
+                case 6:
+                    ss << "Control Frame Extension";
+                    break;
+                case 7:
+                    ss << "Control Wrapper";
+                    break;
+                case 8:
+                    ss << "Block Ack Request";
+                    break;
+                case 9:
+                    ss << "Block Ack";
+                    break;
+                case 10:
+                    ss << "PS-Poll";
+                    break;
+                case 11:
+                    ss << "RTS";
+                    break;
+                case 12:
+                    ss << "CTS";
+                    break;
+                case 13:
+                    ss << "Ack";
+                    break;
+                case 14:
+                    ss << "CF-End";
+                    break;
+                case 15:
+                    ss << "CF-End+CF-Ack";
+                    break;
+                default:
+                    ss << subType;
+                    break;
+            }
+            ss << ')';
+            break;
+        case 2:
+            // Data
+            ss << "Data (";
+            switch (subType) {
+                case 0:
+                    ss << "Data";
+                    break;
+                case 1:
+                    ss << "Data+CF-Ack";
+                    break;
+                case 2:
+                    ss << "Data+CF-Poll";
+                    break;
+                case 3:
+                    ss << "Data+CF-Ack+CF-Poll";
+                    break;
+                case 4:
+                    ss << "Null";
+                    break;
+                case 5:
+                    ss << "CF-Ack";
+                    break;
+                case 6:
+                    ss << "CF-Poll";
+                    break;
+                case 7:
+                    ss << "CF-Ack+CF-Poll";
+                    break;
+                case 8:
+                    ss << "QoS Data";
+                    break;
+                case 9:
+                    ss << "QoS Data+CF-Ack";
+                    break;
+                case 10:
+                    ss << "QoS Data+CF-Poll";
+                    break;
+                case 11:
+                    ss << "QoS Data+CF-Ack+CF-Poll";
+                    break;
+                case 12:
+                    ss << "QoS Null";
+                    break;
+                case 14:
+                    ss << "QoS CF-Poll";
+                    break;
+                case 15:
+                    ss << "QoS CF-Poll+CF-Ack";
+                    break;
+                default:
+                    ss << subType;
+                    break;
+            }
+            ss << ')';
+            break;
+        case 3:
+            // Extension
+            ss << "Extension (";
+            switch (subType) {
+                case 0:
+                    ss << "DMG Beacon";
+                    break;
+                default:
+                    ss << subType;
+                    break;
+            }
+            ss << ')';
+            break;
+        default:
+            ss << type << " (" << subType << ')';
+            break;
+    }
+    return ss.str();
+}
+
+bool Frame::isBeacon() const {
+    uint8_t totalType = mData[0] & 0xFC;
+    return totalType == 0x80;
+}
+
+bool Frame::isData() const {
+    uint8_t totalType = mData[0] & 0x0C;
+    return totalType == 0x08;
+}
+
+bool Frame::isDataQoS() const {
+    uint8_t totalType = mData[0] & 0xFC;
+    return totalType == 0x88;
+}
+
+uint16_t Frame::getQoSControl() const {
+    // Some frames have 4 address fields instead of 3 which pushes QoS control
+    // forward 6 bytes.
+    bool uses4Addresses = !!(mData[1] & 0x03);
+    const uint8_t* addr = &mData[uses4Addresses ? 30 : 24];
+
+    return __le16_to_cpu(*reinterpret_cast<const uint16_t*>(addr));
+}
+
+uint64_t Frame::calcNextTimeout() {
+    mNextTimeout = (mContentionWindow * kSlotTime) / 2;
+    mContentionWindow = std::min((mContentionWindow * 2) + 1,
+                                 mContentionWindowMax);
+    return mNextTimeout;
+}
+
+AccessCategory Frame::getAc() const {
+    if (!isData()) {
+        return AccessCategory::Voice;
+    }
+    if (!isDataQoS()) {
+        return AccessCategory::BestEffort;
+    }
+    uint16_t priority = getQoSControl() & 0x07;
+    return kPriorityToAc[priority];
+}
diff --git a/network/wifi_forwarder/frame.h b/network/wifi_forwarder/frame.h
new file mode 100644
index 0000000..ac76f82
--- /dev/null
+++ b/network/wifi_forwarder/frame.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2019, 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 <stdint.h>
+
+#include <array>
+#include <vector>
+
+#define IEEE80211_TX_MAX_RATES 4
+
+struct hwsim_tx_rate {
+    signed char idx;
+    unsigned char count;
+} __attribute__((__packed__));
+
+enum class FrameType : uint8_t {
+    Unknown,
+    Ack,
+    Data,
+};
+
+enum class AccessCategory {
+    Voice,
+    Video,
+    BestEffort,
+    Background,
+};
+
+FrameType frameTypeFromByte(uint8_t byte);
+
+using Rates = std::array<hwsim_tx_rate, IEEE80211_TX_MAX_RATES>;
+
+class FrameInfo {
+public:
+    FrameInfo() = default;
+    FrameInfo(const FrameInfo&) = default;
+    FrameInfo(FrameInfo&&) = default;
+    FrameInfo(const MacAddress& transmitter,
+              uint64_t cookie,
+              uint32_t flags,
+              uint32_t channel,
+              const hwsim_tx_rate* rates,
+              size_t numRates);
+
+    FrameInfo& operator=(const FrameInfo&) = default;
+    FrameInfo& operator=(FrameInfo&&) = default;
+
+    const Rates& rates() const { return mTxRates; }
+    Rates& rates() { return mTxRates; }
+
+    const MacAddress& transmitter() const { return mTransmitter; }
+    uint64_t cookie() const { return mCookie; }
+    uint32_t flags() const { return mFlags; }
+    uint32_t channel() const { return mChannel; }
+
+    bool shouldAck() const;
+
+private:
+    Rates mTxRates;
+    MacAddress mTransmitter;
+    uint64_t mCookie = 0;
+    uint32_t mFlags = 0;
+    uint32_t mChannel = 0;
+};
+
+class Frame {
+public:
+    Frame() = default;
+    Frame(const uint8_t* data, size_t size);
+    Frame(const uint8_t* data,
+          size_t size,
+          const MacAddress& transmitter,
+          uint64_t cookie,
+          uint32_t flags,
+          uint32_t channel,
+          const hwsim_tx_rate* rates,
+          size_t numRates);
+    Frame(Frame&& other) = default;
+
+    Frame& operator=(Frame&& other) = default;
+
+    size_t size() const { return mData.size(); }
+    const uint8_t* data() const { return mData.data(); }
+    uint8_t* data() { return mData.data(); }
+    uint64_t cookie() const { return mInfo.cookie(); }
+    uint32_t flags() const { return mInfo.flags(); }
+    uint32_t channel() const { return mInfo.channel(); }
+
+    Rates& rates() { return mInfo.rates(); }
+    const Rates& rates() const { return mInfo.rates(); }
+
+    const Rates& initialRates() const { return mInitialTxRates; }
+
+    // Increment the number of attempts made in the tx rates
+    bool incrementAttempts();
+    bool hasRemainingAttempts();
+
+    // The transmitter as indicated by hwsim
+    const MacAddress& transmitter() const { return mInfo.transmitter(); }
+    // The source as indicated by the 802.11 frame
+    const MacAddress& source() const;
+    // The destination as indicated by the 802.11 frame
+    const MacAddress& destination() const;
+
+    std::string str() const;
+
+    const FrameInfo& info() const { return mInfo; }
+    FrameInfo& info() { return mInfo; }
+
+    bool isBeacon() const;
+    bool isData() const;
+    bool isDataQoS() const;
+
+    uint16_t getQoSControl() const;
+
+    bool shouldAck() const { return mInfo.shouldAck(); }
+
+    uint64_t calcNextTimeout();
+
+    void setRadioDestination(const MacAddress& destination) {
+        mRadioDestination = destination;
+    }
+    const MacAddress& radioDestination() const { return mRadioDestination; }
+private:
+    Frame(const Frame&) = delete;
+    Frame& operator=(const Frame&) = delete;
+
+    AccessCategory getAc() const;
+
+    std::vector<uint8_t> mData;
+    FrameInfo mInfo;
+    MacAddress mRadioDestination;
+    Rates mInitialTxRates;
+    uint64_t mNextTimeout = 0;
+    // The contention winow determines how much time to back off on each retry.
+    // The contention window initial value and max value are determined by the
+    // access category of the frame.
+    uint32_t mContentionWindow = 0;
+    uint32_t mContentionWindowMax = 0;
+};
+
diff --git a/network/wifi_forwarder/frame_id.h b/network/wifi_forwarder/frame_id.h
new file mode 100644
index 0000000..545693a
--- /dev/null
+++ b/network/wifi_forwarder/frame_id.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2019, 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 <string.h>
+
+struct FrameId {
+    FrameId() {}
+    FrameId(uint64_t cookie, const MacAddress& transmitter)
+        : cookie(cookie), transmitter(transmitter) {
+    }
+    FrameId(const FrameId&) = default;
+    FrameId(FrameId&&) = default;
+
+    FrameId& operator=(const FrameId&) = default;
+    FrameId& operator=(FrameId&&) = default;
+
+    uint64_t cookie;
+    MacAddress transmitter;
+};
+
+namespace std {
+template<> struct hash<FrameId> {
+    size_t operator()(const FrameId& id) const {
+        size_t seed = 0;
+        hash_combine(seed, id.cookie);
+        hash_combine(seed, id.transmitter);
+        return seed;
+    }
+};
+}
+
+inline bool operator==(const FrameId& left, const FrameId& right) {
+    return left.cookie == right.cookie && left.transmitter == right.transmitter;
+}
+
+inline bool operator<(const FrameId& left, const FrameId& right) {
+    if (left.cookie < right.cookie) {
+        return true;
+    }
+    if (left.cookie > right.cookie) {
+        return false;
+    }
+    return memcmp(left.transmitter.addr,
+                  right.transmitter.addr,
+                  sizeof(left.transmitter.addr)) < 0;
+}
diff --git a/dhcp/server/log.h b/network/wifi_forwarder/hash.h
similarity index 69%
copy from dhcp/server/log.h
copy to network/wifi_forwarder/hash.h
index a0f21e0..ecd8c23 100644
--- a/dhcp/server/log.h
+++ b/network/wifi_forwarder/hash.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2019, 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.
@@ -13,8 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #pragma once
 
-#define LOG_TAG "dhcpserver"
-#include <log/log.h>
+#include <functional>
+
+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);
+}
 
diff --git a/network/wifi_forwarder/hwsim.h b/network/wifi_forwarder/hwsim.h
new file mode 100644
index 0000000..1a7b09b
--- /dev/null
+++ b/network/wifi_forwarder/hwsim.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2019, 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
+
+// mac80211_hwsim flags, from kernel drivers/net/wireless/mac80211_hwsim.h
+#define BIT(num) (1UL << (num))
+enum hwsim_tx_control_flags {
+    HWSIM_TX_CTL_REQ_TX_STATUS = BIT(0),
+    HWSIM_TX_CTL_NO_ACK        = BIT(1),
+    HWSIM_TX_STAT_ACK          = BIT(2),
+};
+
+// mac80211_hwsim commands, from kernel drivers/net/wireless/mac80211_hwsim.h
+enum HwSimCommand {
+    HWSIM_CMD_UNSPEC,
+    HWSIM_CMD_REGISTER,
+    HWSIM_CMD_FRAME,
+    HWSIM_CMD_TX_INFO_FRAME,
+    HWSIM_CMD_NEW_RADIO,
+    HWSIM_CMD_DEL_RADIO,
+    HWSIM_CMD_GET_RADIO,
+    __HWSIM_CMD_MAX,
+};
+
+// mac80211_hwsim attributes, from kernel drivers/net/wireless/mac80211_hwsim.h
+enum HwSimAttribute {
+        HWSIM_ATTR_UNSPEC,
+        HWSIM_ATTR_ADDR_RECEIVER,
+        HWSIM_ATTR_ADDR_TRANSMITTER,
+        HWSIM_ATTR_FRAME,
+        HWSIM_ATTR_FLAGS,
+        HWSIM_ATTR_RX_RATE,
+        HWSIM_ATTR_SIGNAL,
+        HWSIM_ATTR_TX_INFO,
+        HWSIM_ATTR_COOKIE,
+        HWSIM_ATTR_CHANNELS,
+        HWSIM_ATTR_RADIO_ID,
+        HWSIM_ATTR_REG_HINT_ALPHA2,
+        HWSIM_ATTR_REG_CUSTOM_REG,
+        HWSIM_ATTR_REG_STRICT_REG,
+        HWSIM_ATTR_SUPPORT_P2P_DEVICE,
+        HWSIM_ATTR_USE_CHANCTX,
+        HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE,
+        HWSIM_ATTR_RADIO_NAME,
+        HWSIM_ATTR_NO_VIF,
+        HWSIM_ATTR_FREQ,
+        __HWSIM_ATTR_MAX,
+};
+#define HWSIM_ATTR_MAX (__HWSIM_ATTR_MAX - 1)
+
diff --git a/network/netmgr/timestamp.h b/network/wifi_forwarder/ieee80211.h
similarity index 60%
copy from network/netmgr/timestamp.h
copy to network/wifi_forwarder/ieee80211.h
index 8ad7bf8..6bcf232 100644
--- a/network/netmgr/timestamp.h
+++ b/network/wifi_forwarder/ieee80211.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018, The Android Open Source Project
+ * Copyright 2019, 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.
@@ -16,18 +16,16 @@
 
 #pragma once
 
-#include <time.h>
+#include <net/ethernet.h>
+#include <stdint.h>
 
-class Timestamp {
-public:
-    Timestamp();
-
-    static Timestamp now();
-
-    bool operator==(const Timestamp& other) const;
-    bool operator<(const Timestamp& other) const;
-
-private:
-    struct timespec mTime;
-};
+struct ieee80211_hdr {
+        uint16_t frame_control;
+        uint16_t duration_id;
+        uint8_t addr1[ETH_ALEN];
+        uint8_t addr2[ETH_ALEN];
+        uint8_t addr3[ETH_ALEN];
+        uint16_t seq_ctrl;
+        uint8_t addr4[ETH_ALEN];
+} __attribute__((__packed__));
 
diff --git a/network/wifi_forwarder/local_connection.cpp b/network/wifi_forwarder/local_connection.cpp
new file mode 100644
index 0000000..1cebc17
--- /dev/null
+++ b/network/wifi_forwarder/local_connection.cpp
@@ -0,0 +1,392 @@
+/*
+ * Copyright 2019, 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 "local_connection.h"
+
+#include "hwsim.h"
+#include "log.h"
+#include "macaddress.h"
+#include "netlink_message.h"
+
+#include <net/ethernet.h>
+#include <netlink/netlink.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/genl.h>
+
+#include <utils/CallStack.h>
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+static const char kHwSimFamilyName[] = "MAC80211_HWSIM";
+static const int kHwSimVersion = 1;
+static const unsigned int kDefaultSignalStrength = -50;
+static const int kDefaultSocketBufferSize = 8 * (1 << 20);
+
+static uint32_t getSeqNum(struct nl_msg* msg) {
+    return nlmsg_hdr(msg)->nlmsg_seq;
+}
+
+static int onSent(struct nl_msg* , void*) {
+    return NL_OK;
+}
+
+static int onSeqCheck(struct nl_msg* msg, void*) {
+    uint32_t seq = getSeqNum(msg);
+    return seq == 0 ? NL_SKIP : NL_OK;
+}
+
+LocalConnection::LocalConnection(OnFrameCallback onFrameCallback,
+                                 OnAckCallback onAckCallback,
+                                 OnErrorCallback onErrorCallback)
+    : mOnFrameCallback(onFrameCallback)
+    , mOnAckCallback(onAckCallback)
+    , mOnErrorCallback(onErrorCallback) {
+
+}
+
+Result LocalConnection::init(std::chrono::steady_clock::time_point now) {
+    Result res = mNetlinkSocket.init();
+    if (!res) { return res; }
+    res = mNetlinkSocket.setOnMsgInCallback(staticOnMessage, this);
+    if (!res) { return res; }
+    res = mNetlinkSocket.setOnMsgOutCallback(onSent, this);
+    if (!res) { return res; }
+    res = mNetlinkSocket.setOnSeqCheckCallback(onSeqCheck, this);
+    if (!res) { return res; }
+    res = mNetlinkSocket.setOnAckCallback(staticOnAck, this);
+    if (!res) { return res; }
+    res = mNetlinkSocket.setOnErrorCallback(staticOnError, this);
+    if (!res) { return res; }
+    res = mNetlinkSocket.connectGeneric();
+    if (!res) { return res; }
+    res = mNetlinkSocket.setBufferSizes(kDefaultSocketBufferSize,
+                                        kDefaultSocketBufferSize);
+    if (!res) { return res; }
+
+    mNetlinkFamily = mNetlinkSocket.resolveNetlinkFamily(kHwSimFamilyName);
+    if (mNetlinkFamily < 0) {
+        return Result::error("Failed to resolve netlink family name: %s",
+                             nl_geterror(mNetlinkFamily));
+    }
+
+    mPendingFrames.setCurrentTime(now);
+    mSequenceNumberCookies.setCurrentTime(now);
+
+    mLastCacheTimeUpdate = now;
+    mLastCacheExpiration = now;
+
+    return registerReceiver();
+}
+
+int LocalConnection::getFd() const {
+    return mNetlinkSocket.getFd();
+}
+
+bool LocalConnection::receive() {
+    return mNetlinkSocket.receive();
+}
+
+uint32_t LocalConnection::transferFrame(std::unique_ptr<Frame> frame,
+                                        const MacAddress& dest) {
+    NetlinkMessage msg;
+
+    if (!msg.initGeneric(mNetlinkFamily, HWSIM_CMD_FRAME, kHwSimVersion)) {
+        ALOGE("LocalConnection transferFrame failed to init msg");
+        return 0;
+    }
+
+    frame->incrementAttempts();
+
+    if (!msg.addAttribute(HWSIM_ATTR_ADDR_RECEIVER, dest.addr, ETH_ALEN) ||
+        !msg.addAttribute(HWSIM_ATTR_FRAME, frame->data(), frame->size()) ||
+        !msg.addAttribute(HWSIM_ATTR_RX_RATE, 1u) ||
+        !msg.addAttribute(HWSIM_ATTR_SIGNAL, kDefaultSignalStrength) ||
+        !msg.addAttribute(HWSIM_ATTR_FREQ, frame->channel())) {
+
+        ALOGE("LocalConnection transferFrame failed to set attrs");
+        return 0;
+    }
+
+    if (!mNetlinkSocket.send(msg)) {
+        return 0;
+    }
+
+    // Store the radio destination for potential retransmissions.
+    frame->setRadioDestination(dest);
+
+    uint32_t seqNum = msg.getSeqNum();
+    uint64_t cookie = frame->cookie();
+    FrameId id(cookie, frame->transmitter());
+    mSequenceNumberCookies[seqNum] = id;
+    mPendingFrames[id] = std::move(frame);
+
+    return seqNum;
+}
+
+uint32_t LocalConnection::cloneFrame(const Frame& frame,
+                                     const MacAddress& dest) {
+    auto copy = std::make_unique<Frame>(frame.data(),
+                                        frame.size(),
+                                        frame.transmitter(),
+                                        frame.cookie(),
+                                        frame.flags(),
+                                        frame.channel(),
+                                        frame.rates().data(),
+                                        frame.rates().size());
+    return transferFrame(std::move(copy), dest);
+}
+
+bool LocalConnection::ackFrame(FrameInfo& info, bool success) {
+    NetlinkMessage msg;
+
+    if (!msg.initGeneric(mNetlinkFamily,
+                         HWSIM_CMD_TX_INFO_FRAME,
+                         kHwSimVersion)) {
+        ALOGE("LocalConnection ackFrame failed to create msg");
+        return false;
+    }
+
+    uint32_t flags = info.flags();
+    if (success) {
+        flags |= HWSIM_TX_STAT_ACK;
+    }
+
+    const uint8_t* transmitter = info.transmitter().addr;
+    const Rates& rates = info.rates();
+    if (!msg.addAttribute(HWSIM_ATTR_ADDR_TRANSMITTER, transmitter, ETH_ALEN) ||
+        !msg.addAttribute(HWSIM_ATTR_TX_INFO, rates.data(), rates.size()) ||
+        !msg.addAttribute(HWSIM_ATTR_FLAGS, flags) ||
+        !msg.addAttribute(HWSIM_ATTR_SIGNAL, kDefaultSignalStrength) ||
+        !msg.addAttribute(HWSIM_ATTR_COOKIE, info.cookie())) {
+
+        ALOGE("LocalConnection ackFrame failed to set attributes");
+        return false;
+    }
+
+    if (!mNetlinkSocket.send(msg)) {
+        return false;
+    }
+    mPendingFrames.erase(FrameId(info.cookie(), info.transmitter()));
+    return true;
+}
+
+std::chrono::steady_clock::time_point LocalConnection::getTimeout() const {
+    if (mRetryQueue.empty()) {
+        return std::chrono::steady_clock::time_point::max();
+    }
+    return mRetryQueue.top().first;
+}
+
+void LocalConnection::onTimeout(std::chrono::steady_clock::time_point now) {
+
+    if (now - mLastCacheTimeUpdate > std::chrono::seconds(1)) {
+        // Only update the time once per second, there's no need for a super
+        // high resolution here. We just want to make sure these caches don't
+        // fill up over a long period of time.
+        mPendingFrames.setCurrentTime(now);
+        mSequenceNumberCookies.setCurrentTime(now);
+        mLastCacheTimeUpdate = now;
+    }
+    if (now - mLastCacheExpiration > std::chrono::seconds(10)) {
+        // Only expire entries every 10 seconds, this is an operation that has
+        // some cost to it and doesn't have to happen very often.
+        mPendingFrames.expireEntries();
+        mSequenceNumberCookies.expireEntries();
+        mLastCacheExpiration = now;
+    }
+
+    while (!mRetryQueue.empty() && now >= mRetryQueue.top().first) {
+        FrameId id = mRetryQueue.top().second;
+        auto frameIt = mPendingFrames.find(id);
+        if (frameIt != mPendingFrames.end()) {
+            // Frame is still available, retry it
+            std::unique_ptr<Frame> frame = std::move(frameIt->second);
+            mPendingFrames.erase(frameIt);
+            MacAddress dest = frame->radioDestination();
+            transferFrame(std::move(frame), dest);
+        }
+        mRetryQueue.pop();
+    }
+}
+
+Result LocalConnection::registerReceiver() {
+    NetlinkMessage msg;
+
+    if (!msg.initGeneric(mNetlinkFamily, HWSIM_CMD_REGISTER, kHwSimVersion)) {
+        return Result::error("Failed to create register receiver message");
+    }
+
+    if (!mNetlinkSocket.send(msg)) {
+        return Result::error("Failed to send register receiver message");
+    }
+    return Result::success();
+}
+
+int LocalConnection::staticOnMessage(struct nl_msg* msg, void* context) {
+    if (!context) {
+        return NL_SKIP;
+    }
+    auto connection = static_cast<LocalConnection*>(context);
+    return connection->onMessage(msg);
+}
+
+int LocalConnection::staticOnAck(struct nl_msg* msg, void* context) {
+    if (!context) {
+        return NL_SKIP;
+    }
+    auto connection = static_cast<LocalConnection*>(context);
+    return connection->onAck(msg);
+}
+
+int LocalConnection::staticOnError(struct sockaddr_nl* addr,
+                                   struct nlmsgerr* error,
+                                   void* context) {
+    if (!context) {
+        return NL_SKIP;
+    }
+    auto connection = static_cast<LocalConnection*>(context);
+    return connection->onError(addr, error);
+}
+
+int LocalConnection::onMessage(struct nl_msg* msg) {
+    struct nlmsghdr* hdr = nlmsg_hdr(msg);
+    auto generic = reinterpret_cast<const struct genlmsghdr*>(nlmsg_data(hdr));
+
+    switch (generic->cmd) {
+        case HWSIM_CMD_FRAME:
+            return onFrame(msg);
+    }
+    return NL_OK;
+}
+
+int LocalConnection::onFrame(struct nl_msg* msg) {
+
+    struct nlmsghdr* hdr = nlmsg_hdr(msg);
+    std::unique_ptr<Frame> frame = parseFrame(hdr);
+    if (!frame) {
+        return NL_SKIP;
+    }
+
+    mOnFrameCallback(std::move(frame));
+    return NL_OK;
+}
+
+std::unique_ptr<Frame> LocalConnection::parseFrame(struct nlmsghdr* hdr) {
+    struct nlattr* attrs[HWSIM_ATTR_MAX + 1];
+    genlmsg_parse(hdr, 0, attrs, HWSIM_ATTR_MAX, nullptr);
+    if (!attrs[HWSIM_ATTR_ADDR_TRANSMITTER]) {
+        ALOGE("Received cmd frame without transmitter address");
+        return nullptr;
+    }
+    if (!attrs[HWSIM_ATTR_TX_INFO]) {
+        ALOGE("Received cmd frame without tx rates");
+        return nullptr;
+    }
+    if (!attrs[HWSIM_ATTR_FREQ]) {
+        ALOGE("Recieved cmd frame without channel frequency");
+        return nullptr;
+    }
+
+    uint64_t cookie = nla_get_u64(attrs[HWSIM_ATTR_COOKIE]);
+
+    const auto& source = *reinterpret_cast<const MacAddress*>(
+        nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]));
+
+    auto rates = reinterpret_cast<const hwsim_tx_rate*>(
+        nla_data(attrs[HWSIM_ATTR_TX_INFO]));
+    int rateLength = nla_len(attrs[HWSIM_ATTR_TX_INFO]);
+    // Make sure the length is valid, must be multiple of hwsim_tx_rate
+    if (rateLength <= 0 || (rateLength % sizeof(hwsim_tx_rate)) != 0) {
+        ALOGE("Invalid tx rate length %d", rateLength);
+    }
+    size_t numRates = static_cast<size_t>(rateLength) / sizeof(hwsim_tx_rate);
+
+    int length = nla_len(attrs[HWSIM_ATTR_FRAME]);
+    auto data = reinterpret_cast<uint8_t*>(nla_data(attrs[HWSIM_ATTR_FRAME]));
+
+    uint32_t flags = nla_get_u32(attrs[HWSIM_ATTR_FLAGS]);
+
+    uint32_t channel = nla_get_u32(attrs[HWSIM_ATTR_FREQ]);
+
+    return std::make_unique<Frame>(data, length, source, cookie,
+                                   flags, channel, rates, numRates);
+}
+
+int LocalConnection::onAck(struct nl_msg* msg) {
+    struct nlmsghdr* hdr = nlmsg_hdr(msg);
+    uint32_t seqNum = hdr->nlmsg_seq;
+    auto cookie = mSequenceNumberCookies.find(seqNum);
+    if (cookie == mSequenceNumberCookies.end()) {
+        // This is not a frame we sent. This is fairly common for libnl's
+        // internal use so don't log this.
+        return NL_SKIP;
+    }
+    auto pendingFrame = mPendingFrames.find(cookie->second);
+    // We don't need to keep this around anymore, erase it.
+    mSequenceNumberCookies.erase(seqNum);
+    if (pendingFrame == mPendingFrames.end()) {
+        // We have no cookie associated with this sequence number. This might
+        // happen if the remote connection already acked the frame and removed
+        // the frame info. Consider this resolved.
+        return NL_SKIP;
+    }
+
+    Frame* frame = pendingFrame->second.get();
+    mOnAckCallback(frame->info());
+    // Make sure to erase using the cookie instead of the iterator. The callback
+    // might have already erased this entry so the iterator could be invalid at
+    // this point. Instead of a fancy scheme of checking this just play it safe
+    // to allow the callback more freedom.
+    mPendingFrames.erase(cookie->second);
+
+    return NL_OK;
+}
+
+int LocalConnection::onError(struct sockaddr_nl*, struct nlmsgerr* error) {
+    struct nlmsghdr* hdr = &error->msg;
+    uint32_t seqNum = hdr->nlmsg_seq;
+
+    auto idIt = mSequenceNumberCookies.find(seqNum);
+    if (idIt == mSequenceNumberCookies.end()) {
+        return NL_SKIP;
+    }
+    FrameId id = idIt->second;
+    // No need to keep the sequence number around anymore, it's expired and is
+    // no longer useful.
+    mSequenceNumberCookies.erase(idIt);
+
+    auto frameIt = mPendingFrames.find(id);
+    if (frameIt == mPendingFrames.end()) {
+        return NL_SKIP;
+    }
+    Frame* frame = frameIt->second.get();
+
+    if (!frame->hasRemainingAttempts()) {
+        // This frame has used up all its attempts, there's nothing we can do
+        mOnErrorCallback(frame->info());
+        mPendingFrames.erase(id);
+        return NL_SKIP;
+    }
+    // Store the frame in the retry queue
+    uint64_t timeout = frame->calcNextTimeout();
+    auto deadline = std::chrono::steady_clock::now() +
+                    std::chrono::microseconds(timeout);
+    mRetryQueue.emplace(deadline, id);
+
+    return NL_SKIP;
+}
+
diff --git a/network/wifi_forwarder/local_connection.h b/network/wifi_forwarder/local_connection.h
new file mode 100644
index 0000000..9f3969a
--- /dev/null
+++ b/network/wifi_forwarder/local_connection.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2019, 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 "cache.h"
+#include "frame.h"
+#include "frame_id.h"
+#include "macaddress.h"
+#include "netlink_socket.h"
+#include "result.h"
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <queue>
+
+struct MacAddress;
+
+class LocalConnection {
+public:
+    using OnFrameCallback = std::function<void (std::unique_ptr<Frame>)>;
+    using OnAckCallback = std::function<void (FrameInfo&)>;
+    using OnErrorCallback = OnAckCallback;
+
+    LocalConnection(OnFrameCallback onFrameCallback,
+                    OnAckCallback onAckCallback,
+                    OnErrorCallback onErrorCallback);
+    Result init(std::chrono::steady_clock::time_point now);
+
+    int getFd() const;
+    bool receive();
+    uint32_t cloneFrame(const Frame& frame, const MacAddress& destination);
+    uint32_t transferFrame(std::unique_ptr<Frame> frame,
+                           const MacAddress& destination);
+    bool ackFrame(FrameInfo& info, bool success);
+
+    std::chrono::steady_clock::time_point getTimeout() const;
+    void onTimeout(std::chrono::steady_clock::time_point now);
+
+private:
+    using Timestamp = std::chrono::steady_clock::time_point;
+
+    Result registerReceiver();
+    static int staticOnMessage(struct nl_msg* msg, void* context);
+    static int staticOnAck(struct nl_msg* msg, void* context);
+    static int staticOnError(struct sockaddr_nl* addr,
+                             struct nlmsgerr* error,
+                             void* context);
+    int onMessage(struct nl_msg* msg);
+    int onFrame(struct nl_msg* msg);
+    std::unique_ptr<Frame> parseFrame(struct nlmsghdr* hdr);
+
+    int onAck(struct nl_msg* msg);
+    int onError(struct sockaddr_nl* addr, struct nlmsgerr* error);
+    bool sendFrameOnNetlink(const Frame& frame, const MacAddress& dest);
+
+    OnFrameCallback mOnFrameCallback;
+    OnAckCallback mOnAckCallback;
+    OnErrorCallback mOnErrorCallback;
+
+    NetlinkSocket mNetlinkSocket;
+    int mNetlinkFamily = -1;
+
+    // [cookie,transmitter] -> frame.
+    Cache<FrameId, std::unique_ptr<Frame>> mPendingFrames;
+    // sequence number -> [cookie,transmitter]
+    Cache<uint32_t, FrameId> mSequenceNumberCookies;
+
+    Timestamp mLastCacheTimeUpdate;
+    Timestamp mLastCacheExpiration;
+
+    // A queue (using an ordered map) with the next timeout mapping to the
+    // cookie and transmitter of the frame to retry. This way we can easily
+    // determine when the next deadline is by looking at the first entry and we
+    // can quickly determine which frames to retry by starting at the beginning.
+    std::priority_queue<std::pair<Timestamp, FrameId>> mRetryQueue;
+};
+
diff --git a/dhcp/server/log.h b/network/wifi_forwarder/log.h
similarity index 80%
rename from dhcp/server/log.h
rename to network/wifi_forwarder/log.h
index a0f21e0..edadc43 100644
--- a/dhcp/server/log.h
+++ b/network/wifi_forwarder/log.h
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ *     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,
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #pragma once
 
-#define LOG_TAG "dhcpserver"
+#define LOG_TAG "wifi_forwarder"
 #include <log/log.h>
-
diff --git a/network/wifi_forwarder/macaddress.h b/network/wifi_forwarder/macaddress.h
new file mode 100644
index 0000000..a9bcb66
--- /dev/null
+++ b/network/wifi_forwarder/macaddress.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019, 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 "hash.h"
+
+#include <linux/if_ether.h>
+#include <stdint.h>
+#include <string.h>
+
+// Format macros for printf, e.g. printf("MAC address: " PRIMAC, MACARG(mac))
+#define PRIMAC "%02x:%02x:%02x:%02x:%02x:%02x"
+#define MACARG(x) (x)[0], (x)[1], (x)[2], (x)[3],(x)[4], (x)[5]
+
+struct MacAddress {
+    MacAddress() {
+        memset(addr, 0, sizeof(addr));
+    }
+    MacAddress(uint8_t b1, uint8_t b2, uint8_t b3,
+               uint8_t b4, uint8_t b5, uint8_t b6) {
+        addr[0] = b1; addr[1] = b2; addr[2] = b3;
+        addr[3] = b4; addr[4] = b5; addr[5] = b6;
+    }
+    uint8_t addr[ETH_ALEN];
+    bool isBroadcast() const {
+        return memcmp(addr, "\xFF\xFF\xFF\xFF\xFF\xFF", ETH_ALEN) == 0;
+    }
+    bool isMulticast() const {
+        return addr[0] & 0x01;
+    }
+    bool empty() const {
+        return memcmp(addr, "\x00\x00\x00\x00\x00\x00", ETH_ALEN) == 0;
+    }
+    uint8_t operator[](size_t index) const {
+        return addr[index];
+    }
+} __attribute__((__packed__));
+
+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;
+    }
+};
+}
+
+inline bool operator==(const MacAddress& left, const MacAddress& right) {
+    return memcmp(left.addr, right.addr, ETH_ALEN) == 0;
+}
+
+inline bool operator!=(const MacAddress& left, const MacAddress& right) {
+    return memcmp(left.addr, right.addr, ETH_ALEN) != 0;
+}
+
diff --git a/network/wifi_forwarder/main.cpp b/network/wifi_forwarder/main.cpp
new file mode 100644
index 0000000..57127a9
--- /dev/null
+++ b/network/wifi_forwarder/main.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019, 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 "poller.h"
+#include "wifi_forwarder.h"
+
+int main(int /*argc*/, char* /*argv*/[]) {
+    WifiForwarder forwarder;
+    Result res = forwarder.init();
+    if (!res) {
+        ALOGE("%s", res.c_str());
+        return 1;
+    }
+
+    Poller poller;
+    poller.addPollable(&forwarder);
+    return poller.run();
+}
+
diff --git a/network/wifi_forwarder/netlink_message.cpp b/network/wifi_forwarder/netlink_message.cpp
new file mode 100644
index 0000000..265c296
--- /dev/null
+++ b/network/wifi_forwarder/netlink_message.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019, 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 "netlink_message.h"
+
+#include <netlink/genl/genl.h>
+#include <netlink/msg.h>
+
+NetlinkMessage::NetlinkMessage() {
+}
+
+NetlinkMessage::NetlinkMessage(NetlinkMessage&& other) {
+    std::swap(mMessage, other.mMessage);
+}
+
+NetlinkMessage::~NetlinkMessage() {
+    if (mMessage) {
+        nlmsg_free(mMessage);
+        mMessage = nullptr;
+    }
+}
+
+NetlinkMessage& NetlinkMessage::operator=(NetlinkMessage&& other) {
+    if (mMessage) {
+        nlmsg_free(mMessage);
+        mMessage = nullptr;
+    }
+    std::swap(mMessage, other.mMessage);
+    return *this;
+}
+
+bool NetlinkMessage::initGeneric(int family, uint8_t command, int version) {
+    if (mMessage) {
+        return false;
+    }
+
+    mMessage = nlmsg_alloc();
+    if (!mMessage) {
+        return false;
+    }
+
+    return genlmsg_put(mMessage, NL_AUTO_PORT, NL_AUTO_SEQ, family, 0,
+                       NLM_F_REQUEST, command, version) != nullptr;
+}
+
+uint32_t NetlinkMessage::getSeqNum() const {
+    return nlmsg_hdr(mMessage)->nlmsg_seq;
+}
diff --git a/network/wifi_forwarder/netlink_message.h b/network/wifi_forwarder/netlink_message.h
new file mode 100644
index 0000000..53d97a9
--- /dev/null
+++ b/network/wifi_forwarder/netlink_message.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019, 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"
+
+#include <netlink/netlink.h>
+
+#include <type_traits>
+
+class NetlinkMessage {
+public:
+    NetlinkMessage();
+    NetlinkMessage(NetlinkMessage&& other);
+    ~NetlinkMessage();
+
+    NetlinkMessage& operator=(NetlinkMessage&& other);
+
+    bool initGeneric(int family, uint8_t command, int version);
+
+    template<typename T,
+             typename = typename std::enable_if<!std::is_pointer<T>::value>::type>
+    bool addAttribute(int attribute, const T& value) {
+       return nla_put(mMessage, attribute, sizeof(T), &value) == 0;
+    }
+    template<typename T,
+             typename = typename std::enable_if<std::is_pointer<T>::value>::type>
+    bool addAttribute(int attribute, T data, size_t numElements) {
+        return nla_put(mMessage,
+                       attribute,
+                       sizeof(*data) * numElements,
+                       data) == 0;
+    }
+
+    uint32_t getSeqNum() const;
+
+    struct nl_msg* get() { return mMessage; }
+
+private:
+    NetlinkMessage(const NetlinkMessage&) = delete;
+    NetlinkMessage& operator=(const NetlinkMessage&) = delete;
+
+    struct nl_msg* mMessage = nullptr;
+};
+
diff --git a/network/wifi_forwarder/netlink_socket.cpp b/network/wifi_forwarder/netlink_socket.cpp
new file mode 100644
index 0000000..5af66a5
--- /dev/null
+++ b/network/wifi_forwarder/netlink_socket.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019, 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 "netlink_socket.h"
+
+#include "log.h"
+#include "netlink_message.h"
+
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/genl.h>
+#include <netlink/netlink.h>
+
+NetlinkSocket::NetlinkSocket() {
+}
+
+NetlinkSocket::~NetlinkSocket() {
+    if (mSocket) {
+        nl_socket_free(mSocket);
+        mSocket = nullptr;
+        mCallback = nullptr;
+    }
+}
+
+Result NetlinkSocket::init() {
+    if (mSocket || mCallback) {
+        return Result::error("Netlink socket already initialized");
+    }
+    mCallback = nl_cb_alloc(NL_CB_CUSTOM);
+    if (!mCallback) {
+        return Result::error("Netlink socket failed to allocate callbacks");
+    }
+    mSocket = nl_socket_alloc_cb(mCallback);
+    if (!mSocket) {
+        return Result::error("Failed to allocate netlink socket");
+    }
+    return Result::success();
+}
+
+Result NetlinkSocket::setBufferSizes(int rxBufferSize, int txBufferSize) {
+    int res = nl_socket_set_buffer_size(mSocket, rxBufferSize, txBufferSize);
+    if (res != 0) {
+        return Result::error("Failed to set buffer sizes: %s",
+                             nl_geterror(res));
+    }
+    return Result::success();
+}
+
+Result NetlinkSocket::setOnMsgInCallback(int (*callback)(struct nl_msg*, void*),
+                                         void* context) {
+    if (nl_cb_set(mCallback, NL_CB_MSG_IN, NL_CB_CUSTOM, callback, context)) {
+        return Result::error("Failed to set OnMsgIn callback");
+    }
+    return Result::success();
+}
+
+Result NetlinkSocket::setOnMsgOutCallback(int (*callback)(struct nl_msg*,
+                                                          void*),
+                                          void* context) {
+    if (nl_cb_set(mCallback, NL_CB_MSG_OUT, NL_CB_CUSTOM, callback, context)) {
+        return Result::error("Failed to set OnMsgOut callback");
+    }
+    return Result::success();
+}
+
+Result NetlinkSocket::setOnSeqCheckCallback(int (*callback)(struct nl_msg*,
+                                                            void*),
+                                            void* context) {
+    if (nl_cb_set(mCallback, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,
+                  callback, context)) {
+        return Result::error("Failed to set OnSeqCheck callback");
+    }
+    return Result::success();
+}
+
+Result NetlinkSocket::setOnAckCallback(int (*callback)(struct nl_msg*, void*),
+                                       void* context) {
+    if (nl_cb_set(mCallback, NL_CB_ACK, NL_CB_CUSTOM, callback, context)) {
+        return Result::error("Failed to set OnAck callback");
+    }
+    return Result::success();
+}
+
+Result NetlinkSocket::setOnErrorCallback(int (*callback)(struct sockaddr_nl*,
+                                                         struct nlmsgerr*,
+                                                         void*),
+                                         void* context) {
+    if (nl_cb_err(mCallback, NL_CB_CUSTOM, callback, context)) {
+        return Result::error("Failed to set OnError callback");
+    }
+    return Result::success();
+}
+
+Result NetlinkSocket::connectGeneric() {
+    int status = genl_connect(mSocket);
+    if (status < 0) {
+        return Result::error("WifiNetlinkForwarder socket connect failed: %d",
+                             status);
+    }
+    return Result::success();
+}
+
+int NetlinkSocket::resolveNetlinkFamily(const char* familyName) {
+    return genl_ctrl_resolve(mSocket, familyName);
+}
+
+bool NetlinkSocket::send(NetlinkMessage& message) {
+    int status = nl_send_auto(mSocket, message.get()) >= 0;
+    if (status < 0) {
+        ALOGE("Failed to send on netlink socket: %s", nl_geterror(status));
+        return false;
+    }
+    return true;
+}
+
+bool NetlinkSocket::receive() {
+    int res = nl_recvmsgs_default(mSocket);
+    if (res != 0) {
+        ALOGE("Failed to receive messages on netlink socket: %s",
+              nl_geterror(res));
+        return false;
+    }
+    return true;
+}
+
+int NetlinkSocket::getFd() const {
+    if (!mSocket) {
+        return -1;
+    }
+    return nl_socket_get_fd(mSocket);
+}
diff --git a/network/wifi_forwarder/netlink_socket.h b/network/wifi_forwarder/netlink_socket.h
new file mode 100644
index 0000000..9ff392c
--- /dev/null
+++ b/network/wifi_forwarder/netlink_socket.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2019, 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"
+
+struct nl_cb;
+struct nl_msg;
+struct nl_sock;
+struct nlmsgerr;
+struct sockaddr_nl;
+
+class NetlinkMessage;
+
+class NetlinkSocket {
+public:
+    NetlinkSocket();
+    ~NetlinkSocket();
+
+    Result init();
+
+    /* Set the size of the receive buffer to |rxBufferSize| bytes and the
+     * transmit buffer to |txBufferSize| bytes.
+     */
+    Result setBufferSizes(int rxBufferSize, int txBufferSize);
+
+    Result setOnMsgInCallback(int (*callback)(struct nl_msg*, void*),
+                              void* context);
+    Result setOnMsgOutCallback(int (*callback)(struct nl_msg*, void*),
+                              void* context);
+    Result setOnSeqCheckCallback(int (*callback)(struct nl_msg*, void*),
+                                 void* context);
+    Result setOnAckCallback(int (*callback)(struct nl_msg*, void*),
+                            void* context);
+    Result setOnErrorCallback(int (*callback)(struct sockaddr_nl*,
+                                              struct nlmsgerr*,
+                                              void*),
+                              void* context);
+
+    /* Connect socket to generic netlink. This needs to be done before generic
+     * netlink messages can be sent. After this is done the caller should use
+     * @resolveGenericNetlinkFamily to determine the generic family id to use.
+     * Then create NetlinkMessage's with that family id.
+     */
+    Result connectGeneric();
+
+    /* Resolve a generic family name to a family identifier. This is used when
+     * sending generic netlink messages to indicate where the message should go.
+     * Examples of family names are "mac80211_hwsim" or "nl80211".
+     */
+    int resolveNetlinkFamily(const char* familyName);
+
+    /* Send a netlink message on this socket. */
+    bool send(NetlinkMessage& message);
+
+    /* Receive all pending message. This method does not return any messages,
+     * instead they will be provided through the callback set with
+     * setOnMsgInCallback. This callback will be called on the same thread as
+     * this method while this method is running. */
+    bool receive();
+
+    int getFd() const;
+
+private:
+    struct nl_cb* mCallback = nullptr;
+    struct nl_sock* mSocket = nullptr;
+};
+
diff --git a/network/wifi_forwarder/pollable.h b/network/wifi_forwarder/pollable.h
new file mode 100644
index 0000000..51724c0
--- /dev/null
+++ b/network/wifi_forwarder/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(Timestamp now, int* status) = 0;
+};
+
diff --git a/network/wifi_forwarder/poller.cpp b/network/wifi_forwarder/poller.cpp
new file mode 100644
index 0000000..319dff4
--- /dev/null
+++ b/network/wifi_forwarder/poller.cpp
@@ -0,0 +1,157 @@
+/*
+ * 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) {
+            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) {
+        ALOGE("Unable to fill signal set: %s", strerror(errno));
+        return errno;
+    }
+    status = ::sigprocmask(SIG_SETMASK, &blockMask, &mask);
+    if (status != 0) {
+        ALOGE("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
+            ALOGE("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
+                    ALOGE("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(now, &status)) {
+                    // The onTimeout handler signaled an exit
+                    return status;
+                }
+            }
+        }
+    }
+
+    return 0;
+}
diff --git a/network/netmgr/timestamp.h b/network/wifi_forwarder/poller.h
similarity index 75%
rename from network/netmgr/timestamp.h
rename to network/wifi_forwarder/poller.h
index 8ad7bf8..9794f4d 100644
--- a/network/netmgr/timestamp.h
+++ b/network/wifi_forwarder/poller.h
@@ -16,18 +16,19 @@
 
 #pragma once
 
-#include <time.h>
+#include <vector>
 
-class Timestamp {
+#include "pollable.h"
+
+class Poller {
 public:
-    Timestamp();
+    Poller();
 
-    static Timestamp now();
+    void addPollable(Pollable* pollable);
 
-    bool operator==(const Timestamp& other) const;
-    bool operator<(const Timestamp& other) const;
+    int run();
 
 private:
-    struct timespec mTime;
+    std::vector<Pollable*> mPollables;
 };
 
diff --git a/network/wifi_forwarder/remote_connection.cpp b/network/wifi_forwarder/remote_connection.cpp
new file mode 100644
index 0000000..7255e05
--- /dev/null
+++ b/network/wifi_forwarder/remote_connection.cpp
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2019, 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 "remote_connection.h"
+
+#include "hwsim.h"
+#include "frame.h"
+#include "log.h"
+
+#include <errno.h>
+#include <linux/kernel.h>
+#include <netinet/in.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 <sys/uio.h>
+#include <unistd.h>
+
+static const char kQemuPipeName[] = "qemud:wififorward";
+static const size_t kReceiveBufferIncrement = 32768;
+static const size_t kReceiveBufferMaxSize = 1 << 20;
+
+static const uint8_t kWifiForwardVersion = 0x01;
+static const uint32_t kWifiForwardMagic = 0xD6C4B3A2;
+
+// This matches with the kernel constant IEEE80211_TX_MAX_RATES in
+// include/net/mac80211.h in the kernel tree.
+static const uint32_t kMaxNumRates = 4;
+
+struct WifiForwardHeader {
+    WifiForwardHeader(FrameType type, const MacAddress& transmitter,
+                      uint32_t fullLength, uint64_t cookie,
+                      uint32_t flags, uint32_t channel, uint32_t numRates,
+                      const hwsim_tx_rate* txRates)
+        : magic(__cpu_to_le32(kWifiForwardMagic))
+        , version(kWifiForwardVersion)
+        , type(static_cast<uint16_t>(type))
+        , transmitter(transmitter)
+        , dataOffset(sizeof(WifiForwardHeader))
+        , fullLength(__cpu_to_le32(fullLength))
+        , cookie(__cpu_to_le64(cookie))
+        , flags(__cpu_to_le32(flags))
+        , channel(__cpu_to_le32(channel))
+        , numRates(__cpu_to_le32(numRates)) {
+            memcpy(rates, txRates, std::min(numRates, kMaxNumRates));
+            if (numRates < kMaxNumRates) {
+                memset(&rates[numRates],
+                       0,
+                       sizeof(rates[0]) * (kMaxNumRates - numRates));
+            }
+        }
+
+    uint32_t magic;
+    uint8_t version;
+    uint8_t type;
+    MacAddress transmitter;
+    uint16_t dataOffset;
+    uint32_t fullLength;
+    uint64_t cookie;
+    uint32_t flags;
+    uint32_t channel;
+    uint32_t numRates;
+    hwsim_tx_rate rates[kMaxNumRates];
+} __attribute__((__packed__));
+
+RemoteConnection::RemoteConnection(OnFrameCallback onFrameCallback,
+                                   OnAckCallback onAckCallback,
+                                   OnErrorCallback onErrorCallback)
+    : mOnFrameCallback(onFrameCallback)
+    , mOnAckCallback(onAckCallback)
+    , mOnErrorCallback(onErrorCallback) {
+
+}
+
+RemoteConnection::~RemoteConnection() {
+    if (mPipeFd != -1) {
+        ::close(mPipeFd);
+        mPipeFd = -1;
+    }
+}
+
+Result RemoteConnection::init() {
+    if (mPipeFd != -1) {
+        return Result::error("RemoteConnetion already initialized");
+    }
+
+    mPipeFd = qemu_pipe_open(kQemuPipeName);
+    if (mPipeFd == -1) {
+        return Result::error("RemoteConnection failed to open pipe");
+    }
+    return Result::success();
+}
+
+Pollable::Timestamp RemoteConnection::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();
+}
+
+void RemoteConnection::receive() {
+    size_t start = mBuffer.size();
+    size_t newSize = start + kReceiveBufferIncrement;
+    if (newSize > kReceiveBufferMaxSize) {
+        // 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.
+        ALOGE("RemoteConnection ran out of buffer space");
+        newSize = kReceiveBufferIncrement;
+        start = 0;
+    }
+    mBuffer.resize(newSize);
+
+    while (true) {
+        int result = ::read(mPipeFd,
+                            mBuffer.data() + start,
+                            mBuffer.size() - start);
+        if (result < 0) {
+            if (errno == EINTR) {
+                continue;
+            }
+            ALOGE("RemoteConnection failed to read to forward buffer: %s",
+                 strerror(errno));
+            // Return the buffer to its previous size
+            mBuffer.resize(start);
+            return;
+        } else if (result == 0) {
+            // Nothing received, nothing to write
+            // Return the buffer to its previous size
+            mBuffer.resize(start);
+            ALOGE("RemoteConnection did not receive anything to inject");
+            return;
+        }
+        // Adjust the buffer size to match everything we recieved
+        mBuffer.resize(start + static_cast<size_t>(result));
+        break;
+    }
+
+    while (mBuffer.size() >= sizeof(WifiForwardHeader)) {
+        auto fwd = reinterpret_cast<WifiForwardHeader*>(mBuffer.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.
+            ALOGE("RemoteConnection found incorrect magic, finding next magic");
+            uint32_t le32magic = __cpu_to_le32(kWifiForwardMagic);
+            auto next = reinterpret_cast<unsigned char*>(
+                    ::memmem(mBuffer.data(), mBuffer.size(),
+                             &le32magic, sizeof(le32magic)));
+            if (next) {
+                // We've found a possible candidate, erase everything before
+                size_t length = next - mBuffer.data();
+                mBuffer.erase(mBuffer.begin(), mBuffer.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 (mBuffer.size() > 3) {
+                    mBuffer.erase(mBuffer.begin(), mBuffer.end() - 3);
+                }
+                // In this case there is nothing left to parse so just return
+                // right away.
+                return;
+            }
+        }
+        // Check if we support this version
+        if (fwd->version != kWifiForwardVersion) {
+            // Unsupported version
+            if (!mReportedVersionMismatches.test(fwd->version)) {
+                // Only report these once per version or this might become
+                // very spammy.
+                ALOGE("RemoteConnection encountered unknown version %u",
+                     fwd->version);
+                mReportedVersionMismatches.set(fwd->version);
+            }
+            // Drop the magic from the buffer and attempt to find the next magic
+            mBuffer.erase(mBuffer.begin(), mBuffer.begin() + 4);
+            continue;
+        }
+        // The length according to the wifi forward header
+        const uint32_t fullLength = __le32_to_cpu(fwd->fullLength);
+        const uint16_t offset = __le16_to_cpu(fwd->dataOffset);
+        if (offset < sizeof(WifiForwardHeader) || offset > fullLength) {
+            // The frame offset is not large enough to go past the header
+            // or it's outside of the bounds of the length of the frame.
+            ALOGE("Invalid data offset in header %u, full length is %u",
+                 offset, fullLength);
+            // Erase the magic and try again
+            mBuffer.erase(mBuffer.begin(), mBuffer.begin() + 4);
+            continue;
+        }
+        const size_t frameLength = fullLength - offset;
+
+        if (fullLength > mBuffer.size()) {
+            // We have not received enough data yet, wait for more to arrive.
+            return;
+        }
+
+        FrameType type = frameTypeFromByte(fwd->type);
+        if (frameLength == 0 && type != FrameType::Ack) {
+            ALOGE("Received empty frame for non-ack frame");
+            return;
+        }
+        unsigned char* frameData = mBuffer.data() + offset;
+        if (type == FrameType::Ack) {
+            FrameInfo info(fwd->transmitter,
+                           __le64_to_cpu(fwd->cookie),
+                           __le32_to_cpu(fwd->flags),
+                           __le32_to_cpu(fwd->channel),
+                           fwd->rates,
+                           __le32_to_cpu(fwd->numRates));
+
+            if (info.flags() & HWSIM_TX_STAT_ACK) {
+                mOnAckCallback(info);
+            } else {
+                mOnErrorCallback(info);
+            }
+        } else if (type == FrameType::Data) {
+            auto frame = std::make_unique<Frame>(frameData,
+                                                 frameLength,
+                                                 fwd->transmitter,
+                                                 __le64_to_cpu(fwd->cookie),
+                                                 __le32_to_cpu(fwd->flags),
+                                                 __le32_to_cpu(fwd->channel),
+                                                 fwd->rates,
+                                                 __le32_to_cpu(fwd->numRates));
+            mOnFrameCallback(std::move(frame));
+        } else {
+            ALOGE("Received unknown message type %u from remote",
+                 static_cast<uint8_t>(fwd->type));
+        }
+
+        mBuffer.erase(mBuffer.begin(), mBuffer.begin() + fullLength);
+    }
+}
+
+bool RemoteConnection::sendFrame(std::unique_ptr<Frame> frame) {
+    if (mPipeFd == -1) {
+        ALOGE("RemoteConnection unable to forward data, pipe not open");
+        return false;
+    }
+
+    WifiForwardHeader header(FrameType::Data,
+                             frame->transmitter(),
+                             frame->size() + sizeof(WifiForwardHeader),
+                             frame->cookie(),
+                             frame->flags(),
+                             frame->channel(),
+                             frame->rates().size(),
+                             frame->rates().data());
+#if 1
+    constexpr size_t count = 2;
+    struct iovec iov[count];
+    iov[0].iov_base = &header;
+    iov[0].iov_len = sizeof(header);
+    iov[1].iov_base = frame->data();
+    iov[1].iov_len = frame->size();
+
+    size_t totalSize = iov[0].iov_len + iov[1].iov_len;
+
+    size_t current = 0;
+    for (;;) {
+        ssize_t written = ::writev(mPipeFd, iov + current, count - current);
+        if (written < 0) {
+            ALOGE("RemoteConnection failed to write to pipe: %s",
+                  strerror(errno));
+            return false;
+        }
+        if (static_cast<size_t>(written) == totalSize) {
+            // Optimize for most common case, everything was written
+            break;
+        }
+        totalSize -= written;
+        // Determine how much is left to write after this
+        while (current < count && written >= iov[current].iov_len) {
+            written -= iov[current++].iov_len;
+        }
+        if (current == count) {
+            break;
+        }
+        iov[current].iov_base =
+            reinterpret_cast<char*>(iov[current].iov_base) + written;
+        iov[current].iov_len -= written;
+    }
+#else
+    if (!WriteFully(mPipeFd, &header, sizeof(header))) {
+        ALOGE("RemoteConnection failed to write to pipe: %s", strerror(errno));
+        return false;
+    }
+
+    if (!WriteFully(mPipeFd, frame->data(), frame->size())) {
+        ALOGE("RemoteConnection failed to write to pipe: %s", strerror(errno));
+        return false;
+    }
+#endif
+    return true;
+}
+
+bool RemoteConnection::ackFrame(FrameInfo& info, bool success) {
+    uint32_t flags = info.flags();
+    if (success) {
+        flags |= HWSIM_TX_STAT_ACK;
+    }
+    WifiForwardHeader header(FrameType::Ack,
+                             info.transmitter(),
+                             sizeof(WifiForwardHeader),
+                             info.cookie(),
+                             flags,
+                             info.channel(),
+                             info.rates().size(),
+                             info.rates().data());
+
+    if (!WriteFully(mPipeFd, &header, sizeof(header))) {
+        ALOGE("RemoteConnection failed to write to pipe: %s", strerror(errno));
+        return false;
+    }
+    return true;
+}
diff --git a/network/wifi_forwarder/remote_connection.h b/network/wifi_forwarder/remote_connection.h
new file mode 100644
index 0000000..94c7e36
--- /dev/null
+++ b/network/wifi_forwarder/remote_connection.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019, 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 "frame.h"
+#include "pollable.h"
+#include "result.h"
+
+#include <bitset>
+#include <functional>
+#include <memory>
+#include <vector>
+
+struct MacAddress;
+
+class RemoteConnection {
+public:
+    using OnFrameCallback = std::function<void (std::unique_ptr<Frame>)>;
+    using OnAckCallback = std::function<void (FrameInfo&)>;
+    using OnErrorCallback = OnAckCallback;
+
+    RemoteConnection(OnFrameCallback onFrameCallback,
+                     OnAckCallback onAckCallback,
+                     OnErrorCallback onErrorCallback);
+    ~RemoteConnection();
+
+    Result init();
+
+    int getFd() const { return mPipeFd; }
+    Pollable::Timestamp getTimeout() const;
+    void receive();
+    bool sendFrame(std::unique_ptr<Frame> frame);
+    bool ackFrame(FrameInfo& info, bool success);
+
+private:
+    RemoteConnection(const RemoteConnection&) = delete;
+    RemoteConnection& operator=(const RemoteConnection&) = delete;
+
+    OnFrameCallback mOnFrameCallback;
+    OnAckCallback mOnAckCallback;
+    OnErrorCallback mOnErrorCallback;
+
+    Pollable::Timestamp mDeadline = Pollable::Timestamp::max();
+    std::vector<unsigned char> mBuffer;
+    int mPipeFd = -1;
+    std::bitset<256> mReportedVersionMismatches;
+};
+
diff --git a/wifi/ipv6proxy/result.h b/network/wifi_forwarder/result.h
similarity index 63%
rename from wifi/ipv6proxy/result.h
rename to network/wifi_forwarder/result.h
index 5cd2b03..5087e14 100644
--- a/wifi/ipv6proxy/result.h
+++ b/network/wifi_forwarder/result.h
@@ -15,28 +15,40 @@
  */
 #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. NOTE: the data in |message| will
-    // NOT be copied. It must be kept alive for as long as its intended to be
-    // used. This way the object is kept light-weight.
-    static Result error(const char* message) {
+    // 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; }
+    const char* c_str() const { return mMessage.c_str(); }
 private:
     explicit Result(bool success) : mSuccess(success) { }
-    explicit Result(const char* message)
+    explicit Result(std::string message)
         : mMessage(message), mSuccess(false) {
     }
-    const char* mMessage;
+    std::string mMessage;
     bool mSuccess;
 };
 
diff --git a/network/wifi_forwarder/wifi_forwarder.cpp b/network/wifi_forwarder/wifi_forwarder.cpp
new file mode 100644
index 0000000..b56c7cd
--- /dev/null
+++ b/network/wifi_forwarder/wifi_forwarder.cpp
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2019, 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 "frame.h"
+#include "log.h"
+
+static constexpr bool kDebugTraffic = false;
+static constexpr bool kDebugBeaconTraffic = false;
+
+// How many seconds to keep aliases alive for. Since this is used to keep track
+// of randomized MAC addresses they will expire after a while. Set the value
+// pretty high to ensure that we don't accidentally lose entries just because
+// there's not a lot of frames going through.
+static constexpr auto kAliasesCacheTimeout = std::chrono::hours(8);
+
+WifiForwarder::WifiForwarder()
+    : mAliases(kAliasesCacheTimeout)
+    , mLocalConnection([this](std::unique_ptr<Frame> frame) {
+                            forwardFrame(std::move(frame), RadioType::Local);
+                       },
+                       [this](FrameInfo& info) { onAck(info, true); },
+                       [this](FrameInfo& info) { onAck(info, false); })
+    , mRemoteConnection([this](std::unique_ptr<Frame> frame) {
+                            forwardFrame(std::move(frame), RadioType::Remote);
+                        },
+                        [this](FrameInfo& info) { onAck(info, true); },
+                        [this](FrameInfo& info) { onAck(info, false); }) {
+}
+
+Result WifiForwarder::init() {
+    auto now = Pollable::Clock::now();
+    Result res = mRemoteConnection.init();
+    if (!res) {
+        // 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. If
+        // this does not succeed we don't need to perform the rest of the
+        // initialization either. Just let WiFi work the same as normal.
+        ALOGE("RemoteConnection failed to initialize: %s", res.c_str());
+        mInitDeadline = now + std::chrono::minutes(1);
+        return Result::success();
+    }
+
+    mAliases.setCurrentTime(now);
+    res = mLocalConnection.init(now);
+    if (!res) {
+        return res;
+    }
+    mDeadline = now + std::chrono::seconds(1);
+    return Result::success();
+}
+
+void WifiForwarder::getPollData(std::vector<pollfd>* fds) const {
+    int fd = mLocalConnection.getFd();
+    if (fd >= 0) {
+        struct pollfd pfd = { fd, POLLIN, 0 };
+        fds->push_back(pfd);
+    }
+    fd = mRemoteConnection.getFd();
+    if (fd >= 0) {
+        struct pollfd pfd = { fd, POLLIN, 0 };
+        fds->push_back(pfd);
+    }
+}
+
+Pollable::Timestamp WifiForwarder::getTimeout() const {
+    if (mRemoteConnection.getFd() == -1) {
+        return mInitDeadline;
+    }
+    return std::min(mLocalConnection.getTimeout(), mDeadline);
+}
+
+bool WifiForwarder::onReadAvailable(int fd, int* /*status*/) {
+    if (fd == mRemoteConnection.getFd()) {
+        mRemoteConnection.receive();
+    } else if (fd == mLocalConnection.getFd()) {
+        mLocalConnection.receive();
+    }
+    return true;
+}
+
+bool WifiForwarder::onClose(int /*fd*/, int* /*status*/) {
+    ALOGE("WifiForwarder socket closed unexpectedly");
+    return false;
+}
+
+bool WifiForwarder::onTimeout(Timestamp now, int* /*status*/) {
+    bool success = true;
+    if (now >= mInitDeadline) {
+        Result res = init();
+        success = res.isSuccess();
+        if (mRemoteConnection.getFd() == -1) {
+            // Remote connection not set up by init, try again later
+            mInitDeadline = now + std::chrono::minutes(1);
+        }
+    }
+    mLocalConnection.onTimeout(now);
+    if (now >= mDeadline) {
+        mDeadline += std::chrono::seconds(1);
+        mAliases.setCurrentTime(now);
+        mAliases.expireEntries();
+    }
+    return success;
+}
+
+const char* WifiForwarder::radioTypeToStr(RadioType type) const {
+    switch (type) {
+        case RadioType::Unknown:
+            return "Unknown";
+        case RadioType::Local:
+            return "Local";
+        case RadioType::Remote:
+            return "Remote";
+    }
+}
+
+void WifiForwarder::onAck(FrameInfo& info, bool success) {
+    RadioType type = mRadios[info.transmitter()];
+    if (type == RadioType::Remote) {
+        if (kDebugTraffic) {
+            ALOGE("] ACK -] " PRIMAC " [ %" PRIu64 " ] success: %s",
+                  MACARG(info.transmitter()), info.cookie(),
+                  success ? "true" : "false");
+        }
+        if (!mRemoteConnection.ackFrame(info, success)) {
+            ALOGE("WifiForwarder failed to ack remote frame");
+        }
+    } else if (type == RadioType::Local) {
+        if (kDebugTraffic) {
+            ALOGE("> ACK -> " PRIMAC " [ %" PRIu64 " ] success: %s",
+                  MACARG(info.transmitter()), info.cookie(),
+                  success ? "true" : "false");
+        }
+        if (!mLocalConnection.ackFrame(info, success)) {
+            ALOGE("WifiForwarder failed to ack local frame");
+        }
+    } else {
+        ALOGE("Unknown transmitter in ack: " PRIMAC,
+              MACARG(info.transmitter()));
+    }
+}
+
+void WifiForwarder::forwardFrame(std::unique_ptr<Frame> frame,
+                                 RadioType sourceType) {
+    if (kDebugTraffic) {
+        if (!frame->isBeacon() || kDebugBeaconTraffic) {
+            bool isRemote = sourceType == RadioType::Remote;
+            ALOGE("%c " PRIMAC " -%c " PRIMAC " %s",
+                  isRemote ? '[' : '<', isRemote ? ']' : '>',
+                  MACARG(frame->source()),
+                  MACARG(frame->destination()),
+                  frame->str().c_str());
+        }
+    }
+
+    const MacAddress& source = frame->source();
+    const MacAddress& transmitter = frame->transmitter();
+    const MacAddress& destination = frame->destination();
+    auto& currentType = mRadios[transmitter];
+    if (currentType != RadioType::Unknown && currentType != sourceType) {
+        ALOGE("Replacing type for MAC " PRIMAC " of type %s with type %s, "
+              "this might indicate duplicate MACs on different emulators",
+              MACARG(source), radioTypeToStr(currentType),
+              radioTypeToStr(sourceType));
+    }
+    currentType = sourceType;
+    mAliases[source] = transmitter;
+
+    bool isMulticast = destination.isMulticast();
+    bool sendOnRemote = isMulticast;
+    for (const auto& radio : mRadios) {
+        const MacAddress& radioAddress = radio.first;
+        RadioType radioType = radio.second;
+        if (radioAddress == transmitter) {
+            // Don't send back to the transmitter
+            continue;
+        }
+        if (sourceType == RadioType::Remote && radioType == RadioType::Remote) {
+            // Don't forward frames back to the remote, the remote will have
+            // taken care of this.
+            continue;
+        }
+        bool forward = false;
+        if (isMulticast || destination == radioAddress) {
+            // The frame is either multicast or directly intended for this
+            // radio. Forward it.
+            forward = true;
+        } else {
+            auto alias = mAliases.find(destination);
+            if (alias != mAliases.end() && alias->second == radioAddress) {
+                // The frame is destined for an address that is a known alias
+                // for this radio.
+                forward = true;
+            }
+        }
+        uint32_t seq = 0;
+        if (forward) {
+            switch (radioType) {
+                case RadioType::Unknown:
+                    ALOGE("Attempted to forward frame to unknown radio type");
+                    break;
+                case RadioType::Local:
+                    if (kDebugTraffic) {
+                        if (!frame->isBeacon() || kDebugBeaconTraffic) {
+                            ALOGE("> " PRIMAC " -> " PRIMAC " %s",
+                                  MACARG(frame->source()),
+                                  MACARG(frame->destination()),
+                                  frame->str().c_str());
+                        }
+                    }
+                    if (isMulticast) {
+                        // Clone the frame, it might be reused
+                        seq = mLocalConnection.cloneFrame(*frame, radioAddress);
+                    } else {
+                        // This frame has a specific destination, move it
+                        seq = mLocalConnection.transferFrame(std::move(frame),
+                                                             radioAddress);
+                        // Return so that we don't accidentally reuse the frame.
+                        // This should be safe now because it's a unicast frame
+                        // so it should not be sent to multiple radios.
+                        return;
+                    }
+                    break;
+                case RadioType::Remote:
+                    sendOnRemote = true;
+                    break;
+            }
+        }
+    }
+    if (sendOnRemote && sourceType != RadioType::Remote) {
+        if (kDebugTraffic) {
+            if (!frame->isBeacon() || kDebugBeaconTraffic) {
+                ALOGE("] " PRIMAC " -] " PRIMAC " %s",
+                      MACARG(frame->source()),
+                      MACARG(frame->destination()),
+                      frame->str().c_str());
+            }
+        }
+        // This is either a multicast message or destined for a radio known to
+        // be a remote. No need to send multiple times to a remote, the remote
+        // will handle that on its own.
+        mRemoteConnection.sendFrame(std::move(frame));
+    }
+}
diff --git a/network/wifi_forwarder/wifi_forwarder.h b/network/wifi_forwarder/wifi_forwarder.h
new file mode 100644
index 0000000..dfe57d8
--- /dev/null
+++ b/network/wifi_forwarder/wifi_forwarder.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019, 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 "cache.h"
+#include "frame.h"
+#include "local_connection.h"
+#include "macaddress.h"
+#include "pollable.h"
+#include "remote_connection.h"
+#include "result.h"
+
+#include <unordered_map>
+
+class WifiForwarder : public Pollable {
+public:
+    WifiForwarder();
+    virtual ~WifiForwarder() = default;
+    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(Timestamp now, int* status) override;
+private:
+    enum class RadioType {
+        Unknown,
+        Local,
+        Remote
+    };
+
+    const char* radioTypeToStr(RadioType type) const;
+
+    void onAck(FrameInfo& info, bool success);
+
+    void forwardFrame(std::unique_ptr<Frame> frame, RadioType sourceType);
+
+    std::unordered_map<MacAddress, RadioType> mRadios;
+    Cache<MacAddress, MacAddress> mAliases;
+    LocalConnection mLocalConnection;
+    RemoteConnection mRemoteConnection;
+    Pollable::Timestamp mInitDeadline = Pollable::Timestamp::max();
+    Pollable::Timestamp mDeadline = Pollable::Timestamp::max();
+};
+
diff --git a/ril/Android.mk b/ril/Android.mk
index cb5f31a..a5423ce 100644
--- a/ril/Android.mk
+++ b/ril/Android.mk
@@ -9,6 +9,7 @@
     reference-ril.c \
     atchannel.c \
     if_monitor.cpp \
+    ipv6_monitor.cpp \
     misc.c \
     at_tok.c
 
diff --git a/ril/if_monitor.cpp b/ril/if_monitor.cpp
index 289477d..87681f5 100644
--- a/ril/if_monitor.cpp
+++ b/ril/if_monitor.cpp
@@ -16,7 +16,9 @@
 
 #include "if_monitor.h"
 
+#include <arpa/inet.h>
 #include <errno.h>
+#include <ifaddrs.h>
 #include <linux/rtnetlink.h>
 #include <net/if.h>
 #include <poll.h>
@@ -30,6 +32,7 @@
 #include <mutex>
 #include <thread>
 #include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 #define LOG_TAG "RIL-IFMON"
@@ -56,6 +59,18 @@
     }
 }
 
+static const void* getSockAddrData(const struct sockaddr* addr) {
+    switch (addr->sa_family) {
+        case AF_INET:
+            return &reinterpret_cast<const struct sockaddr_in*>(addr)->sin_addr;
+        case AF_INET6:
+            return
+                &reinterpret_cast<const struct sockaddr_in6*>(addr)->sin6_addr;
+        default:
+            return nullptr;
+    }
+}
+
 bool operator==(const struct ifAddress& left, const struct ifAddress& right) {
     // The prefix length does not factor in to whether two addresses are the
     // same or not. Only the family and the address data. This matches the
@@ -133,32 +148,75 @@
         mThread = std::make_unique<std::thread>([this]() { run(); });
     }
 
-    void requestAddress() {
-        struct {
-            struct nlmsghdr hdr;
-            struct ifaddrmsg msg;
-            char padding[16];
-        } request;
+    void requestAddresses() {
+        struct ifaddrs* addresses = nullptr;
 
-        memset(&request, 0, sizeof(request));
-        request.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
-        request.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
-        request.hdr.nlmsg_type = RTM_GETADDR;
+        if (getifaddrs(&addresses) != 0) {
+            RLOGE("Unable to retrieve list of interfaces, cannot get initial "
+                  "interface addresses: %s", strerror(errno));
+            return;
+        }
 
-        int status = ::send(mSocketFd, &request, request.hdr.nlmsg_len, 0);
-        if (status < 0 ||
-            static_cast<unsigned int>(status) != request.hdr.nlmsg_len) {
-            if (status < 0) {
-                RLOGE("Failed to send netlink request: %s", strerror(errno));
-            } else {
-                RLOGE("Short send only sent %d out of %d bytes",
-                      status, (int)request.hdr.nlmsg_len);
+        for (struct ifaddrs* cur = addresses; cur; cur = cur->ifa_next) {
+            if (cur->ifa_name == nullptr ||
+                cur->ifa_addr == nullptr ||
+                cur->ifa_netmask == nullptr) {
+                // Interface doesn't have all the information we need. Rely on
+                // the netlink notification to catch this interface later if it
+                // is configured correctly.
+                continue;
+            }
+            if (cur->ifa_flags & IFF_LOOPBACK) {
+                // Not interested in loopback devices, they will never be radio
+                // interfaces.
+                continue;
+            }
+            unsigned int ifIndex = if_nametoindex(cur->ifa_name);
+            if (ifIndex == 0) {
+                RLOGE("Encountered interface %s with no index: %s",
+                      cur->ifa_name, strerror(errno));
+                continue;
+            }
+            ifAddress addr;
+            addr.family = cur->ifa_addr->sa_family;
+            addr.prefix = getPrefix(cur->ifa_netmask);
+            memcpy(addr.addr,
+                   getSockAddrData(cur->ifa_addr),
+                   addrLength(cur->ifa_addr->sa_family));
+            mAddresses[ifIndex].push_back(addr);
+        }
+        freeifaddrs(addresses);
+
+        if (mOnAddressChangeCallback) {
+            for (const auto& ifAddr : mAddresses) {
+                mOnAddressChangeCallback(ifAddr.first,
+                                         ifAddr.second.data(),
+                                         ifAddr.second.size());
             }
         }
     }
 
+    int getPrefix(const struct sockaddr* addr) {
+        // This uses popcnt, a built-in instruction on some CPUs, to count
+        // the number of bits in a 32-bit word. The number of bits in a netmask
+        // equals the width of the prefix. For example a netmask of
+        // 255.255.255.0 has 24 bits set and that's also its width.
+        if (addr->sa_family == AF_INET) {
+            auto v4 = reinterpret_cast<const struct sockaddr_in*>(addr);
+            return __builtin_popcount(v4->sin_addr.s_addr);
+        } else if (addr->sa_family == AF_INET6) {
+            auto v6 = reinterpret_cast<const struct sockaddr_in6*>(addr);
+            // Copy to our own array to avoid aliasing
+            uint64_t words[2];
+            memcpy(words, v6->sin6_addr.s6_addr, sizeof(words));
+            return __builtin_popcountll(words[0]) +
+                   __builtin_popcountll(words[1]);
+        }
+        return 0;
+    }
+
     void run() {
-        requestAddress();
+        requestAddresses();
 
         std::vector<struct pollfd> fds(2);
         fds[0].events = POLLIN;
@@ -251,11 +309,16 @@
                         RLOGE("Received message type %d", (int)hdr->nlmsg_type);
                         break;
                 }
-                NLMSG_NEXT(hdr, length);
+                hdr = NLMSG_NEXT(hdr, length);
             }
         }
     }
 
+    std::string getInterfaceName(unsigned int ifIndex) {
+        char buffer[IF_NAMESIZE] = { '\0' };
+        return if_indextoname(ifIndex, buffer);
+    }
+
     void handleAddressChange(const struct nlmsghdr* hdr) {
         if (!mOnAddressChangeCallback) {
             return;
diff --git a/ril/ipv6_monitor.cpp b/ril/ipv6_monitor.cpp
new file mode 100644
index 0000000..8d577bc
--- /dev/null
+++ b/ril/ipv6_monitor.cpp
@@ -0,0 +1,496 @@
+/*
+ * Copyright 2019, 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 "ipv6_monitor.h"
+
+#include <errno.h>
+#include <linux/filter.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip6.h>
+#include <poll.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <array>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <unordered_set>
+#include <vector>
+
+#define LOG_TAG "RIL-IPV6MON"
+#include <utils/Log.h>
+
+static constexpr size_t kReadBufferSize = 32768;
+
+static constexpr size_t kRecursiveDnsOptHeaderSize = 8;
+
+static constexpr size_t kControlClient = 0;
+static constexpr size_t kControlServer = 1;
+
+static constexpr char kMonitorAckCommand = '\1';
+static constexpr char kMonitorStopCommand = '\2';
+
+// The amount of time to wait before trying to initialize interface again if
+// it's not ready when rild starts.
+static constexpr int kDeferredTimeoutMilliseconds = 1000;
+
+bool operator==(const in6_addr& left, const in6_addr& right) {
+    return ::memcmp(left.s6_addr, right.s6_addr, sizeof(left.s6_addr)) == 0;
+}
+
+bool operator!=(const in6_addr& left, const in6_addr& right) {
+    return ::memcmp(left.s6_addr, right.s6_addr, sizeof(left.s6_addr)) != 0;
+}
+
+template<class T>
+static 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<in6_addr> {
+    size_t operator()(const in6_addr& ad) const {
+        size_t seed = 0;
+        hash_combine(seed, *reinterpret_cast<const uint32_t*>(&ad.s6_addr[0]));
+        hash_combine(seed, *reinterpret_cast<const uint32_t*>(&ad.s6_addr[4]));
+        hash_combine(seed, *reinterpret_cast<const uint32_t*>(&ad.s6_addr[8]));
+        hash_combine(seed, *reinterpret_cast<const uint32_t*>(&ad.s6_addr[12]));
+        return seed;
+    }
+};
+}  // namespace std
+
+static constexpr uint32_t kIpTypeOffset = offsetof(ip6_hdr, ip6_nxt);
+static constexpr uint32_t kIcmpTypeOffset = sizeof(ip6_hdr) +
+                                            offsetof(icmp6_hdr, icmp6_type); 
+
+// This is BPF program that will filter out anything that is not an NDP router
+// advertisement. It's a very basic assembler syntax. The jumps indicate how
+// many instructions to jump in addition to the automatic increment of the
+// program counter. So a jump statement with a zero means to go to the next
+// instruction, a value of 3 means that the next instruction will be the 4th
+// after the current one.
+static const struct sock_filter kNdpFilter[] = {
+    // Load byte at absolute address kIpTypeOffset
+    BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIpTypeOffset),
+    // Jump, if byte is IPPROTO_ICMPV6 jump 0 instructions, if not jump 3.
+    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
+    // Load byte at absolute address kIcmpTypeOffset
+    BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIcmpTypeOffset),
+    // Jump, if byte is ND_ROUTER_ADVERT jump 0 instructions, if not jump 1
+    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1),
+    // Return the number of bytes to accept, accept all of them
+    BPF_STMT(BPF_RET | BPF_K, std::numeric_limits<uint32_t>::max()),
+    // Accept zero bytes, this is where the failed jumps go
+    BPF_STMT(BPF_RET | BPF_K, 0)
+};
+static constexpr size_t kNdpFilterSize =
+    sizeof(kNdpFilter) / sizeof(kNdpFilter[0]);
+
+class Ipv6Monitor {
+public:
+    Ipv6Monitor(const char* interfaceName);
+    ~Ipv6Monitor();
+
+    enum class InitResult {
+        Error,
+        Deferred,
+        Success,
+    };
+    InitResult init();
+    void setCallback(ipv6MonitorCallback callback);
+    void runAsync();
+    void stop();
+
+private:
+    InitResult initInterfaces();
+    void run();
+    void onReadAvailable();
+
+    ipv6MonitorCallback mMonitorCallback;
+
+    in6_addr mGateway;
+    std::unordered_set<in6_addr> mDnsServers;
+
+    std::unique_ptr<std::thread> mThread;
+    std::mutex mThreadMutex;
+
+    std::string mInterfaceName;
+    int mSocketFd;
+    int mControlSocket[2];
+    int mPollTimeout = -1;
+    bool mFullyInitialized = false;
+};
+
+Ipv6Monitor::Ipv6Monitor(const char* interfaceName) :
+    mMonitorCallback(nullptr),
+    mInterfaceName(interfaceName),
+    mSocketFd(-1) {
+    memset(&mGateway, 0, sizeof(mGateway));
+    mControlSocket[0] = -1;
+    mControlSocket[1] = -1;
+}
+
+Ipv6Monitor::~Ipv6Monitor() {
+    for (int& fd : mControlSocket) {
+        if (fd != -1) {
+            ::close(fd);
+            fd = -1;
+        }
+    }
+    if (mSocketFd != -1) {
+        ::close(mSocketFd);
+        mSocketFd = -1;
+    }
+}
+
+Ipv6Monitor::InitResult Ipv6Monitor::init() {
+    if (mSocketFd != -1) {
+        RLOGE("Ipv6Monitor already initialized");
+        return InitResult::Error;
+    }
+
+    if (::socketpair(AF_UNIX, SOCK_DGRAM, 0, mControlSocket) != 0) {
+        RLOGE("Ipv6Monitor failed to create control socket pair: %s",
+              strerror(errno));
+        return InitResult::Error;
+    }
+
+    mSocketFd = ::socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, ETH_P_IPV6);
+    if (mSocketFd == -1) {
+        RLOGE("Ipv6Monitor failed to open socket: %s", strerror(errno));
+        return InitResult::Error;
+    }
+    // If interface initialization fails we'll retry later
+    return initInterfaces();
+}
+
+void Ipv6Monitor::setCallback(ipv6MonitorCallback callback) {
+    mMonitorCallback = callback;
+}
+
+Ipv6Monitor::InitResult Ipv6Monitor::initInterfaces() {
+    if (mFullyInitialized) {
+        RLOGE("Ipv6Monitor already initialized");
+        return InitResult::Error;
+    }
+    struct ifreq request;
+    memset(&request, 0, sizeof(request));
+    strlcpy(request.ifr_name, mInterfaceName.c_str(), sizeof(request.ifr_name));
+
+    // Set the ALLMULTI flag so we can capture multicast traffic
+    int status = ::ioctl(mSocketFd, SIOCGIFFLAGS, &request);
+    if (status != 0) {
+        if (errno == ENODEV) {
+            // It is not guaranteed that the network is entirely set up by the
+            // time rild has started. If that's the case the radio interface
+            // might not be up yet, try again later.
+            RLOGE("Ipv6Monitor could not initialize %s yet, retrying later",
+                  mInterfaceName.c_str());
+            mPollTimeout = kDeferredTimeoutMilliseconds;
+            return InitResult::Deferred;
+        }
+        RLOGE("Ipv6Monitor failed to get interface flags for %s: %s",
+              mInterfaceName.c_str(), strerror(errno));
+        return InitResult::Error;
+    }
+
+    if ((request.ifr_flags & IFF_ALLMULTI) == 0) {
+        // The flag is not set, we have to make another call
+        request.ifr_flags |= IFF_ALLMULTI;
+
+        status = ::ioctl(mSocketFd, SIOCSIFFLAGS, &request);
+        if (status != 0) {
+            RLOGE("Ipv6Monitor failed to set interface flags for %s: %s",
+                  mInterfaceName.c_str(), strerror(errno));
+            return InitResult::Error;
+        }
+    }
+
+    // Add a BPF filter to the socket so that we only receive the specific
+    // type of packet we're interested in. Otherwise we will receive ALL
+    // traffic on this interface.
+    struct sock_fprog filter;
+    filter.len = kNdpFilterSize;
+    // The API doesn't have const but it's not going to modify it so this is OK
+    filter.filter = const_cast<struct sock_filter*>(kNdpFilter);
+    status = ::setsockopt(mSocketFd,
+                          SOL_SOCKET,
+                          SO_ATTACH_FILTER,
+                          &filter,
+                          sizeof(filter));
+    if (status != 0) {
+        RLOGE("Ipv6Monitor failed to set socket filter: %s", strerror(errno));
+        return InitResult::Error;
+    }
+
+    // Get the hardware address of the interface into a sockaddr struct for bind
+    struct sockaddr_ll ethAddr;
+    memset(&ethAddr, 0, sizeof(ethAddr));
+    ethAddr.sll_family = AF_PACKET;
+    ethAddr.sll_protocol = htons(ETH_P_IPV6);
+    ethAddr.sll_ifindex = if_nametoindex(mInterfaceName.c_str());
+    if (ethAddr.sll_ifindex == 0) {
+        RLOGE("Ipv6Monitor failed to find index for %s: %s",
+              mInterfaceName.c_str(), strerror(errno));
+        return InitResult::Error;
+    }
+
+    status = ::ioctl(mSocketFd, SIOCGIFHWADDR, &request);
+    if (status != 0) {
+        RLOGE("Ipv6Monitor failed to get hardware address for %s: %s",
+              mInterfaceName.c_str(), strerror(errno));
+        return InitResult::Error;
+    }
+    memcpy(ethAddr.sll_addr, request.ifr_addr.sa_data, ETH_ALEN);
+
+    // Now bind to the hardware address
+    status = ::bind(mSocketFd,
+                    reinterpret_cast<const struct sockaddr*>(&ethAddr),
+                    sizeof(ethAddr));
+    if (status != 0) {
+        RLOGE("Ipv6Monitor failed to bind to %s hardware address: %s",
+              mInterfaceName.c_str(), strerror(errno));
+        return InitResult::Error;
+    }
+    mFullyInitialized = true;
+    return InitResult::Success;
+}
+
+void Ipv6Monitor::runAsync() {
+    std::unique_lock<std::mutex> lock(mThreadMutex);
+    mThread = std::make_unique<std::thread>([this]() { run(); });
+}
+
+void Ipv6Monitor::stop() {
+    std::unique_lock<std::mutex> lock(mThreadMutex);
+    if (!mThread) {
+        return;
+    }
+    ::write(mControlSocket[kControlClient], &kMonitorStopCommand, 1);
+    char ack = -1;
+    while (ack != kMonitorAckCommand) {
+        ::read(mControlSocket[kControlClient], &ack, sizeof(ack));
+    }
+    mThread->join();
+    mThread.reset();
+}
+
+void Ipv6Monitor::run() {
+    std::array<struct pollfd, 2> fds;
+    fds[0].events = POLLIN;
+    fds[0].fd = mControlSocket[kControlServer];
+    fds[1].events = POLLIN;
+    fds[1].fd = mSocketFd;
+
+    bool running = true;
+    while (running) {
+        int status = ::poll(fds.data(), fds.size(), mPollTimeout);
+        if (status < 0) {
+            if (errno == EINTR) {
+                // Interrupted, keep going
+                continue;
+            }
+            // An error occurred
+            RLOGE("Ipv6Monitor fatal failure polling failed; %s",
+                  strerror(errno));
+            break;
+        } else if (status == 0) {
+            // Timeout, nothing to read
+            if (!mFullyInitialized) {
+                InitResult result = initInterfaces();
+                switch (result) {
+                    case InitResult::Error:
+                        // Something went wrong this time and we can't recover
+                        running = false;
+                        break;
+                    case InitResult::Deferred:
+                        // We need to keep waiting and then try again
+                        mPollTimeout = kDeferredTimeoutMilliseconds;
+                        break;
+                    case InitResult::Success:
+                        // Interfaces are initialized, no need to timeout again
+                        mPollTimeout = -1;
+                        break;
+                }
+            }
+            continue;
+        }
+
+        if (fds[0].revents & POLLIN) {
+            // Control message received
+            char command = -1;
+            if (::read(mControlSocket[kControlServer],
+                       &command,
+                       sizeof(command)) == 1) {
+                if (command == kMonitorStopCommand) {
+                    break;
+                }
+            }
+        } else if (fds[1].revents & POLLIN) {
+            onReadAvailable();
+        }
+    }
+    ::write(mControlSocket[kControlServer], &kMonitorAckCommand, 1);
+}
+
+void Ipv6Monitor::onReadAvailable() {
+    char buffer[kReadBufferSize];
+
+    ssize_t bytesRead = 0;
+    while (true) {
+        bytesRead = ::recv(mSocketFd, buffer, sizeof(buffer), 0);
+        if (bytesRead < 0) {
+            if (errno == EINTR) {
+                // Interrupted, try again right away
+                continue;
+            }
+            if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                // Do not report an error for the above error codes, they are
+                // part of the normal turn of events. We just need to try again
+                // later when we run into those errors.
+                RLOGE("Ipv6Monitor failed to receive data: %s",
+                      strerror(errno));
+            }
+            return;
+        }
+        break;
+    }
+
+    if (mMonitorCallback == nullptr) {
+        // No point in doing anything, we have read the data so the socket
+        // buffer doesn't fill up and that's all we can do.
+        return;
+    }
+
+    if (static_cast<size_t>(bytesRead) < sizeof(ip6_hdr) + sizeof(icmp6_hdr)) {
+        // This message cannot be an ICMPv6 packet, ignore it
+        return;
+    }
+
+    auto ipv6 = reinterpret_cast<const ip6_hdr*>(buffer);
+    uint8_t version = (ipv6->ip6_vfc & 0xF0) >> 4;
+    if (version != 6 || ipv6->ip6_nxt != IPPROTO_ICMPV6) {
+        // This message is not an IPv6 packet or not an ICMPv6 packet, ignore it
+        return;
+    }
+
+    // The ICMP header starts right after the IPv6 header
+    auto icmp = reinterpret_cast<const icmp6_hdr*>(buffer + sizeof(ip6_hdr));
+    if (icmp->icmp6_code != 0) {
+        // All packets we care about have an icmp code of zero.
+        return;
+    }
+
+    if (icmp->icmp6_type != ND_ROUTER_ADVERT) {
+        // We only care about router advertisements
+        return;
+    }
+
+    // At this point we know it's a valid packet, let's look inside
+
+    // The gateway is the same as the source in the IP header
+    in6_addr gateway = ipv6->ip6_src;
+
+    // Search through the options for DNS servers
+    const char* options = buffer + sizeof(ip6_hdr) + sizeof(nd_router_advert);
+    const nd_opt_hdr* option = reinterpret_cast<const nd_opt_hdr*>(options);
+
+    std::vector<in6_addr> dnsServers;
+    const nd_opt_hdr* nextOpt = nullptr;
+    for (const nd_opt_hdr* opt = option; opt; opt = nextOpt) {
+        auto nextOptLoc =
+            reinterpret_cast<const char*>(opt) + opt->nd_opt_len * 8u;
+        if (nextOptLoc > buffer + bytesRead) {
+            // Not enough room for this option, abort
+            break;
+        }
+        if (nextOptLoc < buffer + bytesRead) {
+            nextOpt = reinterpret_cast<const nd_opt_hdr*>(nextOptLoc);
+        } else {
+            nextOpt = nullptr;
+        }
+        if (opt->nd_opt_type != 25 || opt->nd_opt_len < 1) {
+            // Not an RNDSS option, skip it
+            continue;
+        }
+
+        size_t numEntries = (opt->nd_opt_len - 1) / 2;
+        const char* addrLoc = reinterpret_cast<const char*>(opt);
+        addrLoc += kRecursiveDnsOptHeaderSize;
+        auto addrs = reinterpret_cast<const in6_addr*>(addrLoc);
+
+        for (size_t i = 0; i < numEntries; ++i) {
+            dnsServers.push_back(addrs[i]);
+        }
+    }
+
+    bool changed = false;
+    if (gateway != mGateway) {
+        changed = true;
+        mGateway = gateway;
+    }
+
+    for (const auto& dns : dnsServers) {
+        if (mDnsServers.find(dns) == mDnsServers.end()) {
+            mDnsServers.insert(dns);
+            changed = true;
+        }
+    }
+
+    if (changed) {
+        mMonitorCallback(&gateway, dnsServers.data(), dnsServers.size());
+    }
+}
+
+extern "C"
+struct ipv6Monitor* ipv6MonitorCreate(const char* interfaceName) {
+    auto monitor = std::make_unique<Ipv6Monitor>(interfaceName);
+    if (!monitor || monitor->init() == Ipv6Monitor::InitResult::Error) {
+        return nullptr;
+    }
+    return reinterpret_cast<struct ipv6Monitor*>(monitor.release());
+}
+
+extern "C"
+void ipv6MonitorFree(struct ipv6Monitor* ipv6Monitor) {
+    auto monitor = reinterpret_cast<Ipv6Monitor*>(ipv6Monitor);
+    delete monitor;
+}
+
+extern "C"
+void ipv6MonitorSetCallback(struct ipv6Monitor* ipv6Monitor,
+                            ipv6MonitorCallback callback) {
+    auto monitor = reinterpret_cast<Ipv6Monitor*>(ipv6Monitor);
+    monitor->setCallback(callback);
+}
+
+extern "C"
+void ipv6MonitorRunAsync(struct ipv6Monitor* ipv6Monitor) {
+    auto monitor = reinterpret_cast<Ipv6Monitor*>(ipv6Monitor);
+    monitor->runAsync();
+}
+
+extern "C"
+void ipv6MonitorStop(struct ipv6Monitor* ipv6Monitor) {
+    auto monitor = reinterpret_cast<Ipv6Monitor*>(ipv6Monitor);
+    monitor->stop();
+}
+
diff --git a/ril/ipv6_monitor.h b/ril/ipv6_monitor.h
new file mode 100644
index 0000000..b58402d
--- /dev/null
+++ b/ril/ipv6_monitor.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019, 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 <netinet/in.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ipv6Monitor;
+
+// A callback for when the IPv6 configuration changes.
+typedef void (*ipv6MonitorCallback)(const struct in6_addr* /*gateway*/,
+                                    const struct in6_addr* /*dns servers*/,
+                                    size_t /*number of dns servers */);
+
+// Create an IPv6 monitor that will monitor |interfaceName| for IPv6 router
+// advertisements. The monitor will trigger a callback if the gateway and/or
+// DNS servers provided by router advertisements change at any point.
+struct ipv6Monitor* ipv6MonitorCreate(const char* interfaceName);
+void ipv6MonitorFree(struct ipv6Monitor* monitor);
+
+void ipv6MonitorSetCallback(struct ipv6Monitor* monitor,
+                            ipv6MonitorCallback callback);
+void ipv6MonitorRunAsync(struct ipv6Monitor* monitor);
+void ipv6MonitorStop(struct ipv6Monitor* monitor);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+
diff --git a/ril/reference-ril.c b/ril/reference-ril.c
index 48ad12c..8566231 100644
--- a/ril/reference-ril.c
+++ b/ril/reference-ril.c
@@ -45,11 +45,17 @@
 #include <netinet/in.h>
 
 #include "if_monitor.h"
+#include "ipv6_monitor.h"
 #include "ril.h"
 
 #define LOG_TAG "RIL"
 #include <utils/Log.h>
 
+#define MAX(x, y) ({\
+    __typeof__(x) _x = (x); \
+    __typeof__(y) _y = (y); \
+    _x > _y ? _x : _y; })
+
 static void *noopRemoveWarning( void *a ) { return a; }
 #define RIL_UNUSED_PARM(a) noopRemoveWarning((void *)&(a));
 
@@ -273,9 +279,13 @@
 static int s_lac = 0;
 static int s_cid = 0;
 
-// A string containing all the IPv6 addresses of the radio interface
-static char s_ipv6_addresses[8192];
-static pthread_mutex_t s_ipv6_addresses_mutex = PTHREAD_MUTEX_INITIALIZER;
+// A string containing all IP addresses of the radio interface
+static char s_if_addresses[8192];
+// A string containing the IPv6 gateway of the radio interface
+static char s_ipv6_gateway[INET6_ADDRSTRLEN];
+// A string containing the IPv6 DNS servers of the radio interface
+static char s_ipv6_dns[8192];
+static pthread_mutex_t s_addresses_mutex = PTHREAD_MUTEX_INITIALIZER;
 
 static void pollSIMState (void *param);
 static void setRadioState(RIL_RadioState newState);
@@ -618,6 +628,8 @@
     char propValue[PROP_VALUE_MAX];
     bool hasWifi = hasWifiCapability();
     const char* radioInterfaceName = getRadioInterfaceName(hasWifi);
+    char ipv6Gateway[INET6_ADDRSTRLEN];
+    char ipv6Dns[8192];
 
     err = at_send_command_multiline ("AT+CGACT?", "+CGACT:", &p_response);
     if (err != 0 || p_response->success == 0) {
@@ -735,24 +747,23 @@
         if (err < 0)
             goto error;
 
-        pthread_mutex_lock(&s_ipv6_addresses_mutex);
+        pthread_mutex_lock(&s_addresses_mutex);
 
-        // Extra space for null terminator and separating space
-        int addresses_size = strlen(out) + strlen(s_ipv6_addresses) + 2;
+        // Extra space for null terminator
+        int addresses_size = MAX(strlen(out), strlen(s_if_addresses)) + 1;
         responses[i].addresses = alloca(addresses_size);
-        if (*s_ipv6_addresses) {
-            // IPv6 addresses exist, add them
-            snprintf(responses[i].addresses, addresses_size,
-                     "%s %s",
-                     hasWifi ? RADIO0_IPV4_ADDRESS : out,
-                     s_ipv6_addresses);
+        if (*s_if_addresses) {
+            // Interface addresses exist, use them.
+            strlcpy(responses[i].addresses, s_if_addresses, addresses_size);
         } else {
-            // Only provide the IPv4 address
-            strlcpy(responses[i].addresses,
-                    hasWifi ? RADIO0_IPV4_ADDRESS : out,
-                    addresses_size);
+            // No known interface address, use whatever the modem provided
+            strlcpy(responses[i].addresses, out, addresses_size);
         }
-        pthread_mutex_unlock(&s_ipv6_addresses_mutex);
+
+        strlcpy(ipv6Gateway, s_ipv6_gateway, sizeof(ipv6Gateway));
+        strlcpy(ipv6Dns, s_ipv6_dns, sizeof(ipv6Dns));
+
+        pthread_mutex_unlock(&s_addresses_mutex);
 
         if (isInEmulator()) {
             /* We are in the emulator - the dns servers are listed
@@ -769,12 +780,15 @@
             int         nn;
             char  propName[PROP_NAME_MAX];
             char  propValue[PROP_VALUE_MAX];
+            char* gateways = NULL;
+            size_t gatewaysSize = 0;
 
             dnslist[0] = 0;
             for (nn = 1; nn <= 4; nn++) {
                 /* Probe net.eth0.dns<n> */
 
-                snprintf(propName, sizeof propName, "net.eth0.dns%d", nn);
+                snprintf(propName, sizeof propName, "net.%s.dns%d",
+                         radioInterfaceName, nn);
 
                 /* Ignore if undefined */
                 if (property_get(propName, propValue, "") <= 0) {
@@ -788,7 +802,8 @@
             }
             for (nn = 1; nn <= 4; ++nn) {
                 /* Probe net.eth0.ipv6dns<n> for IPv6 DNS servers */
-                snprintf(propName, sizeof propName, "net.eth0.ipv6dns%d", nn);
+                snprintf(propName, sizeof propName, "net.%s.ipv6dns%d",
+                         radioInterfaceName, nn);
                 /* Ignore if undefined */
                 if (property_get(propName, propValue, "") <= 0) {
                     continue;
@@ -800,13 +815,31 @@
 
             responses[i].dnses = dnslist;
 
-            /* There is only one gateway in the emulator. If WiFi is
-             * configured the interface visible to RIL will be behind a NAT
-             * where the gateway is different. */
-            if (hasWifi) {
-                responses[i].gateways = "192.168.200.1";
-            } else if (property_get("net.eth0.gw", propValue, "") > 0) {
-                responses[i].gateways = propValue;
+            /* There is only one gateway in the emulator. */
+            snprintf(propName, sizeof propName, "net.%s.gw",
+                     radioInterfaceName);
+
+            gatewaysSize = strlen(ipv6Gateway);
+            if (property_get(propName, propValue, "") > 0) {
+                if (gatewaysSize > 0) {
+                    // Room for a separating space
+                    ++gatewaysSize;
+                }
+                gatewaysSize += strlen(propValue);
+            }
+            if (gatewaysSize > 0) {
+                // Room for a terminating null byte
+                ++gatewaysSize;
+                responses[i].gateways = alloca(gatewaysSize);
+                if (ipv6Gateway[0]) {
+                    strlcpy(responses[i].gateways, ipv6Gateway, gatewaysSize);
+                }
+                if (propValue[0]) {
+                    if (responses[i].gateways[0] != '\0') {
+                        strlcat(responses[i].gateways, " ", gatewaysSize);
+                    }
+                    strlcat(responses[i].gateways, propValue, gatewaysSize);
+                }
             } else {
                 responses[i].gateways = "";
             }
@@ -823,13 +856,14 @@
 
     at_response_free(p_response);
 
-    if (t != NULL)
+    if (t != NULL) {
         RIL_onRequestComplete(*t, RIL_E_SUCCESS, responses,
                               n * sizeof(RIL_Data_Call_Response_v11));
-    else
+    } else {
         RIL_onUnsolicitedResponse(RIL_UNSOL_DATA_CALL_LIST_CHANGED,
                                   responses,
                                   n * sizeof(RIL_Data_Call_Response_v11));
+    }
 
     return;
 
@@ -3950,22 +3984,18 @@
         return;
     }
 
-    pthread_mutex_lock(&s_ipv6_addresses_mutex);
+    pthread_mutex_lock(&s_addresses_mutex);
     // Clear out any existing addresses, we receive a full set of addresses
     // that are going to replace the existing ones.
-    s_ipv6_addresses[0] = '\0';
-    currentLoc = s_ipv6_addresses;
-    remaining = sizeof(s_ipv6_addresses);
+    s_if_addresses[0] = '\0';
+    currentLoc = s_if_addresses;
+    remaining = sizeof(s_if_addresses);
     for (i = 0; i < numAddresses; ++i) {
-        if (addresses[i].family != AF_INET6) {
-            // Only care about IPv6 addresses
-            continue;
-        }
         char address[INET6_ADDRSTRLEN];
         if (inet_ntop(addresses[i].family, &addresses[i].addr,
                       address, sizeof(address))) {
             int printed = 0;
-            if (s_ipv6_addresses[0]) {
+            if (s_if_addresses[0]) {
                 // We've already printed something, separate them
                 if (remaining < 1) {
                     continue;
@@ -3983,7 +4013,38 @@
             RLOGE("Unable to convert address to string for if %s", ifName);
         }
     }
-    pthread_mutex_unlock(&s_ipv6_addresses_mutex);
+    pthread_mutex_unlock(&s_addresses_mutex);
+
+    // Send unsolicited call list change to notify upper layers about the new
+    // addresses
+    requestOrSendDataCallList(NULL);
+}
+
+static void onIpv6Change(const struct in6_addr* gateway,
+                         const struct in6_addr* dnsServers,
+                         size_t numDnsServers) {
+    char* dnsLoc = s_ipv6_dns;
+    size_t remaining = sizeof(s_ipv6_dns);
+    char address[INET6_ADDRSTRLEN];
+
+    pthread_mutex_lock(&s_addresses_mutex);
+
+    inet_ntop(AF_INET6, gateway, s_ipv6_gateway, sizeof(s_ipv6_gateway));
+
+    s_ipv6_dns[0] = '\0';
+    for (size_t i = 0; i < numDnsServers; ++i) {
+        if (inet_ntop(AF_INET6, &dnsServers[i], address, sizeof(address))) {
+            size_t len = strlcat(s_ipv6_dns, address, sizeof(s_ipv6_dns));
+            if (i + 1 < numDnsServers && len + 1 < sizeof(s_ipv6_dns)) {
+                // There's more to come and there's room for more, separate
+                // multiple DNS servers by a space.
+                s_ipv6_dns[len] = ' ';
+                s_ipv6_dns[len + 1] = '\0';
+            }
+        }
+    }
+
+    pthread_mutex_unlock(&s_addresses_mutex);
 
     // Send unsolicited call list change to notify upper layers about the new
     // addresses
@@ -3995,14 +4056,20 @@
 {
     int fd;
     int ret;
-    struct ifMonitor* monitor = ifMonitorCreate();
+    bool hasWifi = hasWifiCapability();
+    const char* radioInterfaceName = getRadioInterfaceName(hasWifi);
+    struct ifMonitor* ifMonitor = ifMonitorCreate();
+    struct ipv6Monitor* ipv6Monitor = ipv6MonitorCreate(radioInterfaceName);
 
     AT_DUMP("== ", "entering mainLoop()", -1 );
     at_set_on_reader_closed(onATReaderClosed);
     at_set_on_timeout(onATTimeout);
 
-    ifMonitorSetCallback(monitor, &onInterfaceAddressChange);
-    ifMonitorRunAsync(monitor);
+    ifMonitorSetCallback(ifMonitor, &onInterfaceAddressChange);
+    ifMonitorRunAsync(ifMonitor);
+
+    ipv6MonitorSetCallback(ipv6Monitor, &onIpv6Change);
+    ipv6MonitorRunAsync(ipv6Monitor);
 
     for (;;) {
         fd = -1;
@@ -4052,8 +4119,11 @@
         RLOGI("Re-opening after close");
     }
 
-    ifMonitorStop(monitor);
-    ifMonitorFree(monitor);
+    ifMonitorStop(ifMonitor);
+    ifMonitorFree(ifMonitor);
+
+    ipv6MonitorStop(ipv6Monitor);
+    ipv6MonitorFree(ipv6Monitor);
 
     return NULL;
 }
diff --git a/sepolicy/common/dhcpclient.te b/sepolicy/common/dhcpclient.te
index f1ba3f0..cff4471 100644
--- a/sepolicy/common/dhcpclient.te
+++ b/sepolicy/common/dhcpclient.te
@@ -8,14 +8,14 @@
 allow dhcpclient execns:fd use;
 
 set_prop(dhcpclient, net_eth0_prop);
+set_prop(dhcpclient, net_radio0_prop);
 dontaudit dhcpclient kernel:system module_request;
 allow dhcpclient self:capability { net_admin net_raw };
-allow dhcpclient self:udp_socket create;
-allow dhcpclient self:netlink_route_socket { write nlmsg_write };
+allow dhcpclient self:netlink_route_socket { ioctl write nlmsg_write };
 allow dhcpclient varrun_file:dir search;
 allow dhcpclient self:packet_socket { create bind write read };
-allowxperm dhcpclient self:udp_socket ioctl { SIOCSIFFLAGS
-                                              SIOCSIFADDR
-                                              SIOCSIFNETMASK
-                                              SIOCSIFMTU
-                                              SIOCGIFHWADDR };
+allowxperm dhcpclient self:netlink_route_socket ioctl { SIOCGIFFLAGS
+                                                        SIOCSIFFLAGS
+                                                        SIOCSIFMTU
+                                                        SIOCGIFINDEX
+                                                        SIOCGIFHWADDR };
diff --git a/sepolicy/common/dhcprelay.te b/sepolicy/common/dhcprelay.te
new file mode 100644
index 0000000..4c4ffdf
--- /dev/null
+++ b/sepolicy/common/dhcprelay.te
@@ -0,0 +1,21 @@
+# DHCP relay
+type dhcprelay, domain;
+type dhcprelay_exec, exec_type, vendor_file_type, file_type;
+
+init_daemon_domain(dhcprelay)
+net_domain(dhcprelay)
+
+allow dhcprelay execns:fd use;
+
+set_prop(dhcprelay, net_eth0_prop);
+dontaudit dhcprelay kernel:system module_request;
+allow dhcprelay self:capability { net_admin net_bind_service net_raw };
+allow dhcprelay self:udp_socket create;
+allow dhcprelay self:netlink_route_socket { write nlmsg_write };
+allow dhcprelay varrun_file:dir search;
+allow dhcprelay self:packet_socket { create bind write read };
+allowxperm dhcprelay self:udp_socket ioctl { SIOCSIFFLAGS
+                                             SIOCSIFADDR
+                                             SIOCSIFNETMASK
+                                             SIOCSIFMTU
+                                             SIOCGIFHWADDR };
diff --git a/sepolicy/common/dhcpserver.te b/sepolicy/common/dhcpserver.te
deleted file mode 100644
index 7e8ba26..0000000
--- a/sepolicy/common/dhcpserver.te
+++ /dev/null
@@ -1,12 +0,0 @@
-# DHCP server
-type dhcpserver, domain;
-type dhcpserver_exec, exec_type, vendor_file_type, file_type;
-
-init_daemon_domain(dhcpserver)
-net_domain(dhcpserver)
-
-allow dhcpserver execns:fd use;
-
-get_prop(dhcpserver, net_eth0_prop);
-allow dhcpserver self:udp_socket { ioctl create setopt bind };
-allow dhcpserver self:capability { net_raw net_bind_service };
diff --git a/sepolicy/common/execns.te b/sepolicy/common/execns.te
index 24eee41..265fb83 100644
--- a/sepolicy/common/execns.te
+++ b/sepolicy/common/execns.te
@@ -15,9 +15,6 @@
 # Allow dhcpclient to be run by execns in its own domain
 domain_auto_trans(execns, dhcpclient_exec, dhcpclient);
 
-# Allow dhcpserver to be run by execns in its own domain
-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);
 
diff --git a/sepolicy/common/file_contexts b/sepolicy/common/file_contexts
index 117a59f..240bc49 100644
--- a/sepolicy/common/file_contexts
+++ b/sepolicy/common/file_contexts
@@ -23,12 +23,12 @@
 /vendor/bin/qemu-props       u:object_r:qemu_props_exec:s0
 /vendor/bin/createns         u:object_r:createns_exec:s0
 /vendor/bin/execns           u:object_r:execns_exec:s0
-/vendor/bin/ipv6proxy        u:object_r:ipv6proxy_exec:s0
+/vendor/bin/ip               u:object_r:goldfish_ip_exec:s0
 /vendor/bin/iw               u:object_r:goldfish_iw_exec:s0
 /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/wifi_forwarder   u:object_r:wifi_forwarder_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\.2-service\.widevine          u:object_r:hal_drm_widevine_exec:s0
diff --git a/sepolicy/common/goldfish_ip.te b/sepolicy/common/goldfish_ip.te
new file mode 100644
index 0000000..c914596
--- /dev/null
+++ b/sepolicy/common/goldfish_ip.te
@@ -0,0 +1,5 @@
+type goldfish_ip, domain;
+type goldfish_ip_exec, exec_type, vendor_file_type, file_type;
+
+init_daemon_domain(goldfish_ip)
+net_domain(goldfish_ip)
diff --git a/sepolicy/common/goldfish_setup.te b/sepolicy/common/goldfish_setup.te
index 187d055..e3071f7 100644
--- a/sepolicy/common/goldfish_setup.te
+++ b/sepolicy/common/goldfish_setup.te
@@ -14,6 +14,7 @@
 wakelock_use(goldfish_setup);
 allow goldfish_setup vendor_shell_exec:file { rx_file_perms };
 #============= goldfish_setup ==============
+allow goldfish_setup goldfish_ip_exec:file execute_no_trans;
 allow goldfish_setup goldfish_iw_exec:file execute_no_trans;
 
 # Set system properties to start services
@@ -33,10 +34,6 @@
 allow goldfish_setup kernel:system module_request;
 set_prop(goldfish_setup, qemu_prop);
 get_prop(goldfish_setup, net_share_prop);
-# Allow goldfish_setup to run /system/bin/ip and /system/bin/iw
-# TODO(b/113124961): clean up this Treble violation.
-typeattribute goldfish_setup vendor_executes_system_violators;
-allow goldfish_setup system_file:file rx_file_perms;
 # Allow goldfish_setup to run init.wifi.sh
 allow goldfish_setup goldfish_setup_exec:file execute_no_trans;
 #Allow goldfish_setup to run createns in its own domain
@@ -52,4 +49,5 @@
 # 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;
-allow goldfish_setup system_file:file { execute getattr open read };
+#allow goldfish_setup system_file:file { execute getattr open read };
+dontaudit goldfish_setup self:capability dac_override;
diff --git a/sepolicy/common/ipv6proxy.te b/sepolicy/common/ipv6proxy.te
deleted file mode 100644
index a631501..0000000
--- a/sepolicy/common/ipv6proxy.te
+++ /dev/null
@@ -1,18 +0,0 @@
-# IPv6 proxying
-type ipv6proxy, domain;
-type ipv6proxy_exec, exec_type, vendor_file_type, file_type;
-
-init_daemon_domain(ipv6proxy)
-net_domain(ipv6proxy)
-
-# Allow ipv6proxy to be run by execns in its own domain
-domain_auto_trans(execns, ipv6proxy_exec, ipv6proxy);
-allow ipv6proxy execns:fd use;
-
-set_prop(ipv6proxy, net_eth0_prop);
-dontaudit ipv6proxy kernel:system module_request;
-allow ipv6proxy self:capability { sys_admin sys_module net_admin net_raw };
-allow ipv6proxy self:packet_socket { bind create read };
-allow ipv6proxy self:netlink_route_socket nlmsg_write;
-allow ipv6proxy varrun_file:dir search;
-allowxperm ipv6proxy self:udp_socket ioctl { SIOCSIFFLAGS SIOCGIFHWADDR };
diff --git a/sepolicy/common/netmgr.te b/sepolicy/common/netmgr.te
index 96caf82..c7abcb9 100644
--- a/sepolicy/common/netmgr.te
+++ b/sepolicy/common/netmgr.te
@@ -7,25 +7,31 @@
 
 allow netmgr execns:fd use;
 
+# Set property to indicate bridging is complete
+set_prop(netmgr, vendor_net);
 # 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:file create_file_perms;
 allow netmgr hostapd_data_file:dir rw_dir_perms;
 # Assign addresses to new interfaces as hostapd brings them up
 allow netmgr self:capability { net_raw net_admin };
-allow netmgr self:socket { create ioctl };
-allow netmgr self:packet_socket { ioctl getopt };
+allow netmgr self:socket { create };
+allow netmgr self:unix_dgram_socket ioctl;
+allow netmgr self:packet_socket { ioctl getopt map };
 allow netmgr self:udp_socket { ioctl };
 allow netmgr proc_net:file { read getattr open };
-allowxperm netmgr self:socket ioctl { SIOCETHTOOL };
-allowxperm netmgr self:udp_socket ioctl { SIOCSIFADDR SIOCSIFNETMASK SIOCSIFBRDADDR };
+allowxperm netmgr self:unix_dgram_socket ioctl { SIOCETHTOOL };
+allowxperm netmgr self:udp_socket ioctl { SIOCSIFFLAGS
+                                          SIOCBRADDBR
+                                          SIOCBRADDIF
+                                          SIOCBRDELIF };
 allowxperm netmgr self:packet_socket ioctl { SIOCGIFINDEX SIOCGIFHWADDR };
 
-# Allow netmgr to run iptables to block and unblock network traffic
-# TODO(b/113124961): clean up this Treble violation.
-typeattribute netmgr vendor_executes_system_violators;
-allow netmgr system_file:file execute_no_trans;
-allow netmgr system_file:file lock;
+# Allow netmgr to run ip and modify route table to block unblock traffic
+allow netmgr goldfish_ip_exec:file execute_no_trans;
+allow netmgr self:netlink_route_socket nlmsg_write;
 # Packet socket for wifi forwarding
 allow netmgr self:packet_socket { bind create read setopt write };
+allow netmgr kernel:system module_request;
+allow netmgr self:capability sys_module;
diff --git a/sepolicy/common/property.te b/sepolicy/common/property.te
index 3593a39..4046105 100644
--- a/sepolicy/common/property.te
+++ b/sepolicy/common/property.te
@@ -2,4 +2,6 @@
 type qemu_cmdline, property_type;
 type radio_noril_prop, property_type;
 type net_eth0_prop, property_type;
+type net_radio0_prop, property_type;
 type net_share_prop, property_type;
+type vendor_net, property_type;
diff --git a/sepolicy/common/property_contexts b/sepolicy/common/property_contexts
index 4a82974..86f0114 100644
--- a/sepolicy/common/property_contexts
+++ b/sepolicy/common/property_contexts
@@ -1,9 +1,11 @@
 qemu.                   u:object_r:qemu_prop:s0
 qemu.cmdline            u:object_r:qemu_cmdline:s0
 vendor.qemu		u:object_r:qemu_prop:s0
+vendor.network          u:object_r:vendor_net:s0
 ro.emu.                 u:object_r:qemu_prop:s0
 ro.emulator.            u:object_r:qemu_prop:s0
 ro.radio.noril          u:object_r:radio_noril_prop:s0
 net.eth0.               u:object_r:net_eth0_prop:s0
+net.radio0.             u:object_r:net_radio0_prop:s0
 net.shared_net_ip       u:object_r:net_share_prop:s0
 ro.zygote.disable_gl_preload            u:object_r:qemu_prop:s0
diff --git a/sepolicy/common/radio.te b/sepolicy/common/radio.te
index 742d3b2..38faf6f 100644
--- a/sepolicy/common/radio.te
+++ b/sepolicy/common/radio.te
@@ -1,3 +1,4 @@
 # Allow the radio to read these properties, they only have an SELinux label in
 # the emulator.
 get_prop(radio, net_eth0_prop);
+allow radio net_radio0_prop:file { getattr read open map };
diff --git a/sepolicy/common/rild.te b/sepolicy/common/rild.te
index ea18373..c17fa5e 100644
--- a/sepolicy/common/rild.te
+++ b/sepolicy/common/rild.te
@@ -1,3 +1,10 @@
 # Allow rild to read these properties, they only have an SELinux label in the
 # emulator.
 get_prop(rild, net_eth0_prop);
+get_prop(rild, net_radio0_prop);
+
+# IPv6 router advertisement detection
+allow rild self:packet_socket { bind create ioctl read setopt };
+allowxperm rild self:packet_socket ioctl { SIOCGIFFLAGS
+                                           SIOCSIFFLAGS
+                                           SIOCGIFHWADDR };
diff --git a/sepolicy/common/wifi_forwarder.te b/sepolicy/common/wifi_forwarder.te
new file mode 100644
index 0000000..3eb7bba
--- /dev/null
+++ b/sepolicy/common/wifi_forwarder.te
@@ -0,0 +1,11 @@
+# Wifi forwarder
+type wifi_forwarder, domain;
+type wifi_forwarder_exec, exec_type, vendor_file_type, file_type;
+
+init_daemon_domain(wifi_forwarder)
+net_domain(wifi_forwarder)
+
+allow wifi_forwarder self:capability { net_admin };
+# Generic netlink socket for wifi forwarding
+allow wifi_forwarder self:netlink_generic_socket { bind create getattr setopt read write };
+
diff --git a/tnc/Android.bp b/tnc/Android.bp
new file mode 100644
index 0000000..fb0236f
--- /dev/null
+++ b/tnc/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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: "tnc",
+    vendor: true,
+    cflags: [
+             "-Wall",
+             "-Werror",
+            ],
+    srcs: [
+           "main.cpp",
+          ],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+    ],
+}
+
diff --git a/tnc/main.cpp b/tnc/main.cpp
new file mode 100644
index 0000000..d6ffd11
--- /dev/null
+++ b/tnc/main.cpp
@@ -0,0 +1,217 @@
+
+#include <errno.h>
+#include <netdb.h>
+#include <net/if.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <initializer_list>
+
+static void usage(const char* program) {
+    fprintf(stderr, "Usage: %s [-s|-c|-b] <ip> <port>\n", program);
+}
+
+enum class Mode {
+    Bridge,
+    Client,
+    Server,
+};
+
+bool resolve(const char* name, const char* port, struct addrinfo** addrs) {
+    struct addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_DGRAM;
+
+    int res = ::getaddrinfo(name, port, &hints, addrs);
+    if (res != 0) {
+        fprintf(stderr, "ERROR: Unable to resolve '%s' and port '%s': %s\n",
+                name, port, gai_strerror(res));
+        return false;
+    }
+    return true;
+}
+
+int runClient(struct addrinfo* addrs) {
+    int fd = -1;
+    for (struct addrinfo* addr = addrs; addr != nullptr; addr = addr->ai_next) {
+        fd = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+        if (fd < 0) {
+            continue;
+        }
+        if (::connect(fd, addr->ai_addr, addr->ai_addrlen) == 0) {
+            break;
+        }
+        ::close(fd);
+    }
+    ::freeaddrinfo(addrs);
+    if (fd < 0) {
+        fprintf(stderr, "Unable to connect to server\n");
+        return 1;
+    }
+    if (::send(fd, "boop", 4, 0) != 4) {
+        ::close(fd);
+        fprintf(stderr, "Failed to send message to server\n");
+        return 1;
+    }
+    ::close(fd);
+    return 0;
+}
+
+int runServer(struct addrinfo* addrs) {
+    int fd = -1;
+    for (struct addrinfo* addr = addrs; addr != nullptr; addr = addr->ai_next) {
+        fd = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+        if (fd < 0) {
+            continue;
+        }
+        if (::bind(fd, addr->ai_addr, addr->ai_addrlen) == 0) {
+            break;
+        }
+        ::close(fd);
+    }
+    ::freeaddrinfo(addrs);
+    if (fd < 0) {
+        fprintf(stderr, "Unable to bind to address\n");
+        return 1;
+    }
+    char buffer[1024];
+    for (;;) {
+        struct sockaddr_storage addr;
+        socklen_t addrSize = sizeof(addr);
+        ssize_t bytesRead = recvfrom(fd, buffer, sizeof(buffer), 0,
+                                     reinterpret_cast<struct sockaddr*>(&addr),
+                                     &addrSize);
+        if (bytesRead < 0) {
+            if (errno == EINTR) {
+                continue;
+            }
+            fprintf(stderr, "Error receiving on socket: %s\n", strerror(errno));
+            ::close(fd);
+            return 1;
+        } else if (bytesRead == 0) {
+            fprintf(stderr, "Socket unexpectedly closed\n");
+            ::close(fd);
+            return 1;
+        }
+        printf("Received message from client '%*s'\n",
+               static_cast<int>(bytesRead), buffer);
+    }
+}
+
+static const char kBridgeName[] = "br0";
+
+static int configureBridge() {
+    int fd = ::socket(AF_LOCAL, SOCK_STREAM, 0);
+    if (fd < 0) {
+        fprintf(stderr, "ERROR: Could not open bridge socket: %s\n",
+                strerror(errno));
+        return 1;
+    }
+
+    int res = ::ioctl(fd, SIOCBRADDBR, kBridgeName);
+    if (res < 0) {
+        fprintf(stderr, "ERROR: cannot create bridge: %s\n", strerror(errno));
+        ::close(fd);
+        return 1;
+    }
+
+    for (const auto& ifName : { "eth0", "wlan1", "radio0-peer" }) {
+        struct ifreq request;
+        memset(&request, 0, sizeof(request));
+        request.ifr_ifindex = if_nametoindex(ifName);
+        if (request.ifr_ifindex == 0) {
+            fprintf(stderr, "ERROR: Unable to get interface index for %s\n",
+                    ifName);
+            ::close(fd);
+            return 1;
+        }
+        strlcpy(request.ifr_name, kBridgeName, sizeof(request.ifr_name));
+        res = ::ioctl(fd, SIOCBRADDIF, &request);
+        if (res < 0) {
+            fprintf(stderr, "ERROR: cannot add if %s to bridge: %s\n",
+                    ifName, strerror(errno));
+            ::close(fd);
+            return 1;
+        }
+    }
+
+    struct ifreq request;
+    memset(&request, 0, sizeof(request));
+    request.ifr_ifindex = if_nametoindex(kBridgeName);
+    if (request.ifr_ifindex == 0) {
+        fprintf(stderr, "ERROR: Unable to get interface index for %s\n",
+                kBridgeName);
+        ::close(fd);
+        return 1;
+    }
+    strlcpy(request.ifr_name, kBridgeName, sizeof(request.ifr_name));
+    res = ::ioctl(fd, SIOCGIFFLAGS, &request);
+    if (res != 0) {
+        fprintf(stderr, "ERROR: Unable to get interface index for %s\n",
+                kBridgeName);
+        ::close(fd);
+        return 1;
+    }
+    if ((request.ifr_flags & IFF_UP) == 0) {
+        // Bridge is not up, it needs to be up to work
+        request.ifr_flags |= IFF_UP;
+        res = ::ioctl(fd, SIOCSIFFLAGS, &request);
+        if (res != 0) {
+            fprintf(stderr, "ERROR: Unable to set interface flags for %s\n",
+                    kBridgeName);
+            ::close(fd);
+            return 1;
+        }
+    }
+
+    ::close(fd);
+    return 0;
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 2) {
+        usage(argv[0]);
+        return 1;
+    }
+
+    Mode mode;
+    if (strcmp("-b", argv[1]) == 0) {
+        mode = Mode::Bridge;
+    } else if (strcmp("-c", argv[1]) == 0) {
+        mode = Mode::Client;
+    } else if (strcmp("-s", argv[1]) == 0) {
+        mode = Mode::Server;
+    } else {
+        fprintf(stderr, "ERROR: Invalid option '%s'\n", argv[1]);
+        usage(argv[0]);
+        return 1;
+    }
+
+    struct addrinfo* addrs = nullptr;
+    if (mode == Mode::Client || mode == Mode::Server) {
+        if (argc != 4) {
+            usage(argv[0]);
+            return 1;
+        }
+        if (!resolve(argv[2], argv[3], &addrs)) {
+            usage(argv[0]);
+            return 1;
+        }
+    }
+
+    switch (mode) {
+        case Mode::Bridge:
+            return configureBridge();
+        case Mode::Client:
+            return runClient(addrs);
+        case Mode::Server:
+            return runServer(addrs);
+    }
+}
+
diff --git a/vendor.mk b/vendor.mk
index 16dcbf9..fe4f6a8 100644
--- a/vendor.mk
+++ b/vendor.mk
@@ -86,6 +86,7 @@
     android.hardware.wifi@1.0-service \
     android.hardware.biometrics.fingerprint@2.1-service \
     sh_vendor \
+    ip_vendor \
     iw_vendor \
     audio.r_submix.default \
     local_time.default \
@@ -145,12 +146,11 @@
 PRODUCT_PACKAGES += \
 	createns \
 	dhcpclient \
-	dhcpserver \
 	execns \
 	hostapd \
 	hostapd_nohidl \
-	ipv6proxy \
 	netmgr \
+	wifi_forwarder \
 	wpa_supplicant \
 
 PRODUCT_PACKAGES += android.hardware.thermal@2.0-service.mock
diff --git a/wifi/createns/createns.cpp b/wifi/createns/createns.cpp
index 66f6dd3..95aff53 100644
--- a/wifi/createns/createns.cpp
+++ b/wifi/createns/createns.cpp
@@ -103,8 +103,8 @@
     path += ".pid";
 
     Fd fd(::open(path.c_str(),
-                 O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC,
-                 S_IRUSR | S_IRGRP | S_IROTH));
+                 O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC,
+                 S_IRUSR | S_IWUSR | S_IRGRP));
     if (fd.get() == -1) {
         ALOGE("Unable to create file '%s': %s", path.c_str(), strerror(errno));
         return false;
@@ -223,6 +223,7 @@
         ALOGE("Failed to create pipe: %s", strerror(errno));
         return 1;
     }
+
     Fd readPipe(fds[0]);
     Fd writePipe(fds[1]);
 
@@ -239,7 +240,8 @@
     }
     {
         // Open and then immediately close the fd
-        Fd fd(::open(path.c_str(), O_CREAT | O_EXCL | O_RDONLY | O_CLOEXEC, 0));
+        Fd fd(::open(path.c_str(), O_CREAT | O_TRUNC | O_RDONLY | O_CLOEXEC,
+                     S_IRUSR | S_IWUSR | S_IRGRP));
         if (fd.get() == -1) {
             ALOGE("Failed to open file %s: %s", path.c_str(), strerror(errno));
             return 1;
diff --git a/wifi/init.wifi.sh b/wifi/init.wifi.sh
index ff64e91..fd84d12 100755
--- a/wifi/init.wifi.sh
+++ b/wifi/init.wifi.sh
@@ -42,19 +42,35 @@
 #
 
 NAMESPACE="router"
-rm -rf /data/vendor/var/run/netns/${NAMESPACE}
-rm -rf /data/vendor/var/run/netns/${NAMESPACE}.pid
-# Lower the MTU of the WiFi interface to prevent issues with packet injection.
-# The MTU of the WiFi monitor interface cannot be higher than 1500 but injection
-# requires extra space for injection headers which count against the MTU. So if
-# a 1500 byte payload needs to be injected it will fail because with the
-# additional headers the total amount of data will exceed 1500 bytes. This way
-# the payload is restricted to a smaller size that should leave room for the
-# injection headers.
-/system/bin/ip link set wlan0 mtu 1400
 
 createns ${NAMESPACE}
 
+# createns will have created a file that contains the process id (pid) of a
+# process running in the network namespace. This pid is needed for some commands
+# to access the namespace.
+PID=$(</data/vendor/var/run/netns/${NAMESPACE}.pid)
+
+/vendor/bin/ip link set eth0 netns ${PID}
+
+/vendor/bin/ip link add radio0 type veth peer name radio0-peer netns ${PID}
+
+# Enable privacy addresses for radio0, this is done by the framework for wlan0
+sysctl -wq net.ipv6.conf.radio0.use_tempaddr=2
+
+execns ${NAMESPACE} /vendor/bin/ip link set radio0-peer up
+
+execns ${NAMESPACE} /vendor/bin/ip link set eth0 up
+
+/vendor/bin/ip link set radio0 up
+
+execns ${NAMESPACE} /vendor/bin/ip link set wlan1 up
+
+/vendor/bin/iw phy phy1 set netns $PID
+
+setprop ctl.start netmgr
+
+setprop ctl.start wifi_forwarder
+
 # 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.
@@ -64,48 +80,5 @@
     chmod 660 /data/vendor/wifi/hostapd/hostapd.conf
 fi
 
-# createns will have created a file that contains the process id (pid) of a
-# process running in the network namespace. This pid is needed for some commands
-# to access the namespace.
-PID=$(cat /data/vendor/var/run/netns/${NAMESPACE}.pid)
-
-# Move the WiFi monitor interface to the other namespace and bring it up. This
-# is what we use for injecting WiFi frames from the outside world.
-/system/bin/ip link set hwsim0 netns ${PID}
-execns ${NAMESPACE} /system/bin/ip link set hwsim0 up
-
-# Start the network manager as soon as possible after the namespace is available.
-# This ensures that anything that follows is properly managed and monitored.
-setprop ctl.start netmgr
-
-/system/bin/ip link set eth0 netns ${PID}
-/system/bin/ip link add radio0 type veth peer name radio0-peer
-/system/bin/ip link set radio0-peer netns ${PID}
-# Enable privacy addresses for radio0, this is done by the framework for wlan0
-sysctl -wq net.ipv6.conf.radio0.use_tempaddr=2
-/system/bin/ip addr add 192.168.200.2/24 broadcast 192.168.200.255 dev radio0
-execns ${NAMESPACE} /system/bin/ip addr add 192.168.200.1/24 dev radio0-peer
-execns ${NAMESPACE} sysctl -wq net.ipv6.conf.all.forwarding=1
-execns ${NAMESPACE} /system/bin/ip link set radio0-peer up
-# Start the dhcp client for eth0 to acquire an address
-setprop ctl.start dhcpclient_rtr
-# Create iptables entries. -w will cause an indefinite wait for the exclusive
-# lock. Without this flag iptables can sporadically fail if something else is
-# modifying the iptables at the same time. -W indicates the number of micro-
-# seconds between each retry. The default is one second which seems like a long
-# time. Keep this short so we don't slow down startup too much.
-execns ${NAMESPACE} /system/bin/iptables -w -W 50000 -t nat -A POSTROUTING -s 192.168.232.0/21 -o eth0 -j MASQUERADE
-execns ${NAMESPACE} /system/bin/iptables -w -W 50000 -t nat -A POSTROUTING -s 192.168.200.0/24 -o eth0 -j MASQUERADE
-/vendor/bin/iw phy phy1 set netns $PID
-
-execns ${NAMESPACE} /system/bin/ip addr add 192.168.232.1/21 dev wlan1
-execns ${NAMESPACE} /system/bin/ip link set wlan1 mtu 1400
-execns ${NAMESPACE} /system/bin/ip link set wlan1 up
-# Start the IPv6 proxy that will enable use of IPv6 in the main namespace
-setprop ctl.start ipv6proxy
-execns ${NAMESPACE} sysctl -wq net.ipv4.ip_forward=1
 # Start hostapd, the access point software
 setprop ctl.start emu_hostapd
-# Start DHCP server for the wifi interface
-setprop ctl.start dhcpserver
-/system/bin/ip link set radio0 up
diff --git a/wifi/ipv6proxy/Android.mk b/wifi/ipv6proxy/Android.mk
deleted file mode 100644
index a1695d6..0000000
--- a/wifi/ipv6proxy/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-	address.cpp \
-	interface.cpp \
-	log.cpp \
-	main.cpp \
-	namespace.cpp \
-	packet.cpp \
-	proxy.cpp \
-	router.cpp \
-	socket.cpp \
-
-
-LOCAL_CPPFLAGS += -Werror
-LOCAL_SHARED_LIBRARIES := libcutils liblog
-LOCAL_PROPRIETARY_MODULE := true
-LOCAL_MODULE := ipv6proxy
-
-LOCAL_MODULE_CLASS := EXECUTABLES
-
-include $(BUILD_EXECUTABLE)
diff --git a/wifi/ipv6proxy/address.cpp b/wifi/ipv6proxy/address.cpp
deleted file mode 100644
index def9c7b..0000000
--- a/wifi/ipv6proxy/address.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.
- */
-
-#include "address.h"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <linux/if_ether.h>
-#include <linux/if_packet.h>
-#include <netdb.h>
-#include <net/if.h>
-#include <string.h>
-#include <sys/ioctl.h>
-
-#include "socket.h"
-
-std::string addrToStr(const struct in6_addr& addr) {
-    char buf[INET6_ADDRSTRLEN];
-    if (inet_ntop(AF_INET6, &addr, buf, sizeof(buf)) == nullptr) {
-        return "[unknown]";
-    }
-    return buf;
-}
-
-Address::Address() {
-    mStorage.reserve(sizeof(struct sockaddr_storage));
-}
-
-Address::Address(const struct sockaddr_nl& address)
-        : mStorage(sizeof(address)) {
-    memcpy(mStorage.data(), &address, sizeof(address));
-}
-Address::Address(const struct sockaddr_in6& address)
-        : mStorage(sizeof(address)) {
-    memcpy(mStorage.data(), &address, sizeof(address));
-}
-
-Address::Address(struct in6_addr address)
-        : mStorage(sizeof(struct sockaddr_in6)) {
-    auto sockaddr = reinterpret_cast<struct sockaddr_in6*>(mStorage.data());
-    sockaddr->sin6_family = AF_INET6;
-    sockaddr->sin6_addr = address;
-}
-
-void Address::reset() {
-    mStorage.resize(sizeof(struct sockaddr_storage));
-}
-
-Result Address::resolveInet(const std::string& address) {
-    struct addrinfo hints;
-    memset(&hints, 0, sizeof(hints));
-    hints.ai_family = AF_INET6;
-    hints.ai_socktype = SOCK_RAW;
-    struct addrinfo* addrinfo;
-    int res = ::getaddrinfo(address.c_str(), nullptr, &hints, &addrinfo);
-    if (res != 0) {
-        return Result::error(gai_strerror(res));
-    }
-    mStorage.resize(addrinfo->ai_addrlen);
-    memcpy(mStorage.data(), addrinfo->ai_addr, mStorage.size());
-    freeaddrinfo(addrinfo);
-
-    return Result::success();
-}
-
-Result Address::resolveEth(const std::string& interfaceName) {
-    mStorage.resize(sizeof(struct sockaddr_ll));
-    memset(mStorage.data(), 0, mStorage.size());
-    auto addr = reinterpret_cast<struct sockaddr_ll*>(mStorage.data());
-    addr->sll_family = AF_PACKET;
-    addr->sll_protocol = htons(ETH_P_IPV6);
-
-    unsigned int index = if_nametoindex(interfaceName.c_str());
-    if (index == 0) {
-        return Result::error(strerror(errno));
-    }
-    addr->sll_ifindex = index;
-
-    struct ifreq request;
-    memset(&request, 0, sizeof(request));
-    strncpy(request.ifr_name, interfaceName.c_str(), sizeof(request.ifr_name));
-    request.ifr_name[sizeof(request.ifr_name) - 1] = '\0';
-
-    Socket socket;
-    Result res = socket.open(AF_INET, SOCK_DGRAM, IPPROTO_IP);
-    if (!res) {
-        return res;
-    }
-    int status = ::ioctl(socket.get(), SIOCGIFHWADDR, &request);
-    if (status != 0) {
-        return Result::error(strerror(errno));
-    }
-    memcpy(addr->sll_addr, request.ifr_addr.sa_data, 6);
-
-    return Result::success();
-}
diff --git a/wifi/ipv6proxy/address.h b/wifi/ipv6proxy/address.h
deleted file mode 100644
index f9cec0b..0000000
--- a/wifi/ipv6proxy/address.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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 "result.h"
-
-#include <linux/netlink.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include <string>
-#include <vector>
-
-// Convert an IPv6 address struct to a string for debugging purposes
-std::string addrToStr(const struct in6_addr& addr);
-
-// Represents any kind of address in a struct sockaddr.
-class Address {
-public:
-    Address();
-    explicit Address(const struct sockaddr_nl& address);
-    explicit Address(const struct sockaddr_in6& address);
-    explicit Address(struct in6_addr address);
-
-    template<typename T>
-    const T* get() const {
-        return reinterpret_cast<const T*>(mStorage.data());
-    }
-
-    template<typename T>
-    T* get() {
-        return reinterpret_cast<T*>(mStorage.data());
-    }
-
-    ssize_t size() const { return mStorage.size(); }
-
-    // Reset the address to be the max size possible for an address
-    void reset();
-
-    // Resolve |address| into an IPv6 address. |address| may be either a domain
-    // name or just a string containing a numeric address.
-    Result resolveInet(const std::string& address);
-    // Resolve |interfaceName| into a link layer address. This can be used to
-    // create a struct sockaddr_nl that can be used to listen on the given
-    // interface at the link layer.
-    Result resolveEth(const std::string& interfaceName);
-private:
-    std::vector<char> mStorage;
-};
-
diff --git a/wifi/ipv6proxy/interface.cpp b/wifi/ipv6proxy/interface.cpp
deleted file mode 100644
index e9597ab..0000000
--- a/wifi/ipv6proxy/interface.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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.
- */
-
-#include "interface.h"
-
-#include <errno.h>
-#include <linux/if_ether.h>
-#include <net/if.h>
-#include <string.h>
-#include <sys/ioctl.h>
-
-#include "log.h"
-
-Interface::Interface(const std::string& name) : mName(name) {
-}
-
-bool Interface::init() {
-    // setAllMulti will set the ALLMULTI flag for the interface, this allows us
-    // to capture all the traffic needed to perform proxying.
-    return setAllMulti() &&
-           resolveAddresses() &&
-           configureIpSocket() &&
-           configureIcmpSocket();
-}
-
-bool Interface::setAllMulti() {
-    struct ifreq request;
-    memset(&request, 0, sizeof(request));
-    strncpy(request.ifr_name, mName.c_str(), sizeof(request.ifr_name));
-    request.ifr_name[sizeof(request.ifr_name) - 1] = '\0';
-
-    Socket socket;
-    Result res = socket.open(AF_INET, SOCK_DGRAM, IPPROTO_IP);
-    if (!res) {
-        loge("Failed to open IP socket for interface %s: %s\n",
-             mName.c_str(), strerror(errno));
-        return false;
-    }
-    int status = ::ioctl(socket.get(), SIOCGIFFLAGS, &request);
-    if (status != 0) {
-        loge("Failed to get interface flags for %s: %s\n",
-             mName.c_str(), strerror(errno));
-        return false;
-    }
-
-    if ((request.ifr_flags & IFF_ALLMULTI) != 0) {
-        // AllMulti is already enabled, nothing to do
-        return true;
-    }
-
-    request.ifr_flags |= IFF_ALLMULTI;
-
-    status = ::ioctl(socket.get(), SIOCSIFFLAGS, &request);
-    if (status != 0) {
-        loge("Failed to enable AllMulti flag for %s: %s\n",
-             mName.c_str(), strerror(errno));
-        return false;
-    }
-    return true;
-}
-
-bool Interface::resolveAddresses() {
-    Result res = mLinkAddr.resolveEth(mName);
-    if (!res) {
-        loge("Unable to resolve interface %s: %s\n",
-             mName.c_str(), res.c_str());
-        return false;
-    }
-    mIndex = if_nametoindex(mName.c_str());
-    if (mIndex == 0) {
-        loge("Unable to get interface index for '%s': %s\n",
-             mName.c_str(), strerror(errno));
-        return false;
-    }
-    return true;
-}
-
-bool Interface::configureIcmpSocket() {
-    Result res = mIcmpSocket.open(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
-    if (!res) {
-        loge("Error opening socket: %s\n", res.c_str());
-        return false;
-    }
-
-    // The ICMP messages we are going to send need a hop limit of 255 to be
-    // accepted.
-    res = mIcmpSocket.setMulticastHopLimit(255);
-    if (!res) {
-        loge("Error setting socket hop limit: %s\n", res.c_str());
-        return false;
-    }
-    res = mIcmpSocket.setUnicastHopLimit(255);
-    if (!res) {
-        loge("Error setting socket hop limit: %s\n", res.c_str());
-        return false;
-    }
-
-    // We only care about one specific interface
-    res = mIcmpSocket.setInterface(mName);
-    if (!res) {
-        loge("Error socket interface: %s\n", res.c_str());
-        return false;
-    }
-
-    // Make sure the socket allows transparent proxying, this allows sending of
-    // packets with a source address that is different from the interface.
-    res = mIcmpSocket.setTransparent(true);
-    if (!res) {
-        loge("Error socket interface: %s\n", res.c_str());
-        return false;
-    }
-
-    return true;
-}
-
-bool Interface::configureIpSocket() {
-    Result res = mIpSocket.open(AF_PACKET, SOCK_DGRAM, ETH_P_IPV6);
-    if (!res) {
-        loge("Error opening socket: %s\n", res.c_str());
-        return false;
-    }
-
-    res = mIpSocket.bind(mLinkAddr);
-    if (!res) {
-        loge("Error binding socket: %s\n", res.c_str());
-        return false;
-    }
-    return true;
-}
-
diff --git a/wifi/ipv6proxy/interface.h b/wifi/ipv6proxy/interface.h
deleted file mode 100644
index e6e5d78..0000000
--- a/wifi/ipv6proxy/interface.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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 <stdint.h>
-#include <string>
-
-#include "address.h"
-#include "socket.h"
-
-// A class that contains the information used by the proxy that is specific to a
-// single interface. This includes the name and index of the interface as well
-// as the sockets used to send and receive data on the interface.
-//
-// The class also contains the functionality needed to initialized and configure
-// the interface, sockets and addresses.
-class Interface {
-public:
-    explicit Interface(const std::string& name);
-
-    bool init();
-
-    const std::string& name() const { return mName; }
-    uint32_t index() const { return mIndex; }
-    Socket& ipSocket() { return mIpSocket; }
-    Socket& icmpSocket() { return mIcmpSocket; }
-    const Address& linkAddr() const { return mLinkAddr; }
-
-private:
-    bool setAllMulti();
-    bool resolveAddresses();
-    bool configureIcmpSocket();
-    bool configureIpSocket();
-
-    std::string mName;
-    uint32_t mIndex;
-    Socket mIpSocket;
-    Socket mIcmpSocket;
-    Address mLinkAddr;
-};
-
diff --git a/wifi/ipv6proxy/log.cpp b/wifi/ipv6proxy/log.cpp
deleted file mode 100644
index a23b062..0000000
--- a/wifi/ipv6proxy/log.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.
- */
-
-#include "log.h"
-
-#ifndef ANDROID
-#include <stdarg.h>
-#ifdef USE_LOG_TIMESTAMPS
-#include <time.h>
-#endif  // USE_LOG_TIMESTAMPS
-
-static void vlogf(FILE* stream, const char* fmt, va_list args) {
-#ifdef USE_LOG_TIMESTAMPS
-    struct timespec ts;
-    struct tm localTime;
-    static bool newLine = true;
-    if (newLine && clock_gettime(CLOCK_REALTIME, &ts) == 0) {
-        time_t now = ts.tv_sec;
-        if (localtime_r(&now, &localTime)) {
-            char format[32];
-            char timestamp[1024];
-            snprintf(format, sizeof(format), "[%%T.%03lld] ", (long long)(ts.tv_nsec) / 1000000);
-            strftime(timestamp, sizeof(timestamp), format, &localTime);
-            fprintf(stream, "%s ", timestamp);
-        }
-    }
-    newLine = (fmt[strlen(fmt) - 1] == '\n');
-#endif  // USE_LOG_TIMESTAMPS
-    vfprintf(stream, fmt, args);
-}
-
-static void logf(FILE* stream, const char* fmt, ...) {
-    va_list args;
-    va_start(args, fmt);
-    vlogf(stream, fmt, args);
-    va_end(args);
-}
-
-void loge(const char* fmt, ...) {
-    va_list args;
-    va_start(args, fmt);
-    vlogf(stderr, fmt, args);
-    va_end(args);
-}
-
-void logd(const char* fmt, ...) {
-    va_list args;
-    va_start(args, fmt);
-    vlogf(stdout, fmt, args);
-    va_end(args);
-}
-#endif  // !ANDROID
diff --git a/wifi/ipv6proxy/log.h b/wifi/ipv6proxy/log.h
deleted file mode 100644
index 53e8935..0000000
--- a/wifi/ipv6proxy/log.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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
-
-#ifdef ANDROID
-
-#define LOG_TAG "ipv6proxy"
-#include <log/log.h>
-
-#define loge(...) ALOGE(__VA_ARGS__)
-#define logd(...) ALOGD(__VA_ARGS__)
-
-#else
-#include <stdio.h>
-
-void loge(const char* fmt, ...);
-void logd(const char* fmt, ...);
-
-#endif
diff --git a/wifi/ipv6proxy/main.cpp b/wifi/ipv6proxy/main.cpp
deleted file mode 100644
index 91445ca..0000000
--- a/wifi/ipv6proxy/main.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.
- */
-
-#include <vector>
-
-#include <string.h>
-
-#include "log.h"
-#include "namespace.h"
-#include "proxy.h"
-
-void usage(const char* program) {
-    loge("Usage: %s -n [namespace|pid] -o <outer if> -i <inner ifs>\n",
-         program);
-    loge("  -o   Outer interface that connects to an existing IPv6 network.\n"
-         "  -i   Comma separated list of inner interfaces that would like\n"
-         "       to access the IPv6 network available on the outer interface.\n"
-         "  -n   Optional parameter that causes the proxy to run in the given\n"
-         "       network namespace. If a name is given instead of a PID the\n"
-         "       namespace is expected to be set up by iproute2 or with a\n"
-         "       similar approach where the namespace is linked in\n"
-         "       /var/run/netns. A PID is assumed if the argument is numeric.\n"
-         "       If providing a PID the same namespace that the PID is\n"
-         "       running in will be used. In this scenario there is no\n"
-         "       requirement for a file in /var/run/netns.\n"
-         "\n"
-         "       The proxy will ensure that router solicitations from inner\n"
-         "       interfaces are forwarded to the outer interface and that\n"
-         "       router advertisements from the outer interface are forwarded\n"
-         "       to the inner interfaces. In addition to this neighbor\n"
-         "       solicitations and advertisements will also be forwarded in a\n"
-         "       way that enables IPv6 connectivity and routes will be set up\n"
-         "       for source addresses on the inner interfaces so that replies\n"
-         "       can reach those sources as expected.\n"
-    );
-}
-
-static std::vector<const char*> splitString(char* str, char delimiter) {
-    std::vector<const char*> parts;
-    char* part = nullptr;
-    do {
-        parts.push_back(str);
-        part = strchr(str, delimiter);
-        if (part != nullptr) {
-            *part = '\0';
-            str = part + 1;
-        }
-    } while (part != nullptr);
-    return parts;
-}
-
-int main(int argc, char* argv[]) {
-    char* inner = nullptr;
-    const char* outer = nullptr;
-    const char* netNamespace = nullptr;
-    for (int i = 1; i < argc; ++i) {
-        if (strcmp(argv[i], "-o") == 0) {
-            outer = argv[++i];
-        } else if (strcmp(argv[i], "-i") == 0) {
-            inner = argv[++i];
-        } else if (strcmp(argv[i], "-h") == 0 ||
-                   strcmp(argv[i], "--help") == 0) {
-            usage(argv[0]);
-            return 1;
-        } else if (strcmp(argv[i], "-n") == 0) {
-            netNamespace = argv[++i];
-        } else {
-            loge("ERROR: Unknown argument '%s'\n\n", argv[i]);
-            usage(argv[0]);
-            return 1;
-        }
-    }
-    bool error = false;
-    if (inner == nullptr) {
-        loge("ERROR: Missing inner interface\n");
-        error = true;
-    }
-    if (outer == nullptr) {
-        loge("ERROR: Missing outer interface\n");
-        error = true;
-    }
-    if (netNamespace) {
-        if (!setNetworkNamespace(netNamespace)) {
-            error = true;
-        }
-    }
-    if (error) {
-        usage(argv[0]);
-        return 1;
-    }
-
-    std::vector<const char*> innerInterfaces = splitString(inner, ',');
-
-    Proxy proxy(outer, innerInterfaces.begin(), innerInterfaces.end());
-
-    return proxy.run();
-}
-
diff --git a/wifi/ipv6proxy/message.h b/wifi/ipv6proxy/message.h
deleted file mode 100644
index 9b35dae..0000000
--- a/wifi/ipv6proxy/message.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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
-
-class Message {
-public:
-
-    size_t size() const { return mSize; }
-    size_t capacity() const { return sizeof(mData); }
-    const char* data() const { return mData; }
-    char* data() { return mData; }
-
-    void setSize(size_t size) { mSize = size; }
-
-protected:
-    char mData[8192];
-    size_t mSize;
-};
-
diff --git a/wifi/ipv6proxy/namespace.cpp b/wifi/ipv6proxy/namespace.cpp
deleted file mode 100644
index 04841c2..0000000
--- a/wifi/ipv6proxy/namespace.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.
- */
-
-#include "namespace.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/limits.h>
-#include <sched.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include "log.h"
-
-static const char kNetNsDir[] = "/data/vendor/var/run/netns";
-
-// Set the current namespace to that of the /proc/<pid>/ns/net provided in
-// |path|. This may be a link or mount point for that same file, anything that
-// when opened will be an fd usable by setns is fine.
-static bool setNamespaceFromPath(const char* path) {
-    int nsFd = open(path, O_RDONLY | O_CLOEXEC);
-    if (nsFd == -1) {
-        loge("Cannot open network namespace at '%s': %s\n",
-             path, strerror(errno));
-        return false;
-    }
-
-    if (setns(nsFd, CLONE_NEWNET) == -1) {
-        loge("Cannot set network namespace at '%s': %s\n",
-             path, strerror(errno));
-        close(nsFd);
-        return false;
-    }
-    close(nsFd);
-    return true;
-}
-
-bool setNetworkNamespace(const char* ns) {
-    // There is a file in the net namespace dir (usually /var/run/netns) with
-    // the same name as the namespace. This file is bound to /proc/<pid>/ns/net
-    // by the 'ip' command when the namespace is created. This allows us to
-    // access the file of a process running in that network namespace without
-    // knowing its pid, knowing the namespace name is enough.
-    //
-    // We are going to call setns which requires a file descriptor to that proc
-    // file in /proc/<pid>/net. The process has to already be running in that
-    // namespace. Since the file in the net namespace dir has been bound to
-    // such a file already we just have to open /var/run/netns/<namespace> and
-    // we have the required file descriptor.
-    char nsPath[PATH_MAX];
-    snprintf(nsPath, sizeof(nsPath), "%s/%s", kNetNsDir, ns);
-    return setNamespaceFromPath(nsPath);
-}
-
-bool setNetworkNamespace(pid_t pid) {
-    // If we know the pid we can create the path to the /proc file right away
-    // and use that when we call setns.
-    char nsPath[PATH_MAX];
-    static_assert(sizeof(pid_t) <= sizeof(unsigned long long),
-                  "Cast requires sizeof(pid_t) <= sizeof(unsigned long long)");
-    snprintf(nsPath, sizeof(nsPath), "/proc/%llu/ns/net/",
-             static_cast<unsigned long long>(pid));
-    return setNamespaceFromPath(nsPath);
-}
-
diff --git a/wifi/ipv6proxy/namespace.h b/wifi/ipv6proxy/namespace.h
deleted file mode 100644
index 8a70374..0000000
--- a/wifi/ipv6proxy/namespace.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 <sys/types.h>
-
-// Move the process into the namespace |ns|
-bool setNetworkNamespace(const char* ns);
-
-// Move the process into the same namespace as the process |pid| is in.
-bool setNetworkNamespace(pid_t pid);
diff --git a/wifi/ipv6proxy/packet.cpp b/wifi/ipv6proxy/packet.cpp
deleted file mode 100644
index fa15a75..0000000
--- a/wifi/ipv6proxy/packet.cpp
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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.
- */
-
-#include "packet.h"
-
-#include "address.h"
-
-Packet::Packet(Message& message)
-    : mMessage(message),
-      mType(Type::Other),
-      mIp(nullptr),
-      mIcmp(nullptr),
-      mFirstOpt(nullptr) {
-    if (message.size() < sizeof(ip6_hdr) + sizeof(icmp6_hdr)) {
-        mType = Type::Other;
-        return;
-    }
-    mIp = reinterpret_cast<const ip6_hdr*>(message.data());
-    uint8_t version = (mIp->ip6_vfc & 0xF0) >> 4;
-    if (version != 6 || mIp->ip6_nxt != IPPROTO_ICMPV6) {
-        mType = Type::Other;
-        return;
-    }
-
-    size_t size = message.size() - sizeof(ip6_hdr);
-    char* data = message.data() + sizeof(ip6_hdr);
-    mIcmp = reinterpret_cast<const icmp6_hdr*>(data);
-    if (mIcmp->icmp6_code != 0) {
-        // All messages we care about have a code of zero
-        mType = Type::Other;
-        return;
-    }
-
-    size_t headerSize = 0;
-    switch (mIcmp->icmp6_type) {
-    case ND_ROUTER_SOLICIT:
-        headerSize = sizeof(nd_router_solicit);
-        mType = Type::RouterSolicitation;
-        break;
-    case ND_ROUTER_ADVERT:
-        headerSize = sizeof(nd_router_advert);
-        mType = Type::RouterAdvertisement;
-        break;
-    case ND_NEIGHBOR_SOLICIT:
-        headerSize = sizeof(nd_neighbor_solicit);
-        mType = Type::NeighborSolicitation;
-        break;
-    case ND_NEIGHBOR_ADVERT:
-        headerSize = sizeof(nd_neighbor_advert);
-        mType = Type::NeighborAdvertisement;
-        break;
-    default:
-        mType = Type::Other;
-        return;
-    }
-    if (size < headerSize) {
-        mType = Type::Other;
-        return;
-    }
-
-    // We might have options
-    char* options = data + headerSize;
-    if (options + sizeof(nd_opt_hdr) < data + size) {
-        nd_opt_hdr* option = reinterpret_cast<nd_opt_hdr*>(options);
-        // Option length is in units of 8 bytes, multiply by 8 to get bytes
-        if (options + option->nd_opt_len * 8u <= data + size) {
-            mFirstOpt = option;
-        }
-    }
-}
-
-std::string Packet::description() const {
-    char buffer[256];
-    switch (mType) {
-        case Type::NeighborSolicitation: {
-                auto ns = reinterpret_cast<const nd_neighbor_solicit*>(icmp());
-                snprintf(buffer, sizeof(buffer), "Neighbor Solicitation for %s",
-                         addrToStr(ns->nd_ns_target).c_str());
-                return buffer;
-            }
-        case Type::NeighborAdvertisement: {
-                auto na = reinterpret_cast<const nd_neighbor_advert*>(icmp());
-                snprintf(buffer, sizeof(buffer),
-                         "Neighbor Advertisement for %s",
-                         addrToStr(na->nd_na_target).c_str());
-                return buffer;
-            }
-        case Type::RouterSolicitation:
-            return "Router Solicitation";
-        case Type::RouterAdvertisement:
-            return "Router Advertisement";
-        default:
-            break;
-    }
-    return "[unknown]";
-}
-
-nd_opt_hdr* Packet::firstOpt() {
-    return mFirstOpt;
-}
-
-nd_opt_hdr* Packet::nextOpt(nd_opt_hdr* currentHeader) {
-    char* end = mMessage.data() + mMessage.size();
-    char* current = reinterpret_cast<char*>(currentHeader);
-    if (currentHeader < mFirstOpt || current >= end) {
-        // The provided header does not belong to this packet info.
-        return nullptr;
-    }
-    char* next = current + currentHeader->nd_opt_len * 8u;
-    if (next >= end) {
-        // The next header points passed the message data
-        return nullptr;
-    }
-    nd_opt_hdr* nextHeader = reinterpret_cast<nd_opt_hdr*>(next);
-    if (next + nextHeader->nd_opt_len * 8u > end) {
-        // The next option extends beyond the message data
-        return nullptr;
-    }
-    return nextHeader;
-}
-
diff --git a/wifi/ipv6proxy/packet.h b/wifi/ipv6proxy/packet.h
deleted file mode 100644
index 49e69d4..0000000
--- a/wifi/ipv6proxy/packet.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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 <netinet/icmp6.h>
-#include <netinet/ip6.h>
-
-#include <string>
-#include <vector>
-
-#include "message.h"
-
-class Packet {
-public:
-    enum class Type {
-        NeighborSolicitation,
-        NeighborAdvertisement,
-        RouterSolicitation,
-        RouterAdvertisement,
-        Other
-    };
-
-    explicit Packet(Message& message);
-
-    // Create a string that can be used for debug purposes to describe
-    // what type of packet and potentially its target for certain types.
-    std::string description() const;
-
-    // Full size including IP header
-    size_t fullSize() const {
-        return mMessage.size();
-    }
-    // Remaining size including ICMPv6 header but excluding IP header
-    size_t icmpSize() const {
-        return mMessage.size() - sizeof(ip6_hdr);
-    }
-
-    Type type() const {
-        return mType;
-    }
-    const ip6_hdr* ip() const {
-        return mIp;
-    }
-    const icmp6_hdr* icmp() const {
-        return mIcmp;
-    }
-
-    nd_opt_hdr* firstOpt();
-    nd_opt_hdr* nextOpt(nd_opt_hdr* currentHeader);
-
-private:
-    Message& mMessage;
-    Type mType;
-
-    const ip6_hdr* mIp;
-    const icmp6_hdr* mIcmp;
-    nd_opt_hdr* mFirstOpt;
-};
-
diff --git a/wifi/ipv6proxy/proxy.cpp b/wifi/ipv6proxy/proxy.cpp
deleted file mode 100644
index cb19ff7..0000000
--- a/wifi/ipv6proxy/proxy.cpp
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * 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.
- */
-
-#include "proxy.h"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <linux/if_packet.h>
-#include <poll.h>
-#include <signal.h>
-
-#include <cutils/properties.h>
-
-#include "log.h"
-#include "message.h"
-#include "packet.h"
-#include "result.h"
-
-// The prefix length for an address of a single unique node
-static const uint8_t kNodePrefixLength = 128;
-static const size_t kLinkAddressSize = 6;
-static const size_t kRecursiveDnsOptHeaderSize = 8;
-
-// Rewrite the link address of a neighbor discovery option to the link address
-// of |interface|. This can be either a source or target link address as
-// specified by |optionType|. The valid values are ND_OPT_TARGET_LINKADDR and
-// ND_OPT_SOURCE_LINKADDR. This will modify the message data inside |packet|.
-static void rewriteLinkAddressOption(Packet& packet,
-                                     const Interface& interface,
-                                     int optionType) {
-    for (nd_opt_hdr* opt = packet.firstOpt(); opt; opt = packet.nextOpt(opt)) {
-        if (opt->nd_opt_type == optionType) {
-            auto src = interface.linkAddr().get<sockaddr_ll>();
-            auto dest = reinterpret_cast<char*>(opt) + sizeof(nd_opt_hdr);
-            memcpy(dest, src->sll_addr, kLinkAddressSize);
-        }
-    }
-}
-
-static void extractRecursiveDnsServers(Packet& packet) {
-    for (nd_opt_hdr* opt = packet.firstOpt(); opt; opt = packet.nextOpt(opt)) {
-        if (opt->nd_opt_type != 25 || opt->nd_opt_len < 1) {
-            // Not a RNDSS option, skip it
-            continue;
-        }
-        size_t numEntries = (opt->nd_opt_len - 1) / 2;
-        //Found number of entries, dump  each address
-        const char* option = reinterpret_cast<const char*>(opt);
-        option += kRecursiveDnsOptHeaderSize;
-        auto dnsServers = reinterpret_cast<const struct in6_addr*>(option);
-
-        std::vector<std::string> validServers;
-        for (size_t i = 0; i < numEntries; ++i) {
-            char buffer[INET6_ADDRSTRLEN];
-            if (inet_ntop(AF_INET6, &dnsServers[i], buffer, sizeof(buffer))) {
-                validServers.push_back(buffer);
-            } else {
-                loge("Failed to convert RDNSS to string\n");
-            }
-        }
-
-        auto server = validServers.begin();
-        char propName[PROP_NAME_MAX];
-        char propValue[PROP_VALUE_MAX];
-        for (int i = 1; i <= 4; ++i) {
-            snprintf(propName, sizeof(propName), "net.eth0.ipv6dns%d", i);
-            if (server != validServers.end()) {
-                property_set(propName, server->c_str());
-                ++server;
-            } else {
-                // Clear the property if it's no longer a valid server, don't
-                // want to leave old servers around
-                property_set(propName, "");
-            }
-        }
-    }
-}
-
-int Proxy::run() {
-    sigset_t blockMask, originalMask;
-    int status = ::sigfillset(&blockMask);
-    if (status != 0) {
-        loge("Unable to fill signal set: %s\n", strerror(errno));
-        return 1;
-    }
-    status = ::sigprocmask(SIG_SETMASK, &blockMask, &originalMask);
-    if (status != 0) {
-        loge("Unable to set signal mask: %s\n", strerror(errno));
-        return 1;
-    }
-    // Init outer interface and router
-    if (!mOuterIf.init() || !mRouter.init()) {
-        return 1;
-    }
-    // Init all inner interfaces
-    for (size_t i = 0; i < mInnerIfs.size(); ++i) {
-        if (!mInnerIfs[i].init()) {
-            return 1;
-        }
-    }
-
-    // Create list of FDs to poll, we're only looking for input (POLLIN)
-    std::vector<pollfd> fds(mInnerIfs.size() + 1);
-    fds[0].fd = mOuterIf.ipSocket().get();
-    fds[0].events = POLLIN;
-    for (size_t i = 0; i < mInnerIfs.size(); ++i) {
-        fds[i + 1].fd = mInnerIfs[i].ipSocket().get();
-        fds[i + 1].events = POLLIN;
-    }
-
-    Message message;
-    while (status >= 0) {
-        status = ::ppoll(fds.data(), fds.size(), nullptr, &originalMask);
-        if (status > 0) {
-            // Something available to read
-            for (const struct pollfd& fd : fds) {
-                if (receiveIfPossible(fd, mOuterIf.ipSocket(), &message)) {
-                    // Received a message on the outer interface
-                    handleOuterMessage(message);
-                } else {
-                    for (auto& inner : mInnerIfs) {
-                        if (receiveIfPossible(fd, inner.ipSocket(), &message)) {
-                            // Received a message on the inner interface
-                            handleInnerMessage(inner, message);
-                        }
-                    }
-                }
-            }
-        }
-    }
-    loge("Polling failed: %s\n", strerror(errno));
-    return 1;
-}
-
-bool Proxy::receiveIfPossible(const pollfd& fd,
-                              Socket& socket,
-                              Message* message) {
-    // Check if it's actually the socket we're interested in
-    if (fd.fd != socket.get()) {
-        return false;
-    }
-    // Check if there is something to read on this socket
-    if ((fd.revents & POLLIN) == 0) {
-        return false;
-    }
-
-    // Receive the message and place the data in the message parameter
-    Result res = socket.receive(message);
-    if (!res) {
-        loge("Error receiving on socket: %s\n", res.c_str());
-        return false;
-    }
-    return true;
-}
-
-void Proxy::handleOuterMessage(Message& message) {
-    Packet packet(message);
-    uint32_t options = kForwardOnly;
-    switch (packet.type()) {
-        case Packet::Type::RouterAdvertisement:
-            extractRecursiveDnsServers(packet);
-            options = kRewriteSourceLink | kSetDefaultGateway;
-            break;
-        case Packet::Type::NeighborSolicitation:
-            options = kSpoofSource;
-            break;
-        case Packet::Type::NeighborAdvertisement:
-            options = kRewriteTargetLink;
-            break;
-        default:
-            return;
-    }
-    for (auto& inner : mInnerIfs) {
-        forward(mOuterIf, inner, packet, options);
-    }
-}
-
-void Proxy::handleInnerMessage(const Interface& inner, Message& message) {
-    Packet packet(message);
-    uint32_t options = kForwardOnly;
-    switch (packet.type()) {
-        case Packet::Type::RouterSolicitation:
-            options = kSpoofSource;
-            break;
-        case Packet::Type::NeighborSolicitation:
-            options = kSpoofSource | kAddRoute;
-            break;
-        case Packet::Type::NeighborAdvertisement:
-            options = kRewriteTargetLink | kSpoofSource | kAddRoute;
-            break;
-        default:
-            return;
-    }
-    forward(inner, mOuterIf, packet, options);
-}
-
-void Proxy::forward(const Interface& from,
-                    Interface& to,
-                    Packet& packet,
-                    uint32_t options) {
-    if (mLogDebug) {
-        logd("Forwarding %s from %s/%s to %s/%s\n",
-             packet.description().c_str(),
-             from.name().c_str(), addrToStr(packet.ip()->ip6_src).c_str(),
-             to.name().c_str(), addrToStr(packet.ip()->ip6_dst).c_str());
-    }
-
-    if (options & kRewriteTargetLink) {
-        rewriteLinkAddressOption(packet, to, ND_OPT_TARGET_LINKADDR);
-    }
-    if (options & kRewriteSourceLink) {
-        rewriteLinkAddressOption(packet, to, ND_OPT_SOURCE_LINKADDR);
-    }
-
-    Result res = Result::success();
-    if (options & kSpoofSource) {
-        // Spoof the source of the packet so that it appears to originate from
-        // the same source that we see.
-        res = to.icmpSocket().sendFrom(packet.ip()->ip6_src,
-                                       packet.ip()->ip6_dst,
-                                       packet.icmp(),
-                                       packet.icmpSize());
-    } else {
-        res = to.icmpSocket().sendTo(packet.ip()->ip6_dst,
-                                     packet.icmp(),
-                                     packet.icmpSize());
-    }
-    if (!res) {
-        loge("Failed to forward %s from %s to %s: %s\n",
-             packet.description().c_str(),
-             from.name().c_str(), to.name().c_str(),
-             res.c_str());
-    }
-
-    if (options & kAddRoute) {
-        mRouter.addRoute(packet.ip()->ip6_src, kNodePrefixLength, from.index());
-    }
-    if (packet.type() == Packet::Type::RouterAdvertisement &&
-        options & kSetDefaultGateway) {
-        // Set the default gateway from this router advertisement. This is
-        // needed so that packets that are forwarded as a result of proxying
-        // actually have somewhere to go.
-        if (!mRouter.setDefaultGateway(packet.ip()->ip6_src, from.index())) {
-            loge("Failed to set default gateway %s\n",
-                 addrToStr(packet.ip()->ip6_src).c_str());
-        }
-    }
-}
-
diff --git a/wifi/ipv6proxy/proxy.h b/wifi/ipv6proxy/proxy.h
deleted file mode 100644
index 5179137..0000000
--- a/wifi/ipv6proxy/proxy.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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 <string>
-#include <vector>
-
-#include "interface.h"
-#include "router.h"
-
-struct pollfd;
-class Message;
-class Packet;
-class Socket;
-
-class Proxy {
-public:
-    template<typename Iter>
-    Proxy(std::string outerInterfaceName,
-          Iter innerInterfacesBegin, Iter innerInterfacesEnd)
-        : mOuterIf(outerInterfaceName),
-          mLogDebug(false) {
-
-        for (Iter i = innerInterfacesBegin; i != innerInterfacesEnd; ++i) {
-            mInnerIfs.emplace_back(*i);
-        }
-    }
-
-    int run();
-
-private:
-    enum ForwardOpt {
-        kForwardOnly = 0,
-        kRewriteTargetLink = (1 << 0),
-        kRewriteSourceLink = (1 << 1),
-        kSpoofSource = (1 << 2),
-        kAddRoute = (1 << 3),
-        kSetDefaultGateway = (1 << 4)
-    };
-
-    bool receiveIfPossible(const pollfd&, Socket& socket, Message* message);
-
-    void handleOuterMessage(Message& message);
-    void handleInnerMessage(const Interface& inner, Message& message);
-    void forward(const Interface& from, Interface& to,
-                 Packet& packet, uint32_t options);
-
-    std::vector<Interface> mInnerIfs;
-    Interface mOuterIf;
-
-    Router mRouter;
-    bool mLogDebug;
-};
-
diff --git a/wifi/ipv6proxy/router.cpp b/wifi/ipv6proxy/router.cpp
deleted file mode 100644
index be505c9..0000000
--- a/wifi/ipv6proxy/router.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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.
- */
-
-#include "router.h"
-
-#include <linux/rtnetlink.h>
-#include <stddef.h>
-#include <string.h>
-
-#include "address.h"
-#include "log.h"
-
-template<class Request>
-static void addRouterAttribute(Request& r,
-                               int type,
-                               const void* data,
-                               size_t size) {
-    // Calculate the offset into the character buffer where the RTA data lives
-    // We use offsetof on the buffer to get it. This avoids undefined behavior
-    // by casting the buffer (which is safe because it's char) instead of the
-    // Request struct.(which is undefined because of aliasing)
-    size_t offset = NLMSG_ALIGN(r.hdr.nlmsg_len) - offsetof(Request, buf);
-    auto attr = reinterpret_cast<struct rtattr*>(r.buf + offset);
-    attr->rta_type = type;
-    attr->rta_len = RTA_LENGTH(size);
-    memcpy(RTA_DATA(attr), data, size);
-
-    // Update the message length to include the router attribute.
-    r.hdr.nlmsg_len = NLMSG_ALIGN(r.hdr.nlmsg_len) + RTA_ALIGN(attr->rta_len);
-}
-
-bool Router::init() {
-    // Create a netlink socket to the router
-    Result res = mSocket.open(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
-    if (!res) {
-        loge("Unable to open netlink socket: %s\n", res.c_str());
-        return false;
-    }
-    return true;
-}
-
-bool Router::addNeighbor(const struct in6_addr& address,
-                         unsigned int interfaceIndex) {
-    struct Request {
-        struct nlmsghdr hdr;
-        struct ndmsg msg;
-        char buf[256];
-    } request;
-
-    memset(&request, 0, sizeof(request));
-
-    unsigned short msgLen = NLMSG_LENGTH(sizeof(request.msg));
-    // Set up a request to create a new neighbor
-    request.hdr.nlmsg_len = msgLen;
-    request.hdr.nlmsg_type = RTM_NEWNEIGH;
-    request.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
-
-    // The neighbor is a permanent IPv6 proxy
-    request.msg.ndm_family = AF_INET6;
-    request.msg.ndm_state = NUD_PERMANENT;
-    request.msg.ndm_flags = NTF_PROXY;
-    request.msg.ndm_ifindex = interfaceIndex;
-
-    addRouterAttribute(request, NDA_DST, &address, sizeof(address));
-
-    return sendNetlinkMessage(&request, request.hdr.nlmsg_len);
-}
-
-bool Router::addRoute(const struct in6_addr& address,
-                      uint8_t bits,
-                      uint32_t ifaceIndex) {
-    struct Request {
-        struct nlmsghdr hdr;
-        struct rtmsg msg;
-        char buf[256];
-    } request;
-
-    memset(&request, 0, sizeof(request));
-
-    // Set up a request to create a new route
-    request.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
-    request.hdr.nlmsg_type = RTM_NEWROUTE;
-    request.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
-
-    request.msg.rtm_family = AF_INET6;
-    request.msg.rtm_dst_len = bits;
-    request.msg.rtm_table = RT_TABLE_MAIN;
-    request.msg.rtm_protocol = RTPROT_RA;
-    request.msg.rtm_scope = RT_SCOPE_UNIVERSE;
-    request.msg.rtm_type = RTN_UNICAST;
-
-    addRouterAttribute(request, RTA_DST, &address, sizeof(address));
-    addRouterAttribute(request, RTA_OIF, &ifaceIndex, sizeof(ifaceIndex));
-
-    return sendNetlinkMessage(&request, request.hdr.nlmsg_len);
-}
-
-bool Router::setDefaultGateway(const struct in6_addr& address,
-                               unsigned int ifaceIndex) {
-    struct Request {
-        struct nlmsghdr hdr;
-        struct rtmsg msg;
-        char buf[256];
-    } request;
-
-    memset(&request, 0, sizeof(request));
-
-    // Set up a request to create a new route
-    request.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
-    request.hdr.nlmsg_type = RTM_NEWROUTE;
-    request.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
-
-    request.msg.rtm_family = AF_INET6;
-    request.msg.rtm_dst_len = 0;
-    request.msg.rtm_src_len = 0;
-    request.msg.rtm_table = RT_TABLE_MAIN;
-    request.msg.rtm_protocol = RTPROT_RA;
-    request.msg.rtm_scope = RT_SCOPE_UNIVERSE;
-    request.msg.rtm_type = RTN_UNICAST;
-
-    struct in6_addr anyAddress;
-    memset(&anyAddress, 0, sizeof(anyAddress));
-    addRouterAttribute(request, RTA_GATEWAY, &address, sizeof(address));
-    addRouterAttribute(request, RTA_OIF, &ifaceIndex, sizeof(ifaceIndex));
-    addRouterAttribute(request, RTA_SRC, &anyAddress, sizeof(anyAddress));
-
-    return sendNetlinkMessage(&request, request.hdr.nlmsg_len);
-}
-
-bool Router::sendNetlinkMessage(const void* data, size_t size) {
-    struct sockaddr_nl netlinkAddress;
-    memset(&netlinkAddress, 0, sizeof(netlinkAddress));
-    netlinkAddress.nl_family = AF_NETLINK;
-    Result res = mSocket.sendTo(netlinkAddress, data, size);
-    if (!res) {
-        loge("Unable to send on netlink socket: %s\n", res.c_str());
-        return false;
-    }
-    return true;
-}
-
diff --git a/wifi/ipv6proxy/router.h b/wifi/ipv6proxy/router.h
deleted file mode 100644
index 66e896f..0000000
--- a/wifi/ipv6proxy/router.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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 <stdint.h>
-
-#include <netinet/in.h>
-
-#include "socket.h"
-
-class Router {
-public:
-    // Initialize the router, this has to be called before any other methods can
-    // be called. It only needs to be called once.
-    bool init();
-
-    // Indicate that |address| is a neighbor to this node and that it is
-    // accessible on the interface with index |interfaceIndex|.
-    bool addNeighbor(const struct in6_addr& address, uint32_t interfaceIndex);
-
-    // Add a route to |address|/|bits| on interface |interfaceIndex|. The
-    // |bits| parameter indicates the bitmask of the address, for example in
-    // the routing entry 2001:db8::/32 the |bits| parameter would be 32.
-    bool addRoute(const struct in6_addr& address,
-                  uint8_t bits,
-                  uint32_t interfaceIndex);
-
-    // Set the default gateway route to |address| on interface with index
-    // |interfaceIndex|. Overwrites any existing default gateway with the same
-    // address.
-    bool setDefaultGateway(const struct in6_addr& address,
-                           unsigned int interfaceIndex);
-private:
-    bool sendNetlinkMessage(const void* data, size_t size);
-
-    // Netlink socket for setting up neighbors and routes
-    Socket mSocket;
-};
-
diff --git a/wifi/ipv6proxy/socket.cpp b/wifi/ipv6proxy/socket.cpp
deleted file mode 100644
index 3b67816..0000000
--- a/wifi/ipv6proxy/socket.cpp
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * 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.
- */
-
-#include "socket.h"
-
-#include <errno.h>
-#include <string.h>
-
-#include <linux/in6.h>
-#include <net/ethernet.h>
-#include <netinet/in.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include "address.h"
-#include "message.h"
-
-Socket::Socket() : mState(State::New), mSocket(-1) {
-}
-
-Socket::Socket(Socket&& other) noexcept : mState(other.mState), mSocket(other.mSocket) {
-    other.mSocket = -1;
-    other.mState = State::Moved;
-}
-
-Socket::~Socket() {
-    if (mSocket != -1) {
-        close(mSocket);
-        mSocket = -1;
-    }
-    mState = State::Destructed;
-}
-
-Socket& Socket::operator=(Socket&& other) noexcept {
-    if (mSocket != -1) {
-        close(mSocket);
-    }
-    mSocket = other.mSocket;
-    mState = other.mState;
-    other.mSocket = -1;
-    other.mState = State::Moved;
-
-    return *this;
-}
-
-Result Socket::open(int domain, int type, int protocol) {
-    if (mState != State::New) {
-        return Result::error("open called on socket in invalid state");
-    }
-    mSocket = ::socket(domain, type | SOCK_CLOEXEC, protocol);
-    if (mSocket == -1) {
-        return Result::error(strerror(errno));
-    }
-    mState = State::Open;
-    return Result::success();
-}
-
-Result Socket::setInterface(const std::string& interface) {
-    if (mState != State::Open) {
-        return Result::error("attempting to set option in invalid state");
-    }
-    int res = ::setsockopt(mSocket, SOL_SOCKET, SO_BINDTODEVICE,
-                           interface.c_str(), interface.size());
-
-    return res == -1 ? Result::error(strerror(errno)) : Result::success();
-}
-
-Result Socket::setMulticastHopLimit(int hopLimit) {
-    if (mState != State::Open) {
-        return Result::error("attempting to set option in invalid state");
-    }
-    int res = ::setsockopt(mSocket, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
-                           &hopLimit, sizeof(hopLimit));
-
-    return res == -1 ? Result::error(strerror(errno)) : Result::success();
-}
-
-Result Socket::setUnicastHopLimit(int hopLimit) {
-    if (mState != State::Open) {
-        return Result::error("attempting to set option in invalid state");
-    }
-    int res = ::setsockopt(mSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
-                           &hopLimit, sizeof(hopLimit));
-
-    return res == -1 ? Result::error(strerror(errno)) : Result::success();
-}
-
-Result Socket::setTransparent(bool transparent) {
-    if (mState != State::Open) {
-        return Result::error("attempting to set option in invalid state");
-    }
-    int v = transparent ? 1 : 0;
-    int res = ::setsockopt(mSocket, IPPROTO_IPV6, IPV6_TRANSPARENT,
-                           &v, sizeof(v));
-
-    return res == -1 ? Result::error(strerror(errno)) : Result::success();
-}
-
-Result Socket::bind(const Address& address) {
-    if (mState != State::Open) {
-        return Result::error("bind called on socket in invalid state");
-    }
-
-    int res = ::bind(mSocket, address.get<sockaddr>(), address.size());
-    if (res == -1) {
-        return Result::error(strerror(errno));
-    }
-
-    mState = State::Bound;
-    return Result::success();
-}
-
-Result Socket::receive(Message* receivingMessage) {
-    if (receivingMessage == nullptr) {
-        return Result::error("No receivingMessage provided");
-    }
-    if (mState != State::Bound) {
-        return Result::error("Attempt to receive on a socket that isn't bound");
-    }
-
-    ssize_t rxBytes = ::recv(mSocket,
-                             receivingMessage->data(),
-                             receivingMessage->capacity(),
-                             0);
-    if (rxBytes < 0) {
-        return Result::error(strerror(errno));
-    }
-
-    receivingMessage->setSize(static_cast<size_t>(rxBytes));
-    return Result::success();
-}
-
-Result Socket::receiveFrom(Message* receivingMessage, Address* from) {
-    if (receivingMessage == nullptr) {
-        return Result::error("No receivingMessage provided");
-    }
-    if (from == nullptr) {
-        return Result::error("No from address provided");
-    }
-    if (mState != State::Bound) {
-        return Result::error("Attempt to receive on a socket that isn't bound");
-    }
-
-    from->reset();
-    sockaddr* source = from->get<sockaddr>();
-    socklen_t sourceLen = from->size();
-    ssize_t rxBytes = ::recvfrom(mSocket,
-                                 receivingMessage->data(),
-                                 receivingMessage->capacity(),
-                                 0,
-                                 source,
-                                 &sourceLen);
-    if (rxBytes < 0) {
-        return Result::error(strerror(errno));
-    }
-
-    receivingMessage->setSize(static_cast<size_t>(rxBytes));
-    return Result::success();
-}
-
-Result Socket::send(const void* data, size_t size) {
-    if (mState != State::Bound && mState != State::Open) {
-        return Result::error("Attempt to send on a socket in invalid state");
-    }
-
-    int res = ::send(mSocket, data, size, 0);
-    if (res == -1) {
-        return Result::error(strerror(errno));
-    }
-    return Result::success();
-}
-
-Result Socket::sendTo(const sockaddr& destination,
-                      size_t destinationSize,
-                      const void* data,
-                      size_t size) {
-    if (mState != State::Bound && mState != State::Open) {
-        return Result::error("Attempt to send on a socket in invalid state");
-    }
-
-    int res = ::sendto(mSocket, data, size, 0, &destination, destinationSize);
-    if (res == -1) {
-        return Result::error(strerror(errno));
-    }
-    return Result::success();
-}
-
-Result Socket::sendTo(const in6_addr& destination,
-                      const void* data,
-                      size_t size) {
-    sockaddr_in6 addr;
-    memset(&addr, 0, sizeof(addr));
-    addr.sin6_family = AF_INET6;
-    addr.sin6_addr = destination;
-    return sendTo(*reinterpret_cast<sockaddr*>(&addr),
-                  sizeof(addr),
-                  data,
-                  size);
-}
-
-Result Socket::sendFrom(const struct in6_addr& fromAddress,
-                        const sockaddr& destination,
-                        size_t destinationSize,
-                        const void* data,
-                        size_t size) {
-    struct msghdr messageHeader;
-    memset(&messageHeader, 0, sizeof(messageHeader));
-    // Even when sending this struct requires a non-const pointer, even when
-    // it's only going to be read. Do a const_cast instead of creating a
-    // method signature with illogical const-behavior.
-    messageHeader.msg_name = const_cast<struct sockaddr*>(&destination);
-    messageHeader.msg_namelen = destinationSize;
-
-    struct iovec iov;
-    messageHeader.msg_iov = &iov;
-    messageHeader.msg_iovlen = 1;
-
-    memset(&iov, 0, sizeof(iov));
-    iov.iov_base = const_cast<void*>(data);
-    iov.iov_len = size;
-
-    char control[CMSG_SPACE(sizeof(struct in6_pktinfo))] = { 0 };
-    messageHeader.msg_control = control;
-    messageHeader.msg_controllen = sizeof(control);
-
-    struct cmsghdr* controlHeader = CMSG_FIRSTHDR(&messageHeader);
-    controlHeader->cmsg_level = IPPROTO_IPV6;
-    controlHeader->cmsg_type = IPV6_PKTINFO;
-    controlHeader->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
-
-    auto packetInfoData = CMSG_DATA(controlHeader);
-    auto packetInfo = reinterpret_cast<struct in6_pktinfo*>(packetInfoData);
-    packetInfo->ipi6_addr = fromAddress;
-
-    int res = ::sendmsg(mSocket, &messageHeader, 0);
-    if (res == -1) {
-        int error = errno;
-        printf("sendmsg failed: %d\n", error);
-        return Result::error(strerror(error));
-    }
-    return Result::success();
-}
-
-Result Socket::sendFrom(const in6_addr& fromAddress,
-                        const in6_addr& destination,
-                        const void* data,
-                        size_t size) {
-    sockaddr_in6 addr;
-    memset(&addr, 0, sizeof(addr));
-    addr.sin6_family = AF_INET6;
-    addr.sin6_addr = destination;
-
-    return sendFrom(fromAddress,
-                    *reinterpret_cast<sockaddr*>(&addr),
-                    sizeof(addr),
-                    data,
-                    size);
-}
diff --git a/wifi/ipv6proxy/socket.h b/wifi/ipv6proxy/socket.h
deleted file mode 100644
index 9cbdda3..0000000
--- a/wifi/ipv6proxy/socket.h
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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 "result.h"
-
-#include <netinet/in.h>
-
-#include <stdint.h>
-#include <string>
-
-class Address;
-class Message;
-
-class Socket {
-public:
-    enum class Domain {
-        IpV4,
-        IpV6,
-        Packet,
-    };
-
-    enum class Type {
-        Stream,   // A streaming protocol, use this with Infer for TCP
-        Datagram, // A datagram protocol, use this with Infer for UDP
-        Raw,      // A raw socket
-    };
-    enum class Protocol {
-        Infer,    // Infer the protocol from the type, such as TCP for Stream
-        Ip,       // Internet Protocol for raw sockets
-        IcmpV6,   // ICMPv6 control protocol for Raw sockets
-        EthIpV6,  // Ethernet packets containing IPV6, for packet sockets
-    };
-
-    // Construct an empty socket object, next use open() to start using it
-    Socket();
-    // Move construct a socket, The constructed socket will be in the same state
-    // that |other| is. After this |other| will be in an undefined state and
-    // should no longer be used.
-    Socket(Socket&& other) noexcept;
-    ~Socket();
-
-    // Move the |other| socket object into this one. If this object has an open
-    // socket it will be closed first. After that this object will have the
-    // same state that |other| did. |other| will be left in an undefined state
-    // and should not be used.
-    Socket& operator=(Socket&& other) noexcept;
-
-    int get() const { return mSocket; }
-
-    Result open(int domain, int type, int protocol);
-
-    /** Options, these must be called between open and bind **/
-
-    // Bind to a specific interface regardless of the address that the socket
-    // is going to bind to.
-    Result setInterface(const std::string& interface);
-
-    // Set the hop limit for multicast traffic on the socket. Each router hop
-    // decreases this value by one, when it reaches zero the packet is
-    // discarded.
-    Result setMulticastHopLimit(int hopLimit);
-
-    // Set the hop limit for unicast traffic on the socket. Each router hop
-    // decreases this value by one, when it reaches zero the packet is
-    // discarded.
-    Result setUnicastHopLimit(int hopLimit);
-
-    // Configure the socket to be transparent. This allows packets sent to have
-    // a source address that is different from the network interface's source
-    // address.
-    Result setTransparent(bool transparent);
-
-    /** Binding **/
-
-    Result bind(const Address& address);
-
-    /** Sending and receiving **/
-
-    Result receive(Message* receivingMessage);
-    Result receiveFrom(Message* receivingMessage, Address* from);
-
-    Result send(const void* data, size_t size);
-
-    // Send a packet to a specific |destination| of any address type.
-    Result sendTo(const sockaddr& destination,
-                  size_t destinationSize,
-                  const void* data,
-                  size_t size);
-    // Convenience function to send to a specific IPv6 address.
-    Result sendTo(const in6_addr& destination, const void* data, size_t size);
-    // Convenience method to use sendTo with a more specific sockaddr struct
-    // without having to specify the size or do the casting.
-    template<typename T>
-    Result sendTo(const T& destination, const void* data, size_t size) {
-        return sendTo(*reinterpret_cast<const sockaddr*>(&destination),
-                      sizeof(destination),
-                      data,
-                      size);
-    }
-
-    // Send a packet with a specific source IPv6 address to a given
-    // |destination|. Rewriting the source in this manner usually requires root.
-    Result sendFrom(const in6_addr& fromAddress,
-                    const sockaddr& destination,
-                    size_t destinationSize,
-                    const void* data,
-                    size_t size);
-    Result sendFrom(const in6_addr& fromAddress,
-                    const in6_addr& destination,
-                    const void* data,
-                    size_t size);
-    // Convenience method to use sendFrom with a more specific sockaddr struct
-    // without having to specify the size or do the casting.
-    template<typename T>
-    Result sendFrom(const in6_addr& fromAddress,
-                    const T& destination,
-                    const void* data,
-                    size_t size) {
-        return sendFrom(fromAddress,
-                        *reinterpret_cast<const sockaddr*>(&destination),
-                        sizeof(destination),
-                        data,
-                        size);
-    }
-
-private:
-    // No copy construction or assignment allowed, support move semantics only
-    Socket(const Socket&);
-    Socket& operator=(const Socket&);
-
-    enum class State {
-        New,
-        Open,
-        Bound,
-        Moved,
-        Destructed,
-    };
-
-    State mState;
-    int mSocket;
-};
-
diff --git a/x86-vendor.mk b/x86-vendor.mk
index 29157ba..5fa9dc9 100644
--- a/x86-vendor.mk
+++ b/x86-vendor.mk
@@ -17,5 +17,9 @@
     device/generic/goldfish/data/etc/encryptionkey.img:images/x86/encryptionkey.img \
     prebuilts/qemu-kernel/x86_64/$(PRODUCT_KERNEL_VERSION)/kernel-qemu2:images/x86/kernel-ranchu-64
 
+PRODUCT_COPY_FILES += \
+    device/generic/goldfish/data/etc/x86/emulatorip:$(TARGET_COPY_OUT_VENDOR)/bin/ip
+
+
 PRODUCT_SHIPPING_API_LEVEL := 28
 TARGET_USES_MKE2FS := true
diff --git a/x86_64-vendor.mk b/x86_64-vendor.mk
index b0cbf0a..a41a53e 100644
--- a/x86_64-vendor.mk
+++ b/x86_64-vendor.mk
@@ -17,5 +17,9 @@
     device/generic/goldfish/data/etc/encryptionkey.img:images/x86_64/encryptionkey.img \
     prebuilts/qemu-kernel/x86_64/$(PRODUCT_KERNEL_VERSION)/kernel-qemu2:images/x86_64/kernel-ranchu
 
+PRODUCT_COPY_FILES += \
+    device/generic/goldfish/data/etc/x86/emulatorip:$(TARGET_COPY_OUT_VENDOR)/bin/ip
+
+
 PRODUCT_SHIPPING_API_LEVEL := 28
 TARGET_USES_MKE2FS := true