Create an IPv6 proxy application for emulator wifi

This adds the ipv6proxy application that will proxy neighborhood
dicovery (RFC4861) messages between an "outer" interface and one or more
"inner" interfaces on a router. The "outer" interface is the WAN
interface that the "inner" interfaces would like to access using IPv6.
The proxy application will forward messages such that nodes connected to
the "inner" interfaces can set up an IPv6 gateway and address and
correctly discover neighbors on the network. Additionally the proxy will
also handle requests from the "outer" interface for neighbors that
reside behind one of the "inner" interfaces.

BUG: 74514143
Test: Build emulator image and manually verify that WiFi is working
Change-Id: Id07ea8ca3b88820a90988d6f6cd0ebe959094b8b
(cherry picked from commit 4b7d972d509e56c647b48260904bbf555e6bbab3)
(cherry picked from commit 7b0b9049ef449cba19d6e6e09c4d4406520f11cb)
(cherry picked from commit 2d6ab534d3f95d8f34ed2d08dfb891e3fda8a045)
diff --git a/wifi/ipv6proxy/Android.mk b/wifi/ipv6proxy/Android.mk
new file mode 100644
index 0000000..49ea60c
--- /dev/null
+++ b/wifi/ipv6proxy/Android.mk
@@ -0,0 +1,24 @@
+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_MODULE_TAGS := debug
+LOCAL_MODULE := ipv6proxy
+
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+include $(BUILD_EXECUTABLE)
diff --git a/wifi/ipv6proxy/address.cpp b/wifi/ipv6proxy/address.cpp
new file mode 100644
index 0000000..303f97d
--- /dev/null
+++ b/wifi/ipv6proxy/address.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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 <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
new file mode 100644
index 0000000..f9cec0b
--- /dev/null
+++ b/wifi/ipv6proxy/address.h
@@ -0,0 +1,63 @@
+/*
+ * 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/eintr.h b/wifi/ipv6proxy/eintr.h
new file mode 100644
index 0000000..6290a3e
--- /dev/null
+++ b/wifi/ipv6proxy/eintr.h
@@ -0,0 +1,28 @@
+/*
+ * 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 MAX_EINTR_ATTEMPTS 100
+
+#define HANDLE_EINTR(f) ({ \
+    int attempts = MAX_EINTR_ATTEMPTS; \
+    decltype(f) result; \
+    do { \
+        result = (f); \
+    } while (result == -1 && errno == EINTR && attempts-- > 0); \
+    result; \
+})
+
diff --git a/wifi/ipv6proxy/interface.cpp b/wifi/ipv6proxy/interface.cpp
new file mode 100644
index 0000000..83a4227
--- /dev/null
+++ b/wifi/ipv6proxy/interface.cpp
@@ -0,0 +1,142 @@
+/*
+ * 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 <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
new file mode 100644
index 0000000..e6e5d78
--- /dev/null
+++ b/wifi/ipv6proxy/interface.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <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
new file mode 100644
index 0000000..0e564bb
--- /dev/null
+++ b/wifi/ipv6proxy/log.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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
+#ifdef USE_LOG_TIMESTAMPS
+#include <stdarg.h>
+#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
new file mode 100644
index 0000000..527be29
--- /dev/null
+++ b/wifi/ipv6proxy/log.h
@@ -0,0 +1,32 @@
+/*
+ * 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 <cutils/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
new file mode 100644
index 0000000..4cdb92f
--- /dev/null
+++ b/wifi/ipv6proxy/main.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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 "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;
+    size_t pos = 0;
+    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
new file mode 100644
index 0000000..9b35dae
--- /dev/null
+++ b/wifi/ipv6proxy/message.h
@@ -0,0 +1,32 @@
+/*
+ * 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
new file mode 100644
index 0000000..4bb8e03
--- /dev/null
+++ b/wifi/ipv6proxy/namespace.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 <sched.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "log.h"
+
+static const char kNetNsDir[] = "/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
new file mode 100644
index 0000000..8a70374
--- /dev/null
+++ b/wifi/ipv6proxy/namespace.h
@@ -0,0 +1,24 @@
+/*
+ * 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
new file mode 100644
index 0000000..20c0ef6
--- /dev/null
+++ b/wifi/ipv6proxy/packet.cpp
@@ -0,0 +1,135 @@
+/*
+ * 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];
+    const char* type = nullptr;
+    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
new file mode 100644
index 0000000..49e69d4
--- /dev/null
+++ b/wifi/ipv6proxy/packet.h
@@ -0,0 +1,72 @@
+/*
+ * 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
new file mode 100644
index 0000000..52d35c4
--- /dev/null
+++ b/wifi/ipv6proxy/proxy.cpp
@@ -0,0 +1,193 @@
+/*
+ * 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 <errno.h>
+#include <linux/if_packet.h>
+#include <poll.h>
+
+#include "eintr.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;
+
+// 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);
+        }
+    }
+}
+
+int Proxy::run() {
+    // 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 (HANDLE_EINTR(::poll(fds.data(), fds.size(), -1)) != -1) {
+        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:
+            options = kRewriteSourceLink;
+            break;
+        case Packet::Type::NeighborSolicitation:
+            options = kSpoofSource;
+            break;
+        case Packet::Type::NeighborAdvertisement:
+            options = kRewriteTargetLink | kSpoofSource;
+            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 = kForwardOnly;
+            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, from, 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());
+    }
+}
+
diff --git a/wifi/ipv6proxy/proxy.h b/wifi/ipv6proxy/proxy.h
new file mode 100644
index 0000000..959f2a8
--- /dev/null
+++ b/wifi/ipv6proxy/proxy.h
@@ -0,0 +1,66 @@
+/*
+ * 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)
+    };
+
+    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
new file mode 100644
index 0000000..5cd2b03
--- /dev/null
+++ b/wifi/ipv6proxy/result.h
@@ -0,0 +1,42 @@
+/*
+ * 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
new file mode 100644
index 0000000..63c7c84
--- /dev/null
+++ b/wifi/ipv6proxy/router.cpp
@@ -0,0 +1,120 @@
+/*
+ * 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 "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 | NLM_F_EXCL;
+
+    request.msg.rtm_family = AF_INET6;
+    request.msg.rtm_dst_len = bits;
+    request.msg.rtm_table = RT_TABLE_MAIN;
+    request.msg.rtm_protocol = RTPROT_BOOT;
+    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::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
new file mode 100644
index 0000000..04d14b7
--- /dev/null
+++ b/wifi/ipv6proxy/router.h
@@ -0,0 +1,46 @@
+/*
+ * 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);
+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
new file mode 100644
index 0000000..56a7e1e
--- /dev/null
+++ b/wifi/ipv6proxy/socket.cpp
@@ -0,0 +1,276 @@
+/*
+ * 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 <net/ethernet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "address.h"
+#include "eintr.h"
+#include "message.h"
+
+Socket::Socket() : mState(State::New), mSocket(-1) {
+}
+
+Socket::Socket(Socket&& other) : 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) {
+    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 = HANDLE_EINTR(::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 = HANDLE_EINTR(::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 = HANDLE_EINTR(::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 = HANDLE_EINTR(::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 = HANDLE_EINTR(::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 = HANDLE_EINTR(::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 = HANDLE_EINTR(::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
new file mode 100644
index 0000000..ceda996
--- /dev/null
+++ b/wifi/ipv6proxy/socket.h
@@ -0,0 +1,156 @@
+/*
+ * 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);
+    ~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);
+
+    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;
+};
+