Remove dependency on system binaries for network

Change the way networking in the emulator works by moving away from
using iptables for NAT. Instead create a bridge in the router namespace
so that all packets coming from the regular namespace will be
automatically bridged/switched to the host network. This removes the
need for dhcpserver and ipv6proxy because DHCP requests will now
correctly traverse the router namespace to the host network so no
additional server is needed to forward those DHCP requests. Similarly
the ipv6proxy is no longer needed because all NDP traffic used to
configure IPv6 addresses will now also be bridged to the host network.

This change still requires the 'ip' command but switches to using ip
from the vendor partition. The 'iptables' command could not be made
available to vendors because it uses a lock file on the system
partition.

Note that this change breaks WiFi Direct because all network interfaces
in a bridge need to have the same MTU. The wifi interface in the router
namespace had a smaller MTU to allow for injection with headers to be
within the MTU bound on the wifi monitor interface. Because of this
breakage this change should be accompanied by the change that rewrite
WiFi Direct functionality.

Test: run GtsSecurityHostTestCases com.google.android.security.gts.SELinuxHostTest#testNoExemptionsForVendorExecutingCore
BUG: 120612311

Change-Id: Ie9ac9c2cc404f4337b8b080510a7496541d84d15
Merged-In: Ie9ac9c2cc404f4337b8b080510a7496541d84d15
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/log.h b/dhcp/server/log.h
deleted file mode 100644
index a0f21e0..0000000
--- a/dhcp/server/log.h
+++ /dev/null
@@ -1,20 +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
-
-#define LOG_TAG "dhcpserver"
-#include <log/log.h>
-
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..01b1a52 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,27 @@
     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 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/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..3af07d3 100644
--- a/network/netmgr/main.cpp
+++ b/network/netmgr/main.cpp
@@ -14,23 +14,29 @@
  * 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 kBridgeName[] = "br0";
 static const char kWifiMonitorInterface[] = "hwsim0";
+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 +45,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 +76,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,7 +121,7 @@
         return 1;
     }
 
-    WifiCommand wifiCommand;
+    WifiCommand wifiCommand(bridge);
     commander.registerCommand("wifi", &wifiCommand);
 
     WifiForwarder forwarder(kWifiMonitorInterface);
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%
rename from network/netmgr/timestamp.h
rename 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/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..43a8df2 100644
--- a/sepolicy/common/file_contexts
+++ b/sepolicy/common/file_contexts
@@ -23,10 +23,9 @@
 /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
 
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..4d7eef7 100644
--- a/sepolicy/common/netmgr.te
+++ b/sepolicy/common/netmgr.te
@@ -7,25 +7,29 @@
 
 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 };
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/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/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..b59176a 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,11 +146,9 @@
 PRODUCT_PACKAGES += \
 	createns \
 	dhcpclient \
-	dhcpserver \
 	execns \
 	hostapd \
 	hostapd_nohidl \
-	ipv6proxy \
 	netmgr \
 	wpa_supplicant \
 
diff --git a/wifi/createns/createns.cpp b/wifi/createns/createns.cpp
index 66f6dd3..33dbdf7 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,7 @@
     }
     {
         // 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, 0));
         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..d918240 100755
--- a/wifi/init.wifi.sh
+++ b/wifi/init.wifi.sh
@@ -42,19 +42,33 @@
 #
 
 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
+
 # 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 +78,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/result.h b/wifi/ipv6proxy/result.h
deleted file mode 100644
index 5cd2b03..0000000
--- a/wifi/ipv6proxy/result.h
+++ /dev/null
@@ -1,42 +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 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) {
-        return Result(message);
-    }
-
-    bool isSuccess() const { return mSuccess; }
-    bool operator!() const { return !mSuccess; }
-
-    const char* c_str() const { return mMessage; }
-private:
-    explicit Result(bool success) : mSuccess(success) { }
-    explicit Result(const char* message)
-        : mMessage(message), mSuccess(false) {
-    }
-    const char* mMessage;
-    bool mSuccess;
-};
-
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