Merge "Va_end should be used with va_start"
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..89c8871
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+ek@google.com
+lorenzo@google.com
+jscherpelz@google.com
diff --git a/libnetdutils/Android.bp b/libnetdutils/Android.bp
new file mode 100644
index 0000000..c2f97ab
--- /dev/null
+++ b/libnetdutils/Android.bp
@@ -0,0 +1,29 @@
+cc_library_shared {
+    name: "libnetdutils",
+    srcs: [
+        "Fd.cpp",
+        "Netfilter.cpp",
+        "Netlink.cpp",
+        "Slice.cpp",
+        "Socket.cpp",
+        "Status.cpp",
+        "Syscalls.cpp",
+        "UniqueFd.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+    export_include_dirs: ["include"],
+}
+
+cc_test {
+    name: "netdutils_test",
+    srcs: [
+        "SliceTest.cpp",
+        "StatusTest.cpp",
+        "FdTest.cpp",
+        "SyscallsTest.cpp",
+    ],
+    static_libs: ["libgmock"],
+    shared_libs: ["libnetdutils"],
+}
diff --git a/libnetdutils/Fd.cpp b/libnetdutils/Fd.cpp
new file mode 100644
index 0000000..2651f90
--- /dev/null
+++ b/libnetdutils/Fd.cpp
@@ -0,0 +1,27 @@
+/*
+ * 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 "netdutils/Fd.h"
+
+namespace android {
+namespace netdutils {
+
+std::ostream& operator<<(std::ostream& os, const Fd& fd) {
+    return os << "Fd[" << fd.get() << "]";
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/FdTest.cpp b/libnetdutils/FdTest.cpp
new file mode 100644
index 0000000..2deddd2
--- /dev/null
+++ b/libnetdutils/FdTest.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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 <cstdint>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/MockSyscalls.h"
+#include "netdutils/Status.h"
+#include "netdutils/Syscalls.h"
+
+using testing::Mock;
+using testing::Return;
+using testing::StrictMock;
+
+namespace android {
+namespace netdutils {
+namespace {
+
+// Force implicit conversion from UniqueFd -> Fd
+inline Fd toFd(const UniqueFd& fd) {
+    return fd;
+}
+
+}  // namespace
+
+TEST(Fd, smoke) {
+    // Expect the following lines to compile
+    Fd fd1(1);
+    Fd fd2(fd1);
+    Fd fd3 = fd1;
+    const Fd fd4(8);
+    const Fd fd5(fd4);
+    const Fd fd6 = fd4;
+    EXPECT_TRUE(isWellFormed(fd3));
+    EXPECT_TRUE(isWellFormed(fd6));
+
+    // Corner case
+    Fd zero(0);
+    EXPECT_TRUE(isWellFormed(zero));
+
+    // Invalid file descriptors
+    Fd bad(-1);
+    Fd weird(-9);
+    EXPECT_FALSE(isWellFormed(bad));
+    EXPECT_FALSE(isWellFormed(weird));
+
+    // Default constructor
+    EXPECT_EQ(Fd(-1), Fd());
+    std::stringstream ss;
+    ss << fd3 << " " << fd6 << " " << bad << " " << weird;
+    EXPECT_EQ("Fd[1] Fd[8] Fd[-1] Fd[-9]", ss.str());
+}
+
+class UniqueFdTest : public testing::Test {
+  protected:
+    StrictMock<ScopedMockSyscalls> mSyscalls;
+};
+
+TEST_F(UniqueFdTest, operatorOstream) {
+    UniqueFd u(97);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+    std::stringstream ss;
+    ss << u;
+    EXPECT_EQ("UniqueFd[Fd[97]]", ss.str());
+    u.reset();
+}
+
+TEST_F(UniqueFdTest, destructor) {
+    {
+        UniqueFd u(98);
+        EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+    }
+    // Expectation above should be upon leaving nested scope
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, reset) {
+    UniqueFd u(99);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+    u.reset();
+
+    // Expectation above should be upon reset
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, moveConstructor) {
+    constexpr Fd kFd(101);
+    UniqueFd u1(kFd);
+    {
+        UniqueFd u2(std::move(u1));
+        EXPECT_FALSE(isWellFormed(u1));
+        EXPECT_TRUE(isWellFormed(u2));
+        EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok));
+    }
+    // Expectation above should be upon leaving nested scope
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, moveAssignment) {
+    constexpr Fd kFd(102);
+    UniqueFd u1(kFd);
+    {
+        UniqueFd u2 = std::move(u1);
+        EXPECT_FALSE(isWellFormed(u1));
+        EXPECT_TRUE(isWellFormed(u2));
+        UniqueFd u3;
+        u3 = std::move(u2);
+        EXPECT_FALSE(isWellFormed(u2));
+        EXPECT_TRUE(isWellFormed(u3));
+        EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok));
+    }
+    // Expectation above should be upon leaving nested scope
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, constConstructor) {
+    constexpr Fd kFd(103);
+    const UniqueFd u(kFd);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+}
+
+TEST_F(UniqueFdTest, closeFailure) {
+    constexpr Fd kFd(103);
+    UniqueFd u(kFd);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(statusFromErrno(EINTR, "test")));
+    EXPECT_DEBUG_DEATH(u.reset(), "");
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/Netfilter.cpp b/libnetdutils/Netfilter.cpp
new file mode 100644
index 0000000..6e36f34
--- /dev/null
+++ b/libnetdutils/Netfilter.cpp
@@ -0,0 +1,30 @@
+/*
+ * 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 <endian.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netlink.h>
+#include <ios>
+
+#include "netdutils/Netfilter.h"
+
+std::ostream& operator<<(std::ostream& os, const nfgenmsg& msg) {
+    return os << std::hex << "nfgenmsg["
+              << "family: 0x" << static_cast<int>(msg.nfgen_family) << ", version: 0x"
+              << static_cast<int>(msg.version) << ", res_id: 0x" << be16toh(msg.res_id) << "]"
+              << std::dec;
+}
diff --git a/libnetdutils/Netlink.cpp b/libnetdutils/Netlink.cpp
new file mode 100644
index 0000000..824c0f2
--- /dev/null
+++ b/libnetdutils/Netlink.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 <ios>
+#include <linux/netlink.h>
+
+#include "netdutils/Math.h"
+#include "netdutils/Netlink.h"
+
+namespace android {
+namespace netdutils {
+
+void forEachNetlinkMessage(const Slice buf,
+                           const std::function<void(const nlmsghdr&, const Slice)>& onMsg) {
+    Slice tail = buf;
+    while (tail.size() >= sizeof(nlmsghdr)) {
+        nlmsghdr hdr = {};
+        extract(tail, hdr);
+        const auto len = std::max<size_t>(hdr.nlmsg_len, sizeof(hdr));
+        onMsg(hdr, drop(take(tail, len), sizeof(hdr)));
+        tail = drop(tail, align(len, 2));
+    }
+}
+
+void forEachNetlinkAttribute(const Slice buf,
+                             const std::function<void(const nlattr&, const Slice)>& onAttr) {
+    Slice tail = buf;
+    while (tail.size() >= sizeof(nlattr)) {
+        nlattr hdr = {};
+        extract(tail, hdr);
+        const auto len = std::max<size_t>(hdr.nla_len, sizeof(hdr));
+        onAttr(hdr, drop(take(tail, len), sizeof(hdr)));
+        tail = drop(tail, align(len, 2));
+    }
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+bool operator==(const sockaddr_nl& lhs, const sockaddr_nl& rhs) {
+    return (lhs.nl_family == rhs.nl_family) && (lhs.nl_pid == rhs.nl_pid) &&
+           (lhs.nl_groups == rhs.nl_groups);
+}
+
+bool operator!=(const sockaddr_nl& lhs, const sockaddr_nl& rhs) {
+    return !(lhs == rhs);
+}
+
+std::ostream& operator<<(std::ostream& os, const nlmsghdr& hdr) {
+    return os << std::hex << "nlmsghdr["
+              << "len: 0x" << hdr.nlmsg_len << ", type: 0x" << hdr.nlmsg_type << ", flags: 0x"
+              << hdr.nlmsg_flags << ", seq: 0x" << hdr.nlmsg_seq << ", pid: 0x" << hdr.nlmsg_pid
+              << "]" << std::dec;
+}
+
+std::ostream& operator<<(std::ostream& os, const nlattr& attr) {
+    return os << std::hex << "nlattr["
+              << "len: 0x" << attr.nla_len << ", type: 0x" << attr.nla_type << "]" << std::dec;
+}
+
+std::ostream& operator<<(std::ostream& os, const sockaddr_nl& addr) {
+    return os << std::hex << "sockaddr_nl["
+              << "family: " << addr.nl_family << ", pid: " << addr.nl_pid
+              << ", groups: " << addr.nl_groups << "]" << std::dec;
+}
diff --git a/libnetdutils/Slice.cpp b/libnetdutils/Slice.cpp
new file mode 100644
index 0000000..7a07d47
--- /dev/null
+++ b/libnetdutils/Slice.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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 <sstream>
+
+#include "netdutils/Slice.h"
+
+namespace android {
+namespace netdutils {
+namespace {
+
+// Convert one byte to a two character hexadecimal string
+const std::string toHex(uint8_t byte) {
+    const std::array<char, 16> kLookup = {
+        {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}};
+    return {kLookup[byte >> 4], kLookup[byte & 0xf]};
+}
+
+}  // namespace
+
+std::string toString(const Slice s) {
+    return std::string(reinterpret_cast<char*>(s.base()), s.size());
+}
+
+std::string toHex(const Slice s, int wrap) {
+    Slice tail = s;
+    int count = 0;
+    std::stringstream ss;
+    while (!tail.empty()) {
+        uint8_t byte = 0;
+        extract(tail, byte);
+        ss << toHex(byte);
+        if ((++count % wrap) == 0) {
+            ss << "\n";
+        }
+        tail = drop(tail, 1);
+    }
+    return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& os, const Slice& slice) {
+    return os << std::hex << "Slice[base: " << reinterpret_cast<void*>(slice.base())
+              << ", limit: " << reinterpret_cast<void*>(slice.limit()) << ", size: 0x"
+              << slice.size() << "]" << std::dec;
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/SliceTest.cpp b/libnetdutils/SliceTest.cpp
new file mode 100644
index 0000000..9193c4f
--- /dev/null
+++ b/libnetdutils/SliceTest.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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 <array>
+#include <cstdint>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/Slice.h"
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+class SliceTest : public testing::Test {
+  protected:
+    std::array<char, 256> mRaw = {};
+};
+
+TEST_F(SliceTest, smoke) {
+    Slice s1 = makeSlice(mRaw);
+    Slice s2 = makeSlice(mRaw);
+    auto p = split(s1, 14);
+    std::stringstream ss;
+    ss << Slice();
+    EXPECT_EQ("Slice[base: 0x0, limit: 0x0, size: 0x0]", ss.str());
+    constexpr size_t kBytes = 14;
+    EXPECT_EQ(s1.base(), take(s1, kBytes).base());
+    EXPECT_EQ(kBytes, take(s1, kBytes).size());
+    EXPECT_EQ(s1.base() + kBytes, drop(s1, kBytes).base());
+    EXPECT_EQ(s1.size() - kBytes, drop(s1, kBytes).size());
+    double a = 0;
+    double b = 0;
+    int c = 0;
+    EXPECT_EQ(sizeof(a), extract(s1, a));
+    EXPECT_EQ(sizeof(a) + sizeof(b), extract(s1, a, b));
+    EXPECT_EQ(sizeof(a) + sizeof(b) + sizeof(c), extract(s1, a, b, c));
+}
+
+TEST_F(SliceTest, constructor) {
+    // Expect the following lines to compile
+    Slice s1 = makeSlice(mRaw);
+    Slice s2(s1);
+    Slice s3 = s2;
+    const Slice s4(s3);
+    const Slice s5 = s4;
+    s3 = s5;
+    Slice s6(mRaw.data(), mRaw.size());
+    Slice s7(mRaw.data(), mRaw.data() + mRaw.size());
+    struct {
+      int a;
+      double b;
+      float c;
+    } anon;
+    makeSlice(anon);
+    EXPECT_EQ(reinterpret_cast<uint8_t*>(mRaw.data()), s1.base());
+    EXPECT_EQ(reinterpret_cast<uint8_t*>(mRaw.data()) + mRaw.size(), s1.limit());
+    EXPECT_EQ(mRaw.size(), s1.size());
+    EXPECT_FALSE(mRaw.empty());
+    EXPECT_TRUE(Slice().empty());
+    EXPECT_TRUE(Slice(nullptr, static_cast<size_t>(0)).empty());
+    EXPECT_TRUE(Slice(nullptr, nullptr).empty());
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/Socket.cpp b/libnetdutils/Socket.cpp
new file mode 100644
index 0000000..e962b6e
--- /dev/null
+++ b/libnetdutils/Socket.cpp
@@ -0,0 +1,35 @@
+/*
+ * 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 <arpa/inet.h>
+
+#include "netdutils/Slice.h"
+#include "netdutils/Socket.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::string> toString(const in6_addr& addr) {
+    std::array<char, INET6_ADDRSTRLEN> out = {};
+    auto* rv = inet_ntop(AF_INET6, &addr, out.data(), out.size());
+    if (rv == nullptr) {
+        return statusFromErrno(errno, "inet_ntop() failed");
+    }
+    return std::string(out.data());
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/Status.cpp b/libnetdutils/Status.cpp
new file mode 100644
index 0000000..b373c56
--- /dev/null
+++ b/libnetdutils/Status.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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 <sstream>
+#include "netdutils/Status.h"
+#include "android-base/stringprintf.h"
+
+namespace android {
+namespace netdutils {
+
+void expectOk(const Status&) {
+    // TODO: put something here, for now this function serves solely as documentation.
+}
+
+Status statusFromErrno(int err, const std::string& msg) {
+    return Status(err, base::StringPrintf("[%s] : %s", strerror(err), msg.c_str()));
+}
+
+bool equalToErrno(const Status& status, int err) {
+    return status.code() == err;
+}
+
+std::string toString(const Status& status) {
+    std::stringstream ss;
+    ss << status;
+    return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& os, const Status& s) {
+    return os << "Status[code: " << s.code() << ", msg: \"" << s.msg() << "\"]";
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/StatusTest.cpp b/libnetdutils/StatusTest.cpp
new file mode 100644
index 0000000..242cb9f
--- /dev/null
+++ b/libnetdutils/StatusTest.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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 <cstdint>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+TEST(StatusTest, smoke) {
+    // Expect the following lines to compile
+    Status status1(1);
+    Status status2(status1);
+    Status status3 = status1;
+    const Status status4(8);
+    const Status status5(status4);
+    const Status status6 = status4;
+
+    // Default constructor
+    EXPECT_EQ(status::ok, Status());
+}
+
+TEST(StatusOrTest, ostream) {
+    {
+      StatusOr<int> so(11);
+      std::stringstream ss;
+      ss << so;
+      EXPECT_EQ("StatusOr[status: Status[code: 0, msg: ], value: 11]", ss.str());
+    }
+    {
+      StatusOr<int> err(status::undefined);
+      std::stringstream ss;
+      ss << err;
+      EXPECT_EQ("StatusOr[status: Status[code: 2147483647, msg: undefined]]", ss.str());
+    }
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/Syscalls.cpp b/libnetdutils/Syscalls.cpp
new file mode 100644
index 0000000..bad8631
--- /dev/null
+++ b/libnetdutils/Syscalls.cpp
@@ -0,0 +1,200 @@
+/*
+ * 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 <atomic>
+#include <type_traits>
+#include <utility>
+
+#include "netdutils/Syscalls.h"
+
+namespace android {
+namespace netdutils {
+namespace {
+
+// Retry syscall fn as long as it returns -1 with errno == EINTR
+template <typename FnT, typename... Params>
+typename std::result_of<FnT(Params...)>::type syscallRetry(FnT fn, Params&&... params) {
+    auto rv = fn(std::forward<Params>(params)...);
+    while ((rv == -1) && (errno == EINTR)) {
+        rv = fn(std::forward<Params>(params)...);
+    }
+    return rv;
+}
+
+}  // namespace
+
+// Production implementation of Syscalls that forwards to libc syscalls.
+class RealSyscalls final : public Syscalls {
+  public:
+    ~RealSyscalls() override = default;
+
+    StatusOr<UniqueFd> open(const std::string& pathname, int flags, mode_t mode) const override {
+        UniqueFd fd(::open(pathname.c_str(), flags, mode));
+        if (!isWellFormed(fd)) {
+            return statusFromErrno(errno, "open(\"" + pathname + "\"...) failed");
+        }
+        return fd;
+    }
+
+    StatusOr<UniqueFd> socket(int domain, int type, int protocol) const override {
+        UniqueFd sock(::socket(domain, type, protocol));
+        if (!isWellFormed(sock)) {
+            return statusFromErrno(errno, "socket() failed");
+        }
+        return sock;
+    }
+
+    Status getsockname(Fd sock, sockaddr* addr, socklen_t* addrlen) const override {
+        auto rv = ::getsockname(sock.get(), addr, addrlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "getsockname() failed");
+        }
+        return status::ok;
+    }
+
+    Status setsockopt(Fd sock, int level, int optname, const void* optval,
+                      socklen_t optlen) const override {
+        auto rv = ::setsockopt(sock.get(), level, optname, optval, optlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "setsockopt() failed");
+        }
+        return status::ok;
+    }
+
+    Status bind(Fd sock, const sockaddr* addr, socklen_t addrlen) const override {
+        auto rv = ::bind(sock.get(), addr, addrlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "bind() failed");
+        }
+        return status::ok;
+    }
+
+    Status connect(Fd sock, const sockaddr* addr, socklen_t addrlen) const override {
+        auto rv = syscallRetry(::connect, sock.get(), addr, addrlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "connect() failed");
+        }
+        return status::ok;
+    }
+
+    StatusOr<UniqueFd> eventfd(unsigned int initval, int flags) const override {
+        UniqueFd fd(::eventfd(initval, flags));
+        if (!isWellFormed(fd)) {
+            return statusFromErrno(errno, "eventfd() failed");
+        }
+        return fd;
+    }
+
+    StatusOr<int> ppoll(pollfd* fds, nfds_t nfds, double timeout) const override {
+        timespec ts = {};
+        ts.tv_sec = timeout;
+        ts.tv_nsec = (timeout - ts.tv_sec) * 1e9;
+        auto rv = syscallRetry(::ppoll, fds, nfds, &ts, nullptr);
+        if (rv == -1) {
+            return statusFromErrno(errno, "ppoll() failed");
+        }
+        return rv;
+    }
+
+    StatusOr<size_t> write(Fd fd, const Slice buf) const override {
+        auto rv = syscallRetry(::write, fd.get(), buf.base(), buf.size());
+        if (rv == -1) {
+            return statusFromErrno(errno, "write() failed");
+        }
+        return static_cast<size_t>(rv);
+    }
+
+    StatusOr<Slice> read(Fd fd, const Slice buf) const override {
+        auto rv = syscallRetry(::read, fd.get(), buf.base(), buf.size());
+        if (rv == -1) {
+            return statusFromErrno(errno, "read() failed");
+        }
+        return Slice(buf.base(), rv);
+    }
+
+    StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const sockaddr* dst,
+                            socklen_t dstlen) const override {
+        auto rv = syscallRetry(::sendto, sock.get(), buf.base(), buf.size(), flags, dst, dstlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "sendto() failed");
+        }
+        return static_cast<size_t>(rv);
+    }
+
+    StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags, sockaddr* src,
+                             socklen_t* srclen) const override {
+        auto rv = syscallRetry(::recvfrom, sock.get(), dst.base(), dst.size(), flags, src, srclen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "recvfrom() failed");
+        }
+        if (rv == 0) {
+            return status::eof;
+        }
+        return take(dst, rv);
+    }
+
+    Status shutdown(Fd fd, int how) const override {
+        auto rv = ::shutdown(fd.get(), how);
+        if (rv == -1) {
+            return statusFromErrno(errno, "shutdown() failed");
+        }
+        return status::ok;
+    }
+
+    Status close(Fd fd) const override {
+        auto rv = ::close(fd.get());
+        if (rv == -1) {
+            return statusFromErrno(errno, "close)( failed");
+        }
+        return status::ok;
+    }
+};
+
+SyscallsHolder::~SyscallsHolder() {
+    delete &get();
+}
+
+Syscalls& SyscallsHolder::get() {
+    while (true) {
+        // memory_order_relaxed gives the compiler and hardware more
+        // freedom. If we get a stale value (this should only happen
+        // early in the execution of a program) the exchange code below
+        // will loop until we get the most current value.
+        auto* syscalls = mSyscalls.load(std::memory_order_relaxed);
+        // Common case returns existing syscalls
+        if (syscalls) {
+            return *syscalls;
+        }
+
+        // This code will execute on first get()
+        std::unique_ptr<Syscalls> tmp(new RealSyscalls());
+        Syscalls* expected = nullptr;
+        bool success = mSyscalls.compare_exchange_strong(expected, tmp.get());
+        if (success) {
+            // Ownership was transferred to mSyscalls already, must release()
+            return *tmp.release();
+        }
+    }
+}
+
+Syscalls& SyscallsHolder::swap(Syscalls& syscalls) {
+    return *mSyscalls.exchange(&syscalls);
+}
+
+SyscallsHolder sSyscalls;
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/SyscallsTest.cpp b/libnetdutils/SyscallsTest.cpp
new file mode 100644
index 0000000..a754d1c
--- /dev/null
+++ b/libnetdutils/SyscallsTest.cpp
@@ -0,0 +1,182 @@
+/*
+ * 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 <array>
+#include <cstdint>
+#include <memory>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/Handle.h"
+#include "netdutils/Math.h"
+#include "netdutils/MockSyscalls.h"
+#include "netdutils/Netfilter.h"
+#include "netdutils/Netlink.h"
+#include "netdutils/Slice.h"
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+#include "netdutils/Syscalls.h"
+
+using testing::ByMove;
+using testing::DoAll;
+using testing::Invoke;
+using testing::Mock;
+using testing::Return;
+using testing::StrictMock;
+using testing::_;
+
+namespace android {
+namespace netdutils {
+
+class SyscallsTest : public testing::Test {
+  protected:
+    StrictMock<ScopedMockSyscalls> mSyscalls;
+};
+
+TEST(syscalls, scopedMock) {
+    auto& old = sSyscalls.get();
+    {
+        StrictMock<ScopedMockSyscalls> s;
+        EXPECT_EQ(&s, &sSyscalls.get());
+    }
+    EXPECT_EQ(&old, &sSyscalls.get());
+}
+
+TEST_F(SyscallsTest, open) {
+    const char kPath[] = "/test/path/please/ignore";
+    constexpr Fd kFd(40);
+    constexpr int kFlags = 883;
+    constexpr mode_t kMode = 37373;
+    const auto& sys = sSyscalls.get();
+    EXPECT_CALL(mSyscalls, open(kPath, kFlags, kMode)).WillOnce(Return(ByMove(UniqueFd(kFd))));
+    auto result = sys.open(kPath, kFlags, kMode);
+    EXPECT_EQ(status::ok, result.status());
+    EXPECT_EQ(kFd, result.value());
+}
+
+TEST_F(SyscallsTest, getsockname) {
+    constexpr Fd kFd(40);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, getsockname(kFd, _, _))
+        .WillOnce(Invoke([expected](Fd, sockaddr* addr, socklen_t* addrlen) {
+            memcpy(addr, &expected, sizeof(expected));
+            EXPECT_EQ(*addrlen, static_cast<socklen_t>(sizeof(expected)));
+            return status::ok;
+        }));
+    const auto result = sys.getsockname<sockaddr_nl>(kFd);
+    EXPECT_TRUE(isOk(result));
+    EXPECT_EQ(expected, result.value());
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, getsockname(kFd, _, _)).WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.getsockname<sockaddr_nl>(kFd).status());
+}
+
+TEST_F(SyscallsTest, setsockopt) {
+    constexpr Fd kFd(40);
+    constexpr int kLevel = 50;
+    constexpr int kOptname = 70;
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, setsockopt(kFd, kLevel, kOptname, &expected, sizeof(expected)))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.setsockopt(kFd, kLevel, kOptname, expected));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, setsockopt(kFd, kLevel, kOptname, &expected, sizeof(expected)))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.setsockopt(kFd, kLevel, kOptname, expected));
+}
+
+TEST_F(SyscallsTest, bind) {
+    constexpr Fd kFd(40);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, bind(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.bind(kFd, expected));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, bind(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.bind(kFd, expected));
+}
+
+TEST_F(SyscallsTest, connect) {
+    constexpr Fd kFd(40);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, connect(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.connect(kFd, expected));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, connect(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.connect(kFd, expected));
+}
+
+TEST_F(SyscallsTest, sendto) {
+    constexpr Fd kFd(40);
+    constexpr int kFlags = 0;
+    std::array<char, 10> payload;
+    const auto slice = makeSlice(payload);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, sendto(kFd, slice, kFlags, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(slice.size()));
+    EXPECT_EQ(status::ok, sys.sendto(kFd, slice, kFlags, expected));
+}
+
+TEST_F(SyscallsTest, recvfrom) {
+    constexpr Fd kFd(40);
+    constexpr int kFlags = 0;
+    std::array<char, 10> payload;
+    const auto dst = makeSlice(payload);
+    const auto used = take(dst, 8);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, recvfrom(kFd, dst, kFlags, _, _))
+        .WillOnce(Invoke([expected, used](Fd, const Slice, int, sockaddr* src, socklen_t* srclen) {
+            memcpy(src, &expected, sizeof(src));
+            *srclen = sizeof(expected);
+            return used;
+        }));
+    auto result = sys.recvfrom<sockaddr_nl>(kFd, dst, kFlags);
+    EXPECT_EQ(status::ok, result.status());
+    EXPECT_EQ(used, result.value().first);
+    EXPECT_EQ(expected, result.value().second);
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/UniqueFd.cpp b/libnetdutils/UniqueFd.cpp
new file mode 100644
index 0000000..1cb30ed
--- /dev/null
+++ b/libnetdutils/UniqueFd.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 <algorithm>
+
+#include "netdutils/UniqueFd.h"
+#include "netdutils/Syscalls.h"
+
+namespace android {
+namespace netdutils {
+
+void UniqueFd::reset(Fd fd) {
+    auto& sys = sSyscalls.get();
+    std::swap(fd, mFd);
+    if (isWellFormed(fd)) {
+        expectOk(sys.close(fd));
+    }
+}
+
+std::ostream& operator<<(std::ostream& os, const UniqueFd& fd) {
+    return os << "UniqueFd[" << static_cast<Fd>(fd) << "]";
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/libnetdutils/include/netdutils/Fd.h b/libnetdutils/include/netdutils/Fd.h
new file mode 100644
index 0000000..7db4087
--- /dev/null
+++ b/libnetdutils/include/netdutils/Fd.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.
+ */
+
+#ifndef NETUTILS_FD_H
+#define NETUTILS_FD_H
+
+#include <ostream>
+
+#include "netdutils/Status.h"
+
+namespace android {
+namespace netdutils {
+
+// Strongly typed wrapper for file descriptors with value semantics.
+// This class should typically hold unowned file descriptors.
+class Fd {
+  public:
+    constexpr Fd() = default;
+
+    constexpr Fd(int fd) : mFd(fd) {}
+
+    int get() const { return mFd; }
+
+    bool operator==(const Fd& other) const { return get() == other.get(); }
+    bool operator!=(const Fd& other) const { return get() != other.get(); }
+
+  private:
+    int mFd = -1;
+};
+
+// Return true if fd appears valid (non-negative)
+inline bool isWellFormed(const Fd fd) {
+    return fd.get() >= 0;
+}
+
+std::ostream& operator<<(std::ostream& os, const Fd& fd);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_FD_H */
diff --git a/libnetdutils/include/netdutils/Handle.h b/libnetdutils/include/netdutils/Handle.h
new file mode 100644
index 0000000..82083d4
--- /dev/null
+++ b/libnetdutils/include/netdutils/Handle.h
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_HANDLE_H
+#define NETUTILS_HANDLE_H
+
+#include <ostream>
+
+namespace android {
+namespace netdutils {
+
+// Opaque, strongly typed wrapper for integer-like handles.
+// Explicitly avoids implementing arithmetic operations.
+//
+// This class is intended to avoid common errors when reordering
+// arguments to functions, typos and other cases where plain integer
+// types would silently cover up the mistake.
+//
+// usage:
+// DEFINE_HANDLE(ProductId, uint64_t);
+// DEFINE_HANDLE(ThumbnailHash, uint64_t);
+// void foo(ProductId p, ThumbnailHash th) {...}
+//
+// void test() {
+//     ProductId p(88);
+//     ThumbnailHash th1(100), th2(200);
+//
+//     foo(p, th1);        <- ok!
+//     foo(th1, p);        <- disallowed!
+//     th1 += 10;          <- disallowed!
+//     p = th2;            <- disallowed!
+//     assert(th1 != th2); <- ok!
+// }
+template <typename T, typename TagT>
+class Handle {
+  public:
+    constexpr Handle() = default;
+    constexpr Handle(const T& value) : mValue(value) {}
+
+    const T get() const { return mValue; }
+
+    bool operator==(const Handle& that) const { return get() == that.get(); }
+    bool operator!=(const Handle& that) const { return get() != that.get(); }
+
+  private:
+    T mValue;
+};
+
+#define DEFINE_HANDLE(name, type) \
+    struct _##name##Tag {};       \
+    using name = ::android::netdutils::Handle<type, _##name##Tag>;
+
+template <typename T, typename TagT>
+inline std::ostream& operator<<(std::ostream& os, const Handle<T, TagT>& handle) {
+    return os << handle.get();
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_HANDLE_H */
diff --git a/libnetdutils/include/netdutils/Math.h b/libnetdutils/include/netdutils/Math.h
new file mode 100644
index 0000000..c41fbf5
--- /dev/null
+++ b/libnetdutils/include/netdutils/Math.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_MATH_H
+#define NETUTILS_MATH_H
+
+#include <algorithm>
+#include <cstdint>
+
+namespace android {
+namespace netdutils {
+
+template <class T>
+inline constexpr const T mask(const int shift) {
+    return (1 << shift) - 1;
+}
+
+// Align x up to the nearest integer multiple of 2^shift
+template <class T>
+inline constexpr const T align(const T& x, const int shift) {
+    return (x + mask<T>(shift)) & ~mask<T>(shift);
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MATH_H */
diff --git a/libnetdutils/include/netdutils/Misc.h b/libnetdutils/include/netdutils/Misc.h
new file mode 100644
index 0000000..41f5778
--- /dev/null
+++ b/libnetdutils/include/netdutils/Misc.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_MISC_H
+#define NETUTILS_MISC_H
+
+#include <map>
+
+namespace android {
+namespace netdutils {
+
+// Lookup key in map, returing a default value if key is not found
+template <typename U, typename V>
+inline const V& findWithDefault(const std::map<U, V>& map, const U& key, const V& dflt) {
+    auto it = map.find(key);
+    return (it == map.end()) ? dflt : it->second;
+}
+
+// Movable, copiable, scoped lambda (or std::function) runner. Useful
+// for running arbitrary cleanup or logging code when exiting a scope.
+//
+// Compare to defer in golang.
+template <typename FnT>
+class Cleanup {
+  public:
+    Cleanup() = delete;
+    Cleanup(FnT fn) : mFn(fn) {}
+    ~Cleanup() { mFn(); }
+
+    void release() { mFn = {}; }
+
+  private:
+    FnT mFn;
+};
+
+// Helper to make a new Cleanup. Avoids complex or impossible syntax
+// when wrapping lambdas.
+//
+// Usage:
+// auto cleanup = makeCleanup([](){ your_code_here; });
+template <typename FnT>
+Cleanup<FnT> makeCleanup(FnT fn) {
+    return Cleanup<FnT>(fn);
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MISC_H */
diff --git a/libnetdutils/include/netdutils/MockSyscalls.h b/libnetdutils/include/netdutils/MockSyscalls.h
new file mode 100644
index 0000000..d6a02b5
--- /dev/null
+++ b/libnetdutils/include/netdutils/MockSyscalls.h
@@ -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.
+ */
+
+#ifndef NETUTILS_MOCK_SYSCALLS_H
+#define NETUTILS_MOCK_SYSCALLS_H
+
+#include <atomic>
+#include <cassert>
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "netdutils/Syscalls.h"
+
+namespace android {
+namespace netdutils {
+
+class MockSyscalls : public Syscalls {
+  public:
+    virtual ~MockSyscalls() = default;
+    // Use Return(ByMove(...)) to deal with movable return types.
+    MOCK_CONST_METHOD3(open,
+                       StatusOr<UniqueFd>(const std::string& pathname, int flags, mode_t mode));
+    MOCK_CONST_METHOD3(socket, StatusOr<UniqueFd>(int domain, int type, int protocol));
+    MOCK_CONST_METHOD3(getsockname, Status(Fd sock, sockaddr* addr, socklen_t* addrlen));
+    MOCK_CONST_METHOD5(setsockopt, Status(Fd sock, int level, int optname, const void* optval,
+                                          socklen_t optlen));
+
+    MOCK_CONST_METHOD3(bind, Status(Fd sock, const sockaddr* addr, socklen_t addrlen));
+    MOCK_CONST_METHOD3(connect, Status(Fd sock, const sockaddr* addr, socklen_t addrlen));
+
+    // Use Return(ByMove(...)) to deal with movable return types.
+    MOCK_CONST_METHOD2(eventfd, StatusOr<UniqueFd>(unsigned int initval, int flags));
+    MOCK_CONST_METHOD3(ppoll, StatusOr<int>(pollfd* fds, nfds_t nfds, double timeout));
+    MOCK_CONST_METHOD2(write, StatusOr<size_t>(Fd fd, const Slice buf));
+    MOCK_CONST_METHOD2(read, StatusOr<Slice>(Fd fd, const Slice buf));
+    MOCK_CONST_METHOD5(sendto, StatusOr<size_t>(Fd sock, const Slice buf, int flags,
+                                                const sockaddr* dst, socklen_t dstlen));
+    MOCK_CONST_METHOD5(recvfrom, StatusOr<Slice>(Fd sock, const Slice dst, int flags, sockaddr* src,
+                                                 socklen_t* srclen));
+    MOCK_CONST_METHOD2(shutdown, Status(Fd fd, int how));
+    MOCK_CONST_METHOD1(close, Status(Fd fd));
+};
+
+// For the lifetime of this mock, replace the contents of sSyscalls
+// with a pointer to this mock. Behavior is undefined if multiple
+// ScopedMockSyscalls instances exist concurrently.
+class ScopedMockSyscalls : public MockSyscalls {
+  public:
+    ScopedMockSyscalls() : mOld(sSyscalls.swap(*this)) { assert((mRefcount++) == 1); }
+    virtual ~ScopedMockSyscalls() {
+        sSyscalls.swap(mOld);
+        assert((mRefcount--) == 0);
+    }
+
+  private:
+    std::atomic<int> mRefcount{0};
+    Syscalls& mOld;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MOCK_SYSCALLS_H */
diff --git a/libnetdutils/include/netdutils/Netfilter.h b/libnetdutils/include/netdutils/Netfilter.h
new file mode 100644
index 0000000..22736f1
--- /dev/null
+++ b/libnetdutils/include/netdutils/Netfilter.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.
+ */
+
+#ifndef NETUTILS_NETFILTER_H
+#define NETUTILS_NETFILTER_H
+
+#include <ostream>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netlink.h>
+
+std::ostream& operator<<(std::ostream& os, const nfgenmsg& msg);
+
+#endif /* NETUTILS_NETFILTER_H */
diff --git a/libnetdutils/include/netdutils/Netlink.h b/libnetdutils/include/netdutils/Netlink.h
new file mode 100644
index 0000000..ee5183a
--- /dev/null
+++ b/libnetdutils/include/netdutils/Netlink.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_NETLINK_H
+#define NETUTILS_NETLINK_H
+
+#include <functional>
+#include <ostream>
+#include <linux/netlink.h>
+
+#include "netdutils/Slice.h"
+
+namespace android {
+namespace netdutils {
+
+// Invoke onMsg once for each netlink message in buf. onMsg will be
+// invoked with an aligned and deserialized header along with a Slice
+// containing the message payload.
+//
+// Assume that the first message begins at offset zero within buf.
+void forEachNetlinkMessage(const Slice buf,
+                           const std::function<void(const nlmsghdr&, const Slice)>& onMsg);
+
+// Invoke onAttr once for each netlink attribute in buf. onAttr will be
+// invoked with an aligned and deserialized header along with a Slice
+// containing the attribute payload.
+//
+// Assume that the first attribute begins at offset zero within buf.
+void forEachNetlinkAttribute(const Slice buf,
+                             const std::function<void(const nlattr&, const Slice)>& onAttr);
+
+}  // namespace netdutils
+}  // namespace android
+
+bool operator==(const sockaddr_nl& lhs, const sockaddr_nl& rhs);
+bool operator!=(const sockaddr_nl& lhs, const sockaddr_nl& rhs);
+
+std::ostream& operator<<(std::ostream& os, const nlmsghdr& hdr);
+std::ostream& operator<<(std::ostream& os, const nlattr& attr);
+std::ostream& operator<<(std::ostream& os, const sockaddr_nl& addr);
+
+#endif /* NETUTILS_NETLINK_H */
diff --git a/libnetdutils/include/netdutils/Slice.h b/libnetdutils/include/netdutils/Slice.h
new file mode 100644
index 0000000..85a0980
--- /dev/null
+++ b/libnetdutils/include/netdutils/Slice.h
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_SLICE_H
+#define NETUTILS_SLICE_H
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <ostream>
+#include <tuple>
+#include <vector>
+
+namespace android {
+namespace netdutils {
+
+// Immutable wrapper for a linear region of unowned bytes.
+// Slice represents memory as a half-closed interval [base, limit).
+//
+// Note that without manually invoking the Slice() constructor, it is
+// impossible to increase the size of a slice. This guarantees that
+// applications that properly use the slice API will never access
+// memory outside of a slice.
+//
+// Note that const Slice still wraps mutable memory, however copy
+// assignment and move assignment to slice are disabled.
+class Slice {
+  public:
+    Slice() = default;
+
+    // Create a slice beginning at base and continuing to but not including limit
+    Slice(void* base, void* limit) : mBase(toUint8(base)), mLimit(toUint8(limit)) {}
+
+    // Create a slice beginning at base and continuing for size bytes
+    Slice(void* base, size_t size) : Slice(base, toUint8(base) + size) {}
+
+    // Return the address of the first byte in this slice
+    uint8_t* base() const { return mBase; }
+
+    // Return the address of the first byte following this slice
+    uint8_t* limit() const { return mLimit; }
+
+    // Return the size of this slice in bytes
+    size_t size() const { return limit() - base(); }
+
+    // Return true if size() == 0
+    bool empty() const { return base() == limit(); }
+
+  private:
+    static uint8_t* toUint8(void* ptr) { return reinterpret_cast<uint8_t*>(ptr); }
+
+    uint8_t* mBase = nullptr;
+    uint8_t* mLimit = nullptr;
+};
+
+// Return slice representation of ref which must be a POD type
+template <typename T>
+inline const Slice makeSlice(const T& ref) {
+    static_assert(std::is_pod<T>::value, "value must be a POD type");
+    static_assert(!std::is_pointer<T>::value, "value must not be a pointer type");
+    return {const_cast<T*>(&ref), sizeof(ref)};
+}
+
+// Return slice representation of string data()
+inline const Slice makeSlice(const std::string& s) {
+    using ValueT = std::string::value_type;
+    return {const_cast<ValueT*>(s.data()), s.size() * sizeof(ValueT)};
+}
+
+// Return slice representation of vector data()
+template <typename T>
+inline const Slice makeSlice(const std::vector<T>& v) {
+    return {const_cast<T*>(v.data()), v.size() * sizeof(T)};
+}
+
+// Return slice representation of array data()
+template <typename U, size_t V>
+inline const Slice makeSlice(const std::array<U, V>& a) {
+    return {const_cast<U*>(a.data()), a.size() * sizeof(U)};
+}
+
+// Return prefix and suffix of Slice s ending and starting at position cut
+inline std::pair<const Slice, const Slice> split(const Slice s, size_t cut) {
+    const size_t tmp = std::min(cut, s.size());
+    return {{s.base(), s.base() + tmp}, {s.base() + tmp, s.limit()}};
+}
+
+// Return prefix of Slice s ending at position cut
+inline const Slice take(const Slice s, size_t cut) {
+    return std::get<0>(split(s, cut));
+}
+
+// Return suffix of Slice s starting at position cut
+inline const Slice drop(const Slice s, size_t cut) {
+    return std::get<1>(split(s, cut));
+}
+
+// Copy from src into dst. Bytes copied is the lesser of dst.size() and src.size()
+inline size_t copy(const Slice dst, const Slice src) {
+    const auto min = std::min(dst.size(), src.size());
+    memcpy(dst.base(), src.base(), min);
+    return min;
+}
+
+// Base case for variadic extract below
+template <typename Head>
+inline size_t extract(const Slice src, Head& head) {
+    return copy(makeSlice(head), src);
+}
+
+// Copy from src into one or more pointers to POD data.  If src.size()
+// is less than the sum of all data pointers a suffix of data will be
+// left unmodified. Return the number of bytes copied.
+template <typename Head, typename... Tail>
+inline size_t extract(const Slice src, Head& head, Tail... tail) {
+    const auto extracted = extract(src, head);
+    return extracted + extract(drop(src, extracted), tail...);
+}
+
+// Return a string containing a copy of the contents of s
+std::string toString(const Slice s);
+
+// Return a string containing a hexadecimal representation of the contents of s.
+// This function inserts a newline into its output every wrap bytes.
+std::string toHex(const Slice s, int wrap);
+
+inline bool operator==(const Slice& lhs, const Slice& rhs) {
+    return (lhs.base() == rhs.base()) && (lhs.limit() == rhs.limit());
+}
+
+inline bool operator!=(const Slice& lhs, const Slice& rhs) {
+    return !(lhs == rhs);
+}
+
+std::ostream& operator<<(std::ostream& os, const Slice& slice);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_SLICE_H */
diff --git a/libnetdutils/include/netdutils/Socket.h b/libnetdutils/include/netdutils/Socket.h
new file mode 100644
index 0000000..e5aaab9
--- /dev/null
+++ b/libnetdutils/include/netdutils/Socket.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef NETDUTILS_SOCKET_H
+#define NETDUTILS_SOCKET_H
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <string>
+
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+inline sockaddr* asSockaddrPtr(void* addr) {
+    return reinterpret_cast<sockaddr*>(addr);
+}
+
+inline const sockaddr* asSockaddrPtr(const void* addr) {
+    return reinterpret_cast<const sockaddr*>(addr);
+}
+
+// Return a string representation of addr or Status if there was a
+// failure during conversion.
+StatusOr<std::string> toString(const in6_addr& addr);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_SOCKET_H */
diff --git a/libnetdutils/include/netdutils/Status.h b/libnetdutils/include/netdutils/Status.h
new file mode 100644
index 0000000..a9209bf
--- /dev/null
+++ b/libnetdutils/include/netdutils/Status.h
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_STATUS_H
+#define NETUTILS_STATUS_H
+
+#include <cassert>
+#include <ostream>
+
+namespace android {
+namespace netdutils {
+
+// Simple status implementation suitable for use on the stack in low
+// or moderate performance code. This can definitely be improved but
+// for now short string optimization is expected to keep the common
+// success case fast.
+class Status {
+  public:
+    Status() = default;
+
+    Status(int code) : mCode(code) {}
+
+    Status(int code, const std::string& msg) : mCode(code), mMsg(msg) { assert(!ok()); }
+
+    int code() const { return mCode; }
+
+    bool ok() const { return code() == 0; }
+
+    const std::string& msg() const { return mMsg; }
+
+    bool operator==(const Status& other) const { return code() == other.code(); }
+    bool operator!=(const Status& other) const { return !(*this == other); }
+
+  private:
+    int mCode = 0;
+    std::string mMsg;
+};
+
+namespace status {
+
+const Status ok{0};
+// EOF is not part of errno space, we'll place it far above the
+// highest existing value.
+const Status eof{0x10001, "end of file"};
+const Status undefined{std::numeric_limits<int>::max(), "undefined"};
+
+}  // namespace status
+
+// Return true if status is "OK". This is sometimes preferable to
+// status.ok() when we want to check the state of Status-like objects
+// that implicitly cast to Status.
+inline bool isOk(const Status& status) {
+    return status.ok();
+}
+
+// Document that status is expected to be ok. This function may log
+// (or assert when running in debug mode) if status has an unexpected
+// value.
+void expectOk(const Status& status);
+
+// Convert POSIX errno to a Status object.
+// If Status is extended to have more features, this mapping may
+// become more complex.
+Status statusFromErrno(int err, const std::string& msg);
+
+// Helper that checks Status-like object (notably StatusOr) against a
+// value in the errno space.
+bool equalToErrno(const Status& status, int err);
+
+// Helper that converts Status-like object (notably StatusOr) to a
+// message.
+std::string toString(const Status& status);
+
+std::ostream& operator<<(std::ostream& os, const Status& s);
+
+#define RETURN_IF_NOT_OK_IMPL(tmp, stmt)           \
+    do {                                           \
+        ::android::netdutils::Status tmp = (stmt); \
+        if (!isOk(tmp)) {                          \
+            return tmp;                            \
+        }                                          \
+    } while (false)
+
+#define RETURN_IF_NOT_OK_CONCAT(line, stmt) RETURN_IF_NOT_OK_IMPL(__CONCAT(_status_, line), stmt)
+
+// Macro to allow exception-like handling of error return values.
+//
+// If the evaluation of stmt results in an error, return that error
+// from current function.
+//
+// Example usage:
+// Status bar() { ... }
+//
+// RETURN_IF_NOT_OK(status);
+// RETURN_IF_NOT_OK(bar());
+#define RETURN_IF_NOT_OK(stmt) RETURN_IF_NOT_OK_CONCAT(__LINE__, stmt)
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_STATUS_H */
diff --git a/libnetdutils/include/netdutils/StatusOr.h b/libnetdutils/include/netdutils/StatusOr.h
new file mode 100644
index 0000000..6afbfcb
--- /dev/null
+++ b/libnetdutils/include/netdutils/StatusOr.h
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_STATUSOR_H
+#define NETUTILS_STATUSOR_H
+
+#include <cassert>
+#include "netdutils/Status.h"
+
+namespace android {
+namespace netdutils {
+
+// Wrapper around a combination of Status and application value type.
+// T may be any copyable or movable type.
+template <typename T>
+class StatusOr {
+  public:
+    StatusOr() = default;
+    StatusOr(const Status status) : mStatus(status) { assert(!isOk(status)); }
+    StatusOr(const T& value) : mStatus(status::ok), mValue(value) {}
+    StatusOr(T&& value) : mStatus(status::ok), mValue(std::move(value)) {}
+
+    // Move constructor ok (if T supports move)
+    StatusOr(StatusOr&&) = default;
+    // Move assignment ok (if T supports move)
+    StatusOr& operator=(StatusOr&&) = default;
+    // Copy constructor ok (if T supports copy)
+    StatusOr(const StatusOr&) = default;
+    // Copy assignment ok (if T supports copy)
+    StatusOr& operator=(const StatusOr&) = default;
+
+    // Return const references to wrapped type
+    // It is an error to call value() when !isOk(status())
+    const T& value() const & { return mValue; }
+    const T&& value() const && { return mValue; }
+
+    // Return rvalue references to wrapped type
+    // It is an error to call value() when !isOk(status())
+    T& value() & { return mValue; }
+    T&& value() && { return mValue; }
+
+    // Return status assigned in constructor
+    const Status status() const { return mStatus; }
+
+    // Implict cast to Status
+    operator Status() const { return status(); }
+
+  private:
+    Status mStatus = status::undefined;
+    T mValue;
+};
+
+template <typename T>
+inline std::ostream& operator<<(std::ostream& os, const StatusOr<T>& s) {
+    os << "StatusOr[status: " << s.status();
+    if (isOk(s)) {
+        os << ", value: " << s.value();
+    }
+    return os << "]";
+}
+
+#define ASSIGN_OR_RETURN_IMPL(tmp, lhs, stmt) \
+    auto tmp = (stmt);                        \
+    RETURN_IF_NOT_OK(tmp);                    \
+    lhs = std::move(tmp.value());
+
+#define ASSIGN_OR_RETURN_CONCAT(line, lhs, stmt) \
+    ASSIGN_OR_RETURN_IMPL(__CONCAT(_status_or_, line), lhs, stmt)
+
+// Macro to allow exception-like handling of error return values.
+//
+// If the evaluation of stmt results in an error, return that error
+// from the current function. Otherwise, assign the result to lhs.
+//
+// This macro supports both move and copy assignment operators. lhs
+// may be either a new local variable or an existing non-const
+// variable accessible in the current scope.
+//
+// Example usage:
+// StatusOr<MyType> foo() { ... }
+//
+// ASSIGN_OR_RETURN(auto myVar, foo());
+// ASSIGN_OR_RETURN(myExistingVar, foo());
+// ASSIGN_OR_RETURN(myMemberVar, foo());
+#define ASSIGN_OR_RETURN(lhs, stmt) ASSIGN_OR_RETURN_CONCAT(__LINE__, lhs, stmt)
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_STATUSOR_H */
diff --git a/libnetdutils/include/netdutils/Syscalls.h b/libnetdutils/include/netdutils/Syscalls.h
new file mode 100644
index 0000000..48be5a2
--- /dev/null
+++ b/libnetdutils/include/netdutils/Syscalls.h
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_SYSCALLS_H
+#define NETUTILS_SYSCALLS_H
+
+#include <poll.h>
+#include <sys/eventfd.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "netdutils/Slice.h"
+#include "netdutils/Socket.h"
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+#include "netdutils/UniqueFd.h"
+
+namespace android {
+namespace netdutils {
+
+class Syscalls {
+  public:
+    virtual ~Syscalls() = default;
+
+    virtual StatusOr<UniqueFd> open(const std::string& pathname, int flags,
+                                    mode_t mode = 0) const = 0;
+
+    virtual StatusOr<UniqueFd> socket(int domain, int type, int protocol) const = 0;
+
+    virtual Status getsockname(Fd sock, sockaddr* addr, socklen_t* addrlen) const = 0;
+
+    virtual Status setsockopt(Fd sock, int level, int optname, const void* optval,
+                              socklen_t optlen) const = 0;
+
+    virtual Status bind(Fd sock, const sockaddr* addr, socklen_t addrlen) const = 0;
+
+    virtual Status connect(Fd sock, const sockaddr* addr, socklen_t addrlen) const = 0;
+
+    virtual StatusOr<UniqueFd> eventfd(unsigned int initval, int flags) const = 0;
+
+    virtual StatusOr<int> ppoll(pollfd* fds, nfds_t nfds, double timeout) const = 0;
+
+    virtual StatusOr<size_t> write(Fd fd, const Slice buf) const = 0;
+
+    virtual StatusOr<Slice> read(Fd fd, const Slice buf) const = 0;
+
+    virtual StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const sockaddr* dst,
+                                    socklen_t dstlen) const = 0;
+
+    virtual StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags, sockaddr* src,
+                                     socklen_t* srclen) const = 0;
+
+    virtual Status shutdown(Fd fd, int how) const = 0;
+
+    virtual Status close(Fd fd) const = 0;
+
+    // Templated helpers that forward directly to methods declared above
+    template <typename SockaddrT>
+    StatusOr<SockaddrT> getsockname(Fd sock) const {
+        SockaddrT addr = {};
+        socklen_t addrlen = sizeof(addr);
+        RETURN_IF_NOT_OK(getsockname(sock, asSockaddrPtr(&addr), &addrlen));
+        return addr;
+    }
+
+    template <typename SockoptT>
+    Status setsockopt(Fd sock, int level, int optname, const SockoptT& opt) const {
+        return setsockopt(sock, level, optname, &opt, sizeof(opt));
+    }
+
+    template <typename SockaddrT>
+    Status bind(Fd sock, const SockaddrT& addr) const {
+        return bind(sock, asSockaddrPtr(&addr), sizeof(addr));
+    }
+
+    template <typename SockaddrT>
+    Status connect(Fd sock, const SockaddrT& addr) const {
+        return connect(sock, asSockaddrPtr(&addr), sizeof(addr));
+    }
+
+    template <size_t size>
+    StatusOr<std::array<uint16_t, size>> ppoll(const std::array<Fd, size>& fds, uint16_t events,
+                                               double timeout) const {
+        std::array<pollfd, size> tmp;
+        for (size_t i = 0; i < size; ++i) {
+            tmp[i].fd = fds[i].get();
+            tmp[i].events = events;
+            tmp[i].revents = 0;
+        }
+        RETURN_IF_NOT_OK(ppoll(tmp.data(), tmp.size(), timeout).status());
+        std::array<uint16_t, size> out;
+        for (size_t i = 0; i < size; ++i) {
+            out[i] = tmp[i].revents;
+        }
+        return out;
+    }
+
+    template <typename SockaddrT>
+    StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const SockaddrT& dst) const {
+        return sendto(sock, buf, flags, asSockaddrPtr(&dst), sizeof(dst));
+    }
+
+    // Ignore src sockaddr
+    StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags) const {
+        return recvfrom(sock, dst, flags, nullptr, nullptr);
+    }
+
+    template <typename SockaddrT>
+    StatusOr<std::pair<Slice, SockaddrT>> recvfrom(Fd sock, const Slice dst, int flags) const {
+        SockaddrT addr = {};
+        socklen_t addrlen = sizeof(addr);
+        ASSIGN_OR_RETURN(auto used, recvfrom(sock, dst, flags, asSockaddrPtr(&addr), &addrlen));
+        return std::make_pair(used, addr);
+    }
+};
+
+// Specialized singleton that supports zero initialization and runtime
+// override of contained pointer.
+class SyscallsHolder {
+  public:
+    ~SyscallsHolder();
+
+    // Return a pointer to an unowned instance of Syscalls.
+    Syscalls& get();
+
+    // Testing only: set the value returned by getSyscalls. Return the old value.
+    // Callers are responsible for restoring the previous value returned
+    // by getSyscalls to avoid leaks.
+    Syscalls& swap(Syscalls& syscalls);
+
+  private:
+    std::atomic<Syscalls*> mSyscalls{nullptr};
+};
+
+// Syscalls instance used throughout netdutils
+extern SyscallsHolder sSyscalls;
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_SYSCALLS_H */
diff --git a/libnetdutils/include/netdutils/UniqueFd.h b/libnetdutils/include/netdutils/UniqueFd.h
new file mode 100644
index 0000000..3b63f34
--- /dev/null
+++ b/libnetdutils/include/netdutils/UniqueFd.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_UNIQUEFD_H
+#define NETUTILS_UNIQUEFD_H
+
+#include <unistd.h>
+#include <ostream>
+
+#include "netdutils/Fd.h"
+
+namespace android {
+namespace netdutils {
+
+// Stricter unique_fd implementation that:
+// *) Does not implement release()
+// *) Does not implicitly cast to int
+// *) Uses a strongly typed wrapper (Fd) for the underlying file descriptor
+//
+// Users of UniqueFd should endeavor to treat this as a completely
+// opaque object. The only code that should interpret the wrapped
+// value is in Syscalls.h
+class UniqueFd {
+  public:
+    UniqueFd() = default;
+
+    UniqueFd(Fd fd) : mFd(fd) {}
+
+    ~UniqueFd() { reset(); }
+
+    // Disallow copy
+    UniqueFd(const UniqueFd&) = delete;
+    UniqueFd& operator=(const UniqueFd&) = delete;
+
+    // Allow move
+    UniqueFd(UniqueFd&& other) { std::swap(mFd, other.mFd); }
+    UniqueFd& operator=(UniqueFd&& other) {
+        std::swap(mFd, other.mFd);
+        return *this;
+    }
+
+    // Cleanup any currently owned Fd, replacing it with the optional
+    // parameter fd
+    void reset(Fd fd = Fd());
+
+    // Implict cast to Fd
+    const operator Fd() const { return mFd; }
+
+  private:
+    Fd mFd;
+};
+
+std::ostream& operator<<(std::ostream& os, const UniqueFd& fd);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_UNIQUEFD_H */
diff --git a/server/Android.mk b/server/Android.mk
index 0ee65c2..c8ba20f 100644
--- a/server/Android.mk
+++ b/server/Android.mk
@@ -52,7 +52,13 @@
 LOCAL_MODULE := netd
 
 # Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
-LOCAL_CPPFLAGS +=  -Wno-varargs
+LOCAL_CPPFLAGS +=  -Wno-varargs \
+
+ifeq ($(TARGET_ARCH), x86)
+ifneq ($(TARGET_PRODUCT), gce_x86_phone)
+        LOCAL_CPPFLAGS += -D NETLINK_COMPAT32
+endif
+endif
 
 LOCAL_INIT_RC := netd.rc
 
@@ -65,6 +71,7 @@
         libmdnssd \
         libnetdaidl \
         libnetutils \
+        libnetdutils \
         libnl \
         libsysutils \
         libbase \
@@ -94,8 +101,10 @@
         NetlinkHandler.cpp \
         NetlinkManager.cpp \
         NetlinkCommands.cpp \
+        NetlinkListener.cpp \
         Network.cpp \
         NetworkController.cpp \
+        NFLogListener.cpp \
         PhysicalNetwork.cpp \
         PppController.cpp \
         ResolverController.cpp \
@@ -105,6 +114,8 @@
         TetherController.cpp \
         UidRanges.cpp \
         VirtualNetwork.cpp \
+        WakeupController.cpp \
+        XfrmController.cpp \
         main.cpp \
         oem_iptables_hook.cpp \
         binder/android/net/UidRange.cpp \
@@ -134,6 +145,7 @@
 ###
 include $(CLEAR_VARS)
 LOCAL_MODULE := netd_unit_test
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_SANITIZE := unsigned-integer-overflow
 LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter
 # Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
@@ -148,6 +160,7 @@
         system/core/logwrapper/include \
 
 LOCAL_SRC_FILES := \
+        InterfaceController.cpp InterfaceControllerTest.cpp \
         Controllers.cpp \
         NetdConstants.cpp IptablesBaseTest.cpp \
         IptablesRestoreController.cpp IptablesRestoreControllerTest.cpp \
@@ -155,23 +168,30 @@
         FirewallControllerTest.cpp FirewallController.cpp \
         IdletimerController.cpp \
         NatControllerTest.cpp NatController.cpp \
-        NetlinkCommands.cpp \
+        NetlinkCommands.cpp NetlinkManager.cpp \
         RouteController.cpp RouteControllerTest.cpp \
         SockDiagTest.cpp SockDiag.cpp \
         StrictController.cpp StrictControllerTest.cpp \
         UidRanges.cpp \
+        NetlinkListener.cpp \
+        WakeupController.cpp WakeupControllerTest.cpp \
+        NFLogListener.cpp NFLogListenerTest.cpp \
         binder/android/net/UidRange.cpp \
         binder/android/net/metrics/INetdEventListener.aidl \
         ../tests/tun_interface.cpp \
 
 LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_LIBRARIES := libgmock libpcap
 LOCAL_SHARED_LIBRARIES := \
+        libnetdaidl \
         libbase \
         libbinder \
         libcutils \
         liblog \
         liblogwrap \
         libnetutils \
+        libnetdutils \
+        libnl \
         libsysutils \
         libutils \
 
diff --git a/server/AndroidTest.xml b/server/AndroidTest.xml
new file mode 100644
index 0000000..2501d78
--- /dev/null
+++ b/server/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for netd_unit_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="netd_unit_test->/data/local/tmp/netd_unit_test" />
+    </target_preparer>
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="netd_unit_test" />
+    </test>
+</configuration>
diff --git a/server/BandwidthController.cpp b/server/BandwidthController.cpp
index c8e8d14..9aace9d 100644
--- a/server/BandwidthController.cpp
+++ b/server/BandwidthController.cpp
@@ -57,30 +57,34 @@
 #include "ResponseCode.h"
 
 /* Alphabetical */
-#define ALERT_IPT_TEMPLATE "%s %s -m quota2 ! --quota %" PRId64" --name %s"
-const char* BandwidthController::LOCAL_INPUT = "bw_INPUT";
-const char* BandwidthController::LOCAL_FORWARD = "bw_FORWARD";
-const char* BandwidthController::LOCAL_OUTPUT = "bw_OUTPUT";
-const char* BandwidthController::LOCAL_RAW_PREROUTING = "bw_raw_PREROUTING";
-const char* BandwidthController::LOCAL_MANGLE_POSTROUTING = "bw_mangle_POSTROUTING";
+#define ALERT_IPT_TEMPLATE "%s %s -m quota2 ! --quota %" PRId64" --name %s\n"
+const char BandwidthController::LOCAL_INPUT[] = "bw_INPUT";
+const char BandwidthController::LOCAL_FORWARD[] = "bw_FORWARD";
+const char BandwidthController::LOCAL_OUTPUT[] = "bw_OUTPUT";
+const char BandwidthController::LOCAL_RAW_PREROUTING[] = "bw_raw_PREROUTING";
+const char BandwidthController::LOCAL_MANGLE_POSTROUTING[] = "bw_mangle_POSTROUTING";
 
 auto BandwidthController::execFunction = android_fork_execvp;
 auto BandwidthController::popenFunction = popen;
 auto BandwidthController::iptablesRestoreFunction = execIptablesRestoreWithOutput;
 
+using android::base::StringAppendF;
+using android::base::StringPrintf;
+
 namespace {
 
 const char ALERT_GLOBAL_NAME[] = "globalAlert";
 const int  MAX_CMD_ARGS = 32;
 const int  MAX_CMD_LEN = 1024;
-const int  MAX_IFACENAME_LEN = 64;
 const int  MAX_IPT_OUTPUT_LINE_LEN = 256;
 const std::string NEW_CHAIN_COMMAND = "-N ";
-const std::string GET_TETHER_STATS_COMMAND = android::base::StringPrintf(
+const std::string GET_TETHER_STATS_COMMAND = StringPrintf(
     "*filter\n"
     "-nvx -L %s\n"
     "COMMIT\n", NatController::LOCAL_TETHER_COUNTERS_CHAIN);
 
+const char NAUGHTY_CHAIN[] = "bw_penalty_box";
+const char NICE_CHAIN[] = "bw_happy_box";
 
 /**
  * Some comments about the rules:
@@ -145,8 +149,7 @@
  */
 
 const std::string COMMIT_AND_CLOSE = "COMMIT\n";
-const std::string DATA_SAVER_ENABLE_COMMAND = "-R bw_data_saver 1";
-const std::string HAPPY_BOX_WHITELIST_COMMAND = android::base::StringPrintf(
+const std::string HAPPY_BOX_WHITELIST_COMMAND = StringPrintf(
     "-I bw_happy_box -m owner --uid-owner %d-%d --jump RETURN", 0, MAX_SYSTEM_UID);
 
 static const std::vector<std::string> IPT_FLUSH_COMMANDS = {
@@ -192,30 +195,36 @@
     COMMIT_AND_CLOSE
 };
 
+std::vector<std::string> toStrVec(int num, char* strs[]) {
+    std::vector<std::string> tmp;
+    for (int i = 0; i < num; ++i) {
+        tmp.emplace_back(strs[i]);
+    }
+    return tmp;
+}
 
 }  // namespace
 
-BandwidthController::BandwidthController(void) {
+BandwidthController::BandwidthController() {
 }
 
-int BandwidthController::runIpxtablesCmd(const char *cmd, IptJumpOp jumpHandling,
+int BandwidthController::runIpxtablesCmd(const std::string& cmd, IptJumpOp jumpHandling,
                                          IptFailureLog failureHandling) {
     int res = 0;
 
-    ALOGV("runIpxtablesCmd(cmd=%s)", cmd);
+    ALOGV("runIpxtablesCmd(cmd=%s)", cmd.c_str());
     res |= runIptablesCmd(cmd, jumpHandling, IptIpV4, failureHandling);
     res |= runIptablesCmd(cmd, jumpHandling, IptIpV6, failureHandling);
     return res;
 }
 
-int BandwidthController::StrncpyAndCheck(char *buffer, const char *src, size_t buffSize) {
-
+int BandwidthController::StrncpyAndCheck(char* buffer, const std::string& src, size_t buffSize) {
     memset(buffer, '\0', buffSize);  // strncpy() is not filling leftover with '\0'
-    strncpy(buffer, src, buffSize);
+    strncpy(buffer, src.c_str(), buffSize);
     return buffer[buffSize - 1];
 }
 
-int BandwidthController::runIptablesCmd(const char *cmd, IptJumpOp jumpHandling,
+int BandwidthController::runIptablesCmd(const std::string& cmd, IptJumpOp jumpHandling,
                                         IptIpVer iptVer, IptFailureLog failureHandling) {
     char buffer[MAX_CMD_LEN];
     const char *argv[MAX_CMD_ARGS];
@@ -226,27 +235,12 @@
     int status = 0;
 
     std::string fullCmd = cmd;
-
-    switch (jumpHandling) {
-    case IptJumpReject:
-        /*
-         * Must be carefull what one rejects with, as uper layer protocols will just
-         * keep on hammering the device until the number of retries are done.
-         * For port-unreachable (default), TCP should consider as an abort (RFC1122).
-         */
-        fullCmd += " --jump REJECT";
-        break;
-    case IptJumpReturn:
-        fullCmd += " --jump RETURN";
-        break;
-    case IptJumpNoAdd:
-        break;
-    }
+    fullCmd += jumpToString(jumpHandling);
 
     fullCmd.insert(0, " -w ");
     fullCmd.insert(0, iptVer == IptIpV4 ? IPTABLES_PATH : IP6TABLES_PATH);
 
-    if (StrncpyAndCheck(buffer, fullCmd.c_str(), sizeof(buffer))) {
+    if (StrncpyAndCheck(buffer, fullCmd, sizeof(buffer))) {
         ALOGE("iptables command too long");
         return -1;
     }
@@ -279,7 +273,7 @@
     iptablesRestoreFunction(V4V6, commands, nullptr);
 }
 
-int BandwidthController::setupIptablesHooks(void) {
+int BandwidthController::setupIptablesHooks() {
     /* flush+clean is allowed to fail */
     flushCleanTables(true);
     return 0;
@@ -306,170 +300,78 @@
     return iptablesRestoreFunction(V4V6, commands, nullptr);
 }
 
-int BandwidthController::disableBandwidthControl(void) {
+int BandwidthController::disableBandwidthControl() {
 
     flushCleanTables(false);
     return 0;
 }
 
 int BandwidthController::enableDataSaver(bool enable) {
-    return runIpxtablesCmd(DATA_SAVER_ENABLE_COMMAND.c_str(),
-                           enable ? IptJumpReject : IptJumpReturn, IptFailShow);
-}
-
-int BandwidthController::runCommands(int numCommands, const char *commands[],
-                                     RunCmdErrHandling cmdErrHandling) {
-    int res = 0;
-    IptFailureLog failureLogging = IptFailShow;
-    if (cmdErrHandling == RunCmdFailureOk) {
-        failureLogging = IptFailHide;
-    }
-    ALOGV("runCommands(): %d commands", numCommands);
-    for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) {
-        res = runIpxtablesCmd(commands[cmdNum], IptJumpNoAdd, failureLogging);
-        if (res && cmdErrHandling != RunCmdFailureOk)
-            return res;
-    }
-    return 0;
-}
-
-std::string BandwidthController::makeIptablesSpecialAppCmd(IptOp op, int uid, const char *chain) {
-    std::string res;
-    char *buff;
-    const char *opFlag;
-
-    switch (op) {
-    case IptOpInsert:
-        opFlag = "-I";
-        break;
-    case IptOpAppend:
-        ALOGE("Append op not supported for %s uids", chain);
-        res = "";
-        return res;
-        break;
-    case IptOpReplace:
-        opFlag = "-R";
-        break;
-    default:
-    case IptOpDelete:
-        opFlag = "-D";
-        break;
-    }
-    asprintf(&buff, "%s %s -m owner --uid-owner %d", opFlag, chain, uid);
-    res = buff;
-    free(buff);
-    return res;
+    std::string cmd = StringPrintf(
+        "*filter\n"
+        "-R bw_data_saver 1%s\n"
+        "COMMIT\n", jumpToString(enable ? IptJumpReject : IptJumpReturn));
+    return iptablesRestoreFunction(V4V6, cmd, nullptr);
 }
 
 int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) {
-    return manipulateNaughtyApps(numUids, appUids, SpecialAppOpAdd);
+    return manipulateSpecialApps(toStrVec(numUids, appUids), NAUGHTY_CHAIN,
+                                 IptJumpReject, IptOpInsert);
 }
 
 int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) {
-    return manipulateNaughtyApps(numUids, appUids, SpecialAppOpRemove);
+    return manipulateSpecialApps(toStrVec(numUids, appUids), NAUGHTY_CHAIN,
+                                 IptJumpReject, IptOpDelete);
 }
 
 int BandwidthController::addNiceApps(int numUids, char *appUids[]) {
-    return manipulateNiceApps(numUids, appUids, SpecialAppOpAdd);
+    return manipulateSpecialApps(toStrVec(numUids, appUids), NICE_CHAIN,
+                                 IptJumpReturn, IptOpInsert);
 }
 
 int BandwidthController::removeNiceApps(int numUids, char *appUids[]) {
-    return manipulateNiceApps(numUids, appUids, SpecialAppOpRemove);
+    return manipulateSpecialApps(toStrVec(numUids, appUids), NICE_CHAIN,
+                                 IptJumpReturn, IptOpDelete);
 }
 
-int BandwidthController::manipulateNaughtyApps(int numUids, char *appStrUids[], SpecialAppOp appOp) {
-    return manipulateSpecialApps(numUids, appStrUids, "bw_penalty_box", IptJumpReject, appOp);
-}
-
-int BandwidthController::manipulateNiceApps(int numUids, char *appStrUids[], SpecialAppOp appOp) {
-    return manipulateSpecialApps(numUids, appStrUids, "bw_happy_box", IptJumpReturn, appOp);
-}
-
-
-int BandwidthController::manipulateSpecialApps(int numUids, char *appStrUids[],
-                                               const char *chain,
-                                               IptJumpOp jumpHandling, SpecialAppOp appOp) {
-
-    int uidNum;
-    const char *failLogTemplate;
-    IptOp op;
-    int appUids[numUids];
-    std::string iptCmd;
-
-    switch (appOp) {
-    case SpecialAppOpAdd:
-        op = IptOpInsert;
-        failLogTemplate = "Failed to add app uid %s(%d) to %s.";
-        break;
-    case SpecialAppOpRemove:
-        op = IptOpDelete;
-        failLogTemplate = "Failed to delete app uid %s(%d) from %s box.";
-        break;
-    default:
-        ALOGE("Unexpected app Op %d", appOp);
-        return -1;
+int BandwidthController::manipulateSpecialApps(const std::vector<std::string>& appStrUids,
+                                               const std::string& chain, IptJumpOp jumpHandling,
+                                               IptOp op) {
+    std::string cmd = "*filter\n";
+    for (const auto& appStrUid : appStrUids) {
+        StringAppendF(&cmd, "%s %s -m owner --uid-owner %s%s\n", opToString(op), chain.c_str(),
+                      appStrUid.c_str(), jumpToString(jumpHandling));
     }
-
-    for (uidNum = 0; uidNum < numUids; uidNum++) {
-        char *end;
-        appUids[uidNum] = strtoul(appStrUids[uidNum], &end, 0);
-        if (*end || !*appStrUids[uidNum]) {
-            ALOGE(failLogTemplate, appStrUids[uidNum], appUids[uidNum], chain);
-            goto fail_parse;
-        }
-    }
-
-    for (uidNum = 0; uidNum < numUids; uidNum++) {
-        int uid = appUids[uidNum];
-
-        iptCmd = makeIptablesSpecialAppCmd(op, uid, chain);
-        if (runIpxtablesCmd(iptCmd.c_str(), jumpHandling)) {
-            ALOGE(failLogTemplate, appStrUids[uidNum], uid, chain);
-            goto fail_with_uidNum;
-        }
-    }
-    return 0;
-
-fail_with_uidNum:
-    /* Try to remove the uid that failed in any case*/
-    iptCmd = makeIptablesSpecialAppCmd(IptOpDelete, appUids[uidNum], chain);
-    runIpxtablesCmd(iptCmd.c_str(), jumpHandling);
-fail_parse:
-    return -1;
+    StringAppendF(&cmd, "COMMIT\n");
+    return iptablesRestoreFunction(V4V6, cmd, nullptr);
 }
 
-std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota) {
+std::string BandwidthController::makeIptablesQuotaCmd(IptFullOp op, const std::string& costName,
+                                                      int64_t quota) {
     std::string res;
-    char *buff;
     const char *opFlag;
 
     ALOGV("makeIptablesQuotaCmd(%d, %" PRId64")", op, quota);
 
     switch (op) {
-    case IptOpInsert:
+    case IptFullOpInsert:
         opFlag = "-I";
         break;
-    case IptOpAppend:
+    case IptFullOpAppend:
         opFlag = "-A";
         break;
-    case IptOpReplace:
-        opFlag = "-R";
-        break;
-    default:
-    case IptOpDelete:
+    case IptFullOpDelete:
         opFlag = "-D";
         break;
     }
 
     // The requried IP version specific --jump REJECT ... will be added later.
-    asprintf(&buff, "%s bw_costly_%s -m quota2 ! --quota %" PRId64" --name %s", opFlag, costName, quota,
-             costName);
-    res = buff;
-    free(buff);
+    StringAppendF(&res, "%s bw_costly_%s -m quota2 ! --quota %" PRId64 " --name %s", opFlag,
+                  costName.c_str(), quota, costName.c_str());
     return res;
 }
 
-int BandwidthController::prepCostlyIface(const char *ifn, QuotaType quotaType) {
+int BandwidthController::prepCostlyIface(const std::string& ifn, QuotaType quotaType) {
     char cmd[MAX_CMD_LEN];
     int res = 0, res1, res2;
     int ruleInsertPos = 1;
@@ -499,9 +401,6 @@
     case QuotaShared:
         costCString = "bw_costly_shared";
         break;
-    default:
-        ALOGE("Unexpected quotatype %d", quotaType);
-        return -1;
     }
 
     if (globalAlertBytes) {
@@ -509,27 +408,29 @@
         ruleInsertPos = 2;
     }
 
-    snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString);
+    snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn.c_str(), costCString);
     runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
 
-    snprintf(cmd, sizeof(cmd), "-I bw_INPUT %d -i %s --jump %s", ruleInsertPos, ifn, costCString);
+    snprintf(cmd, sizeof(cmd), "-I bw_INPUT %d -i %s --jump %s", ruleInsertPos, ifn.c_str(),
+             costCString);
     res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
 
-    snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString);
+    snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn.c_str(), costCString);
     runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
 
-    snprintf(cmd, sizeof(cmd), "-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, ifn, costCString);
+    snprintf(cmd, sizeof(cmd), "-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, ifn.c_str(),
+             costCString);
     res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
 
-    snprintf(cmd, sizeof(cmd), "-D bw_FORWARD -o %s --jump %s", ifn, costCString);
+    snprintf(cmd, sizeof(cmd), "-D bw_FORWARD -o %s --jump %s", ifn.c_str(), costCString);
     runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
-    snprintf(cmd, sizeof(cmd), "-A bw_FORWARD -o %s --jump %s", ifn, costCString);
+    snprintf(cmd, sizeof(cmd), "-A bw_FORWARD -o %s --jump %s", ifn.c_str(), costCString);
     res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
 
     return res;
 }
 
-int BandwidthController::cleanupCostlyIface(const char *ifn, QuotaType quotaType) {
+int BandwidthController::cleanupCostlyIface(const std::string& ifn, QuotaType quotaType) {
     char cmd[MAX_CMD_LEN];
     int res = 0;
     std::string costString;
@@ -544,15 +445,12 @@
     case QuotaShared:
         costCString = "bw_costly_shared";
         break;
-    default:
-        ALOGE("Unexpected quotatype %d", quotaType);
-        return -1;
     }
 
-    snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString);
+    snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn.c_str(), costCString);
     res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
     for (const auto tableName : {LOCAL_OUTPUT, LOCAL_FORWARD}) {
-        snprintf(cmd, sizeof(cmd), "-D %s -o %s --jump %s", tableName, ifn, costCString);
+        snprintf(cmd, sizeof(cmd), "-D %s -o %s --jump %s", tableName, ifn.c_str(), costCString);
         res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
     }
 
@@ -566,13 +464,10 @@
     return res;
 }
 
-int BandwidthController::setInterfaceSharedQuota(const char *iface, int64_t maxBytes) {
-    char ifn[MAX_IFACENAME_LEN];
+int BandwidthController::setInterfaceSharedQuota(const std::string& iface, int64_t maxBytes) {
     int res = 0;
     std::string quotaCmd;
-    std::string ifaceName;
-    ;
-    const char *costName = "shared";
+    const char costName[] = "shared";
     std::list<std::string>::iterator it;
 
     if (!maxBytes) {
@@ -582,26 +477,21 @@
     }
     if (!isIfaceName(iface))
         return -1;
-    if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
-        ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
-        return -1;
-    }
-    ifaceName = ifn;
 
     if (maxBytes == -1) {
-        return removeInterfaceSharedQuota(ifn);
+        return removeInterfaceSharedQuota(iface);
     }
 
     /* Insert ingress quota. */
     for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) {
-        if (*it == ifaceName)
+        if (*it == iface)
             break;
     }
 
     if (it == sharedQuotaIfaces.end()) {
-        res |= prepCostlyIface(ifn, QuotaShared);
+        res |= prepCostlyIface(iface, QuotaShared);
         if (sharedQuotaIfaces.empty()) {
-            quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes);
+            quotaCmd = makeIptablesQuotaCmd(IptFullOpInsert, costName, maxBytes);
             res |= runIpxtablesCmd(quotaCmd.c_str(), IptJumpReject);
             if (res) {
                 ALOGE("Failed set quota rule");
@@ -609,7 +499,7 @@
             }
             sharedQuotaBytes = maxBytes;
         }
-        sharedQuotaIfaces.push_front(ifaceName);
+        sharedQuotaIfaces.push_front(iface);
 
     }
 
@@ -630,41 +520,34 @@
      * For now callers needs to choose if they want to "ndc bandwidth enable"
      * which resets everything.
      */
-    removeInterfaceSharedQuota(ifn);
+    removeInterfaceSharedQuota(iface);
     return -1;
 }
 
 /* It will also cleanup any shared alerts */
-int BandwidthController::removeInterfaceSharedQuota(const char *iface) {
-    char ifn[MAX_IFACENAME_LEN];
+int BandwidthController::removeInterfaceSharedQuota(const std::string& iface) {
     int res = 0;
-    std::string ifaceName;
     std::list<std::string>::iterator it;
-    const char *costName = "shared";
+    const char costName[] = "shared";
 
     if (!isIfaceName(iface))
         return -1;
-    if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
-        ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
-        return -1;
-    }
-    ifaceName = ifn;
 
     for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) {
-        if (*it == ifaceName)
+        if (*it == iface)
             break;
     }
     if (it == sharedQuotaIfaces.end()) {
-        ALOGE("No such iface %s to delete", ifn);
+        ALOGE("No such iface %s to delete", iface.c_str());
         return -1;
     }
 
-    res |= cleanupCostlyIface(ifn, QuotaShared);
+    res |= cleanupCostlyIface(iface, QuotaShared);
     sharedQuotaIfaces.erase(it);
 
     if (sharedQuotaIfaces.empty()) {
         std::string quotaCmd;
-        quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes);
+        quotaCmd = makeIptablesQuotaCmd(IptFullOpDelete, costName, sharedQuotaBytes);
         res |= runIpxtablesCmd(quotaCmd.c_str(), IptJumpReject);
         sharedQuotaBytes = 0;
         if (sharedAlertBytes) {
@@ -675,11 +558,9 @@
     return res;
 }
 
-int BandwidthController::setInterfaceQuota(const char *iface, int64_t maxBytes) {
-    char ifn[MAX_IFACENAME_LEN];
+int BandwidthController::setInterfaceQuota(const std::string& iface, int64_t maxBytes) {
     int res = 0;
-    std::string ifaceName;
-    const char *costName;
+    const auto& costName = iface;
     std::list<QuotaInfo>::iterator it;
     std::string quotaCmd;
 
@@ -695,40 +576,33 @@
         return removeInterfaceQuota(iface);
     }
 
-    if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
-        ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
-        return -1;
-    }
-    ifaceName = ifn;
-    costName = iface;
-
     /* Insert ingress quota. */
     for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
-        if (it->ifaceName == ifaceName)
+        if (it->ifaceName == iface)
             break;
     }
 
     if (it == quotaIfaces.end()) {
         /* Preparing the iface adds a penalty/happy box check */
-        res |= prepCostlyIface(ifn, QuotaUnique);
+        res |= prepCostlyIface(iface, QuotaUnique);
         /*
          * The rejecting quota limit should go after the penalty/happy box checks
          * or else a naughty app could just eat up the quota.
          * So we append here.
          */
-        quotaCmd = makeIptablesQuotaCmd(IptOpAppend, costName, maxBytes);
+        quotaCmd = makeIptablesQuotaCmd(IptFullOpAppend, costName, maxBytes);
         res |= runIpxtablesCmd(quotaCmd.c_str(), IptJumpReject);
         if (res) {
             ALOGE("Failed set quota rule");
             goto fail;
         }
 
-        quotaIfaces.push_front(QuotaInfo(ifaceName, maxBytes, 0));
+        quotaIfaces.push_front(QuotaInfo(iface, maxBytes, 0));
 
     } else {
         res |= updateQuota(costName, maxBytes);
         if (res) {
-            ALOGE("Failed update quota for %s", iface);
+            ALOGE("Failed update quota for %s", iface.c_str());
             goto fail;
         }
         it->quota = maxBytes;
@@ -742,7 +616,7 @@
      * For now callers needs to choose if they want to "ndc bandwidth enable"
      * which resets everything.
      */
-    removeInterfaceSharedQuota(ifn);
+    removeInterfaceSharedQuota(iface);
     return -1;
 }
 
@@ -750,19 +624,16 @@
     return getInterfaceQuota("shared", bytes);
 }
 
-int BandwidthController::getInterfaceQuota(const char *costName, int64_t *bytes) {
+int BandwidthController::getInterfaceQuota(const std::string& iface, int64_t* bytes) {
     FILE *fp;
-    char *fname;
+    const std::string fname = "/proc/net/xt_quota/" + iface;
     int scanRes;
 
-    if (!isIfaceName(costName))
-        return -1;
+    if (!isIfaceName(iface)) return -1;
 
-    asprintf(&fname, "/proc/net/xt_quota/%s", costName);
-    fp = fopen(fname, "re");
-    free(fname);
+    fp = fopen(fname.c_str(), "re");
     if (!fp) {
-        ALOGE("Reading quota %s failed (%s)", costName, strerror(errno));
+        ALOGE("Reading quota %s failed (%s)", iface.c_str(), strerror(errno));
         return -1;
     }
     scanRes = fscanf(fp, "%" SCNd64, bytes);
@@ -771,53 +642,45 @@
     return scanRes == 1 ? 0 : -1;
 }
 
-int BandwidthController::removeInterfaceQuota(const char *iface) {
-
-    char ifn[MAX_IFACENAME_LEN];
+int BandwidthController::removeInterfaceQuota(const std::string& iface) {
     int res = 0;
-    std::string ifaceName;
     std::list<QuotaInfo>::iterator it;
 
     if (!isIfaceName(iface))
         return -1;
-    if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
-        ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
-        return -1;
-    }
-    ifaceName = ifn;
 
     for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
-        if (it->ifaceName == ifaceName)
+        if (it->ifaceName == iface)
             break;
     }
 
     if (it == quotaIfaces.end()) {
-        ALOGE("No such iface %s to delete", ifn);
+        ALOGE("No such iface %s to delete", iface.c_str());
         return -1;
     }
 
     /* This also removes the quota command of CostlyIface chain. */
-    res |= cleanupCostlyIface(ifn, QuotaUnique);
+    res |= cleanupCostlyIface(iface, QuotaUnique);
 
     quotaIfaces.erase(it);
 
     return res;
 }
 
-int BandwidthController::updateQuota(const char *quotaName, int64_t bytes) {
+int BandwidthController::updateQuota(const std::string& quotaName, int64_t bytes) {
     FILE *fp;
     char *fname;
 
     if (!isIfaceName(quotaName)) {
-        ALOGE("updateQuota: Invalid quotaName \"%s\"", quotaName);
+        ALOGE("updateQuota: Invalid quotaName \"%s\"", quotaName.c_str());
         return -1;
     }
 
-    asprintf(&fname, "/proc/net/xt_quota/%s", quotaName);
+    asprintf(&fname, "/proc/net/xt_quota/%s", quotaName.c_str());
     fp = fopen(fname, "we");
     free(fname);
     if (!fp) {
-        ALOGE("Updating quota %s failed (%s)", quotaName, strerror(errno));
+        ALOGE("Updating quota %s failed (%s)", quotaName.c_str(), strerror(errno));
         return -1;
     }
     fprintf(fp, "%" PRId64"\n", bytes);
@@ -825,64 +688,31 @@
     return 0;
 }
 
-int BandwidthController::runIptablesAlertCmd(IptOp op, const char *alertName, int64_t bytes) {
-    int res = 0;
-    const char *opFlag;
-    char *alertQuotaCmd;
+int BandwidthController::runIptablesAlertCmd(IptOp op, const std::string& alertName,
+                                             int64_t bytes) {
+    const char *opFlag = opToString(op);
+    std::string alertQuotaCmd = "*filter\n";
 
-    switch (op) {
-    case IptOpInsert:
-        opFlag = "-I";
-        break;
-    case IptOpAppend:
-        opFlag = "-A";
-        break;
-    case IptOpReplace:
-        opFlag = "-R";
-        break;
-    default:
-    case IptOpDelete:
-        opFlag = "-D";
-        break;
-    }
+    // TODO: consider using an alternate template for the delete that does not include the --quota
+    // value. This code works because the --quota value is ignored by deletes
+    StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_INPUT", bytes,
+                  alertName.c_str());
+    StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_OUTPUT", bytes,
+                  alertName.c_str());
+    StringAppendF(&alertQuotaCmd, "COMMIT\n");
 
-    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_INPUT",
-        bytes, alertName);
-    res |= runIpxtablesCmd(alertQuotaCmd, IptJumpNoAdd);
-    free(alertQuotaCmd);
-    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_OUTPUT",
-        bytes, alertName);
-    res |= runIpxtablesCmd(alertQuotaCmd, IptJumpNoAdd);
-    free(alertQuotaCmd);
-    return res;
+    return iptablesRestoreFunction(V4V6, alertQuotaCmd, nullptr);
 }
 
-int BandwidthController::runIptablesAlertFwdCmd(IptOp op, const char *alertName, int64_t bytes) {
-    int res = 0;
-    const char *opFlag;
-    char *alertQuotaCmd;
+int BandwidthController::runIptablesAlertFwdCmd(IptOp op, const std::string& alertName,
+                                                int64_t bytes) {
+    const char *opFlag = opToString(op);
+    std::string alertQuotaCmd = "*filter\n";
+    StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_FORWARD", bytes,
+                  alertName.c_str());
+    StringAppendF(&alertQuotaCmd, "COMMIT\n");
 
-    switch (op) {
-    case IptOpInsert:
-        opFlag = "-I";
-        break;
-    case IptOpAppend:
-        opFlag = "-A";
-        break;
-    case IptOpReplace:
-        opFlag = "-R";
-        break;
-    default:
-    case IptOpDelete:
-        opFlag = "-D";
-        break;
-    }
-
-    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_FORWARD",
-        bytes, alertName);
-    res = runIpxtablesCmd(alertQuotaCmd, IptJumpNoAdd);
-    free(alertQuotaCmd);
-    return res;
+    return iptablesRestoreFunction(V4V6, alertQuotaCmd, nullptr);
 }
 
 int BandwidthController::setGlobalAlert(int64_t bytes) {
@@ -906,7 +736,7 @@
     return res;
 }
 
-int BandwidthController::setGlobalAlertInForwardChain(void) {
+int BandwidthController::setGlobalAlertInForwardChain() {
     const char *alertName = ALERT_GLOBAL_NAME;
     int res = 0;
 
@@ -927,7 +757,7 @@
     return res;
 }
 
-int BandwidthController::removeGlobalAlert(void) {
+int BandwidthController::removeGlobalAlert() {
 
     const char *alertName = ALERT_GLOBAL_NAME;
     int res = 0;
@@ -944,7 +774,7 @@
     return res;
 }
 
-int BandwidthController::removeGlobalAlertInForwardChain(void) {
+int BandwidthController::removeGlobalAlertInForwardChain() {
     int res = 0;
     const char *alertName = ALERT_GLOBAL_NAME;
 
@@ -980,15 +810,15 @@
     return setCostlyAlert("shared", bytes, &sharedAlertBytes);
 }
 
-int BandwidthController::removeSharedAlert(void) {
+int BandwidthController::removeSharedAlert() {
     return removeCostlyAlert("shared", &sharedAlertBytes);
 }
 
-int BandwidthController::setInterfaceAlert(const char *iface, int64_t bytes) {
+int BandwidthController::setInterfaceAlert(const std::string& iface, int64_t bytes) {
     std::list<QuotaInfo>::iterator it;
 
     if (!isIfaceName(iface)) {
-        ALOGE("setInterfaceAlert: Invalid iface \"%s\"", iface);
+        ALOGE("setInterfaceAlert: Invalid iface \"%s\"", iface.c_str());
         return -1;
     }
 
@@ -1009,11 +839,11 @@
     return setCostlyAlert(iface, bytes, &it->alert);
 }
 
-int BandwidthController::removeInterfaceAlert(const char *iface) {
+int BandwidthController::removeInterfaceAlert(const std::string& iface) {
     std::list<QuotaInfo>::iterator it;
 
     if (!isIfaceName(iface)) {
-        ALOGE("removeInterfaceAlert: Invalid iface \"%s\"", iface);
+        ALOGE("removeInterfaceAlert: Invalid iface \"%s\"", iface.c_str());
         return -1;
     }
 
@@ -1023,21 +853,22 @@
     }
 
     if (it == quotaIfaces.end()) {
-        ALOGE("No prior alert set for interface %s", iface);
+        ALOGE("No prior alert set for interface %s", iface.c_str());
         return -1;
     }
 
     return removeCostlyAlert(iface, &it->alert);
 }
 
-int BandwidthController::setCostlyAlert(const char *costName, int64_t bytes, int64_t *alertBytes) {
+int BandwidthController::setCostlyAlert(const std::string& costName, int64_t bytes,
+                                        int64_t* alertBytes) {
     char *alertQuotaCmd;
     char *chainName;
     int res = 0;
     char *alertName;
 
     if (!isIfaceName(costName)) {
-        ALOGE("setCostlyAlert: Invalid costName \"%s\"", costName);
+        ALOGE("setCostlyAlert: Invalid costName \"%s\"", costName.c_str());
         return -1;
     }
 
@@ -1045,11 +876,11 @@
         ALOGE("Invalid bytes value. 1..max_int64.");
         return -1;
     }
-    asprintf(&alertName, "%sAlert", costName);
+    asprintf(&alertName, "%sAlert", costName.c_str());
     if (*alertBytes) {
         res = updateQuota(alertName, *alertBytes);
     } else {
-        asprintf(&chainName, "bw_costly_%s", costName);
+        asprintf(&chainName, "bw_costly_%s", costName.c_str());
         asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "-A", chainName, bytes, alertName);
         res |= runIpxtablesCmd(alertQuotaCmd, IptJumpNoAdd);
         free(alertQuotaCmd);
@@ -1060,24 +891,24 @@
     return res;
 }
 
-int BandwidthController::removeCostlyAlert(const char *costName, int64_t *alertBytes) {
+int BandwidthController::removeCostlyAlert(const std::string& costName, int64_t* alertBytes) {
     char *alertQuotaCmd;
     char *chainName;
     char *alertName;
     int res = 0;
 
     if (!isIfaceName(costName)) {
-        ALOGE("removeCostlyAlert: Invalid costName \"%s\"", costName);
+        ALOGE("removeCostlyAlert: Invalid costName \"%s\"", costName.c_str());
         return -1;
     }
 
     if (!*alertBytes) {
-        ALOGE("No prior alert set for %s alert", costName);
+        ALOGE("No prior alert set for %s alert", costName.c_str());
         return -1;
     }
 
-    asprintf(&alertName, "%sAlert", costName);
-    asprintf(&chainName, "bw_costly_%s", costName);
+    asprintf(&alertName, "%sAlert", costName.c_str());
+    asprintf(&chainName, "bw_costly_%s", costName.c_str());
     asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "-D", chainName, *alertBytes, alertName);
     res |= runIpxtablesCmd(alertQuotaCmd, IptJumpNoAdd);
     free(alertQuotaCmd);
@@ -1132,9 +963,7 @@
 
     bool filterPair = filter.intIface[0] && filter.extIface[0];
 
-    char *filterMsg = filter.getStatsLine();
-    ALOGV("filter: %s",  filterMsg);
-    free(filterMsg);
+    ALOGV("filter: %s",  filter.getStatsLine().c_str());
 
     stats = filter;
 
@@ -1226,10 +1055,10 @@
     return 0;
 }
 
-char *BandwidthController::TetherStats::getStatsLine(void) const {
-    char *msg;
-    asprintf(&msg, "%s %s %" PRId64" %" PRId64" %" PRId64" %" PRId64, intIface.c_str(), extIface.c_str(),
-            rxBytes, rxPackets, txBytes, txPackets);
+std::string BandwidthController::TetherStats::getStatsLine() const {
+    std::string msg;
+    StringAppendF(&msg, "%s %s %" PRId64" %" PRId64" %" PRId64" %" PRId64, intIface.c_str(),
+                  extIface.c_str(), rxBytes, rxPackets, txBytes, txPackets);
     return msg;
 }
 
@@ -1254,10 +1083,12 @@
     }
 
     if (filter.intIface[0] && filter.extIface[0] && statsList.size() == 1) {
-        cli->sendMsg(ResponseCode::TetheringStatsResult, statsList[0].getStatsLine(), false);
+        cli->sendMsg(ResponseCode::TetheringStatsResult,
+                     statsList[0].getStatsLine().c_str(), false);
     } else {
         for (const auto& stats: statsList) {
-            cli->sendMsg(ResponseCode::TetheringStatsListResult, stats.getStatsLine(), false);
+            cli->sendMsg(ResponseCode::TetheringStatsListResult,
+                         stats.getStatsLine().c_str(), false);
         }
         if (res == 0) {
             cli->sendMsg(ResponseCode::CommandOkay, "Tethering stats list completed", false);
@@ -1296,9 +1127,9 @@
             continue;
         }
 
-        clearCommands.push_back(android::base::StringPrintf(":%s -", chainName.c_str()));
+        clearCommands.push_back(StringPrintf(":%s -", chainName.c_str()));
         if (doRemove) {
-            clearCommands.push_back(android::base::StringPrintf("-X %s", chainName.c_str()));
+            clearCommands.push_back(StringPrintf("-X %s", chainName.c_str()));
         }
     }
 
@@ -1310,3 +1141,28 @@
     clearCommands.push_back("COMMIT\n");
     iptablesRestoreFunction(V4V6, android::base::Join(clearCommands, '\n'), nullptr);
 }
+
+inline const char *BandwidthController::opToString(IptOp op) {
+    switch (op) {
+    case IptOpInsert:
+        return "-I";
+    case IptOpDelete:
+        return "-D";
+    }
+}
+
+inline const char *BandwidthController::jumpToString(IptJumpOp jumpHandling) {
+    /*
+     * Must be careful what one rejects with, as upper layer protocols will just
+     * keep on hammering the device until the number of retries are done.
+     * For port-unreachable (default), TCP should consider as an abort (RFC1122).
+     */
+    switch (jumpHandling) {
+    case IptJumpNoAdd:
+        return "";
+    case IptJumpReject:
+        return " --jump REJECT";
+    case IptJumpReturn:
+        return " --jump RETURN";
+    }
+}
diff --git a/server/BandwidthController.h b/server/BandwidthController.h
index 0398ce9..b739841 100644
--- a/server/BandwidthController.h
+++ b/server/BandwidthController.h
@@ -18,7 +18,8 @@
 
 #include <list>
 #include <string>
-#include <utility>  // for pair
+#include <utility>
+#include <vector>
 
 #include <sysutils/SocketClient.h>
 #include <utils/RWLock.h>
@@ -31,9 +32,7 @@
 
     class TetherStats {
     public:
-        TetherStats(void)
-                : rxBytes(-1), rxPackets(-1),
-                    txBytes(-1), txPackets(-1) {};
+        TetherStats() = default;
         TetherStats(std::string intIfn, std::string extIfn,
                 int64_t rxB, int64_t rxP,
                 int64_t txB, int64_t txP)
@@ -44,14 +43,16 @@
         std::string intIface;
         /* External interface. Same as NatController's notion. */
         std::string extIface;
-        int64_t rxBytes, rxPackets;
-        int64_t txBytes, txPackets;
+        int64_t rxBytes = -1;
+        int64_t rxPackets = -1;
+        int64_t txBytes = -1;
+        int64_t txPackets = -1;
         /*
          * Allocates a new string representing this:
          * intIface extIface rx_bytes rx_packets tx_bytes tx_packets
          * The caller is responsible for free()'ing the returned ptr.
          */
-        char *getStatsLine(void) const;
+        std::string getStatsLine() const;
 
         bool addStatsIfMatch(const TetherStats& other) {
             if (intIface == other.intIface && extIface == other.extIface) {
@@ -67,19 +68,19 @@
 
     BandwidthController();
 
-    int setupIptablesHooks(void);
+    int setupIptablesHooks();
 
     int enableBandwidthControl(bool force);
-    int disableBandwidthControl(void);
+    int disableBandwidthControl();
     int enableDataSaver(bool enable);
 
-    int setInterfaceSharedQuota(const char *iface, int64_t bytes);
+    int setInterfaceSharedQuota(const std::string& iface, int64_t bytes);
     int getInterfaceSharedQuota(int64_t *bytes);
-    int removeInterfaceSharedQuota(const char *iface);
+    int removeInterfaceSharedQuota(const std::string& iface);
 
-    int setInterfaceQuota(const char *iface, int64_t bytes);
-    int getInterfaceQuota(const char *iface, int64_t *bytes);
-    int removeInterfaceQuota(const char *iface);
+    int setInterfaceQuota(const std::string& iface, int64_t bytes);
+    int getInterfaceQuota(const std::string& iface, int64_t* bytes);
+    int removeInterfaceQuota(const std::string& iface);
 
     int addNaughtyApps(int numUids, char *appUids[]);
     int removeNaughtyApps(int numUids, char *appUids[]);
@@ -87,15 +88,15 @@
     int removeNiceApps(int numUids, char *appUids[]);
 
     int setGlobalAlert(int64_t bytes);
-    int removeGlobalAlert(void);
-    int setGlobalAlertInForwardChain(void);
-    int removeGlobalAlertInForwardChain(void);
+    int removeGlobalAlert();
+    int setGlobalAlertInForwardChain();
+    int removeGlobalAlertInForwardChain();
 
     int setSharedAlert(int64_t bytes);
-    int removeSharedAlert(void);
+    int removeSharedAlert();
 
-    int setInterfaceAlert(const char *iface, int64_t bytes);
-    int removeInterfaceAlert(const char *iface);
+    int setInterfaceAlert(const std::string& iface, int64_t bytes);
+    int removeInterfaceAlert(const std::string& iface);
 
     /*
      * For single pair of ifaces, stats should have ifaceIn and ifaceOut initialized.
@@ -107,13 +108,13 @@
      */
     int getTetherStats(SocketClient *cli, TetherStats &stats, std::string &extraProcessingInfo);
 
-    static const char* LOCAL_INPUT;
-    static const char* LOCAL_FORWARD;
-    static const char* LOCAL_OUTPUT;
-    static const char* LOCAL_RAW_PREROUTING;
-    static const char* LOCAL_MANGLE_POSTROUTING;
+    static const char LOCAL_INPUT[];
+    static const char LOCAL_FORWARD[];
+    static const char LOCAL_OUTPUT[];
+    static const char LOCAL_RAW_PREROUTING[];
+    static const char LOCAL_MANGLE_POSTROUTING[];
 
-protected:
+  private:
     class QuotaInfo {
     public:
       QuotaInfo(std::string ifn, int64_t q, int64_t a)
@@ -124,9 +125,9 @@
     };
 
     enum IptIpVer { IptIpV4, IptIpV6 };
-    enum IptOp { IptOpInsert, IptOpReplace, IptOpDelete, IptOpAppend };
+    enum IptFullOp { IptFullOpInsert, IptFullOpDelete, IptFullOpAppend };
     enum IptJumpOp { IptJumpReject, IptJumpReturn, IptJumpNoAdd };
-    enum SpecialAppOp { SpecialAppOpAdd, SpecialAppOpRemove };
+    enum IptOp { IptOpInsert, IptOpDelete };
     enum QuotaType { QuotaUnique, QuotaShared };
     enum RunCmdErrHandling { RunCmdFailureBad, RunCmdFailureOk };
 #if LOG_NDEBUG
@@ -135,37 +136,30 @@
     enum IptFailureLog { IptFailShow, IptFailHide = IptFailShow };
 #endif
 
-    int manipulateSpecialApps(int numUids, char *appStrUids[],
-                               const char *chain,
-                               IptJumpOp jumpHandling, SpecialAppOp appOp);
-    int manipulateNaughtyApps(int numUids, char *appStrUids[], SpecialAppOp appOp);
-    int manipulateNiceApps(int numUids, char *appStrUids[], SpecialAppOp appOp);
+    int manipulateSpecialApps(const std::vector<std::string>& appStrUids, const std::string& chain,
+                              IptJumpOp jumpHandling, IptOp appOp);
 
-    int prepCostlyIface(const char *ifn, QuotaType quotaType);
-    int cleanupCostlyIface(const char *ifn, QuotaType quotaType);
+    int prepCostlyIface(const std::string& ifn, QuotaType quotaType);
+    int cleanupCostlyIface(const std::string& ifn, QuotaType quotaType);
 
-    std::string makeIptablesSpecialAppCmd(IptOp op, int uid, const char *chain);
-    std::string makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota);
+    std::string makeIptablesQuotaCmd(IptFullOp op, const std::string& costName, int64_t quota);
 
-    int runIptablesAlertCmd(IptOp op, const char *alertName, int64_t bytes);
-    int runIptablesAlertFwdCmd(IptOp op, const char *alertName, int64_t bytes);
+    int runIptablesAlertCmd(IptOp op, const std::string& alertName, int64_t bytes);
+    int runIptablesAlertFwdCmd(IptOp op, const std::string& alertName, int64_t bytes);
 
-    /* Runs for both ipv4 and ipv6 iptables */
-    int runCommands(int numCommands, const char *commands[], RunCmdErrHandling cmdErrHandling);
     /* Runs for both ipv4 and ipv6 iptables, appends -j REJECT --reject-with ...  */
-    static int runIpxtablesCmd(const char *cmd, IptJumpOp jumpHandling,
+    static int runIpxtablesCmd(const std::string& cmd, IptJumpOp jumpHandling,
                                IptFailureLog failureHandling = IptFailShow);
-    static int runIptablesCmd(const char *cmd, IptJumpOp jumpHandling, IptIpVer iptIpVer,
+    static int runIptablesCmd(const std::string& cmd, IptJumpOp jumpHandling, IptIpVer iptIpVer,
                               IptFailureLog failureHandling = IptFailShow);
 
-
     // Provides strncpy() + check overflow.
-    static int StrncpyAndCheck(char *buffer, const char *src, size_t buffSize);
+    static int StrncpyAndCheck(char* buffer, const std::string& src, size_t buffSize);
 
-    int updateQuota(const char *alertName, int64_t bytes);
+    int updateQuota(const std::string& alertName, int64_t bytes);
 
-    int setCostlyAlert(const char *costName, int64_t bytes, int64_t *alertBytes);
-    int removeCostlyAlert(const char *costName, int64_t *alertBytes);
+    int setCostlyAlert(const std::string& costName, int64_t bytes, int64_t* alertBytes);
+    int removeCostlyAlert(const std::string& costName, int64_t* alertBytes);
 
     typedef std::vector<TetherStats> TetherStatsList;
 
@@ -223,6 +217,10 @@
     static int (*execFunction)(int, char **, int *, bool, bool);
     static FILE *(*popenFunction)(const char *, const char *);
     static int (*iptablesRestoreFunction)(IptablesTarget, const std::string&, std::string *);
+
+private:
+    static const char *opToString(IptOp op);
+    static const char *jumpToString(IptJumpOp jumpHandling);
 };
 
 #endif
diff --git a/server/BandwidthControllerTest.cpp b/server/BandwidthControllerTest.cpp
index c6b21d8..954db57 100644
--- a/server/BandwidthControllerTest.cpp
+++ b/server/BandwidthControllerTest.cpp
@@ -102,6 +102,16 @@
 
         expectIptablesRestoreCommands(expected);
     }
+
+    using IptOp = BandwidthController::IptOp;
+
+    int runIptablesAlertCmd(IptOp a, const char *b, int64_t c) {
+        return mBw.runIptablesAlertCmd(a, b, c);
+    }
+
+    int runIptablesAlertFwdCmd(IptOp a, const char *b, int64_t c) {
+        return mBw.runIptablesAlertFwdCmd(a, b, c);
+    }
 };
 
 TEST_F(BandwidthControllerTest, TestSetupIptablesHooks) {
@@ -180,15 +190,19 @@
 TEST_F(BandwidthControllerTest, TestEnableDataSaver) {
     mBw.enableDataSaver(true);
     std::vector<std::string> expected = {
-        "-R bw_data_saver 1 --jump REJECT",
+        "*filter\n"
+        "-R bw_data_saver 1 --jump REJECT\n"
+        "COMMIT\n"
     };
-    expectIptablesCommands(expected);
+    expectIptablesRestoreCommands(expected);
 
     mBw.enableDataSaver(false);
     expected = {
-        "-R bw_data_saver 1 --jump RETURN",
+        "*filter\n"
+        "-R bw_data_saver 1 --jump RETURN\n"
+        "COMMIT\n"
     };
-    expectIptablesCommands(expected);
+    expectIptablesRestoreCommands(expected);
 }
 
 std::string kIPv4TetherCounters = android::base::Join(std::vector<std::string> {
@@ -388,3 +402,65 @@
     EXPECT_EQ(0, mBw.removeInterfaceQuota(iface));
     expectIptablesCommands(expected);
 }
+
+TEST_F(BandwidthControllerTest, IptablesAlertCmd) {
+    std::vector<std::string> expected = {
+        "*filter\n"
+        "-I bw_INPUT -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
+        "-I bw_OUTPUT -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
+        "COMMIT\n"
+    };
+    EXPECT_EQ(0, runIptablesAlertCmd(IptOp::IptOpInsert, "MyWonderfulAlert", 123456));
+    expectIptablesRestoreCommands(expected);
+
+    expected = {
+        "*filter\n"
+        "-D bw_INPUT -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
+        "-D bw_OUTPUT -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
+        "COMMIT\n"
+    };
+    EXPECT_EQ(0, runIptablesAlertCmd(IptOp::IptOpDelete, "MyWonderfulAlert", 123456));
+    expectIptablesRestoreCommands(expected);
+}
+
+TEST_F(BandwidthControllerTest, IptablesAlertFwdCmd) {
+    std::vector<std::string> expected = {
+        "*filter\n"
+        "-I bw_FORWARD -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
+        "COMMIT\n"
+    };
+    EXPECT_EQ(0, runIptablesAlertFwdCmd(IptOp::IptOpInsert, "MyWonderfulAlert", 123456));
+    expectIptablesRestoreCommands(expected);
+
+    expected = {
+        "*filter\n"
+        "-D bw_FORWARD -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
+        "COMMIT\n"
+    };
+    EXPECT_EQ(0, runIptablesAlertFwdCmd(IptOp::IptOpDelete, "MyWonderfulAlert", 123456));
+    expectIptablesRestoreCommands(expected);
+}
+
+TEST_F(BandwidthControllerTest, ManipulateSpecialApps) {
+    std::vector<const char *> appUids = { "1000", "1001", "10012" };
+
+    std::vector<std::string> expected = {
+        "*filter\n"
+        "-I bw_happy_box -m owner --uid-owner 1000 --jump RETURN\n"
+        "-I bw_happy_box -m owner --uid-owner 1001 --jump RETURN\n"
+        "-I bw_happy_box -m owner --uid-owner 10012 --jump RETURN\n"
+        "COMMIT\n"
+    };
+    EXPECT_EQ(0, mBw.addNiceApps(appUids.size(), const_cast<char**>(&appUids[0])));
+    expectIptablesRestoreCommands(expected);
+
+    expected = {
+        "*filter\n"
+        "-D bw_penalty_box -m owner --uid-owner 1000 --jump REJECT\n"
+        "-D bw_penalty_box -m owner --uid-owner 1001 --jump REJECT\n"
+        "-D bw_penalty_box -m owner --uid-owner 10012 --jump REJECT\n"
+        "COMMIT\n"
+    };
+    EXPECT_EQ(0, mBw.removeNaughtyApps(appUids.size(), const_cast<char**>(&appUids[0])));
+    expectIptablesRestoreCommands(expected);
+}
diff --git a/server/CommandListener.cpp b/server/CommandListener.cpp
index b5aff11..5b9fa8a 100644
--- a/server/CommandListener.cpp
+++ b/server/CommandListener.cpp
@@ -1147,39 +1147,6 @@
         return sendGenericOkFail(cli, res);
     }
 
-    if (!strcmp(argv[1], "set_egress_source_rule")) {
-        if (argc != 4) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                         "Usage: firewall set_egress_source_rule <192.168.0.1> <allow|deny>",
-                         false);
-            return 0;
-        }
-
-        const char* addr = argv[2];
-        FirewallRule rule = parseRule(argv[3]);
-
-        int res = gCtls->firewallCtrl.setEgressSourceRule(addr, rule);
-        return sendGenericOkFail(cli, res);
-    }
-
-    if (!strcmp(argv[1], "set_egress_dest_rule")) {
-        if (argc != 5) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                         "Usage: firewall set_egress_dest_rule <192.168.0.1> <80> <allow|deny>",
-                         false);
-            return 0;
-        }
-
-        const char* addr = argv[2];
-        int port = atoi(argv[3]);
-        FirewallRule rule = parseRule(argv[4]);
-
-        int res = 0;
-        res |= gCtls->firewallCtrl.setEgressDestRule(addr, PROTOCOL_TCP, port, rule);
-        res |= gCtls->firewallCtrl.setEgressDestRule(addr, PROTOCOL_UDP, port, rule);
-        return sendGenericOkFail(cli, res);
-    }
-
     if (!strcmp(argv[1], "set_uid_rule")) {
         if (argc != 5) {
             cli->sendMsg(ResponseCode::CommandSyntaxError,
diff --git a/server/Controllers.cpp b/server/Controllers.cpp
index 1dccec8..ad77ee1 100644
--- a/server/Controllers.cpp
+++ b/server/Controllers.cpp
@@ -72,6 +72,12 @@
         NULL,
 };
 
+static const char* MANGLE_INPUT[] = {
+        WakeupController::LOCAL_MANGLE_INPUT,
+        RouteController::LOCAL_MANGLE_INPUT,
+        NULL,
+};
+
 static const char* MANGLE_FORWARD[] = {
         NatController::LOCAL_MANGLE_FORWARD,
         NULL,
@@ -116,7 +122,18 @@
 
 }  // namespace
 
-Controllers::Controllers() : clatdCtrl(&netCtrl) {
+Controllers::Controllers()
+    : clatdCtrl(&netCtrl),
+      wakeupCtrl(
+          [this](const std::string& prefix, uid_t uid, gid_t gid, uint64_t timestampNs) {
+              const auto listener = eventReporter.getNetdEventListener();
+              if (listener == nullptr) {
+                  ALOGE("getNetdEventListener() returned nullptr. dropping wakeup event");
+                  return;
+              }
+              listener->onWakeupEvent(String16(prefix.c_str()), uid, gid, timestampNs);
+          },
+          &iptablesRestoreCtrl) {
     InterfaceController::initializeAll();
 }
 
@@ -141,6 +158,7 @@
     createChildChains(V4V6, "raw", "PREROUTING", RAW_PREROUTING, true);
     createChildChains(V4V6, "mangle", "POSTROUTING", MANGLE_POSTROUTING, false);
     createChildChains(V4V6, "mangle", "FORWARD", MANGLE_FORWARD, true);
+    createChildChains(V4V6, "mangle", "INPUT", MANGLE_INPUT, true);
     createChildChains(V4, "nat", "PREROUTING", NAT_PREROUTING, true);
     createChildChains(V4, "nat", "POSTROUTING", NAT_POSTROUTING, true);
     ALOGI("Creating child chains: %.1fms", s.getTimeAndReset());
diff --git a/server/Controllers.h b/server/Controllers.h
index ac17fc1..0bfa0e7 100644
--- a/server/Controllers.h
+++ b/server/Controllers.h
@@ -19,19 +19,21 @@
 
 #include <sysutils/FrameworkListener.h>
 
-#include "NetworkController.h"
-#include "TetherController.h"
-#include "NatController.h"
-#include "PppController.h"
 #include "BandwidthController.h"
+#include "ClatdController.h"
+#include "EventReporter.h"
+#include "FirewallController.h"
 #include "IdletimerController.h"
 #include "InterfaceController.h"
 #include "IptablesRestoreController.h"
+#include "NatController.h"
+#include "NetworkController.h"
+#include "PppController.h"
 #include "ResolverController.h"
-#include "FirewallController.h"
-#include "ClatdController.h"
 #include "StrictController.h"
-#include "EventReporter.h"
+#include "TetherController.h"
+#include "WakeupController.h"
+#include "XfrmController.h"
 
 namespace android {
 namespace net {
@@ -52,6 +54,8 @@
     StrictController strictCtrl;
     EventReporter eventReporter;
     IptablesRestoreController iptablesRestoreCtrl;
+    WakeupController wakeupCtrl;
+    XfrmController xfrmCtrl;
 
     void init();
 
diff --git a/server/DnsProxyListener.cpp b/server/DnsProxyListener.cpp
index 59a85f0..a925a22 100644
--- a/server/DnsProxyListener.cpp
+++ b/server/DnsProxyListener.cpp
@@ -46,6 +46,7 @@
 #include "NetworkController.h"
 #include "ResponseCode.h"
 #include "Stopwatch.h"
+#include "thread_util.h"
 #include "android/net/metrics/INetdEventListener.h"
 
 using android::String16;
@@ -57,43 +58,6 @@
 namespace {
 
 template<typename T>
-void* threadMain(void* obj) {
-    std::unique_ptr<T> handler(reinterpret_cast<T*>(obj));
-    handler->run();
-    return nullptr;
-}
-
-struct scoped_pthread_attr {
-    scoped_pthread_attr() { pthread_attr_init(&attr); }
-    ~scoped_pthread_attr() { pthread_attr_destroy(&attr); }
-
-    int detach() {
-        return pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-    }
-
-    pthread_attr_t attr;
-};
-
-template<typename T>
-int threadLaunch(T* self) {
-    if (self == nullptr) { return -EINVAL;}
-
-    scoped_pthread_attr scoped_attr;
-
-    int rval = scoped_attr.detach();
-    if (rval != 0) { return -errno; }
-
-    pthread_t thread;
-    rval = pthread_create(&thread, &scoped_attr.attr, &threadMain<T>, self);
-    if (rval != 0) {
-        ALOGW("pthread_create failed: %d", errno);
-        return -errno;
-    }
-
-    return rval;
-}
-
-template<typename T>
 void tryThreadOrError(SocketClient* cli, T* handler) {
     cli->incRef();
 
@@ -123,7 +87,7 @@
 
 DnsProxyListener::GetAddrInfoHandler::GetAddrInfoHandler(
         SocketClient *c, char* host, char* service, struct addrinfo* hints,
-        const struct android_net_context& netcontext, const int reportingLevel,
+        const android_net_context& netcontext, const int reportingLevel,
         const android::sp<android::net::metrics::INetdEventListener>& netdEventListener)
         : mClient(c),
           mHost(host),
@@ -339,7 +303,7 @@
     unsigned netId = strtoul(argv[7], NULL, 10);
     uid_t uid = cli->getUid();
 
-    struct android_net_context netcontext;
+    android_net_context netcontext;
     mDnsProxyListener->mNetCtrl->getNetworkContext(netId, uid, &netcontext);
 
     if (ai_flags != -1 || ai_family != -1 ||
@@ -404,24 +368,25 @@
         name = strdup(name);
     }
 
-    uint32_t mark = mDnsProxyListener->mNetCtrl->getNetworkForDns(&netId, uid);
+    android_net_context netcontext;
+    mDnsProxyListener->mNetCtrl->getNetworkContext(netId, uid, &netcontext);
+
     const int metricsLevel = mDnsProxyListener->mEventReporter->getMetricsReportingLevel();
 
     DnsProxyListener::GetHostByNameHandler* handler =
-            new DnsProxyListener::GetHostByNameHandler(cli, name, af, netId, mark, metricsLevel,
+            new DnsProxyListener::GetHostByNameHandler(cli, name, af, netcontext, metricsLevel,
                     mDnsProxyListener->mEventReporter->getNetdEventListener());
     tryThreadOrError(cli, handler);
     return 0;
 }
 
-DnsProxyListener::GetHostByNameHandler::GetHostByNameHandler(
-        SocketClient* c, char* name, int af, unsigned netId, uint32_t mark, const int metricsLevel,
+DnsProxyListener::GetHostByNameHandler::GetHostByNameHandler(SocketClient* c, char* name, int af,
+        const android_net_context& netcontext, const int metricsLevel,
         const android::sp<android::net::metrics::INetdEventListener>& netdEventListener)
         : mClient(c),
           mName(name),
           mAf(af),
-          mNetId(netId),
-          mMark(mark),
+          mNetContext(netcontext),
           mReportingLevel(metricsLevel),
           mNetdEventListener(netdEventListener) {
 }
@@ -436,7 +401,7 @@
     }
 
     Stopwatch s;
-    struct hostent* hp = android_gethostbynamefornet(mName, mAf, mNetId, mMark);
+    struct hostent* hp = android_gethostbynamefornetcontext(mName, mAf, &mNetContext);
     const int latencyMs = lround(s.timeTaken());
 
     if (DBG) {
@@ -484,12 +449,14 @@
                 break;
             case INetdEventListener::REPORTING_LEVEL_METRICS:
                 // Metrics reporting is on. Send metrics.
-                mNetdEventListener->onDnsEvent(mNetId, INetdEventListener::EVENT_GETHOSTBYNAME,
+                mNetdEventListener->onDnsEvent(mNetContext.dns_netid,
+                                               INetdEventListener::EVENT_GETHOSTBYNAME,
                                                h_errno, latencyMs, String16(""), {}, -1, -1);
                 break;
             case INetdEventListener::REPORTING_LEVEL_FULL:
                 // Full event info reporting is on. Send full info.
-                mNetdEventListener->onDnsEvent(mNetId, INetdEventListener::EVENT_GETHOSTBYNAME,
+                mNetdEventListener->onDnsEvent(mNetContext.dns_netid,
+                                               INetdEventListener::EVENT_GETHOSTBYNAME,
                                                h_errno, latencyMs, String16(mName), ip_addrs,
                                                total_ip_addr_count, mClient->getUid());
                 break;
@@ -543,26 +510,26 @@
         return -1;
     }
 
-    uint32_t mark = mDnsProxyListener->mNetCtrl->getNetworkForDns(&netId, uid);
+    android_net_context netcontext;
+    mDnsProxyListener->mNetCtrl->getNetworkContext(netId, uid, &netcontext);
 
     DnsProxyListener::GetHostByAddrHandler* handler =
-            new DnsProxyListener::GetHostByAddrHandler(cli, addr, addrLen, addrFamily, netId, mark);
+            new DnsProxyListener::GetHostByAddrHandler(cli, addr, addrLen, addrFamily, netcontext);
     tryThreadOrError(cli, handler);
     return 0;
 }
 
-DnsProxyListener::GetHostByAddrHandler::GetHostByAddrHandler(SocketClient* c,
-                                                             void* address,
-                                                             int   addressLen,
-                                                             int   addressFamily,
-                                                             unsigned netId,
-                                                             uint32_t mark)
+DnsProxyListener::GetHostByAddrHandler::GetHostByAddrHandler(
+          SocketClient* c,
+          void* address,
+          int addressLen,
+          int addressFamily,
+          const android_net_context& netcontext)
         : mClient(c),
           mAddress(address),
           mAddressLen(addressLen),
           mAddressFamily(addressFamily),
-          mNetId(netId),
-          mMark(mark) {
+          mNetContext(netcontext) {
 }
 
 DnsProxyListener::GetHostByAddrHandler::~GetHostByAddrHandler() {
@@ -576,7 +543,8 @@
     struct hostent* hp;
 
     // NOTE gethostbyaddr should take a void* but bionic thinks it should be char*
-    hp = android_gethostbyaddrfornet((char*)mAddress, mAddressLen, mAddressFamily, mNetId, mMark);
+    hp = android_gethostbyaddrfornetcontext(
+            (char*)mAddress, mAddressLen, mAddressFamily, &mNetContext);
 
     if (DBG) {
         ALOGD("GetHostByAddrHandler::run gethostbyaddr errno: %s hp->h_name = %s, name_len = %zu\n",
diff --git a/server/DnsProxyListener.h b/server/DnsProxyListener.h
index 2c24bda..b08a114 100644
--- a/server/DnsProxyListener.h
+++ b/server/DnsProxyListener.h
@@ -89,8 +89,7 @@
         GetHostByNameHandler(SocketClient *c,
                             char *name,
                             int af,
-                            unsigned netId,
-                            uint32_t mark,
+                            const android_net_context& netcontext,
                             int reportingLevel,
                             const android::sp<android::net::metrics::INetdEventListener>& listener);
         ~GetHostByNameHandler();
@@ -101,8 +100,7 @@
         SocketClient* mClient; //ref counted
         char* mName; // owned
         int mAf;
-        unsigned mNetId;
-        uint32_t mMark;
+        android_net_context mNetContext;
         const int mReportingLevel;
         android::sp<android::net::metrics::INetdEventListener> mNetdEventListener;
     };
@@ -123,8 +121,7 @@
                             void* address,
                             int addressLen,
                             int addressFamily,
-                            unsigned netId,
-                            uint32_t mark);
+                            const android_net_context& netcontext);
         ~GetHostByAddrHandler();
 
         void run();
@@ -134,8 +131,7 @@
         void* mAddress;    // address to lookup; owned
         int mAddressLen; // length of address to look up
         int mAddressFamily;  // address family
-        unsigned mNetId;
-        uint32_t mMark;
+        android_net_context mNetContext;
     };
 };
 
diff --git a/server/FirewallController.cpp b/server/FirewallController.cpp
index 9cab90a..b235f91 100644
--- a/server/FirewallController.cpp
+++ b/server/FirewallController.cpp
@@ -118,14 +118,13 @@
             return res;
     }
 
-    if (enable) {
-        res |= attachChain(name, LOCAL_INPUT);
-        res |= attachChain(name, LOCAL_OUTPUT);
-    } else {
-        res |= detachChain(name, LOCAL_INPUT);
-        res |= detachChain(name, LOCAL_OUTPUT);
+    std::string command = "*filter\n";
+    for (const char *parent : { LOCAL_INPUT, LOCAL_OUTPUT }) {
+        StringAppendF(&command, "%s %s -j %s\n", (enable ? "-A" : "-D"), parent, name);
     }
-    return res;
+    StringAppendF(&command, "COMMIT\n");
+
+    return execIptablesRestore(V4V6, command);
 }
 
 int FirewallController::isFirewallEnabled(void) {
@@ -157,63 +156,6 @@
     return res;
 }
 
-int FirewallController::setEgressSourceRule(const char* addr, FirewallRule rule) {
-    if (mFirewallType == BLACKLIST) {
-        // Unsupported in BLACKLIST mode
-        return -1;
-    }
-
-    IptablesTarget target = V4;
-    if (strchr(addr, ':')) {
-        target = V6;
-    }
-
-    const char* op;
-    if (rule == ALLOW) {
-        op = "-I";
-    } else {
-        op = "-D";
-    }
-
-    int res = 0;
-    res |= execIptables(target, op, LOCAL_INPUT, "-d", addr, "-j", "RETURN", NULL);
-    res |= execIptables(target, op, LOCAL_OUTPUT, "-s", addr, "-j", "RETURN", NULL);
-    return res;
-}
-
-int FirewallController::setEgressDestRule(const char* addr, int protocol, int port,
-        FirewallRule rule) {
-    if (mFirewallType == BLACKLIST) {
-        // Unsupported in BLACKLIST mode
-        return -1;
-    }
-
-    IptablesTarget target = V4;
-    if (strchr(addr, ':')) {
-        target = V6;
-    }
-
-    char protocolStr[16];
-    sprintf(protocolStr, "%d", protocol);
-
-    char portStr[16];
-    sprintf(portStr, "%d", port);
-
-    const char* op;
-    if (rule == ALLOW) {
-        op = "-I";
-    } else {
-        op = "-D";
-    }
-
-    int res = 0;
-    res |= execIptables(target, op, LOCAL_INPUT, "-s", addr, "-p", protocolStr,
-            "--sport", portStr, "-j", "RETURN", NULL);
-    res |= execIptables(target, op, LOCAL_OUTPUT, "-d", addr, "-p", protocolStr,
-            "--dport", portStr, "-j", "RETURN", NULL);
-    return res;
-}
-
 FirewallType FirewallController::getFirewallType(ChildChain chain) {
     switch(chain) {
         case DOZABLE:
@@ -230,9 +172,6 @@
 }
 
 int FirewallController::setUidRule(ChildChain chain, int uid, FirewallRule rule) {
-    char uidStr[16];
-    sprintf(uidStr, "%d", uid);
-
     const char* op;
     const char* target;
     FirewallType firewallType = getFirewallType(chain);
@@ -246,39 +185,33 @@
         op = (rule == DENY)? "-A" : "-D";
     }
 
-    int res = 0;
+    std::vector<std::string> chainNames;
     switch(chain) {
         case DOZABLE:
-            res |= execIptables(V4V6, op, LOCAL_DOZABLE, "-m", "owner", "--uid-owner",
-                    uidStr, "-j", target, NULL);
+            chainNames = { LOCAL_DOZABLE };
             break;
         case STANDBY:
-            res |= execIptables(V4V6, op, LOCAL_STANDBY, "-m", "owner", "--uid-owner",
-                    uidStr, "-j", target, NULL);
+            chainNames = { LOCAL_STANDBY };
             break;
         case POWERSAVE:
-            res |= execIptables(V4V6, op, LOCAL_POWERSAVE, "-m", "owner", "--uid-owner",
-                    uidStr, "-j", target, NULL);
+            chainNames = { LOCAL_POWERSAVE };
             break;
         case NONE:
-            res |= execIptables(V4V6, op, LOCAL_INPUT, "-m", "owner", "--uid-owner", uidStr,
-                    "-j", target, NULL);
-            res |= execIptables(V4V6, op, LOCAL_OUTPUT, "-m", "owner", "--uid-owner", uidStr,
-                    "-j", target, NULL);
+            chainNames = { LOCAL_INPUT, LOCAL_OUTPUT };
             break;
         default:
             ALOGW("Unknown child chain: %d", chain);
-            break;
+            return -1;
     }
-    return res;
-}
 
-int FirewallController::attachChain(const char* childChain, const char* parentChain) {
-    return execIptables(V4V6, "-t", TABLE, "-A", parentChain, "-j", childChain, NULL);
-}
+    std::string command = "*filter\n";
+    for (std::string chainName : chainNames) {
+        StringAppendF(&command, "%s %s -m owner --uid-owner %d -j %s\n",
+                      op, chainName.c_str(), uid, target);
+    }
+    StringAppendF(&command, "COMMIT\n");
 
-int FirewallController::detachChain(const char* childChain, const char* parentChain) {
-    return execIptables(V4V6, "-t", TABLE, "-D", parentChain, "-j", childChain, NULL);
+    return execIptablesRestore(V4V6, command);
 }
 
 int FirewallController::createChain(const char* chain, FirewallType type) {
@@ -291,8 +224,20 @@
     std::string commands;
     StringAppendF(&commands, "*filter\n:%s -\n", name);
 
+    // Whitelist chains have UIDs at the beginning, and new UIDs are added with '-I'.
+    if (isWhitelist) {
+        for (auto uid : uids) {
+            StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j RETURN\n", name, uid);
+        }
+
+        // Always whitelist system UIDs.
+        StringAppendF(&commands,
+                "-A %s -m owner --uid-owner %d-%d -j RETURN\n", name, 0, MAX_SYSTEM_UID);
+    }
+
     // Always allow networking on loopback.
-    StringAppendF(&commands, "-A %s -i lo -o lo -j RETURN\n", name);
+    StringAppendF(&commands, "-A %s -i lo -j RETURN\n", name);
+    StringAppendF(&commands, "-A %s -o lo -j RETURN\n", name);
 
     // Allow TCP RSTs so we can cleanly close TCP connections of apps that no longer have network
     // access. Both incoming and outgoing RSTs are allowed.
@@ -306,16 +251,13 @@
                        name, ICMPV6_TYPES[i]);
             }
         }
-
-        // Always whitelist system UIDs.
-        StringAppendF(&commands,
-                "-A %s -m owner --uid-owner %d-%d -j RETURN\n", name, 0, MAX_SYSTEM_UID);
     }
 
-    // Whitelist or blacklist the specified UIDs.
-    const char *action = isWhitelist ? "RETURN" : "DROP";
-    for (auto uid : uids) {
-        StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j %s\n", name, uid, action);
+    // Blacklist chains have UIDs at the end, and new UIDs are added with '-A'.
+    if (!isWhitelist) {
+        for (auto uid : uids) {
+            StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j DROP\n", name, uid);
+        }
     }
 
     // If it's a whitelist chain, add a default DROP at the end. This is not necessary for a
diff --git a/server/FirewallController.h b/server/FirewallController.h
index 67d632c..016b347 100644
--- a/server/FirewallController.h
+++ b/server/FirewallController.h
@@ -56,10 +56,6 @@
 
     /* Match traffic going in/out over the given iface. */
     int setInterfaceRule(const char*, FirewallRule);
-    /* Match traffic coming-in-to or going-out-from given address. */
-    int setEgressSourceRule(const char*, FirewallRule);
-    /* Match traffic coming-in-from or going-out-to given address, port, and protocol. */
-    int setEgressDestRule(const char*, int, int, FirewallRule);
     /* Match traffic owned by given UID. This is specific to a particular chain. */
     int setUidRule(ChildChain, int, FirewallRule);
 
diff --git a/server/FirewallControllerTest.cpp b/server/FirewallControllerTest.cpp
index 9d43636..7f6f0ae 100644
--- a/server/FirewallControllerTest.cpp
+++ b/server/FirewallControllerTest.cpp
@@ -52,16 +52,19 @@
     std::vector<std::string> expectedRestore4 = {
         "*filter",
         ":fw_whitelist -",
-        "-A fw_whitelist -i lo -o lo -j RETURN",
-        "-A fw_whitelist -p tcp --tcp-flags RST RST -j RETURN",
         "-A fw_whitelist -m owner --uid-owner 0-9999 -j RETURN",
+        "-A fw_whitelist -i lo -j RETURN",
+        "-A fw_whitelist -o lo -j RETURN",
+        "-A fw_whitelist -p tcp --tcp-flags RST RST -j RETURN",
         "-A fw_whitelist -j DROP",
         "COMMIT\n"
     };
     std::vector<std::string> expectedRestore6 = {
         "*filter",
         ":fw_whitelist -",
-        "-A fw_whitelist -i lo -o lo -j RETURN",
+        "-A fw_whitelist -m owner --uid-owner 0-9999 -j RETURN",
+        "-A fw_whitelist -i lo -j RETURN",
+        "-A fw_whitelist -o lo -j RETURN",
         "-A fw_whitelist -p tcp --tcp-flags RST RST -j RETURN",
         "-A fw_whitelist -p icmpv6 --icmpv6-type packet-too-big -j RETURN",
         "-A fw_whitelist -p icmpv6 --icmpv6-type router-solicitation -j RETURN",
@@ -69,7 +72,6 @@
         "-A fw_whitelist -p icmpv6 --icmpv6-type neighbour-solicitation -j RETURN",
         "-A fw_whitelist -p icmpv6 --icmpv6-type neighbour-advertisement -j RETURN",
         "-A fw_whitelist -p icmpv6 --icmpv6-type redirect -j RETURN",
-        "-A fw_whitelist -m owner --uid-owner 0-9999 -j RETURN",
         "-A fw_whitelist -j DROP",
         "COMMIT\n"
     };
@@ -86,7 +88,8 @@
     std::vector<std::string> expectedRestore = {
         "*filter",
         ":fw_blacklist -",
-        "-A fw_blacklist -i lo -o lo -j RETURN",
+        "-A fw_blacklist -i lo -j RETURN",
+        "-A fw_blacklist -o lo -j RETURN",
         "-A fw_blacklist -p tcp --tcp-flags RST RST -j RETURN",
         "COMMIT\n"
     };
@@ -101,45 +104,56 @@
 
 TEST_F(FirewallControllerTest, TestSetStandbyRule) {
     ExpectedIptablesCommands expected = {
-        { V4V6, "-D fw_standby -m owner --uid-owner 12345 -j DROP" }
+        { V4V6, "*filter\n-D fw_standby -m owner --uid-owner 12345 -j DROP\nCOMMIT\n" }
     };
     mFw.setUidRule(STANDBY, 12345, ALLOW);
-    expectIptablesCommands(expected);
+    expectIptablesRestoreCommands(expected);
 
     expected = {
-        { V4V6, "-A fw_standby -m owner --uid-owner 12345 -j DROP" }
+        { V4V6, "*filter\n-A fw_standby -m owner --uid-owner 12345 -j DROP\nCOMMIT\n" }
     };
     mFw.setUidRule(STANDBY, 12345, DENY);
-    expectIptablesCommands(expected);
+    expectIptablesRestoreCommands(expected);
 }
 
 TEST_F(FirewallControllerTest, TestSetDozeRule) {
     ExpectedIptablesCommands expected = {
-        { V4V6, "-I fw_dozable -m owner --uid-owner 54321 -j RETURN" }
+        { V4V6, "*filter\n-I fw_dozable -m owner --uid-owner 54321 -j RETURN\nCOMMIT\n" }
     };
     mFw.setUidRule(DOZABLE, 54321, ALLOW);
-    expectIptablesCommands(expected);
+    expectIptablesRestoreCommands(expected);
 
     expected = {
-        { V4V6, "-D fw_dozable -m owner --uid-owner 54321 -j RETURN" }
+        { V4V6, "*filter\n-D fw_dozable -m owner --uid-owner 54321 -j RETURN\nCOMMIT\n" }
     };
     mFw.setUidRule(DOZABLE, 54321, DENY);
-    expectIptablesCommands(expected);
+    expectIptablesRestoreCommands(expected);
+}
+
+TEST_F(FirewallControllerTest, TestSetFirewallRule) {
+    ExpectedIptablesCommands expected = {
+        { V4V6, "*filter\n"
+                "-A fw_INPUT -m owner --uid-owner 54321 -j DROP\n"
+                "-A fw_OUTPUT -m owner --uid-owner 54321 -j DROP\n"
+                "COMMIT\n" }
+    };
+    mFw.setUidRule(NONE, 54321, DENY);
+    expectIptablesRestoreCommands(expected);
+
+    expected = {
+        { V4V6, "*filter\n"
+                "-D fw_INPUT -m owner --uid-owner 54321 -j DROP\n"
+                "-D fw_OUTPUT -m owner --uid-owner 54321 -j DROP\n"
+                "COMMIT\n" }
+    };
+    mFw.setUidRule(NONE, 54321, ALLOW);
+    expectIptablesRestoreCommands(expected);
 }
 
 TEST_F(FirewallControllerTest, TestReplaceWhitelistUidRule) {
     std::string expected =
             "*filter\n"
             ":FW_whitechain -\n"
-            "-A FW_whitechain -i lo -o lo -j RETURN\n"
-            "-A FW_whitechain -p tcp --tcp-flags RST RST -j RETURN\n"
-            "-A FW_whitechain -p icmpv6 --icmpv6-type packet-too-big -j RETURN\n"
-            "-A FW_whitechain -p icmpv6 --icmpv6-type router-solicitation -j RETURN\n"
-            "-A FW_whitechain -p icmpv6 --icmpv6-type router-advertisement -j RETURN\n"
-            "-A FW_whitechain -p icmpv6 --icmpv6-type neighbour-solicitation -j RETURN\n"
-            "-A FW_whitechain -p icmpv6 --icmpv6-type neighbour-advertisement -j RETURN\n"
-            "-A FW_whitechain -p icmpv6 --icmpv6-type redirect -j RETURN\n"
-            "-A FW_whitechain -m owner --uid-owner 0-9999 -j RETURN\n"
             "-A FW_whitechain -m owner --uid-owner 10023 -j RETURN\n"
             "-A FW_whitechain -m owner --uid-owner 10059 -j RETURN\n"
             "-A FW_whitechain -m owner --uid-owner 10124 -j RETURN\n"
@@ -147,6 +161,16 @@
             "-A FW_whitechain -m owner --uid-owner 110122 -j RETURN\n"
             "-A FW_whitechain -m owner --uid-owner 210153 -j RETURN\n"
             "-A FW_whitechain -m owner --uid-owner 210024 -j RETURN\n"
+            "-A FW_whitechain -m owner --uid-owner 0-9999 -j RETURN\n"
+            "-A FW_whitechain -i lo -j RETURN\n"
+            "-A FW_whitechain -o lo -j RETURN\n"
+            "-A FW_whitechain -p tcp --tcp-flags RST RST -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type packet-too-big -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type router-solicitation -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type router-advertisement -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type neighbour-solicitation -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type neighbour-advertisement -j RETURN\n"
+            "-A FW_whitechain -p icmpv6 --icmpv6-type redirect -j RETURN\n"
             "-A FW_whitechain -j DROP\n"
             "COMMIT\n";
 
@@ -158,7 +182,8 @@
     std::string expected =
             "*filter\n"
             ":FW_blackchain -\n"
-            "-A FW_blackchain -i lo -o lo -j RETURN\n"
+            "-A FW_blackchain -i lo -j RETURN\n"
+            "-A FW_blackchain -o lo -j RETURN\n"
             "-A FW_blackchain -p tcp --tcp-flags RST RST -j RETURN\n"
             "-A FW_blackchain -m owner --uid-owner 10023 -j DROP\n"
             "-A FW_blackchain -m owner --uid-owner 10059 -j DROP\n"
@@ -168,3 +193,23 @@
     std::vector<int32_t> uids = { 10023, 10059, 10124 };
     EXPECT_EQ(expected, makeUidRules(V4 ,"FW_blackchain", false, uids));
 }
+
+TEST_F(FirewallControllerTest, TestEnableChildChains) {
+    std::vector<std::string> expected = {
+        "*filter\n"
+        "-A fw_INPUT -j fw_dozable\n"
+        "-A fw_OUTPUT -j fw_dozable\n"
+        "COMMIT\n"
+    };
+    EXPECT_EQ(0, mFw.enableChildChains(DOZABLE, true));
+    expectIptablesRestoreCommands(expected);
+
+    expected = {
+        "*filter\n"
+        "-D fw_INPUT -j fw_powersave\n"
+        "-D fw_OUTPUT -j fw_powersave\n"
+        "COMMIT\n"
+    };
+    EXPECT_EQ(0, mFw.enableChildChains(POWERSAVE, false));
+    expectIptablesRestoreCommands(expected);
+}
diff --git a/server/InterfaceController.cpp b/server/InterfaceController.cpp
index ed5d8d0..a8b985a 100644
--- a/server/InterfaceController.cpp
+++ b/server/InterfaceController.cpp
@@ -19,20 +19,36 @@
 #include <malloc.h>
 #include <sys/socket.h>
 
+#include <functional>
+
 #define LOG_TAG "InterfaceController"
 #include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <cutils/log.h>
 #include <logwrap/logwrap.h>
 #include <netutils/ifc.h>
 
+#include <android/net/INetd.h>
+#include <netdutils/Misc.h>
+#include <netdutils/Slice.h>
+#include <netdutils/Syscalls.h>
+
 #include "InterfaceController.h"
 #include "RouteController.h"
 
-using android::base::StringPrintf;
 using android::base::ReadFileToString;
+using android::base::StringPrintf;
 using android::base::WriteStringToFile;
+using android::net::INetd;
 using android::net::RouteController;
+using android::netdutils::Status;
+using android::netdutils::StatusOr;
+using android::netdutils::makeSlice;
+using android::netdutils::sSyscalls;
+using android::netdutils::status::ok;
+using android::netdutils::statusFromErrno;
+using android::netdutils::toString;
 
 namespace {
 
@@ -47,6 +63,26 @@
 
 const char wl_util_path[] = "/vendor/xbin/wlutil";
 
+constexpr int kRouteInfoMinPrefixLen = 48;
+
+// RFC 7421 prefix length.
+constexpr int kRouteInfoMaxPrefixLen = 64;
+
+// Property used to persist RFC 7217 stable secret. Protected by SELinux policy.
+const char kStableSecretProperty[] = "persist.netd.stable_secret";
+
+// RFC 7217 stable secret on linux is formatted as an IPv6 address.
+// This function uses 128 bits of high quality entropy to generate an
+// address for this purpose. This function should be not be called
+// frequently.
+StatusOr<std::string> randomIPv6Address() {
+    in6_addr addr = {};
+    const auto& sys = sSyscalls.get();
+    ASSIGN_OR_RETURN(auto fd, sys.open("/dev/random", O_RDONLY));
+    RETURN_IF_NOT_OK(sys.read(fd, makeSlice(addr)));
+    return toString(addr);
+}
+
 inline bool isNormalPathComponent(const char *component) {
     return (strcmp(component, ".") != 0) &&
            (strcmp(component, "..") != 0) &&
@@ -70,26 +106,36 @@
     return WriteStringToFile(value, path) ? 0 : -1;
 }
 
-void setOnAllInterfaces(const char* dirname, const char* basename, const char* value) {
-    // Set the default value, which is used by any interfaces that are created in the future.
-    writeValueToPath(dirname, "default", basename, value);
-
-    // Set the value on all the interfaces that currently exist.
-    DIR* dir = opendir(dirname);
+// Run @fn on each interface as well as 'default' in the path @dirname.
+void forEachInterface(const std::string& dirname,
+                      std::function<void(const std::string& path, const std::string& iface)> fn) {
+    // Run on default, which controls the behavior of any interfaces that are created in the future.
+    fn(dirname, "default");
+    DIR* dir = opendir(dirname.c_str());
     if (!dir) {
-        ALOGE("Can't list %s: %s", dirname, strerror(errno));
+        ALOGE("Can't list %s: %s", dirname.c_str(), strerror(errno));
         return;
     }
-    dirent* d;
-    while ((d = readdir(dir))) {
-        if ((d->d_type != DT_DIR) || !isInterfaceName(d->d_name)) {
+    while (true) {
+        const dirent *ent = readdir(dir);
+        if (!ent) {
+            break;
+        }
+        if ((ent->d_type != DT_DIR) || !isInterfaceName(ent->d_name)) {
             continue;
         }
-        writeValueToPath(dirname, d->d_name, basename, value);
+        fn(dirname, ent->d_name);
     }
     closedir(dir);
 }
 
+void setOnAllInterfaces(const char* dirname, const char* basename, const char* value) {
+    auto fn = [basename, value](const std::string& path, const std::string& iface) {
+        writeValueToPath(path.c_str(), iface.c_str(), basename, value);
+    };
+    forEachInterface(dirname, fn);
+}
+
 void setIPv6UseOutgoingInterfaceAddrsOnly(const char *value) {
     setOnAllInterfaces(ipv6_proc_path, "use_oif_addrs_only", value);
 }
@@ -109,8 +155,77 @@
     return StringPrintf("%s/%s/%s/%s/%s", proc_net_path, family, which, interface, parameter);
 }
 
+void setAcceptIPv6RIO(int min, int max) {
+    auto fn = [min, max](const std::string& prefix, const std::string& iface) {
+        int rv = writeValueToPath(prefix.c_str(), iface.c_str(), "accept_ra_rt_info_min_plen",
+                                  std::to_string(min).c_str());
+        if (rv != 0) {
+            // Only update max_plen if the write to min_plen succeeded. This ordering will prevent
+            // RIOs from being accepted unless both min and max are written successfully.
+            return;
+        }
+        writeValueToPath(prefix.c_str(), iface.c_str(), "accept_ra_rt_info_max_plen",
+                         std::to_string(max).c_str());
+    };
+    forEachInterface(ipv6_proc_path, fn);
+}
+
+// Ideally this function would return StatusOr<std::string>, however
+// there is no safe value for dflt that will always differ from the
+// stored property. Bugs code could conceivably end up persisting the
+// reserved value resulting in surprising behavior.
+std::string getProperty(const std::string& key, const std::string& dflt) {
+    return android::base::GetProperty(key, dflt);
+};
+
+Status setProperty(const std::string& key, const std::string& val) {
+    // SetProperty tries to encode something useful in errno, however
+    // the value may get clobbered by async_safe_format_log() in
+    // __system_property_set(). Use with care.
+    return android::base::SetProperty(key, val)
+        ? ok
+        : statusFromErrno(errno, "SetProperty failed");
+};
+
+
 }  // namespace
 
+android::netdutils::Status InterfaceController::enableStablePrivacyAddresses(
+        const std::string& iface, GetPropertyFn getProperty, SetPropertyFn setProperty) {
+    const auto& sys = sSyscalls.get();
+    const std::string procTarget = std::string(ipv6_proc_path) + "/" + iface + "/stable_secret";
+    auto procFd = sys.open(procTarget, O_CLOEXEC | O_WRONLY);
+
+    // Devices with old kernels (typically < 4.4) don't support
+    // RFC 7217 stable privacy addresses.
+    if (equalToErrno(procFd, ENOENT)) {
+        return statusFromErrno(EOPNOTSUPP,
+                               "Failed to open stable_secret. Assuming unsupported kernel version");
+    }
+
+    // If stable_secret exists but we can't open it, something strange is going on.
+    RETURN_IF_NOT_OK(procFd);
+
+    const char kUninitialized[] = "uninitialized";
+    const auto oldSecret = getProperty(kStableSecretProperty, kUninitialized);
+    std::string secret = oldSecret;
+
+    // Generate a new secret if no persistent property existed.
+    if (oldSecret == kUninitialized) {
+        ASSIGN_OR_RETURN(secret, randomIPv6Address());
+    }
+
+    // Ask the OS to generate SLAAC addresses on iface using secret.
+    RETURN_IF_NOT_OK(sys.write(procFd.value(), makeSlice(secret)));
+
+    // Don't persist an existing secret.
+    if (oldSecret != kUninitialized) {
+        return ok;
+    }
+
+    return setProperty(kStableSecretProperty, secret);
+}
+
 void InterfaceController::initializeAll() {
     // Initial IPv6 settings.
     // By default, accept_ra is set to 1 (accept RAs unless forwarding is on) on all interfaces.
@@ -119,6 +234,9 @@
     // by always setting accept_ra to 2.
     setAcceptRA("2");
 
+    // Accept RIOs with prefix length in the closed interval [48, 64].
+    setAcceptIPv6RIO(kRouteInfoMinPrefixLen, kRouteInfoMaxPrefixLen);
+
     setAcceptRARouteTable(-RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX);
 
     // Enable optimistic DAD for IPv6 addresses on all interfaces.
@@ -144,6 +262,31 @@
     return writeValueToPath(ipv6_proc_path, interface, "disable_ipv6", disable_ipv6);
 }
 
+// Changes to addrGenMode will not fully take effect until the next
+// time disable_ipv6 transitions from 1 to 0.
+Status InterfaceController::setIPv6AddrGenMode(const std::string& interface, int mode) {
+    if (!isIfaceName(interface)) {
+        return statusFromErrno(ENOENT, "invalid iface name: " + interface);
+    }
+
+    switch (mode) {
+        case INetd::IPV6_ADDR_GEN_MODE_EUI64:
+            // Ignore return value. If /proc/.../stable_secret is
+            // missing we're probably in EUI64 mode already.
+            writeValueToPath(ipv6_proc_path, interface.c_str(), "stable_secret", "");
+            break;
+        case INetd::IPV6_ADDR_GEN_MODE_STABLE_PRIVACY: {
+            return enableStablePrivacyAddresses(interface, getProperty, setProperty);
+        }
+        case INetd::IPV6_ADDR_GEN_MODE_NONE:
+        case INetd::IPV6_ADDR_GEN_MODE_RANDOM:
+        default:
+            return statusFromErrno(EOPNOTSUPP, "unsupported addrGenMode");
+    }
+
+    return ok;
+}
+
 int InterfaceController::setAcceptIPv6Ra(const char *interface, const int on) {
     if (!isIfaceName(interface)) {
         errno = ENOENT;
@@ -178,7 +321,7 @@
         return -1;
     }
     // 0: disable IPv6 privacy addresses
-    // 0: enable IPv6 privacy addresses and prefer them over non-privacy ones.
+    // 2: enable IPv6 privacy addresses and prefer them over non-privacy ones.
     return writeValueToPath(ipv6_proc_path, interface, "use_tempaddr", on ? "2" : "0");
 }
 
diff --git a/server/InterfaceController.h b/server/InterfaceController.h
index 80ebe5c..30898f2 100644
--- a/server/InterfaceController.h
+++ b/server/InterfaceController.h
@@ -17,13 +17,24 @@
 #ifndef _INTERFACE_CONTROLLER_H
 #define _INTERFACE_CONTROLLER_H
 
+#include <functional>
 #include <string>
 
+#include <netdutils/Status.h>
+
+// TODO: move InterfaceController into android::net namespace.
+namespace android {
+namespace net {
+class StablePrivacyTest;
+}  // namespace net
+}  // namespace android
+
 class InterfaceController {
 public:
     static void initializeAll();
 
     static int setEnableIPv6(const char *interface, const int on);
+    static android::netdutils::Status setIPv6AddrGenMode(const std::string& interface, int mode);
     static int setAcceptIPv6Ra(const char *interface, const int on);
     static int setAcceptIPv6Dad(const char *interface, const int on);
     static int setIPv6DadTransmits(const char *interface, const char *value);
@@ -43,13 +54,25 @@
             const char *value);
 
 private:
-    static void setAcceptRA(const char* value);
-    static void setAcceptRARouteTable(int tableOrOffset);
-    static void setBaseReachableTimeMs(unsigned int millis);
-    static void setIPv6OptimisticMode(const char *value);
+  friend class android::net::StablePrivacyTest;
 
-    InterfaceController() = delete;
-    ~InterfaceController() = delete;
+  using GetPropertyFn =
+      std::function<std::string(const std::string& key, const std::string& dflt)>;
+  using SetPropertyFn =
+      std::function<android::netdutils::Status(const std::string& key, const std::string& val)>;
+
+  // Helper function exported from this compilation unit for testing.
+  static android::netdutils::Status enableStablePrivacyAddresses(const std::string& iface,
+                                                                 GetPropertyFn getProperty,
+                                                                 SetPropertyFn setProperty);
+
+  static void setAcceptRA(const char* value);
+  static void setAcceptRARouteTable(int tableOrOffset);
+  static void setBaseReachableTimeMs(unsigned int millis);
+  static void setIPv6OptimisticMode(const char* value);
+
+  InterfaceController() = delete;
+  ~InterfaceController() = delete;
 };
 
 #endif
diff --git a/server/InterfaceControllerTest.cpp b/server/InterfaceControllerTest.cpp
new file mode 100644
index 0000000..014d05d
--- /dev/null
+++ b/server/InterfaceControllerTest.cpp
@@ -0,0 +1,176 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <netdutils/MockSyscalls.h>
+
+#include "InterfaceController.h"
+
+using testing::ByMove;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+using testing::_;
+
+namespace android {
+namespace net {
+namespace {
+
+using netdutils::Fd;
+using netdutils::ScopedMockSyscalls;
+using netdutils::Slice;
+using netdutils::Status;
+using netdutils::StatusOr;
+using netdutils::UniqueFd;
+using netdutils::makeSlice;
+using netdutils::status::ok;
+using netdutils::statusFromErrno;
+
+constexpr Fd kDevRandomFd(777);
+constexpr Fd kStableSecretFd(9999);
+const char kDevRandomPath[] = "/dev/random";
+const char kTestIface[] = "wlan5";
+const char kStableSecretProperty[] = "persist.netd.stable_secret";
+const char kStableSecretPath[] = "/proc/sys/net/ipv6/conf/wlan5/stable_secret";
+const char kTestIPv6Address[] = "\x20\x01\x0d\xb8\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10";
+const char kTestIPv6AddressString[] = "2001:db8:506:708:90a:b0c:d0e:f10";
+
+// getProperty() and setProperty() are forwarded to this mock
+class MockProperties {
+  public:
+    MOCK_CONST_METHOD2(get, std::string(const std::string& key, const std::string& dflt));
+    MOCK_CONST_METHOD2(set, Status(const std::string& key, const std::string& val));
+};
+
+}  // namespace
+
+class StablePrivacyTest : public testing::Test {
+  protected:
+    void expectOpenFile(const std::string& path, const Fd fd, int err) {
+        if (err == 0) {
+            EXPECT_CALL(mSyscalls, open(path, _, _)).WillOnce(Return(ByMove(UniqueFd(fd))));
+            EXPECT_CALL(mSyscalls, close(fd)).WillOnce(Return(ok));
+        } else {
+            EXPECT_CALL(mSyscalls, open(path, _, _))
+                .WillOnce(Return(ByMove(statusFromErrno(err, "open() failed"))));
+        }
+    }
+
+    void expectReadFromDevRandom(const std::string& data) {
+        expectOpenFile(kDevRandomPath, kDevRandomFd, 0);
+        EXPECT_CALL(mSyscalls, read(kDevRandomFd, _)).WillOnce(Invoke([data](Fd, const Slice buf) {
+            EXPECT_EQ(data.size(), buf.size());
+            return take(buf, copy(buf, makeSlice(data)));
+        }));
+    }
+
+    void expectGetPropertyDefault(const std::string& key) {
+        EXPECT_CALL(mProperties, get(key, _))
+            .WillOnce(Invoke([](const std::string&, const std::string& dflt) { return dflt; }));
+    }
+
+    void expectGetProperty(const std::string& key, const std::string& val) {
+        EXPECT_CALL(mProperties, get(key, _))
+            .WillOnce(Invoke([val](const std::string&, const std::string&) { return val; }));
+    }
+
+    void expectSetProperty(const std::string& key, const std::string& val, Status status) {
+        EXPECT_CALL(mProperties, set(key, val)).WillOnce(Return(status));
+    }
+
+    void expectWriteToFile(const Fd fd, const std::string& val, int err) {
+        EXPECT_CALL(mSyscalls, write(fd, _))
+            .WillOnce(Invoke([val, err](Fd, const Slice buf) -> StatusOr<size_t> {
+                EXPECT_EQ(val, toString(buf));
+                if (err) {
+                    return statusFromErrno(err, "write() failed");
+                }
+                return val.size();
+            }));
+    }
+
+    Status enableStablePrivacyAddresses(const std::string& iface) {
+        return InterfaceController::enableStablePrivacyAddresses(iface, mGet, mSet);
+    }
+
+    StrictMock<ScopedMockSyscalls> mSyscalls;
+    StrictMock<MockProperties> mProperties;
+
+    const std::function<std::string(const std::string&, const std::string&)> mGet =
+        [this](const std::string& key, const std::string& dflt) {
+            return mProperties.get(key, dflt);
+        };
+    const std::function<Status(const std::string&, const std::string&)> mSet =
+        [this](const std::string& key, const std::string& val) {
+            return mProperties.set(key, val);
+        };
+};
+
+TEST_F(StablePrivacyTest, PropertyOpenEnoent) {
+    expectOpenFile(kStableSecretPath, kStableSecretFd, ENOENT);
+    EXPECT_NE(ok, enableStablePrivacyAddresses(kTestIface));
+}
+
+TEST_F(StablePrivacyTest, PropertyOpenEaccess) {
+    expectOpenFile(kStableSecretPath, kStableSecretFd, EACCES);
+    EXPECT_NE(ok, enableStablePrivacyAddresses(kTestIface));
+}
+
+TEST_F(StablePrivacyTest, FirstBootWriteOkSetPropertyOk) {
+    expectOpenFile(kStableSecretPath, kStableSecretFd, 0);
+    expectGetPropertyDefault(kStableSecretProperty);
+    expectReadFromDevRandom(kTestIPv6Address);
+    expectWriteToFile(kStableSecretFd, kTestIPv6AddressString, 0);
+    expectSetProperty(kStableSecretProperty, kTestIPv6AddressString, ok);
+    EXPECT_EQ(ok, enableStablePrivacyAddresses(kTestIface));
+}
+
+TEST_F(StablePrivacyTest, FirstBootWriteOkSetPropertyFail) {
+    const auto kError = statusFromErrno(EINVAL, "");
+    expectOpenFile(kStableSecretPath, kStableSecretFd, 0);
+    expectGetPropertyDefault(kStableSecretProperty);
+    expectReadFromDevRandom(kTestIPv6Address);
+    expectWriteToFile(kStableSecretFd, kTestIPv6AddressString, 0);
+    expectSetProperty(kStableSecretProperty, kTestIPv6AddressString, kError);
+    EXPECT_EQ(kError, enableStablePrivacyAddresses(kTestIface));
+}
+
+TEST_F(StablePrivacyTest, FirstBootWriteFail) {
+    expectOpenFile(kStableSecretPath, kStableSecretFd, 0);
+    expectGetPropertyDefault(kStableSecretProperty);
+    expectReadFromDevRandom(kTestIPv6Address);
+    expectWriteToFile(kStableSecretFd, kTestIPv6AddressString, ENOSPC);
+    EXPECT_NE(ok, enableStablePrivacyAddresses(kTestIface));
+}
+
+TEST_F(StablePrivacyTest, ExistingPropertyWriteOk) {
+    expectOpenFile(kStableSecretPath, kStableSecretFd, 0);
+    expectGetProperty(kStableSecretProperty, kTestIPv6AddressString);
+    expectWriteToFile(kStableSecretFd, kTestIPv6AddressString, 0);
+    EXPECT_EQ(ok, enableStablePrivacyAddresses(kTestIface));
+}
+
+TEST_F(StablePrivacyTest, ExistingPropertyWriteFail) {
+    expectOpenFile(kStableSecretPath, kStableSecretFd, 0);
+    expectGetProperty(kStableSecretProperty, kTestIPv6AddressString);
+    expectWriteToFile(kStableSecretFd, kTestIPv6AddressString, EACCES);
+    EXPECT_NE(ok, enableStablePrivacyAddresses(kTestIface));
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/IptablesBaseTest.cpp b/server/IptablesBaseTest.cpp
index 3179149..b5fd9a0 100644
--- a/server/IptablesBaseTest.cpp
+++ b/server/IptablesBaseTest.cpp
@@ -30,6 +30,8 @@
 #define LOG_TAG "IptablesBaseTest"
 #include <cutils/log.h>
 
+using android::base::StringPrintf;
+
 IptablesBaseTest::IptablesBaseTest() {
     sCmds.clear();
     sRestoreCmds.clear();
@@ -88,7 +90,7 @@
         return NULL;
     }
 
-    std::string realCmd = android::base::StringPrintf("echo '%s'", sPopenContents.front().c_str());
+    std::string realCmd = StringPrintf("echo '%s'", sPopenContents.front().c_str());
     sPopenContents.pop_front();
     return popen(realCmd.c_str(), "r");
 }
@@ -110,6 +112,14 @@
     return fakeExecIptablesRestoreWithOutput(target, commands, nullptr);
 }
 
+int IptablesBaseTest::fakeExecIptablesRestoreCommand(IptablesTarget target,
+                                                     const std::string& table,
+                                                     const std::string& command,
+                                                     std::string *output) {
+    std::string fullCmd = StringPrintf("-t %s %s", table.c_str(), command.c_str());
+    return fakeExecIptablesRestoreWithOutput(target, fullCmd, output);
+}
+
 int IptablesBaseTest::expectIptablesCommand(IptablesTarget target, int pos,
                                             const std::string& cmd) {
 
diff --git a/server/IptablesBaseTest.h b/server/IptablesBaseTest.h
index b8ce1e2..a8a511f 100644
--- a/server/IptablesBaseTest.h
+++ b/server/IptablesBaseTest.h
@@ -32,6 +32,8 @@
     static int fakeExecIptablesRestore(IptablesTarget target, const std::string& commands);
     static int fakeExecIptablesRestoreWithOutput(IptablesTarget target, const std::string& commands,
                                                  std::string *output);
+    static int fakeExecIptablesRestoreCommand(IptablesTarget target, const std::string& table,
+                                              const std::string& commands, std::string *output);
     static FILE *fake_popen(const char *cmd, const char *type);
     void expectIptablesCommands(const std::vector<std::string>& expectedCmds);
     void expectIptablesCommands(const ExpectedIptablesCommands& expectedCmds);
diff --git a/server/IptablesRestoreController.h b/server/IptablesRestoreController.h
index 4f58461..6850d0d 100644
--- a/server/IptablesRestoreController.h
+++ b/server/IptablesRestoreController.h
@@ -25,17 +25,25 @@
 
 class IptablesProcess;
 
-class IptablesRestoreController {
-public:
+class IptablesRestoreInterface {
+  public:
+    virtual ~IptablesRestoreInterface() = default;
+
+    // Execute |commands| on the given |target|, and populate |output| with stdout.
+    virtual int execute(const IptablesTarget target, const std::string& commands,
+                        std::string* output) = 0;
+};
+
+class IptablesRestoreController final : public IptablesRestoreInterface {
+  public:
     // Not for general use. Use gCtls->iptablesRestoreCtrl
     // to get an instance of this class.
     IptablesRestoreController();
 
-    // Execute |commands| on the given |target|.
-    int execute(const IptablesTarget target, const std::string& commands);
+    ~IptablesRestoreController() override;
 
-    // Execute |commands| on the given |target|, and populate |output| with stdout.
-    int execute(const IptablesTarget target, const std::string& commands, std::string *output);
+    int execute(const IptablesTarget target, const std::string& commands,
+                std::string* output) override;
 
     enum IptablesProcessType {
         IPTABLES_PROCESS,
@@ -47,8 +55,6 @@
     // of the forked iptables[6]-restore process has died.
     IptablesProcessType notifyChildTermination(pid_t pid);
 
-    virtual ~IptablesRestoreController();
-
 protected:
     friend class IptablesRestoreControllerTest;
     pid_t getIpRestorePid(const IptablesProcessType type);
diff --git a/server/IptablesRestoreControllerTest.cpp b/server/IptablesRestoreControllerTest.cpp
index a5d8a7b..43041ec 100644
--- a/server/IptablesRestoreControllerTest.cpp
+++ b/server/IptablesRestoreControllerTest.cpp
@@ -16,6 +16,7 @@
 
 #include <string>
 #include <fcntl.h>
+#include <sys/file.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 
@@ -28,8 +29,11 @@
 
 #include "IptablesRestoreController.h"
 #include "NetdConstants.h"
+#include "Stopwatch.h"
 
-#define XTABLES_LOCK "@xtables"
+#define XT_LOCK_NAME "/system/etc/xtables.lock"
+#define XT_LOCK_ATTEMPTS 10
+#define XT_LOCK_POLL_INTERVAL_MS 100
 
 using android::base::Join;
 using android::base::StringPrintf;
@@ -42,6 +46,10 @@
   int mIptablesLock = -1;
   std::string mChainName;
 
+  static void SetUpTestCase() {
+      blockSigpipe();
+  }
+
   void SetUp() {
     ASSERT_EQ(0, createTestChain());
   }
@@ -110,19 +118,19 @@
   }
 
   int acquireIptablesLock() {
-    mIptablesLock = socket(AF_UNIX, SOCK_STREAM, 0);
-    if (mIptablesLock == -1) {
-      return -errno;
+    mIptablesLock = open(XT_LOCK_NAME, O_CREAT, 0600);
+    if (mIptablesLock == -1) return mIptablesLock;
+    int attempts;
+    for (attempts = 0; attempts < XT_LOCK_ATTEMPTS; attempts++) {
+      if (flock(mIptablesLock, LOCK_EX | LOCK_NB) == 0) {
+        return 0;
+      }
+      usleep(XT_LOCK_POLL_INTERVAL_MS * 1000);
     }
-    sockaddr_un sun = { AF_UNIX, XTABLES_LOCK };
-    sun.sun_path[0] = '\0';
-    size_t len = offsetof(struct sockaddr_un, sun_path) + sizeof(XTABLES_LOCK) - 1;
-    if (int ret = bind(mIptablesLock, reinterpret_cast<sockaddr *>(&sun), len) == -1) {
-      ret = -errno;
-      close(mIptablesLock);
-      return ret;
-    }
-    return 0;
+    EXPECT_LT(attempts, XT_LOCK_ATTEMPTS) <<
+        "Could not acquire iptables lock after " << XT_LOCK_ATTEMPTS << " attempts " <<
+        XT_LOCK_POLL_INTERVAL_MS << "ms apart";
+    return -1;
   }
 
   void releaseIptablesLock() {
@@ -232,3 +240,33 @@
   EXPECT_EQ(0, con.execute(IptablesTarget::V4V6, commandString, &output));
   EXPECT_EQ(expected, output);
 }
+
+TEST_F(IptablesRestoreControllerTest, TestUidRuleBenchmark) {
+    const std::vector<int> ITERATIONS = { 1, 5, 10 };
+
+    const std::string IPTABLES_RESTORE_ADD =
+            "*filter\n-I fw_powersave -m owner --uid-owner 2000000000 -j RETURN\nCOMMIT\n";
+    const std::string IPTABLES_RESTORE_DEL =
+            "*filter\n-D fw_powersave -m owner --uid-owner 2000000000 -j RETURN\nCOMMIT\n";
+
+    for (const int iterations : ITERATIONS) {
+        Stopwatch s;
+        for (int i = 0; i < iterations; i++) {
+            EXPECT_EQ(0, con.execute(V4V6, IPTABLES_RESTORE_ADD, nullptr));
+            EXPECT_EQ(0, con.execute(V4V6, IPTABLES_RESTORE_DEL, nullptr));
+        }
+        float timeTaken = s.getTimeAndReset();
+        fprintf(stderr, "    Add/del %d UID rules via restore: %.1fms (%.2fms per operation)\n",
+                iterations, timeTaken, timeTaken / 2 / iterations);
+
+        for (int i = 0; i < iterations; i++) {
+            EXPECT_EQ(0, execIptables(V4V6, "-I", "fw_powersave", "-m", "owner",
+                                      "--uid-owner", "2000000000", "-j", "RETURN", nullptr));
+            EXPECT_EQ(0, execIptables(V4V6, "-D", "fw_powersave", "-m", "owner",
+                                      "--uid-owner", "2000000000", "-j", "RETURN", nullptr));
+        }
+        timeTaken = s.getTimeAndReset();
+        fprintf(stderr, "    Add/del %d UID rules via iptables: %.1fms (%.2fms per operation)\n",
+                iterations, timeTaken, timeTaken / 2 / iterations);
+    }
+}
diff --git a/server/MDnsSdListener.cpp b/server/MDnsSdListener.cpp
index 883fe81..9551d45 100644
--- a/server/MDnsSdListener.cpp
+++ b/server/MDnsSdListener.cpp
@@ -38,6 +38,7 @@
 
 #include "MDnsSdListener.h"
 #include "ResponseCode.h"
+#include "thread_util.h"
 
 #define MDNS_SERVICE_NAME "mdnsd"
 #define MDNS_SERVICE_STATUS "init.svc.mdnsd"
@@ -524,17 +525,10 @@
     socketpair(AF_LOCAL, SOCK_STREAM, 0, mCtrlSocketPair);
     pthread_mutex_init(&mHeadMutex, NULL);
 
-    pthread_create(&mThread, NULL, MDnsSdListener::Monitor::threadStart, this);
-    pthread_detach(mThread);
-}
-
-void *MDnsSdListener::Monitor::threadStart(void *obj) {
-    Monitor *monitor = reinterpret_cast<Monitor *>(obj);
-
-    monitor->run();
-    delete monitor;
-    pthread_exit(NULL);
-    return NULL;
+    const int rval = ::android::net::threadLaunch(this);
+    if (rval != 0) {
+        ALOGW("Error spawning monitor thread: %s (%d)", strerror(-rval), -rval);
+    }
 }
 
 #define NAP_TIME 200  // 200 ms between polls
diff --git a/server/MDnsSdListener.h b/server/MDnsSdListener.h
index e9c6066..3833ad6 100644
--- a/server/MDnsSdListener.h
+++ b/server/MDnsSdListener.h
@@ -65,6 +65,7 @@
         }
     };
 
+private:
     class Monitor {
     public:
         Monitor();
@@ -73,11 +74,11 @@
         void startMonitoring(int id);
         DNSServiceRef *lookupServiceRef(int id);
         void freeServiceRef(int id);
-        static void *threadStart(void *handler);
         int startService();
         int stopService();
-    private:
         void run();
+
+    private:
         int rescan(); // returns the number of elements in the poll
         class Element {
         public:
@@ -95,7 +96,6 @@
         struct pollfd *mPollFds;
         DNSServiceRef **mPollRefs;
         int mPollSize;
-        pthread_t mThread;
         int mCtrlSocketPair[2];
         pthread_mutex_t mHeadMutex;
     };
diff --git a/server/NFLogListener.cpp b/server/NFLogListener.cpp
new file mode 100644
index 0000000..874cb16
--- /dev/null
+++ b/server/NFLogListener.cpp
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NFLogListener"
+
+#include <sstream>
+#include <vector>
+
+#include <endian.h>
+#include <linux/netfilter/nfnetlink_log.h>
+
+#include <cutils/log.h>
+#include <netdutils/Misc.h>
+#include <netdutils/Netfilter.h>
+#include <netdutils/Syscalls.h>
+
+#include "NFLogListener.h"
+
+namespace android {
+namespace net {
+
+using netdutils::Slice;
+using netdutils::Status;
+using netdutils::StatusOr;
+using netdutils::UniqueFd;
+using netdutils::Status;
+using netdutils::makeSlice;
+using netdutils::sSyscalls;
+using netdutils::findWithDefault;
+using netdutils::status::ok;
+using netdutils::extract;
+
+constexpr int kNFLogConfigMsgType = (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_CONFIG;
+constexpr int kNFLogPacketMsgType = (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET;
+constexpr int kNetlinkDoneMsgType = (NFNL_SUBSYS_NONE << 8) | NLMSG_DONE;
+constexpr size_t kPacketRange = 0;
+constexpr size_t kCopyMode = NFULNL_COPY_NONE;
+
+namespace {
+
+const NFLogListener::DispatchFn kDefaultDispatchFn = [](const nlmsghdr& nlmsg,
+                                                        const nfgenmsg& nfmsg, const Slice msg) {
+    std::stringstream ss;
+    ss << nlmsg << " " << nfmsg << " " << msg << " " << netdutils::toHex(msg, 32);
+    ALOGE("unhandled nflog message: %s", ss.str().c_str());
+};
+
+using SendFn = std::function<Status(const Slice msg)>;
+
+// Required incantation?
+Status cfgCmdPfUnbind(const SendFn& send) {
+    struct {
+        nlmsghdr nlhdr;
+        nfgenmsg nfhdr;
+        nfattr attr;
+        nfulnl_msg_config_cmd cmd;
+    } __attribute__((packed)) msg = {};
+
+    msg.nlhdr.nlmsg_len = sizeof(msg);
+    msg.nlhdr.nlmsg_type = kNFLogConfigMsgType;
+    msg.nlhdr.nlmsg_flags = NLM_F_REQUEST;
+    msg.nfhdr.nfgen_family = AF_UNSPEC;
+    msg.attr.nfa_len = sizeof(msg.attr) + sizeof(msg.cmd);
+    msg.attr.nfa_type = NFULA_CFG_CMD;
+    msg.cmd.command = NFULNL_CFG_CMD_PF_UNBIND;
+    return send(makeSlice(msg));
+}
+
+// Control delivery mode for NFLOG messages marked with nfLogGroup.
+// range controls maximum bytes to copy
+// mode must be one of: NFULNL_COPY_NONE, NFULNL_COPY_META, NFULNL_COPY_PACKET
+Status cfgMode(const SendFn& send, uint16_t nfLogGroup, uint32_t range, uint8_t mode) {
+    struct {
+        nlmsghdr nlhdr;
+        nfgenmsg nfhdr;
+        nfattr attr;
+        nfulnl_msg_config_mode mode;
+    } __attribute__((packed)) msg = {};
+
+    msg.nlhdr.nlmsg_len = sizeof(msg);
+    msg.nlhdr.nlmsg_type = kNFLogConfigMsgType;
+    msg.nlhdr.nlmsg_flags = NLM_F_REQUEST;
+    msg.nfhdr.nfgen_family = AF_UNSPEC;
+    msg.nfhdr.res_id = htobe16(nfLogGroup);
+    msg.attr.nfa_len = sizeof(msg.attr) + sizeof(msg.mode);
+    msg.attr.nfa_type = NFULA_CFG_MODE;
+    msg.mode.copy_mode = mode;
+    msg.mode.copy_range = htobe32(range);
+    return send(makeSlice(msg));
+}
+
+// Request that NFLOG messages marked with nfLogGroup are delivered to this socket
+Status cfgCmdBind(const SendFn& send, uint16_t nfLogGroup) {
+    struct {
+        nlmsghdr nlhdr;
+        nfgenmsg nfhdr;
+        nfattr attr;
+        nfulnl_msg_config_cmd cmd;
+    } __attribute__((packed)) msg = {};
+
+    msg.nlhdr.nlmsg_len = sizeof(msg);
+    msg.nlhdr.nlmsg_type = kNFLogConfigMsgType;
+    msg.nlhdr.nlmsg_flags = NLM_F_REQUEST;
+    msg.nfhdr.nfgen_family = AF_UNSPEC;
+    msg.nfhdr.res_id = htobe16(nfLogGroup);
+    msg.attr.nfa_len = sizeof(msg.attr) + sizeof(msg.cmd);
+    msg.attr.nfa_type = NFULA_CFG_CMD;
+    msg.cmd.command = NFULNL_CFG_CMD_BIND;
+    return send(makeSlice(msg));
+}
+
+// Request that NFLOG messages marked with nfLogGroup are not delivered to this socket
+Status cfgCmdUnbind(const SendFn& send, uint16_t nfLogGroup) {
+    struct {
+        nlmsghdr nlhdr;
+        nfgenmsg nfhdr;
+        nfattr attr;
+        nfulnl_msg_config_cmd cmd;
+    } __attribute__((packed)) msg = {};
+
+    msg.nlhdr.nlmsg_len = sizeof(msg);
+    msg.nlhdr.nlmsg_type = kNFLogConfigMsgType;
+    msg.nlhdr.nlmsg_flags = NLM_F_REQUEST;
+    msg.nfhdr.nfgen_family = AF_UNSPEC;
+    msg.nfhdr.res_id = htobe16(nfLogGroup);
+    msg.attr.nfa_len = sizeof(msg.attr) + sizeof(msg.cmd);
+    msg.attr.nfa_type = NFULA_CFG_CMD;
+    msg.cmd.command = NFULNL_CFG_CMD_UNBIND;
+    return send(makeSlice(msg));
+}
+
+}  // namespace
+
+NFLogListener::NFLogListener(std::shared_ptr<NetlinkListenerInterface> listener)
+    : mListener(std::move(listener)) {
+    // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function.
+    const auto rxHandler = [this](const nlmsghdr& nlmsg, const Slice msg) {
+        nfgenmsg nfmsg = {};
+        extract(msg, nfmsg);
+        std::lock_guard<std::mutex> guard(mMutex);
+        const auto& fn = findWithDefault(mDispatchMap, be16toh(nfmsg.res_id), kDefaultDispatchFn);
+        fn(nlmsg, nfmsg, drop(msg, sizeof(nfmsg)));
+    };
+    expectOk(mListener->subscribe(kNFLogPacketMsgType, rxHandler));
+
+    // Each batch of NFLOG messages is terminated with NLMSG_DONE which is useless to us
+    const auto rxDoneHandler = [](const nlmsghdr&, const Slice msg) {
+        // Ignore NLMSG_DONE  messages
+        nfgenmsg nfmsg = {};
+        extract(msg, nfmsg);
+        // TODO: why is nfmsg filled with garbage?
+    };
+    expectOk(mListener->subscribe(kNetlinkDoneMsgType, rxDoneHandler));
+}
+
+NFLogListener::~NFLogListener() {
+    expectOk(mListener->unsubscribe(kNFLogPacketMsgType));
+    expectOk(mListener->unsubscribe(kNetlinkDoneMsgType));
+    const auto sendFn = [this](const Slice msg) { return mListener->send(msg); };
+    for (auto pair : mDispatchMap) {
+        expectOk(cfgCmdUnbind(sendFn, pair.first));
+    }
+}
+
+Status NFLogListener::subscribe(uint16_t nfLogGroup, const DispatchFn& fn) {
+    const auto sendFn = [this](const Slice msg) { return mListener->send(msg); };
+    // Install fn into the dispatch map BEFORE requesting delivery of messages
+    {
+        std::lock_guard<std::mutex> guard(mMutex);
+        mDispatchMap[nfLogGroup] = fn;
+    }
+    RETURN_IF_NOT_OK(cfgCmdBind(sendFn, nfLogGroup));
+
+    // Mode must be set for every nfLogGroup
+    return cfgMode(sendFn, nfLogGroup, kPacketRange, kCopyMode);
+}
+
+Status NFLogListener::unsubscribe(uint16_t nfLogGroup) {
+    const auto sendFn = [this](const Slice msg) { return mListener->send(msg); };
+    RETURN_IF_NOT_OK(cfgCmdUnbind(sendFn, nfLogGroup));
+    // Remove from the dispatch map AFTER stopping message delivery.
+    {
+        std::lock_guard<std::mutex> guard(mMutex);
+        mDispatchMap.erase(nfLogGroup);
+    }
+    return ok;
+}
+
+StatusOr<std::unique_ptr<NFLogListener>> makeNFLogListener() {
+    const auto& sys = sSyscalls.get();
+    ASSIGN_OR_RETURN(auto event, sys.eventfd(0, EFD_CLOEXEC));
+    const auto domain = AF_NETLINK;
+    const auto flags = SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK;
+    const auto protocol = NETLINK_NETFILTER;
+    ASSIGN_OR_RETURN(auto sock, sys.socket(domain, flags, protocol));
+
+    // Timestamps are disabled by default. Request RX timestamping
+    RETURN_IF_NOT_OK(sys.setsockopt<int32_t>(sock, SOL_SOCKET, SO_TIMESTAMP, 1));
+
+    std::shared_ptr<NetlinkListenerInterface> listener =
+        std::make_unique<NetlinkListener>(std::move(event), std::move(sock));
+    const auto sendFn = [&listener](const Slice msg) { return listener->send(msg); };
+    RETURN_IF_NOT_OK(cfgCmdPfUnbind(sendFn));
+    return std::unique_ptr<NFLogListener>(new NFLogListener(std::move(listener)));
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/NFLogListener.h b/server/NFLogListener.h
new file mode 100644
index 0000000..9e5b8a4
--- /dev/null
+++ b/server/NFLogListener.h
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+#ifndef NFLOG_LISTENER_H
+#define NFLOG_LISTENER_H
+
+#include <netdutils/Netfilter.h>
+
+#include "NetlinkListener.h"
+
+namespace android {
+namespace net {
+
+class NFLogListenerInterface {
+  public:
+    using DispatchFn =
+        std::function<void(const nlmsghdr& nlmsg, const nfgenmsg& nfmsg,
+                           const netdutils::Slice msg)>;
+
+    virtual ~NFLogListenerInterface() = default;
+
+    // Similar to NetlinkListener::subscribe() but performs an additional
+    // level of deserialization and dispatch.
+    //
+    // Threadsafe.
+    // All dispatch functions invoked on a single service thread.
+    // subscribe() and join() must not be called from the stack of fn().
+    virtual netdutils::Status subscribe(uint16_t nfLogGroup, const DispatchFn& fn) = 0;
+
+    // Halt delivery of messages from a nfLogGroup previously subscribed to above.
+    //
+    // Threadsafe.
+    virtual netdutils::Status unsubscribe(uint16_t nfLogGroup) = 0;
+};
+
+// NFLogListener manages a single netlink socket with specialized
+// settings required for processing of NFLOG messages.
+//
+// NFLogListener currently assumes that it is ok to drop messages
+// generated by the kernel when under heavy load. This makes the
+// class most suitable for advisory tasks and statistics.
+class NFLogListener : public NFLogListenerInterface {
+  public:
+    using DispatchFn = NFLogListenerInterface::DispatchFn;
+
+    // Do not invoke this constructor directly outside of tests. Use
+    // makeNFLogListener() instead.
+    NFLogListener(std::shared_ptr<NetlinkListenerInterface> listener);
+
+    ~NFLogListener() override;
+
+    netdutils::Status subscribe(uint16_t nfLogGroup, const DispatchFn& fn) override;
+
+    netdutils::Status unsubscribe(uint16_t nfLogGroup) override;
+
+  private:
+    std::shared_ptr<NetlinkListenerInterface> mListener;
+    std::mutex mMutex;
+    std::map<uint16_t, DispatchFn> mDispatchMap;  // guarded by mMutex
+};
+
+// Allocate and return a new NFLogListener. On success, the returned
+// listener is ready to use with a running service thread.
+netdutils::StatusOr<std::unique_ptr<NFLogListener>> makeNFLogListener();
+
+}  // namespace net
+}  // namespace android
+
+#endif /* NFLOG_LISTENER_H */
diff --git a/server/NFLogListenerTest.cpp b/server/NFLogListenerTest.cpp
new file mode 100644
index 0000000..d397b2a
--- /dev/null
+++ b/server/NFLogListenerTest.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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 <atomic>
+#include <deque>
+#include <iostream>
+#include <mutex>
+
+#include <endian.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <linux/netfilter/nfnetlink_log.h>
+
+#include <netdutils/MockSyscalls.h>
+#include "NFLogListener.h"
+
+using ::testing::ByMove;
+using ::testing::Exactly;
+using ::testing::Invoke;
+using ::testing::Mock;
+using ::testing::SaveArg;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::StrictMock;
+using ::testing::_;
+
+namespace android {
+namespace net {
+
+using netdutils::Fd;
+using netdutils::Slice;
+using netdutils::StatusOr;
+using netdutils::UniqueFd;
+using netdutils::forEachNetlinkAttribute;
+using netdutils::makeSlice;
+using netdutils::status::ok;
+
+constexpr int kNFLogPacketMsgType = (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET;
+constexpr int kNetlinkMsgDoneType = (NFNL_SUBSYS_NONE << 8) | NLMSG_DONE;
+
+class MockNetlinkListener : public NetlinkListenerInterface {
+  public:
+    ~MockNetlinkListener() override = default;
+
+    MOCK_METHOD1(send, netdutils::Status(const netdutils::Slice msg));
+    MOCK_METHOD2(subscribe, netdutils::Status(uint16_t type, const DispatchFn& fn));
+    MOCK_METHOD1(unsubscribe, netdutils::Status(uint16_t type));
+    MOCK_METHOD0(join, void());
+};
+
+class NFLogListenerTest : public testing::Test {
+  protected:
+    NFLogListenerTest() {
+        EXPECT_CALL(*mNLListener, subscribe(kNFLogPacketMsgType, _))
+            .WillOnce(DoAll(SaveArg<1>(&mPacketFn), Return(ok)));
+        EXPECT_CALL(*mNLListener, subscribe(kNetlinkMsgDoneType, _))
+            .WillOnce(DoAll(SaveArg<1>(&mDoneFn), Return(ok)));
+        mListener.reset(new NFLogListener(mNLListener));
+    }
+
+    ~NFLogListenerTest() {
+        EXPECT_CALL(*mNLListener, unsubscribe(kNFLogPacketMsgType)).WillOnce(Return(ok));
+        EXPECT_CALL(*mNLListener, unsubscribe(kNetlinkMsgDoneType)).WillOnce(Return(ok));
+    }
+
+    static StatusOr<size_t> sendOk(const Slice buf) { return buf.size(); }
+
+    void subscribe(uint16_t type, NFLogListenerInterface::DispatchFn fn) {
+        // Two sends for cfgCmdBind() & cfgMode(), one send at destruction time for cfgCmdUnbind()
+        EXPECT_CALL(*mNLListener, send(_)).Times(Exactly(3)).WillRepeatedly(Invoke(sendOk));
+        mListener->subscribe(type, fn);
+    }
+
+    void sendEmptyMsg(uint16_t type) {
+        struct {
+            nlmsghdr nlmsg;
+            nfgenmsg nfmsg;
+        } msg = {};
+
+        msg.nlmsg.nlmsg_type = kNFLogPacketMsgType;
+        msg.nlmsg.nlmsg_len = sizeof(msg);
+        msg.nfmsg.res_id = htobe16(type);
+        mPacketFn(msg.nlmsg, drop(makeSlice(msg), sizeof(msg.nlmsg)));
+    }
+
+    NetlinkListenerInterface::DispatchFn mPacketFn;
+    NetlinkListenerInterface::DispatchFn mDoneFn;
+    std::shared_ptr<StrictMock<MockNetlinkListener>> mNLListener{
+        new StrictMock<MockNetlinkListener>()};
+    std::unique_ptr<NFLogListener> mListener;
+};
+
+TEST_F(NFLogListenerTest, subscribe) {
+    constexpr uint16_t kType = 38;
+    const auto dispatchFn = [](const nlmsghdr&, const nfgenmsg&, const netdutils::Slice) {};
+    subscribe(kType, dispatchFn);
+}
+
+TEST_F(NFLogListenerTest, nlmsgDone) {
+    constexpr uint16_t kType = 38;
+    const auto dispatchFn = [](const nlmsghdr&, const nfgenmsg&, const netdutils::Slice) {};
+    subscribe(kType, dispatchFn);
+    mDoneFn({}, {});
+}
+
+TEST_F(NFLogListenerTest, dispatchOk) {
+    int invocations = 0;
+    constexpr uint16_t kType = 38;
+    const auto dispatchFn = [&invocations, kType](const nlmsghdr&, const nfgenmsg& nfmsg,
+                                                  const netdutils::Slice) {
+        EXPECT_EQ(kType, be16toh(nfmsg.res_id));
+        ++invocations;
+    };
+    subscribe(kType, dispatchFn);
+    sendEmptyMsg(kType);
+    EXPECT_EQ(1, invocations);
+}
+
+TEST_F(NFLogListenerTest, dispatchUnknownType) {
+    constexpr uint16_t kType = 38;
+    constexpr uint16_t kBadType = kType + 1;
+    const auto dispatchFn = [](const nlmsghdr&, const nfgenmsg&, const netdutils::Slice) {
+        // Expect no invocations
+        ASSERT_TRUE(false);
+    };
+    subscribe(kType, dispatchFn);
+    sendEmptyMsg(kBadType);
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/NetdConstants.cpp b/server/NetdConstants.cpp
index ff3fc2c..0a0ca5d 100644
--- a/server/NetdConstants.cpp
+++ b/server/NetdConstants.cpp
@@ -26,6 +26,7 @@
 
 #define LOG_TAG "Netd"
 
+#include <android-base/stringprintf.h>
 #include <cutils/log.h>
 #include <logwrap/logwrap.h>
 
@@ -128,14 +129,20 @@
     return execIptablesRestoreWithOutput(target, commands, nullptr);
 }
 
+int execIptablesRestoreCommand(IptablesTarget target, const std::string& table,
+                               const std::string& command, std::string *output) {
+    std::string fullCmd = android::base::StringPrintf("*%s\n%s\nCOMMIT\n", table.c_str(),
+                                                      command.c_str());
+    return execIptablesRestoreWithOutput(target, fullCmd, output);
+}
+
 /*
  * Check an interface name for plausibility. This should e.g. help against
  * directory traversal.
  */
-bool isIfaceName(const char *name) {
+bool isIfaceName(const std::string& name) {
     size_t i;
-    size_t name_len = strlen(name);
-    if ((name_len == 0) || (name_len > IFNAMSIZ)) {
+    if ((name.empty()) || (name.size() > IFNAMSIZ)) {
         return false;
     }
 
@@ -144,7 +151,7 @@
         return false;
     }
 
-    for (i = 1; i < name_len; i++) {
+    for (i = 1; i < name.size(); i++) {
         if (!isalnum(name[i]) && (name[i] != '_') && (name[i] != '-') && (name[i] != ':')) {
             return false;
         }
@@ -227,3 +234,12 @@
 
     return rawLength;
 }
+
+void blockSigpipe() {
+    sigset_t mask;
+
+    sigemptyset(&mask);
+    sigaddset(&mask, SIGPIPE);
+    if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0)
+        ALOGW("WARNING: SIGPIPE not blocked\n");
+}
diff --git a/server/NetdConstants.h b/server/NetdConstants.h
index 668b9be..54ed812 100644
--- a/server/NetdConstants.h
+++ b/server/NetdConstants.h
@@ -47,8 +47,11 @@
 int execIptablesRestore(IptablesTarget target, const std::string& commands);
 int execIptablesRestoreWithOutput(IptablesTarget target, const std::string& commands,
                                   std::string *output);
-bool isIfaceName(const char *name);
+int execIptablesRestoreCommand(IptablesTarget target, const std::string& table,
+                               const std::string& command, std::string *output);
+bool isIfaceName(const std::string& name);
 int parsePrefix(const char *prefix, uint8_t *family, void *address, int size, uint8_t *prefixlen);
+void blockSigpipe();
 
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
 
diff --git a/server/NetdNativeService.cpp b/server/NetdNativeService.cpp
index a575642..67bf9c0 100644
--- a/server/NetdNativeService.cpp
+++ b/server/NetdNativeService.cpp
@@ -46,8 +46,16 @@
 namespace {
 
 const char CONNECTIVITY_INTERNAL[] = "android.permission.CONNECTIVITY_INTERNAL";
+const char NETWORK_STACK[] = "android.permission.NETWORK_STACK";
 const char DUMP[] = "android.permission.DUMP";
 
+binder::Status toBinderStatus(const netdutils::Status s) {
+    if (isOk(s)) {
+        return binder::Status::ok();
+    }
+    return binder::Status::fromServiceSpecificError(s.code(), s.msg().c_str());
+}
+
 binder::Status checkPermission(const char *permission) {
     pid_t pid;
     uid_t uid;
@@ -60,6 +68,16 @@
     }
 }
 
+binder::Status getXfrmStatus(int xfrmCode) {
+    switch(xfrmCode) {
+        case 0:
+            return binder::Status::ok();
+        case -ENOENT:
+            return binder::Status::fromServiceSpecificError(xfrmCode);
+    }
+    return binder::Status::fromExceptionCode(xfrmCode);
+}
+
 #define ENFORCE_DEBUGGABLE() {                              \
     char value[PROPERTY_VALUE_MAX + 1];                     \
     if (property_get("ro.debuggable", value, NULL) != 1     \
@@ -309,5 +327,116 @@
             : binder::Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT);
 }
 
+binder::Status NetdNativeService::ipSecAllocateSpi(
+        int32_t transformId,
+        int32_t direction,
+        const std::string& localAddress,
+        const std::string& remoteAddress,
+        int32_t inSpi,
+        int32_t* outSpi) {
+    // Necessary locking done in IpSecService and kernel
+    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+    ALOGD("ipSecAllocateSpi()");
+    return getXfrmStatus(gCtls->xfrmCtrl.ipSecAllocateSpi(
+                    transformId,
+                    direction,
+                    localAddress,
+                    remoteAddress,
+                    inSpi,
+                    outSpi));
+}
+
+binder::Status NetdNativeService::ipSecAddSecurityAssociation(
+        int32_t transformId,
+        int32_t mode,
+        int32_t direction,
+        const std::string& localAddress,
+        const std::string& remoteAddress,
+        int64_t underlyingNetworkHandle,
+        int32_t spi,
+        const std::string& authAlgo, const std::vector<uint8_t>& authKey, int32_t authTruncBits,
+        const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits,
+        int32_t encapType,
+        int32_t encapLocalPort,
+        int32_t encapRemotePort,
+        int32_t* allocatedSpi) {
+    // Necessary locking done in IpSecService and kernel
+    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+    ALOGD("ipSecAddSecurityAssociation()");
+    return getXfrmStatus(gCtls->xfrmCtrl.ipSecAddSecurityAssociation(
+              transformId, mode, direction, localAddress, remoteAddress,
+              underlyingNetworkHandle,
+              spi,
+              authAlgo, authKey, authTruncBits,
+              cryptAlgo, cryptKey, cryptTruncBits,
+              encapType, encapLocalPort, encapRemotePort,
+              allocatedSpi));
+}
+
+binder::Status NetdNativeService::ipSecDeleteSecurityAssociation(
+        int32_t transformId,
+        int32_t direction,
+        const std::string& localAddress,
+        const std::string& remoteAddress,
+        int32_t spi) {
+    // Necessary locking done in IpSecService and kernel
+    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+    ALOGD("ipSecDeleteSecurityAssociation()");
+    return getXfrmStatus(gCtls->xfrmCtrl.ipSecDeleteSecurityAssociation(
+                    transformId,
+                    direction,
+                    localAddress,
+                    remoteAddress,
+                    spi));
+}
+
+binder::Status NetdNativeService::ipSecApplyTransportModeTransform(
+        const android::base::unique_fd& socket,
+        int32_t transformId,
+        int32_t direction,
+        const std::string& localAddress,
+        const std::string& remoteAddress,
+        int32_t spi) {
+    // Necessary locking done in IpSecService and kernel
+    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+    ALOGD("ipSecApplyTransportModeTransform()");
+    return getXfrmStatus(gCtls->xfrmCtrl.ipSecApplyTransportModeTransform(
+                    socket,
+                    transformId,
+                    direction,
+                    localAddress,
+                    remoteAddress,
+                    spi));
+}
+
+binder::Status NetdNativeService::ipSecRemoveTransportModeTransform(
+            const android::base::unique_fd& socket) {
+    // Necessary locking done in IpSecService and kernel
+    ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
+    ALOGD("ipSecRemoveTransportModeTransform()");
+    return getXfrmStatus(gCtls->xfrmCtrl.ipSecRemoveTransportModeTransform(
+                    socket));
+}
+
+binder::Status NetdNativeService::setIPv6AddrGenMode(const std::string& ifName,
+                                                     int32_t mode) {
+    ENFORCE_PERMISSION(NETWORK_STACK);
+    return toBinderStatus(InterfaceController::setIPv6AddrGenMode(ifName, mode));
+}
+
+binder::Status NetdNativeService::wakeupAddInterface(const std::string& ifName,
+                                                     const std::string& prefix, int32_t mark,
+                                                     int32_t mask) {
+    ENFORCE_PERMISSION(NETWORK_STACK);
+    return toBinderStatus(gCtls->wakeupCtrl.addInterface(ifName, prefix, mark, mask));
+}
+
+binder::Status NetdNativeService::wakeupDelInterface(const std::string& ifName,
+                                                     const std::string& prefix, int32_t mark,
+                                                     int32_t mask) {
+    ENFORCE_PERMISSION(NETWORK_STACK);
+    return toBinderStatus(gCtls->wakeupCtrl.delInterface(ifName, prefix, mark, mask));
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/server/NetdNativeService.h b/server/NetdNativeService.h
index dd01dbc..407c563 100644
--- a/server/NetdNativeService.h
+++ b/server/NetdNativeService.h
@@ -48,6 +48,15 @@
             std::vector<std::string>* domains, std::vector<int32_t>* params,
             std::vector<int32_t>* stats) override;
 
+    binder::Status setIPv6AddrGenMode(const std::string& ifName, int32_t mode) override;
+
+    // NFLOG-related commands
+    binder::Status wakeupAddInterface(const std::string& ifName, const std::string& prefix,
+                                      int32_t mark, int32_t mask) override;
+
+    binder::Status wakeupDelInterface(const std::string& ifName, const std::string& prefix,
+                                      int32_t mark, int32_t mask) override;
+
     // Tethering-related commands.
     binder::Status tetherApplyDnsInterfaces(bool *ret) override;
 
@@ -63,6 +72,51 @@
     // Metrics reporting level set / get (internal use only).
     binder::Status getMetricsReportingLevel(int *reportingLevel) override;
     binder::Status setMetricsReportingLevel(const int reportingLevel) override;
+
+    binder::Status ipSecAllocateSpi(
+            int32_t transformId,
+            int32_t direction,
+            const std::string& localAddress,
+            const std::string& remoteAddress,
+            int32_t inSpi,
+            int32_t* outSpi);
+
+    binder::Status ipSecAddSecurityAssociation(
+            int32_t transformId,
+            int32_t mode,
+            int32_t direction,
+            const std::string& localAddress,
+            const std::string& remoteAddress,
+            int64_t underlyingNetworkHandle,
+            int32_t spi,
+            const std::string& authAlgo,
+            const std::vector<uint8_t>& authKey,
+            int32_t authTruncBits,
+            const std::string& cryptAlgo,
+            const std::vector<uint8_t>& cryptKey,
+            int32_t cryptTruncBits,
+            int32_t encapType,
+            int32_t encapLocalPort,
+            int32_t encapRemotePort,
+            int32_t* allocatedSpi);
+
+    binder::Status ipSecDeleteSecurityAssociation(
+            int32_t transformId,
+            int32_t direction,
+            const std::string& localAddress,
+            const std::string& remoteAddress,
+            int32_t spi);
+
+    binder::Status ipSecApplyTransportModeTransform(
+            const android::base::unique_fd& socket,
+            int32_t transformId,
+            int32_t direction,
+            const std::string& localAddress,
+            const std::string& remoteAddress,
+            int32_t spi);
+
+    binder::Status ipSecRemoveTransportModeTransform(
+            const android::base::unique_fd& socket);
 };
 
 }  // namespace net
diff --git a/server/NetlinkCommands.cpp b/server/NetlinkCommands.cpp
index 1380196..9f1bae9 100644
--- a/server/NetlinkCommands.cpp
+++ b/server/NetlinkCommands.cpp
@@ -30,8 +30,8 @@
 namespace android {
 namespace net {
 
-int openRtNetlinkSocket() {
-    int sock = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE);
+int openNetlinkSocket(int protocol) {
+    int sock = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, protocol);
     if (sock == -1) {
         return -errno;
     }
@@ -85,7 +85,7 @@
         nlmsg.nlmsg_len += iov[i].iov_len;
     }
 
-    int sock = openRtNetlinkSocket();
+    int sock = openNetlinkSocket(NETLINK_ROUTE);
     if (sock < 0) {
         return sock;
     }
@@ -153,7 +153,7 @@
         return -EINVAL;
     }
 
-    int writeSock = openRtNetlinkSocket();
+    int writeSock = openNetlinkSocket(NETLINK_ROUTE);
     if (writeSock < 0) {
         return writeSock;
     }
diff --git a/server/NetlinkCommands.h b/server/NetlinkCommands.h
index 2db7c16..7c0d4a8 100644
--- a/server/NetlinkCommands.h
+++ b/server/NetlinkCommands.h
@@ -38,7 +38,7 @@
 typedef std::function<bool(nlmsghdr *)> NetlinkDumpFilter;
 
 // Opens an RTNetlink socket and connects it to the kernel.
-WARN_UNUSED_RESULT int openRtNetlinkSocket();
+WARN_UNUSED_RESULT int openNetlinkSocket(int protocol);
 
 // Receives a netlink ACK. Returns 0 if the command succeeded or negative errno if the command
 // failed or receiving the ACK failed.
diff --git a/server/NetlinkListener.cpp b/server/NetlinkListener.cpp
new file mode 100644
index 0000000..82ed6d8
--- /dev/null
+++ b/server/NetlinkListener.cpp
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NetlinkListener"
+
+#include <sstream>
+#include <vector>
+
+#include <linux/netfilter/nfnetlink.h>
+
+#include <cutils/log.h>
+#include <netdutils/Misc.h>
+#include <netdutils/Syscalls.h>
+
+#include "NetlinkListener.h"
+
+namespace android {
+namespace net {
+
+using netdutils::Fd;
+using netdutils::Slice;
+using netdutils::Status;
+using netdutils::UniqueFd;
+using netdutils::findWithDefault;
+using netdutils::forEachNetlinkMessage;
+using netdutils::makeSlice;
+using netdutils::sSyscalls;
+using netdutils::status::ok;
+using netdutils::statusFromErrno;
+
+namespace {
+
+constexpr int kNetlinkMsgErrorType = (NFNL_SUBSYS_NONE << 8) | NLMSG_ERROR;
+
+constexpr sockaddr_nl kKernelAddr = {
+    .nl_family = AF_NETLINK, .nl_pad = 0, .nl_pid = 0, .nl_groups = 0,
+};
+
+const NetlinkListener::DispatchFn kDefaultDispatchFn = [](const nlmsghdr& nlmsg, const Slice) {
+    std::stringstream ss;
+    ss << nlmsg;
+    ALOGE("unhandled netlink message: %s", ss.str().c_str());
+};
+
+}  // namespace
+
+NetlinkListener::NetlinkListener(UniqueFd event, UniqueFd sock)
+    : mEvent(std::move(event)), mSock(std::move(sock)), mWorker([this]() { run(); }) {
+    const auto rxErrorHandler = [](const nlmsghdr& nlmsg, const Slice msg) {
+        std::stringstream ss;
+        ss << nlmsg << " " << msg << " " << netdutils::toHex(msg, 32);
+        ALOGE("unhandled netlink message: %s", ss.str().c_str());
+    };
+    expectOk(NetlinkListener::subscribe(kNetlinkMsgErrorType, rxErrorHandler));
+}
+
+NetlinkListener::~NetlinkListener() {
+    const auto& sys = sSyscalls.get();
+    const uint64_t data = 1;
+    // eventfd should never enter an error state unexpectedly
+    expectOk(sys.write(mEvent, makeSlice(data)).status());
+    mWorker.join();
+}
+
+Status NetlinkListener::send(const Slice msg) {
+    const auto& sys = sSyscalls.get();
+    ASSIGN_OR_RETURN(auto sent, sys.sendto(mSock, msg, 0, kKernelAddr));
+    if (sent != msg.size()) {
+        return statusFromErrno(EMSGSIZE, "unexpect message size");
+    }
+    return ok;
+}
+
+Status NetlinkListener::subscribe(uint16_t type, const DispatchFn& fn) {
+    std::lock_guard<std::mutex> guard(mMutex);
+    mDispatchMap[type] = fn;
+    return ok;
+}
+
+Status NetlinkListener::unsubscribe(uint16_t type) {
+    std::lock_guard<std::mutex> guard(mMutex);
+    mDispatchMap.erase(type);
+    return ok;
+}
+
+Status NetlinkListener::run() {
+    std::vector<char> rxbuf(4096);
+
+    const auto rxHandler = [this](const nlmsghdr& nlmsg, const Slice& buf) {
+        std::lock_guard<std::mutex> guard(mMutex);
+        const auto& fn = findWithDefault(mDispatchMap, nlmsg.nlmsg_type, kDefaultDispatchFn);
+        fn(nlmsg, buf);
+    };
+
+    const auto& sys = sSyscalls.get();
+    const std::array<Fd, 2> fds{{{mEvent}, {mSock}}};
+    const int events = POLLIN | POLLRDHUP | POLLERR | POLLHUP;
+    const double timeout = 3600;
+    while (true) {
+        ASSIGN_OR_RETURN(auto revents, sys.ppoll(fds, events, timeout));
+        // After mEvent becomes readable, we should stop servicing mSock and return
+        if (revents[0] & POLLIN) {
+            break;
+        }
+        if (revents[1] & POLLIN) {
+            auto rx = sys.recvfrom(mSock, makeSlice(rxbuf), 0);
+            if (rx.status().code() == ENOBUFS) {
+                // Ignore ENOBUFS - the socket is still usable
+                // TODO: Users other than NFLOG may need to know about this
+                continue;
+            }
+            forEachNetlinkMessage(rx.value(), rxHandler);
+        }
+    }
+    return ok;
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/NetlinkListener.h b/server/NetlinkListener.h
new file mode 100644
index 0000000..6e53c34
--- /dev/null
+++ b/server/NetlinkListener.h
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+#ifndef NETLINK_LISTENER_H
+#define NETLINK_LISTENER_H
+
+#include <functional>
+#include <map>
+#include <mutex>
+#include <thread>
+
+#include <netdutils/Netlink.h>
+#include <netdutils/Slice.h>
+#include <netdutils/StatusOr.h>
+#include <netdutils/UniqueFd.h>
+
+namespace android {
+namespace net {
+
+class NetlinkListenerInterface {
+  public:
+    using DispatchFn = std::function<void(const nlmsghdr& nlmsg, const netdutils::Slice msg)>;
+
+    virtual ~NetlinkListenerInterface() = default;
+
+    // Send message to the kernel using the underlying netlink socket
+    virtual netdutils::Status send(const netdutils::Slice msg) = 0;
+
+    // Deliver future messages with nlmsghdr.nlmsg_type == type to fn.
+    //
+    // Threadsafe.
+    // All dispatch functions invoked on a single service thread.
+    // subscribe() and join() must not be called from the stack of fn().
+    virtual netdutils::Status subscribe(uint16_t type, const DispatchFn& fn) = 0;
+
+    // Halt delivery of future messages with nlmsghdr.nlmsg_type == type.
+    // Threadsafe.
+    virtual netdutils::Status unsubscribe(uint16_t type) = 0;
+};
+
+// NetlinkListener manages a netlink socket and associated blocking
+// service thread.
+//
+// This class is written in a generic way to allow multiple different
+// netlink subsystems to share this common infrastructure. If multiple
+// subsystems share the same message delivery requirements (drops ok,
+// no drops) they may share a single listener by calling subscribe()
+// with multiple types.
+//
+// This class is suitable for moderate performance message
+// processing. In particular it avoids extra copies of received
+// message data and allows client code to control which message
+// attributes are processed.
+//
+// Note that NetlinkListener is capable of processing multiple batched
+// netlink messages in a single system call. This is useful to
+// netfilter extensions that allow batching of events like NFLOG.
+class NetlinkListener : public NetlinkListenerInterface {
+  public:
+    NetlinkListener(netdutils::UniqueFd event, netdutils::UniqueFd sock);
+
+    ~NetlinkListener() override;
+
+    netdutils::Status send(const netdutils::Slice msg) override;
+
+    netdutils::Status subscribe(uint16_t type, const DispatchFn& fn) override;
+
+    netdutils::Status unsubscribe(uint16_t type) override;
+
+  private:
+    netdutils::Status run();
+
+    netdutils::UniqueFd mEvent;
+    netdutils::UniqueFd mSock;
+    std::mutex mMutex;
+    std::map<uint16_t, DispatchFn> mDispatchMap;  // guarded by mMutex
+    std::thread mWorker;
+};
+
+}  // namespace net
+}  // namespace android
+
+#endif /* NETLINK_LISTENER_H */
diff --git a/server/NetlinkManager.cpp b/server/NetlinkManager.cpp
index 35349d2..698de9f 100644
--- a/server/NetlinkManager.cpp
+++ b/server/NetlinkManager.cpp
@@ -51,6 +51,7 @@
 
 const int NetlinkManager::NFLOG_QUOTA_GROUP = 1;
 const int NetlinkManager::NETFILTER_STRICT_GROUP = 2;
+const int NetlinkManager::NFLOG_WAKEUP_GROUP = 3;
 
 NetlinkManager *NetlinkManager::sInstance = NULL;
 
@@ -76,7 +77,8 @@
 
     memset(&nladdr, 0, sizeof(nladdr));
     nladdr.nl_family = AF_NETLINK;
-    nladdr.nl_pid = getpid();
+    // Kernel will assign a unique nl_pid if set to zero.
+    nladdr.nl_pid = 0;
     nladdr.nl_groups = groups;
 
     if ((*sock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, netlinkFamily)) < 0) {
diff --git a/server/NetlinkManager.h b/server/NetlinkManager.h
index d5d18b2..ea94e7d 100644
--- a/server/NetlinkManager.h
+++ b/server/NetlinkManager.h
@@ -55,6 +55,8 @@
     static const int NFLOG_QUOTA_GROUP;
     /* Group used by StrictController rules */
     static const int NETFILTER_STRICT_GROUP;
+    /* Group used by WakeupController rules */
+    static const int NFLOG_WAKEUP_GROUP;
 
 private:
     NetlinkManager();
diff --git a/server/NetworkController.cpp b/server/NetworkController.cpp
index 8e4c69d..b90976b 100644
--- a/server/NetworkController.cpp
+++ b/server/NetworkController.cpp
@@ -47,6 +47,8 @@
 #include "RouteController.h"
 #include "VirtualNetwork.h"
 
+#define DBG 0
+
 namespace android {
 namespace net {
 
@@ -287,12 +289,17 @@
     Fwmark fwmark;
     fwmark.netId = nc.app_netid;
     fwmark.explicitlySelected = explicitlySelected;
-    fwmark.protectedFromVpn = canProtect(uid);
+    fwmark.protectedFromVpn = explicitlySelected && canProtect(uid);
     fwmark.permission = getPermissionForUser(uid);
     nc.app_mark = fwmark.intValue;
 
     nc.dns_mark = getNetworkForDns(&(nc.dns_netid), uid);
 
+    if (DBG) {
+        ALOGD("app_netid:0x%x app_mark:0x%x dns_netid:0x%x dns_mark:0x%x uid:%d",
+              nc.app_netid, nc.app_mark, nc.dns_netid, nc.dns_mark, uid);
+    }
+
     if (netcontext) {
         *netcontext = nc;
     }
diff --git a/server/NetworkController.h b/server/NetworkController.h
index 0c6cd63..d5451ee 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -118,7 +118,6 @@
     std::map<unsigned, Network*> mNetworks;  // Map keys are NetIds.
     std::map<uid_t, Permission> mUsers;
     std::set<uid_t> mProtectableUsers;
-
 };
 
 }  // namespace net
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index aba1458..aeed3e9 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -29,22 +29,27 @@
 
 #include "DummyNetwork.h"
 #include "Fwmark.h"
+#include "NetdConstants.h"
 #include "NetlinkCommands.h"
 #include "UidRanges.h"
 
 #include "android-base/file.h"
+#include <android-base/stringprintf.h>
 #define LOG_TAG "Netd"
 #include "log/log.h"
 #include "logwrap/logwrap.h"
 #include "netutils/ifc.h"
 #include "resolv_netid.h"
 
+using android::base::StringPrintf;
 using android::base::WriteStringToFile;
 using android::net::UidRange;
 
 namespace android {
 namespace net {
 
+auto RouteController::iptablesRestoreCommandFunction = execIptablesRestoreCommand;
+
 // BEGIN CONSTANTS --------------------------------------------------------------------------------
 
 const uint32_t RULE_PRIORITY_VPN_OVERRIDE_SYSTEM = 10000;
@@ -76,11 +81,7 @@
 const char* const ROUTE_TABLE_NAME_LOCAL = "local";
 const char* const ROUTE_TABLE_NAME_MAIN  = "main";
 
-// TODO: These values aren't defined by the Linux kernel, because legacy UID routing (as used in N
-// and below) was not upstreamed. Now that the UID routing code is upstream, we should remove these
-// and rely on the kernel header values.
-const uint16_t FRA_UID_START = 18;
-const uint16_t FRA_UID_END   = 19;
+const char* const RouteController::LOCAL_MANGLE_INPUT = "routectrl_mangle_INPUT";
 
 // These values are upstream, but not yet in our headers.
 // TODO: delete these definitions when updating the headers.
@@ -115,8 +116,6 @@
 rtattr FRATTR_TABLE     = { U16_RTA_LENGTH(sizeof(uint32_t)),           FRA_TABLE };
 rtattr FRATTR_FWMARK    = { U16_RTA_LENGTH(sizeof(uint32_t)),           FRA_FWMARK };
 rtattr FRATTR_FWMASK    = { U16_RTA_LENGTH(sizeof(uint32_t)),           FRA_FWMASK };
-rtattr FRATTR_UID_START = { U16_RTA_LENGTH(sizeof(uid_t)),              FRA_UID_START };
-rtattr FRATTR_UID_END   = { U16_RTA_LENGTH(sizeof(uid_t)),              FRA_UID_END };
 rtattr FRATTR_UID_RANGE = { U16_RTA_LENGTH(sizeof(fib_rule_uid_range)), FRA_UID_RANGE };
 
 rtattr RTATTR_TABLE     = { U16_RTA_LENGTH(sizeof(uint32_t)),           RTA_TABLE };
@@ -276,18 +275,6 @@
         { &fwmark,           mask ? sizeof(fwmark) : 0 },
         { &FRATTR_FWMASK,    mask ? sizeof(FRATTR_FWMASK) : 0 },
         { &mask,             mask ? sizeof(mask) : 0 },
-        // Rules that contain both legacy and new UID routing attributes will work on old kernels,
-        // which will simply ignore the FRA_UID_RANGE attribute since it is larger than their
-        // FRA_MAX. They will also work on kernels that are not too new:
-        // - FRA_UID_START clashes with FRA_PAD in 4.7, but that shouldn't be a problem because
-        //   FRA_PAD has no validation.
-        // - FRA_UID_END clashes with FRA_L3MDEV in 4.8 and above, and will cause an error because
-        //   FRA_L3MDEV has a maximum length of 1.
-        // TODO: delete the legacy UID routing code before running it on 4.8 or above.
-        { &FRATTR_UID_START, isUidRule ? sizeof(FRATTR_UID_START) : 0 },
-        { &uidStart,         isUidRule ? sizeof(uidStart) : 0 },
-        { &FRATTR_UID_END,   isUidRule ? sizeof(FRATTR_UID_END) : 0 },
-        { &uidEnd,           isUidRule ? sizeof(uidEnd) : 0 },
         { &FRATTR_UID_RANGE, isUidRule ? sizeof(FRATTR_UID_RANGE) : 0 },
         { &uidRange,         isUidRule ? sizeof(uidRange) : 0 },
         { &fraIifName,       iif != IIF_NONE ? sizeof(fraIifName) : 0 },
@@ -437,11 +424,10 @@
     fwmark.protectedFromVpn = true;
     fwmark.permission = permission;
 
-    char markString[UINT32_HEX_STRLEN];
-    snprintf(markString, sizeof(markString), "0x%x", fwmark.intValue);
-
-    if (execIptables(V4V6, "-t", "mangle", add ? "-A" : "-D", "INPUT", "-i", interface, "-j",
-                     "MARK", "--set-mark", markString, NULL)) {
+    std::string cmd = StringPrintf("%s %s -i %s -j MARK --set-mark 0x%x",
+                                   add ? "-A" : "-D", RouteController::LOCAL_MANGLE_INPUT,
+                                   interface, fwmark.intValue);
+    if (RouteController::iptablesRestoreCommandFunction(V4V6, "mangle", cmd, nullptr) != 0) {
         ALOGE("failed to change iptables rule that sets incoming packet mark");
         return -EREMOTEIO;
     }
diff --git a/server/RouteController.h b/server/RouteController.h
index 48239d7..d13ea58 100644
--- a/server/RouteController.h
+++ b/server/RouteController.h
@@ -40,6 +40,8 @@
 
     static const int ROUTE_TABLE_OFFSET_FROM_INDEX = 1000;
 
+    static const char* const LOCAL_MANGLE_INPUT;
+
     static int Init(unsigned localNetId) WARN_UNUSED_RESULT;
 
     static int addInterfaceToLocalNetwork(unsigned netId, const char* interface) WARN_UNUSED_RESULT;
@@ -91,6 +93,10 @@
                                             Permission permission) WARN_UNUSED_RESULT;
     static int removeVirtualNetworkFallthrough(unsigned vpnNetId, const char* physicalInterface,
                                                Permission permission) WARN_UNUSED_RESULT;
+
+    // For testing.
+    static int (*iptablesRestoreCommandFunction)(IptablesTarget, const std::string&,
+                                                 const std::string&, std::string *);
 };
 
 // Public because they are called by by RouteControllerTest.cpp.
@@ -100,6 +106,8 @@
                   const char* nexthop) WARN_UNUSED_RESULT;
 int flushRoutes(uint32_t table) WARN_UNUSED_RESULT;
 uint32_t getRulePriority(const nlmsghdr *nlh);
+WARN_UNUSED_RESULT int modifyIncomingPacketMark(unsigned netId, const char* interface,
+                                                Permission permission, bool add);
 
 }  // namespace net
 }  // namespace android
diff --git a/server/RouteControllerTest.cpp b/server/RouteControllerTest.cpp
index 6bcb231..090b383 100644
--- a/server/RouteControllerTest.cpp
+++ b/server/RouteControllerTest.cpp
@@ -18,13 +18,21 @@
 
 #include <gtest/gtest.h>
 
+#include "IptablesBaseTest.h"
 #include "NetlinkCommands.h"
 #include "RouteController.h"
 
 namespace android {
 namespace net {
 
-TEST(RouteControllerTest, TestGetRulePriority) {
+class RouteControllerTest : public IptablesBaseTest {
+public:
+    RouteControllerTest() {
+        RouteController::iptablesRestoreCommandFunction = fakeExecIptablesRestoreCommand;
+    }
+};
+
+TEST_F(RouteControllerTest, TestGetRulePriority) {
     // Expect a rule dump for these two families to contain at least the following priorities.
     for (int family : {AF_INET, AF_INET6 }) {
         std::set<uint32_t> expectedPriorities = {
@@ -53,7 +61,7 @@
     }
 }
 
-TEST(RouteControllerTest, TestRouteFlush) {
+TEST_F(RouteControllerTest, TestRouteFlush) {
     // Pick a table number that's not used by the system.
     const uint32_t table1 = 500;
     const uint32_t table2 = 600;
@@ -76,5 +84,16 @@
               modifyIpRoute(RTM_DELROUTE, table2, "lo", "192.0.2.4/32", NULL));
 }
 
+TEST_F(RouteControllerTest, TestModifyIncomingPacketMark) {
+    static constexpr int TEST_NETID = 30;
+    EXPECT_EQ(0, modifyIncomingPacketMark(TEST_NETID, "netdtest0", PERMISSION_NONE, true));
+    expectIptablesRestoreCommands({
+        "-t mangle -A routectrl_mangle_INPUT -i netdtest0 -j MARK --set-mark 0x3001e" });
+
+    EXPECT_EQ(0, modifyIncomingPacketMark(TEST_NETID, "netdtest0", PERMISSION_NONE, false));
+    expectIptablesRestoreCommands({
+          "-t mangle -D routectrl_mangle_INPUT -i netdtest0 -j MARK --set-mark 0x3001e" });
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/server/TetherController.cpp b/server/TetherController.cpp
index 7bf4a92..1785ec7 100644
--- a/server/TetherController.cpp
+++ b/server/TetherController.cpp
@@ -36,6 +36,7 @@
 #include "NetdConstants.h"
 #include "Permission.h"
 #include "InterfaceController.h"
+#include "NetworkController.h"
 #include "TetherController.h"
 
 namespace {
@@ -86,6 +87,9 @@
 
 }  // namespace
 
+namespace android {
+namespace net {
+
 TetherController::TetherController() {
     mDnsNetId = 0;
     mDaemonFd = -1;
@@ -129,7 +133,7 @@
     return mForwardingRequests.size();
 }
 
-#define TETHER_START_CONST_ARG        8
+#define TETHER_START_CONST_ARG        10
 
 int TetherController::startTethering(int num_addrs, char **dhcp_ranges) {
     if (mDaemonPid != 0) {
@@ -169,6 +173,14 @@
             close(pipefd[0]);
         }
 
+        Fwmark fwmark;
+        fwmark.netId = NetworkController::LOCAL_NET_ID;
+        fwmark.explicitlySelected = true;
+        fwmark.protectedFromVpn = true;
+        fwmark.permission = PERMISSION_SYSTEM;
+        char markStr[UINT32_HEX_STRLEN];
+        snprintf(markStr, sizeof(markStr), "0x%x", fwmark.intValue);
+
         int num_processed_args = TETHER_START_CONST_ARG + (num_addrs/2) + 1;
         char **args = (char **)malloc(sizeof(char *) * num_processed_args);
         args[num_processed_args - 1] = NULL;
@@ -180,7 +192,9 @@
         // TODO: pipe through metered status from ConnService
         args[5] = (char *)"--dhcp-option-force=43,ANDROID_METERED";
         args[6] = (char *)"--pid-file";
-        args[7] = (char *)"";
+        args[7] = (char *)"--listen-mark";
+        args[8] = (char *)markStr;
+        args[9] = (char *)"";
 
         int nextArg = TETHER_START_CONST_ARG;
         for (int addrIndex = 0; addrIndex < num_addrs; addrIndex += 2) {
@@ -356,3 +370,6 @@
 const std::list<std::string> &TetherController::getTetheredInterfaceList() const {
     return mInterfaces;
 }
+
+}  // namespace net
+}  // namespace android
diff --git a/server/TetherController.h b/server/TetherController.h
index 3769890..13da05c 100644
--- a/server/TetherController.h
+++ b/server/TetherController.h
@@ -23,6 +23,8 @@
 #include <set>
 #include <string>
 
+namespace android {
+namespace net {
 
 class TetherController {
 private:
@@ -60,4 +62,7 @@
     bool setIpFwdEnabled();
 };
 
+}  // namespace net
+}  // namespace android
+
 #endif
diff --git a/server/WakeupController.cpp b/server/WakeupController.cpp
new file mode 100644
index 0000000..b3eae7e
--- /dev/null
+++ b/server/WakeupController.cpp
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "WakeupController"
+
+#include <endian.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nfnetlink_log.h>
+#include <iostream>
+
+#include <android-base/stringprintf.h>
+#include <cutils/log.h>
+#include <netdutils/Netfilter.h>
+#include <netdutils/Netlink.h>
+
+#include "IptablesRestoreController.h"
+#include "NetlinkManager.h"
+#include "WakeupController.h"
+
+namespace android {
+namespace net {
+
+using base::StringPrintf;
+using netdutils::Slice;
+using netdutils::Status;
+
+const char WakeupController::LOCAL_MANGLE_INPUT[] = "wakeupctrl_mangle_INPUT";
+
+WakeupController::~WakeupController() {
+    expectOk(mListener->unsubscribe(NetlinkManager::NFLOG_WAKEUP_GROUP));
+}
+
+netdutils::Status WakeupController::init(NFLogListenerInterface* listener) {
+    mListener = listener;
+    const auto msgHandler = [this](const nlmsghdr&, const nfgenmsg&, const Slice msg) {
+        std::string prefix;
+        uid_t uid = -1;
+        gid_t gid = -1;
+        uint64_t timestampNs = -1;
+        const auto attrHandler = [&prefix, &uid, &gid, &timestampNs](const nlattr attr,
+                                                                     const Slice payload) {
+            switch (attr.nla_type) {
+                case NFULA_TIMESTAMP: {
+                    timespec timespec = {};
+                    extract(payload, timespec);
+                    constexpr uint64_t kNsPerS = 1000000000ULL;
+                    timestampNs = be32toh(timespec.tv_nsec) + (be32toh(timespec.tv_sec) * kNsPerS);
+                    break;
+                }
+                case NFULA_PREFIX:
+                    // Strip trailing '\0'
+                    prefix = toString(take(payload, payload.size() - 1));
+                    break;
+                case NFULA_UID:
+                    extract(payload, uid);
+                    uid = be32toh(uid);
+                    break;
+                case NFULA_GID:
+                    extract(payload, gid);
+                    gid = be32toh(gid);
+                    break;
+                default:
+                    break;
+            }
+        };
+        forEachNetlinkAttribute(msg, attrHandler);
+        mReport(prefix, uid, gid, timestampNs);
+    };
+    return mListener->subscribe(NetlinkManager::NFLOG_WAKEUP_GROUP, msgHandler);
+}
+
+Status WakeupController::addInterface(const std::string& ifName, const std::string& prefix,
+                                    uint32_t mark, uint32_t mask) {
+    return execIptables("-A", ifName, prefix, mark, mask);
+}
+
+Status WakeupController::delInterface(const std::string& ifName, const std::string& prefix,
+                                    uint32_t mark, uint32_t mask) {
+    return execIptables("-D", ifName, prefix, mark, mask);
+}
+
+Status WakeupController::execIptables(const std::string& action, const std::string& ifName,
+                                      const std::string& prefix, uint32_t mark, uint32_t mask) {
+    // NFLOG messages to batch before releasing to userspace
+    constexpr int kBatch = 8;
+    // Max log message rate in packets/second
+    constexpr int kRateLimit = 10;
+    const char kFormat[] =
+        "*mangle\n%s %s -i %s -j NFLOG --nflog-prefix %s --nflog-group %d --nflog-threshold %d"
+        " -m mark --mark 0x%08x/0x%08x -m limit --limit %d/s\nCOMMIT\n";
+    const auto cmd = StringPrintf(
+        kFormat, action.c_str(), WakeupController::LOCAL_MANGLE_INPUT, ifName.c_str(),
+        prefix.c_str(), NetlinkManager::NFLOG_WAKEUP_GROUP, kBatch, mark, mask, kRateLimit);
+
+    std::string out;
+    auto rv = mIptables->execute(V4V6, cmd, &out);
+    if (rv != 0) {
+        auto s = Status(rv, "Failed to execute iptables cmd: " + cmd + ", out: " + out);
+        ALOGE("%s", toString(s).c_str());
+        return s;
+    }
+    return netdutils::status::ok;
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/WakeupController.h b/server/WakeupController.h
new file mode 100644
index 0000000..e147f3e
--- /dev/null
+++ b/server/WakeupController.h
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#ifndef WAKEUP_CONTROLLER_H
+#define WAKEUP_CONTROLLER_H
+
+#include <functional>
+
+#include <netdutils/Status.h>
+
+#include "IptablesRestoreController.h"
+#include "NFLogListener.h"
+
+namespace android {
+namespace net {
+
+class WakeupController {
+  public:
+    using ReportFn = std::function<void(const std::string&, uid_t, gid_t, uint64_t)>;
+
+    // iptables chain where wakeup packets are matched
+    static const char LOCAL_MANGLE_INPUT[];
+
+    WakeupController(ReportFn report, IptablesRestoreInterface* iptables)
+        : mReport(report), mIptables(iptables) {}
+
+    ~WakeupController();
+
+    // Subscribe this controller to a NFLOG events arriving at |listener|.
+    netdutils::Status init(NFLogListenerInterface* listener);
+
+    // Install iptables rules to match packets arriving on |ifName|
+    // which match |mark|/|mask|. Metadata from matching packets will
+    // be delivered along with the arbitrary string |prefix| to
+    // INetdEventListener::onWakeupEvent.
+    netdutils::Status addInterface(const std::string& ifName, const std::string& prefix,
+                                   uint32_t mark, uint32_t mask);
+
+    // Remove iptables rules previously installed by addInterface().
+    // |ifName|, |prefix|, |mark| and |mask| must match precisely.
+    netdutils::Status delInterface(const std::string& ifName, const std::string& prefix,
+                                   uint32_t mark, uint32_t mask);
+
+  private:
+    netdutils::Status execIptables(const std::string& action, const std::string& ifName,
+                                   const std::string& prefix, uint32_t mark, uint32_t mask);
+
+    ReportFn const mReport;
+    IptablesRestoreInterface* const mIptables;
+    NFLogListenerInterface* mListener;
+};
+
+}  // namespace net
+}  // namespace android
+
+#endif /* WAKEUP_CONTROLLER_H */
diff --git a/server/WakeupControllerTest.cpp b/server/WakeupControllerTest.cpp
new file mode 100644
index 0000000..05e899c
--- /dev/null
+++ b/server/WakeupControllerTest.cpp
@@ -0,0 +1,216 @@
+/*
+ * 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 <linux/netfilter/nfnetlink_log.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "NetlinkManager.h"
+#include "WakeupController.h"
+
+using ::testing::StrictMock;
+using ::testing::Test;
+using ::testing::DoAll;
+using ::testing::SaveArg;
+using ::testing::Return;
+using ::testing::_;
+
+namespace android {
+namespace net {
+
+using netdutils::status::ok;
+
+class MockNetdEventListener {
+  public:
+    MOCK_METHOD4(onWakeupEvent,
+                 void(const std::string& prefix, uid_t uid, gid_t gid, uint64_t timestampNs));
+};
+
+class MockIptablesRestore : public IptablesRestoreInterface {
+  public:
+    ~MockIptablesRestore() override = default;
+    MOCK_METHOD3(execute, int(const IptablesTarget target, const std::string& commands,
+                              std::string* output));
+};
+
+class MockNFLogListener : public NFLogListenerInterface {
+  public:
+    ~MockNFLogListener() override = default;
+    MOCK_METHOD2(subscribe, netdutils::Status(uint16_t nfLogGroup, const DispatchFn& fn));
+    MOCK_METHOD1(unsubscribe, netdutils::Status(uint16_t nfLogGroup));
+};
+
+class WakeupControllerTest : public Test {
+  protected:
+    WakeupControllerTest() {
+        EXPECT_CALL(mListener, subscribe(NetlinkManager::NFLOG_WAKEUP_GROUP, _))
+            .WillOnce(DoAll(SaveArg<1>(&mMessageHandler), Return(ok)));
+        EXPECT_CALL(mListener, unsubscribe(NetlinkManager::NFLOG_WAKEUP_GROUP)).WillOnce(Return(ok));
+        mController.init(&mListener);
+    }
+
+    StrictMock<MockNetdEventListener> mEventListener;
+    StrictMock<MockIptablesRestore> mIptables;
+    StrictMock<MockNFLogListener> mListener;
+    WakeupController mController{
+        [this](const std::string& prefix, uid_t uid, gid_t gid, uint64_t timestampNs) {
+            mEventListener.onWakeupEvent(prefix, uid, gid, timestampNs);
+        },
+        &mIptables};
+    NFLogListenerInterface::DispatchFn mMessageHandler;
+};
+
+TEST_F(WakeupControllerTest, msgHandler) {
+    const char kPrefix[] = "test:prefix";
+    const uid_t kUid = 8734;
+    const gid_t kGid = 2222;
+    const uint64_t kNsPerS = 1000000000ULL;
+    const uint64_t kTsNs = 9999 + (34 * kNsPerS);
+
+    struct Msg {
+        nlmsghdr nlmsg;
+        nfgenmsg nfmsg;
+        nlattr uidAttr;
+        uid_t uid;
+        nlattr gidAttr;
+        gid_t gid;
+        nlattr tsAttr;
+        timespec ts;
+        nlattr prefixAttr;
+        char prefix[sizeof(kPrefix)];
+    } msg = {};
+
+    msg.uidAttr.nla_type = NFULA_UID;
+    msg.uidAttr.nla_len = sizeof(msg.uidAttr) + sizeof(msg.uid);
+    msg.uid = htobe32(kUid);
+
+    msg.gidAttr.nla_type = NFULA_GID;
+    msg.gidAttr.nla_len = sizeof(msg.gidAttr) + sizeof(msg.gid);
+    msg.gid = htobe32(kGid);
+
+    msg.tsAttr.nla_type = NFULA_TIMESTAMP;
+    msg.tsAttr.nla_len = sizeof(msg.tsAttr) + sizeof(msg.ts);
+    msg.ts.tv_sec = htobe32(kTsNs / kNsPerS);
+    msg.ts.tv_nsec = htobe32(kTsNs % kNsPerS);
+
+    msg.prefixAttr.nla_type = NFULA_PREFIX;
+    msg.prefixAttr.nla_len = sizeof(msg.prefixAttr) + sizeof(msg.prefix);
+    memcpy(msg.prefix, kPrefix, sizeof(kPrefix));
+
+    auto payload = drop(netdutils::makeSlice(msg), offsetof(Msg, uidAttr));
+    EXPECT_CALL(mEventListener, onWakeupEvent(kPrefix, kUid, kGid, kTsNs));
+    mMessageHandler(msg.nlmsg, msg.nfmsg, payload);
+}
+
+TEST_F(WakeupControllerTest, badAttr) {
+    const char kPrefix[] = "test:prefix";
+    const uid_t kUid = 8734;
+    const gid_t kGid = 2222;
+    const uint64_t kNsPerS = 1000000000ULL;
+    const uint64_t kTsNs = 9999 + (34 * kNsPerS);
+
+    struct Msg {
+        nlmsghdr nlmsg;
+        nfgenmsg nfmsg;
+        nlattr uidAttr;
+        uid_t uid;
+        nlattr invalid0;
+        nlattr invalid1;
+        nlattr gidAttr;
+        gid_t gid;
+        nlattr tsAttr;
+        timespec ts;
+        nlattr prefixAttr;
+        char prefix[sizeof(kPrefix)];
+    } msg = {};
+
+    msg.uidAttr.nla_type = 999;
+    msg.uidAttr.nla_len = sizeof(msg.uidAttr) + sizeof(msg.uid);
+    msg.uid = htobe32(kUid);
+
+    msg.invalid0.nla_type = 0;
+    msg.invalid0.nla_len = 0;
+    msg.invalid1.nla_type = 0;
+    msg.invalid1.nla_len = 1;
+
+    msg.gidAttr.nla_type = NFULA_GID;
+    msg.gidAttr.nla_len = sizeof(msg.gidAttr) + sizeof(msg.gid);
+    msg.gid = htobe32(kGid);
+
+    msg.tsAttr.nla_type = NFULA_TIMESTAMP;
+    msg.tsAttr.nla_len = sizeof(msg.tsAttr) - 2;
+    msg.ts.tv_sec = htobe32(kTsNs / kNsPerS);
+    msg.ts.tv_nsec = htobe32(kTsNs % kNsPerS);
+
+    msg.prefixAttr.nla_type = NFULA_UID;
+    msg.prefixAttr.nla_len = sizeof(msg.prefixAttr) + sizeof(msg.prefix);
+    memcpy(msg.prefix, kPrefix, sizeof(kPrefix));
+
+    auto payload = drop(netdutils::makeSlice(msg), offsetof(Msg, uidAttr));
+    EXPECT_CALL(mEventListener, onWakeupEvent("", 1952805748, kGid, 0));
+    mMessageHandler(msg.nlmsg, msg.nfmsg, payload);
+}
+
+TEST_F(WakeupControllerTest, unterminatedString) {
+    char ones[20] = {};
+    memset(ones, 1, sizeof(ones));
+
+    struct Msg {
+        nlmsghdr nlmsg;
+        nfgenmsg nfmsg;
+        nlattr prefixAttr;
+        char prefix[sizeof(ones)];
+    } msg = {};
+
+    msg.prefixAttr.nla_type = NFULA_PREFIX;
+    msg.prefixAttr.nla_len = sizeof(msg.prefixAttr) + sizeof(msg.prefix);
+    memcpy(msg.prefix, ones, sizeof(ones));
+
+    const auto expected = std::string(ones, sizeof(ones) - 1);
+    auto payload = drop(netdutils::makeSlice(msg), offsetof(Msg, prefixAttr));
+    EXPECT_CALL(mEventListener, onWakeupEvent(expected, -1, -1, -1));
+    mMessageHandler(msg.nlmsg, msg.nfmsg, payload);
+}
+
+TEST_F(WakeupControllerTest, addInterface) {
+    const char kPrefix[] = "test:prefix";
+    const char kIfName[] = "wlan8";
+    const uint32_t kMark = 0x12345678;
+    const uint32_t kMask = 0x0F0F0F0F;
+    const char kExpected[] =
+        "*mangle\n-A wakeupctrl_mangle_INPUT -i test:prefix"
+        " -j NFLOG --nflog-prefix wlan8 --nflog-group 3 --nflog-threshold 8"
+        " -m mark --mark 0x12345678/0x0f0f0f0f -m limit --limit 10/s\nCOMMIT\n";
+    EXPECT_CALL(mIptables, execute(V4V6, kExpected, _)).WillOnce(Return(0));
+    mController.addInterface(kPrefix, kIfName, kMark, kMask);
+}
+
+TEST_F(WakeupControllerTest, delInterface) {
+    const char kPrefix[] = "test:prefix";
+    const char kIfName[] = "wlan8";
+    const uint32_t kMark = 0x12345678;
+    const uint32_t kMask = 0xF0F0F0F0;
+    const char kExpected[] =
+        "*mangle\n-D wakeupctrl_mangle_INPUT -i test:prefix"
+        " -j NFLOG --nflog-prefix wlan8 --nflog-group 3 --nflog-threshold 8"
+        " -m mark --mark 0x12345678/0xf0f0f0f0 -m limit --limit 10/s\nCOMMIT\n";
+    EXPECT_CALL(mIptables, execute(V4V6, kExpected, _)).WillOnce(Return(0));
+    mController.delInterface(kPrefix, kIfName, kMark, kMask);
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/server/XfrmController.cpp b/server/XfrmController.cpp
new file mode 100644
index 0000000..344b268
--- /dev/null
+++ b/server/XfrmController.cpp
@@ -0,0 +1,823 @@
+/*
+ *
+ * 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 <string>
+#include <vector>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <linux/in.h>
+#include <linux/netlink.h>
+#include <linux/xfrm.h>
+
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "android-base/unique_fd.h"
+#define LOG_TAG "XfrmController"
+#include "NetdConstants.h"
+#include "NetlinkCommands.h"
+#include "ResponseCode.h"
+#include "XfrmController.h"
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <logwrap/logwrap.h>
+
+#define VDBG 1 // STOPSHIP if true
+
+namespace android {
+namespace net {
+
+namespace {
+
+constexpr uint32_t ALGO_MASK_AUTH_ALL = ~0;
+constexpr uint32_t ALGO_MASK_CRYPT_ALL = ~0;
+
+constexpr uint8_t REPLAY_WINDOW_SIZE = 4;
+
+constexpr uint32_t RAND_SPI_MIN = 1;
+constexpr uint32_t RAND_SPI_MAX = 0xFFFFFFFE;
+
+constexpr uint32_t INVALID_SPI = 0;
+
+#define XFRM_MSG_TRANS(x)                                                                          \
+    case x:                                                                                        \
+        return #x;
+
+const char* xfrmMsgTypeToString(uint16_t msg) {
+    switch (msg) {
+        XFRM_MSG_TRANS(XFRM_MSG_NEWSA)
+        XFRM_MSG_TRANS(XFRM_MSG_DELSA)
+        XFRM_MSG_TRANS(XFRM_MSG_GETSA)
+        XFRM_MSG_TRANS(XFRM_MSG_NEWPOLICY)
+        XFRM_MSG_TRANS(XFRM_MSG_DELPOLICY)
+        XFRM_MSG_TRANS(XFRM_MSG_GETPOLICY)
+        XFRM_MSG_TRANS(XFRM_MSG_ALLOCSPI)
+        XFRM_MSG_TRANS(XFRM_MSG_ACQUIRE)
+        XFRM_MSG_TRANS(XFRM_MSG_EXPIRE)
+        XFRM_MSG_TRANS(XFRM_MSG_UPDPOLICY)
+        XFRM_MSG_TRANS(XFRM_MSG_UPDSA)
+        XFRM_MSG_TRANS(XFRM_MSG_POLEXPIRE)
+        XFRM_MSG_TRANS(XFRM_MSG_FLUSHSA)
+        XFRM_MSG_TRANS(XFRM_MSG_FLUSHPOLICY)
+        XFRM_MSG_TRANS(XFRM_MSG_NEWAE)
+        XFRM_MSG_TRANS(XFRM_MSG_GETAE)
+        XFRM_MSG_TRANS(XFRM_MSG_REPORT)
+        XFRM_MSG_TRANS(XFRM_MSG_MIGRATE)
+        XFRM_MSG_TRANS(XFRM_MSG_NEWSADINFO)
+        XFRM_MSG_TRANS(XFRM_MSG_GETSADINFO)
+        XFRM_MSG_TRANS(XFRM_MSG_GETSPDINFO)
+        XFRM_MSG_TRANS(XFRM_MSG_NEWSPDINFO)
+        XFRM_MSG_TRANS(XFRM_MSG_MAPPING)
+        default:
+            return "XFRM_MSG UNKNOWN";
+    }
+}
+
+// actually const but cannot be declared as such for reasons
+uint8_t kPadBytesArray[] = {0, 0, 0};
+void* kPadBytes = static_cast<void*>(kPadBytesArray);
+
+#if VDBG
+#define LOG_HEX(__desc16__, __buf__, __len__)                                                      \
+    do {                                                                                           \
+        logHex(__desc16__, __buf__, __len__);                                                      \
+    } while (0)
+#define LOG_IOV(__iov__, __iov_len__)                                                              \
+    do {                                                                                           \
+        logIov(__iov__, __iov_len__);                                                              \
+    } while (0)
+
+void logHex(const char* desc16, const char* buf, size_t len) {
+    char* printBuf = new char[len * 2 + 1 + 26]; // len->ascii, +newline, +prefix strlen
+    int offset = 0;
+    if (desc16) {
+        sprintf(printBuf, "{%-16s}", desc16);
+        offset += 18; // prefix string length
+    }
+    sprintf(printBuf + offset, "[%4.4u]: ", (len > 9999) ? 9999 : (unsigned)len);
+    offset += 8;
+
+    for (uint32_t j = 0; j < (uint32_t)len; j++) {
+        sprintf(&printBuf[j * 2 + offset], "%0.2x", buf[j]);
+    }
+    ALOGD("%s", printBuf);
+    delete[] printBuf;
+}
+
+void logIov(const iovec* iov, size_t iovLen) {
+    for (uint32_t i = 0; i < (uint32_t)iovLen; i++) {
+        const iovec* row = &iov[i];
+        logHex(0, reinterpret_cast<char*>(row->iov_base), row->iov_len);
+    }
+}
+
+#else
+#define LOG_HEX(__desc16__, __buf__, __len__)
+#define LOG_IOV(__iov__, __iov_len__)
+#endif
+
+class XfrmSocketImpl : public XfrmSocket {
+private:
+    static constexpr int NLMSG_DEFAULTSIZE = 8192;
+
+    union NetlinkResponse {
+        nlmsghdr hdr;
+        struct _err_ {
+            nlmsghdr hdr;
+            nlmsgerr err;
+        } err;
+
+        struct _buf_ {
+            nlmsghdr hdr;
+            char buf[NLMSG_DEFAULTSIZE];
+        } buf;
+    };
+
+public:
+    virtual bool open() {
+        mSock = openNetlinkSocket(NETLINK_XFRM);
+        if (mSock <= 0) {
+            ALOGW("Could not get a new socket, line=%d", __LINE__);
+            return false;
+        }
+
+        return true;
+    }
+
+    static int validateResponse(NetlinkResponse response, size_t len) {
+        if (len < sizeof(nlmsghdr)) {
+            ALOGW("Invalid response message received over netlink");
+            return -EBADMSG;
+        }
+
+        switch (response.hdr.nlmsg_type) {
+            case NLMSG_NOOP:
+            case NLMSG_DONE:
+                return 0;
+            case NLMSG_OVERRUN:
+                ALOGD("Netlink request overran kernel buffer");
+                return -EBADMSG;
+            case NLMSG_ERROR:
+                if (len < sizeof(NetlinkResponse::_err_)) {
+                    ALOGD("Netlink message received malformed error response");
+                    return -EBADMSG;
+                }
+                return response.err.err.error; // Netlink errors are negative errno.
+            case XFRM_MSG_NEWSA:
+                break;
+        }
+
+        if (response.hdr.nlmsg_type < XFRM_MSG_BASE /*== NLMSG_MIN_TYPE*/ ||
+            response.hdr.nlmsg_type > XFRM_MSG_MAX) {
+            ALOGD("Netlink message responded with an out-of-range message ID");
+            return -EBADMSG;
+        }
+
+        // TODO Add more message validation here
+        return 0;
+    }
+
+    virtual int sendMessage(uint16_t nlMsgType, uint16_t nlMsgFlags, uint16_t nlMsgSeqNum,
+                            iovec* iov, int iovLen) const {
+        nlmsghdr nlMsg = {
+            .nlmsg_type = nlMsgType, .nlmsg_flags = nlMsgFlags, .nlmsg_seq = nlMsgSeqNum,
+        };
+
+        iov[0].iov_base = &nlMsg;
+        iov[0].iov_len = NLMSG_HDRLEN;
+        for (int i = 0; i < iovLen; ++i) {
+            nlMsg.nlmsg_len += iov[i].iov_len;
+        }
+
+        ALOGD("Sending Netlink XFRM Message: %s", xfrmMsgTypeToString(nlMsgType));
+        if (VDBG)
+            LOG_IOV(iov, iovLen);
+
+        int ret;
+
+        if (writev(mSock, iov, iovLen) < 0) {
+            ALOGE("netlink socket writev failed (%s)", strerror(errno));
+            return -errno;
+        }
+
+        NetlinkResponse* response = new NetlinkResponse{};
+
+        if ((ret = recv(mSock, response, sizeof(*response), 0)) < 0) {
+            ALOGE("netlink response contains error (%s)", strerror(errno));
+            delete response;
+            return -errno;
+        }
+
+        LOG_HEX("netlink msg resp", reinterpret_cast<char*>(response), ret);
+
+        ret = validateResponse(*response, ret);
+        delete response;
+        if (ret < 0)
+            ALOGE("netlink response contains error (%s)", strerror(-ret));
+        return ret;
+    }
+};
+
+int convertToXfrmAddr(const std::string& strAddr, xfrm_address_t* xfrmAddr) {
+    if (strAddr.length() == 0) {
+        memset(xfrmAddr, 0, sizeof(*xfrmAddr));
+        return AF_UNSPEC;
+    }
+
+    if (inet_pton(AF_INET6, strAddr.c_str(), reinterpret_cast<void*>(xfrmAddr))) {
+        return AF_INET6;
+    } else if (inet_pton(AF_INET, strAddr.c_str(), reinterpret_cast<void*>(xfrmAddr))) {
+        return AF_INET;
+    } else {
+        return -EAFNOSUPPORT;
+    }
+}
+
+void fillXfrmNlaHdr(nlattr* hdr, uint16_t type, uint16_t len) {
+    hdr->nla_type = type;
+    hdr->nla_len = len;
+}
+
+void fillXfrmCurLifetimeDefaults(xfrm_lifetime_cur* cur) {
+    memset(reinterpret_cast<char*>(cur), 0, sizeof(*cur));
+}
+void fillXfrmLifetimeDefaults(xfrm_lifetime_cfg* cfg) {
+    cfg->soft_byte_limit = XFRM_INF;
+    cfg->hard_byte_limit = XFRM_INF;
+    cfg->soft_packet_limit = XFRM_INF;
+    cfg->hard_packet_limit = XFRM_INF;
+}
+
+/*
+ * Allocate SPIs within an (inclusive) range of min-max.
+ * returns 0 (INVALID_SPI) once the entire range has been parsed.
+ */
+class RandomSpi {
+public:
+    RandomSpi(int min, int max) : mMin(min) {
+        time_t t;
+        srand((unsigned int)time(&t));
+        // TODO: more random random
+        mNext = rand();
+        mSize = max - min + 1;
+        mCount = mSize;
+    }
+
+    uint32_t next() {
+        if (!mCount)
+            return 0;
+        mCount--;
+        return (mNext++ % mSize) + mMin;
+    }
+
+private:
+    uint32_t mNext;
+    uint32_t mSize;
+    uint32_t mMin;
+    uint32_t mCount;
+};
+
+} // namespace
+
+//
+// Begin XfrmController Impl
+//
+//
+XfrmController::XfrmController(void) {}
+
+int XfrmController::ipSecAllocateSpi(int32_t transformId, int32_t direction,
+                                     const std::string& localAddress,
+                                     const std::string& remoteAddress, int32_t inSpi,
+                                     int32_t* outSpi) {
+    ALOGD("XfrmController:%s, line=%d", __FUNCTION__, __LINE__);
+    ALOGD("transformId=%d", transformId);
+    ALOGD("direction=%d", direction);
+    ALOGD("localAddress=%s", localAddress.c_str());
+    ALOGD("remoteAddress=%s", remoteAddress.c_str());
+    ALOGD("inSpi=%0.8x", inSpi);
+
+    XfrmSaInfo saInfo{};
+    int ret;
+
+    if ((ret = fillXfrmSaId(direction, localAddress, remoteAddress, INVALID_SPI, &saInfo)) < 0) {
+        return ret;
+    }
+
+    XfrmSocketImpl sock;
+    if (!sock.open()) {
+        ALOGD("Sock open failed for XFRM, line=%d", __LINE__);
+        return -1; // TODO: return right error; for whatever reason the sock
+                   // failed to open
+    }
+
+    int minSpi = RAND_SPI_MIN, maxSpi = RAND_SPI_MAX;
+
+    if (inSpi)
+        minSpi = maxSpi = inSpi;
+    ret = allocateSpi(saInfo, minSpi, maxSpi, reinterpret_cast<uint32_t*>(outSpi), sock);
+    if (ret < 0) {
+        ALOGD("Failed to Allocate an SPI, line=%d", __LINE__);
+        *outSpi = INVALID_SPI;
+    }
+
+    return ret;
+}
+
+int XfrmController::ipSecAddSecurityAssociation(
+    int32_t transformId, int32_t mode, int32_t direction, const std::string& localAddress,
+    const std::string& remoteAddress, int64_t underlyingNetworkHandle, int32_t spi,
+    const std::string& authAlgo, const std::vector<uint8_t>& authKey, int32_t authTruncBits,
+    const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits,
+    int32_t encapType, int32_t encapLocalPort, int32_t encapRemotePort, int32_t* allocatedSpi) {
+    android::RWLock::AutoWLock lock(mLock);
+
+    ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
+    ALOGD("transformId=%d", transformId);
+    ALOGD("mode=%d", mode);
+    ALOGD("direction=%d", direction);
+    ALOGD("localAddress=%s", localAddress.c_str());
+    ALOGD("remoteAddress=%s", remoteAddress.c_str());
+    ALOGD("underlyingNetworkHandle=%" PRIx64, underlyingNetworkHandle);
+    ALOGD("spi=%0.8x", spi);
+    ALOGD("authAlgo=%s", authAlgo.c_str());
+    ALOGD("authTruncBits=%d", authTruncBits);
+    ALOGD("cryptAlgo=%s", cryptAlgo.c_str());
+    ALOGD("cryptTruncBits=%d,", cryptTruncBits);
+    ALOGD("encapType=%d", encapType);
+    ALOGD("encapLocalPort=%d", encapLocalPort);
+    ALOGD("encapRemotePort=%d", encapRemotePort);
+
+    XfrmSaInfo saInfo{};
+    int ret;
+
+    if ((ret = fillXfrmSaId(direction, localAddress, remoteAddress, spi, &saInfo)) < 0) {
+        return ret;
+    }
+
+    saInfo.transformId = transformId;
+
+    // STOPSHIP : range check the key lengths to prevent puncturing and overflow
+    saInfo.auth = XfrmAlgo{
+        .name = authAlgo, .key = authKey, .truncLenBits = static_cast<uint16_t>(authTruncBits)};
+
+    saInfo.crypt = XfrmAlgo{
+        .name = cryptAlgo, .key = cryptKey, .truncLenBits = static_cast<uint16_t>(cryptTruncBits)};
+
+    saInfo.direction = static_cast<XfrmDirection>(direction);
+
+    switch (static_cast<XfrmMode>(mode)) {
+        case XfrmMode::TRANSPORT:
+        case XfrmMode::TUNNEL:
+            saInfo.mode = static_cast<XfrmMode>(mode);
+            break;
+        default:
+            return -EINVAL;
+    }
+
+    XfrmSocketImpl sock;
+    if (!sock.open()) {
+        ALOGD("Sock open failed for XFRM, line=%d", __LINE__);
+        return -1; // TODO: return right error; for whatever reason the sock
+                   // failed to open
+    }
+
+    switch (static_cast<XfrmEncapType>(encapType)) {
+        case XfrmEncapType::ESPINUDP:
+        case XfrmEncapType::ESPINUDP_NON_IKE:
+            if (saInfo.addrFamily != AF_INET) {
+                return -EAFNOSUPPORT;
+            }
+            switch (saInfo.direction) {
+                case XfrmDirection::IN:
+                    saInfo.encap.srcPort = encapRemotePort;
+                    saInfo.encap.dstPort = encapLocalPort;
+                    break;
+                case XfrmDirection::OUT:
+                    saInfo.encap.srcPort = encapLocalPort;
+                    saInfo.encap.dstPort = encapRemotePort;
+                    break;
+                default:
+                    return -EINVAL;
+            }
+        // fall through
+        case XfrmEncapType::NONE:
+            saInfo.encap.type = static_cast<XfrmEncapType>(encapType);
+            break;
+        default:
+            return -EINVAL;
+    }
+
+    ret = createTransportModeSecurityAssociation(saInfo, sock);
+    if (ret < 0) {
+        ALOGD("Failed creating a Security Association, line=%d", __LINE__);
+        return ret; // something went wrong creating the SA
+    }
+
+    *allocatedSpi = spi;
+    return 0;
+}
+
+int XfrmController::ipSecDeleteSecurityAssociation(int32_t transformId, int32_t direction,
+                                                   const std::string& localAddress,
+                                                   const std::string& remoteAddress, int32_t spi) {
+    ALOGD("XfrmController:%s, line=%d", __FUNCTION__, __LINE__);
+    ALOGD("transformId=%d", transformId);
+    ALOGD("direction=%d", direction);
+    ALOGD("localAddress=%s", localAddress.c_str());
+    ALOGD("remoteAddress=%s", remoteAddress.c_str());
+    ALOGD("spi=%0.8x", spi);
+
+    XfrmSaId saId;
+    int ret;
+
+    if ((ret = fillXfrmSaId(direction, localAddress, remoteAddress, spi, &saId)) < 0) {
+        return ret;
+    }
+
+    XfrmSocketImpl sock;
+    if (!sock.open()) {
+        ALOGD("Sock open failed for XFRM, line=%d", __LINE__);
+        return -1; // TODO: return right error; for whatever reason the sock
+                   // failed to open
+    }
+
+    ret = deleteSecurityAssociation(saId, sock);
+    if (ret < 0) {
+        ALOGD("Failed to delete Security Association, line=%d", __LINE__);
+        return ret; // something went wrong deleting the SA
+    }
+
+    return ret;
+}
+
+int XfrmController::fillXfrmSaId(int32_t direction, const std::string& localAddress,
+                                 const std::string& remoteAddress, int32_t spi, XfrmSaId* xfrmId) {
+    xfrm_address_t localXfrmAddr{}, remoteXfrmAddr{};
+
+    int addrFamilyLocal, addrFamilyRemote;
+    addrFamilyRemote = convertToXfrmAddr(remoteAddress, &remoteXfrmAddr);
+    addrFamilyLocal = convertToXfrmAddr(localAddress, &localXfrmAddr);
+    if (addrFamilyRemote < 0 || addrFamilyLocal < 0) {
+        return -EINVAL;
+    }
+
+    if (addrFamilyRemote == AF_UNSPEC ||
+        (addrFamilyLocal != AF_UNSPEC && addrFamilyLocal != addrFamilyRemote)) {
+        ALOGD("Invalid or Mismatched Address Families, %d != %d, line=%d", addrFamilyLocal,
+              addrFamilyRemote, __LINE__);
+        return -EINVAL;
+    }
+
+    xfrmId->addrFamily = addrFamilyRemote;
+
+    xfrmId->spi = htonl(spi);
+
+    switch (static_cast<XfrmDirection>(direction)) {
+        case XfrmDirection::IN:
+            xfrmId->dstAddr = localXfrmAddr;
+            xfrmId->srcAddr = remoteXfrmAddr;
+            break;
+
+        case XfrmDirection::OUT:
+            xfrmId->dstAddr = remoteXfrmAddr;
+            xfrmId->srcAddr = localXfrmAddr;
+            break;
+
+        default:
+            ALOGD("Invalid XFRM direction, line=%d", __LINE__);
+            // Invalid direction for Transport mode transform: time to bail
+            return -EINVAL;
+    }
+    return 0;
+}
+
+int XfrmController::ipSecApplyTransportModeTransform(const android::base::unique_fd& socket,
+                                                     int32_t transformId, int32_t direction,
+                                                     const std::string& localAddress,
+                                                     const std::string& remoteAddress,
+                                                     int32_t spi) {
+    ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
+    ALOGD("transformId=%d", transformId);
+    ALOGD("direction=%d", direction);
+    ALOGD("localAddress=%s", localAddress.c_str());
+    ALOGD("remoteAddress=%s", remoteAddress.c_str());
+    ALOGD("spi=%0.8x", spi);
+
+    struct sockaddr_storage saddr;
+
+    socklen_t len = sizeof(saddr);
+    int err;
+    int userSocket = socket.get();
+
+    if ((err = getsockname(userSocket, reinterpret_cast<struct sockaddr*>(&saddr), &len)) < 0) {
+        ALOGE("Failed to get socket info in %s", __FUNCTION__);
+        return -err;
+    }
+
+    XfrmSaInfo saInfo{};
+    saInfo.transformId = transformId;
+    saInfo.direction = static_cast<XfrmDirection>(direction);
+    saInfo.spi = spi;
+
+    if ((err = fillXfrmSaId(direction, localAddress, remoteAddress, spi, &saInfo)) < 0) {
+        ALOGE("Couldn't build SA ID %s", __FUNCTION__);
+        return -err;
+    }
+
+    if (saInfo.addrFamily != saddr.ss_family) {
+        ALOGE("Transform address family(%d) differs from socket address "
+              "family(%d)!",
+              saInfo.addrFamily, saddr.ss_family);
+        return -EINVAL;
+    }
+
+    struct {
+        xfrm_userpolicy_info info;
+        xfrm_user_tmpl tmpl;
+    } policy{};
+
+    fillTransportModeUserSpInfo(saInfo, &policy.info);
+    fillUserTemplate(saInfo, &policy.tmpl);
+
+    LOG_HEX("XfrmUserPolicy", reinterpret_cast<char*>(&policy), sizeof(policy));
+
+    int sockOpt, sockLayer;
+    switch (saInfo.addrFamily) {
+        case AF_INET:
+            sockOpt = IP_XFRM_POLICY;
+            sockLayer = SOL_IP;
+            break;
+        case AF_INET6:
+            sockOpt = IPV6_XFRM_POLICY;
+            sockLayer = SOL_IPV6;
+            break;
+        default:
+            return -EAFNOSUPPORT;
+    }
+
+    err = setsockopt(userSocket, sockLayer, sockOpt, &policy, sizeof(policy));
+    if (err < 0) {
+        err = errno;
+        ALOGE("Error setting socket option for XFRM! (%s)", strerror(err));
+    }
+
+    return -err;
+}
+
+int XfrmController::ipSecRemoveTransportModeTransform(const android::base::unique_fd& socket) {
+    (void)socket;
+    return 0;
+}
+
+void XfrmController::fillTransportModeSelector(const XfrmSaInfo& record, xfrm_selector* selector) {
+    selector->family = record.addrFamily;
+    selector->proto = AF_UNSPEC;      // TODO: do we need to match the protocol? it's
+                                      // possible via the socket
+    selector->ifindex = record.netId; // TODO : still need to sort this out
+}
+
+int XfrmController::createTransportModeSecurityAssociation(const XfrmSaInfo& record,
+                                                           const XfrmSocket& sock) {
+    xfrm_usersa_info usersa{};
+    nlattr_algo_crypt crypt{};
+    nlattr_algo_auth auth{};
+    nlattr_encap_tmpl encap{};
+
+    enum {
+        NLMSG_HDR,
+        USERSA,
+        USERSA_PAD,
+        CRYPT,
+        CRYPT_PAD,
+        AUTH,
+        AUTH_PAD,
+        ENCAP,
+        ENCAP_PAD,
+        iovLen
+    };
+
+    iovec iov[] = {
+        {NULL, 0},      // reserved for the eventual addition of a NLMSG_HDR
+        {&usersa, 0},   // main usersa_info struct
+        {kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
+        {&crypt, 0},    // adjust size if crypt algo is present
+        {kPadBytes, 0}, // up to NLATTR_ALIGNTO pad bytes
+        {&auth, 0},     // adjust size if auth algo is present
+        {kPadBytes, 0}, // up to NLATTR_ALIGNTO pad bytes
+        {&encap, 0},    // adjust size if auth algo is present
+        {kPadBytes, 0}, // up to NLATTR_ALIGNTO pad bytes
+    };
+
+    int len;
+    len = iov[USERSA].iov_len = fillUserSaInfo(record, &usersa);
+    iov[USERSA_PAD].iov_len = NLMSG_ALIGN(len) - len;
+
+    len = iov[CRYPT].iov_len = fillNlAttrXfrmAlgoEnc(record.crypt, &crypt);
+    iov[CRYPT_PAD].iov_len = NLA_ALIGN(len) - len;
+
+    len = iov[AUTH].iov_len = fillNlAttrXfrmAlgoAuth(record.auth, &auth);
+    iov[AUTH_PAD].iov_len = NLA_ALIGN(len) - len;
+
+    len = iov[ENCAP].iov_len = fillNlAttrXfrmEncapTmpl(record, &encap);
+    iov[ENCAP_PAD].iov_len = NLA_ALIGN(len) - len;
+    return sock.sendMessage(XFRM_MSG_UPDSA, NETLINK_REQUEST_FLAGS, 0, iov, iovLen);
+}
+
+int XfrmController::fillNlAttrXfrmAlgoEnc(const XfrmAlgo& inAlgo, nlattr_algo_crypt* algo) {
+    int len = NLA_HDRLEN + sizeof(xfrm_algo);
+    strncpy(algo->crypt.alg_name, inAlgo.name.c_str(), sizeof(algo->crypt.alg_name));
+    algo->crypt.alg_key_len = inAlgo.key.size() * 8;      // bits
+    memcpy(algo->key, &inAlgo.key[0], inAlgo.key.size()); // FIXME :safety checks
+    len += inAlgo.key.size();
+    fillXfrmNlaHdr(&algo->hdr, XFRMA_ALG_CRYPT, len);
+    return len;
+}
+
+int XfrmController::fillNlAttrXfrmAlgoAuth(const XfrmAlgo& inAlgo, nlattr_algo_auth* algo) {
+    int len = NLA_HDRLEN + sizeof(xfrm_algo_auth);
+    strncpy(algo->auth.alg_name, inAlgo.name.c_str(), sizeof(algo->auth.alg_name));
+    algo->auth.alg_key_len = inAlgo.key.size() * 8; // bits
+
+    // This is the extra field for ALG_AUTH_TRUNC
+    algo->auth.alg_trunc_len = inAlgo.truncLenBits;
+
+    memcpy(algo->key, &inAlgo.key[0], inAlgo.key.size()); // FIXME :safety checks
+    len += inAlgo.key.size();
+
+    fillXfrmNlaHdr(&algo->hdr, XFRMA_ALG_AUTH_TRUNC, len);
+    return len;
+}
+
+int XfrmController::fillNlAttrXfrmEncapTmpl(const XfrmSaInfo& record, nlattr_encap_tmpl* tmpl) {
+    if (record.encap.type == XfrmEncapType::NONE) {
+        return 0;
+    }
+
+    int len = NLA_HDRLEN + sizeof(xfrm_encap_tmpl);
+    tmpl->tmpl.encap_type = static_cast<uint16_t>(record.encap.type);
+    tmpl->tmpl.encap_sport = htons(record.encap.srcPort);
+    tmpl->tmpl.encap_dport = htons(record.encap.dstPort);
+    fillXfrmNlaHdr(&tmpl->hdr, XFRMA_ENCAP, len);
+    return len;
+}
+
+int XfrmController::fillUserSaInfo(const XfrmSaInfo& record, xfrm_usersa_info* usersa) {
+    fillTransportModeSelector(record, &usersa->sel);
+
+    usersa->id.proto = IPPROTO_ESP;
+    usersa->id.spi = record.spi;
+    usersa->id.daddr = record.dstAddr;
+
+    usersa->saddr = record.srcAddr;
+
+    fillXfrmLifetimeDefaults(&usersa->lft);
+    fillXfrmCurLifetimeDefaults(&usersa->curlft);
+    memset(&usersa->stats, 0, sizeof(usersa->stats)); // leave stats zeroed out
+    usersa->reqid = record.transformId;
+    usersa->family = record.addrFamily;
+    usersa->mode = static_cast<uint8_t>(record.mode);
+    usersa->replay_window = REPLAY_WINDOW_SIZE;
+    usersa->flags = 0; // TODO: should we actually set flags, XFRM_SA_XFLAG_DONT_ENCAP_DSCP?
+    return sizeof(*usersa);
+}
+
+int XfrmController::fillUserSaId(const XfrmSaId& record, xfrm_usersa_id* said) {
+    said->daddr = record.dstAddr;
+    said->spi = record.spi;
+    said->family = record.addrFamily;
+    said->proto = IPPROTO_ESP;
+
+    return sizeof(*said);
+}
+
+int XfrmController::deleteSecurityAssociation(const XfrmSaId& record, const XfrmSocket& sock) {
+    xfrm_usersa_id said{};
+
+    enum { NLMSG_HDR, USERSAID, USERSAID_PAD, iovLen };
+
+    iovec iov[] = {
+        {NULL, 0},      // reserved for the eventual addition of a NLMSG_HDR
+        {&said, 0},     // main usersa_info struct
+        {kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
+    };
+
+    int len;
+    len = iov[USERSAID].iov_len = fillUserSaId(record, &said);
+    iov[USERSAID_PAD].iov_len = NLMSG_ALIGN(len) - len;
+
+    return sock.sendMessage(XFRM_MSG_DELSA, NETLINK_REQUEST_FLAGS, 0, iov, iovLen);
+}
+
+int XfrmController::allocateSpi(const XfrmSaInfo& record, uint32_t minSpi, uint32_t maxSpi,
+                                uint32_t* outSpi, const XfrmSocket& sock) {
+    xfrm_userspi_info spiInfo{};
+
+    enum { NLMSG_HDR, USERSAID, USERSAID_PAD, iovLen };
+
+    iovec iov[] = {
+        {NULL, 0},      // reserved for the eventual addition of a NLMSG_HDR
+        {&spiInfo, 0},  // main userspi_info struct
+        {kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
+    };
+
+    int len;
+    if (fillUserSaInfo(record, &spiInfo.info) == 0) {
+        ALOGE("Failed to fill transport SA Info");
+    }
+
+    len = iov[USERSAID].iov_len = sizeof(spiInfo);
+    iov[USERSAID_PAD].iov_len = NLMSG_ALIGN(len) - len;
+
+    RandomSpi spiGen = RandomSpi(minSpi, maxSpi);
+    int spi, ret;
+    while ((spi = spiGen.next()) != INVALID_SPI) {
+        spiInfo.min = spi;
+        spiInfo.max = spi;
+        ret = sock.sendMessage(XFRM_MSG_ALLOCSPI, NETLINK_REQUEST_FLAGS, 0, iov, iovLen);
+
+        /* If the SPI is in use, we'll get ENOENT */
+        if (ret == -ENOENT)
+            continue;
+
+        if (ret == 0) {
+            *outSpi = spi;
+            ALOGD("Allocated an SPI: %x", *outSpi);
+        } else {
+            *outSpi = INVALID_SPI;
+            ALOGE("SPI Allocation Failed with error %d", ret);
+        }
+
+        return ret;
+    }
+
+    // Should always be -ENOENT if we get here
+    return ret;
+}
+
+int XfrmController::fillTransportModeUserSpInfo(const XfrmSaInfo& record,
+                                                xfrm_userpolicy_info* usersp) {
+    fillTransportModeSelector(record, &usersp->sel);
+    fillXfrmLifetimeDefaults(&usersp->lft);
+    fillXfrmCurLifetimeDefaults(&usersp->curlft);
+    /* if (index) index & 0x3 == dir -- must be true
+     * xfrm_user.c:verify_newpolicy_info() */
+    usersp->index = 0;
+    usersp->dir = static_cast<uint8_t>(record.direction);
+    usersp->action = XFRM_POLICY_ALLOW;
+    usersp->flags = XFRM_POLICY_LOCALOK;
+    usersp->share = XFRM_SHARE_UNIQUE;
+    return sizeof(*usersp);
+}
+
+int XfrmController::fillUserTemplate(const XfrmSaInfo& record, xfrm_user_tmpl* tmpl) {
+    tmpl->id.daddr = record.dstAddr;
+    tmpl->id.spi = record.spi;
+    tmpl->id.proto = IPPROTO_ESP;
+
+    tmpl->family = record.addrFamily;
+    tmpl->saddr = record.srcAddr;
+    tmpl->reqid = record.transformId;
+    tmpl->mode = static_cast<uint8_t>(record.mode);
+    tmpl->share = XFRM_SHARE_UNIQUE;
+    tmpl->optional = 0; // if this is true, then a failed state lookup will be considered OK:
+                        // http://lxr.free-electrons.com/source/net/xfrm/xfrm_policy.c#L1492
+    tmpl->aalgos = ALGO_MASK_AUTH_ALL;  // TODO: if there's a bitmask somewhere of
+                                        // algos, we should find it and apply it.
+                                        // I can't find one.
+    tmpl->ealgos = ALGO_MASK_CRYPT_ALL; // TODO: if there's a bitmask somewhere...
+    return 0;
+}
+
+} // namespace net
+} // namespace android
diff --git a/server/XfrmController.h b/server/XfrmController.h
new file mode 100644
index 0000000..1c9b406
--- /dev/null
+++ b/server/XfrmController.h
@@ -0,0 +1,238 @@
+/*
+ * 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.
+ */
+#ifndef _XFRM_CONTROLLER_H
+#define _XFRM_CONTROLLER_H
+
+#include <atomic>
+#include <list>
+#include <map>
+#include <string>
+#include <utility> // for pair
+
+#include <linux/netlink.h>
+#include <linux/udp.h>
+#include <linux/xfrm.h>
+#include <sysutils/SocketClient.h>
+#include <utils/RWLock.h>
+
+#include "NetdConstants.h"
+
+namespace android {
+namespace net {
+
+// Suggest we avoid the smallest and largest ints
+class XfrmMessage;
+class TransportModeSecurityAssociation;
+
+class XfrmSocket {
+public:
+    virtual void close() {
+        if (mSock >= 0) {
+            ::close(mSock);
+        }
+        mSock = -1;
+    }
+
+    virtual bool open() = 0;
+
+    virtual ~XfrmSocket() { close(); }
+
+    virtual int sendMessage(uint16_t nlMsgType, uint16_t nlMsgFlags, uint16_t nlMsgSeqNum,
+                            iovec* iov, int iovLen) const = 0;
+
+protected:
+    int mSock;
+};
+
+enum struct XfrmDirection : uint8_t {
+    IN = XFRM_POLICY_IN,
+    OUT = XFRM_POLICY_OUT,
+    FORWARD = XFRM_POLICY_FWD,
+    MASK = XFRM_POLICY_MASK,
+};
+
+enum struct XfrmMode : uint8_t {
+    TRANSPORT = XFRM_MODE_TRANSPORT,
+    TUNNEL = XFRM_MODE_TUNNEL,
+};
+
+enum struct XfrmEncapType : uint16_t {
+    NONE = 0,
+    ESPINUDP_NON_IKE = UDP_ENCAP_ESPINUDP_NON_IKE,
+    ESPINUDP = UDP_ENCAP_ESPINUDP
+};
+
+struct XfrmAlgo {
+    std::string name;
+    std::vector<uint8_t> key;
+    uint16_t truncLenBits;
+};
+
+struct XfrmEncap {
+    XfrmEncapType type;
+    uint16_t srcPort;
+    uint16_t dstPort;
+};
+
+struct XfrmSaId {
+    XfrmDirection direction;
+    xfrm_address_t dstAddr; // network order
+    xfrm_address_t srcAddr;
+    int addrFamily;  // AF_INET or AF_INET6
+    int transformId; // requestId
+    int spi;
+};
+
+struct XfrmSaInfo : XfrmSaId {
+    XfrmAlgo auth;
+    XfrmAlgo crypt;
+    int netId;
+    XfrmMode mode;
+    XfrmEncap encap;
+};
+
+class XfrmController {
+public:
+    XfrmController();
+
+    int ipSecAllocateSpi(int32_t transformId, int32_t direction, const std::string& localAddress,
+                         const std::string& remoteAddress, int32_t inSpi, int32_t* outSpi);
+
+    int ipSecAddSecurityAssociation(
+        int32_t transformId, int32_t mode, int32_t direction, const std::string& localAddress,
+        const std::string& remoteAddress, int64_t underlyingNetworkHandle, int32_t spi,
+        const std::string& authAlgo, const std::vector<uint8_t>& authKey, int32_t authTruncBits,
+        const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits,
+        int32_t encapType, int32_t encapLocalPort, int32_t encapRemotePort, int32_t* allocatedSpi);
+
+    int ipSecDeleteSecurityAssociation(int32_t transformId, int32_t direction,
+                                       const std::string& localAddress,
+                                       const std::string& remoteAddress, int32_t spi);
+
+    int ipSecApplyTransportModeTransform(const android::base::unique_fd& socket,
+                                         int32_t transformId, int32_t direction,
+                                         const std::string& localAddress,
+                                         const std::string& remoteAddress, int32_t spi);
+
+    int ipSecRemoveTransportModeTransform(const android::base::unique_fd& socket);
+
+private:
+    // prevent concurrent modification of XFRM
+    android::RWLock mLock;
+
+    static constexpr size_t MAX_ALGO_LENGTH = 128;
+
+/*
+ * Below is a redefinition of the xfrm_usersa_info struct that is part
+ * of the Linux uapi <linux/xfrm.h> to align the structures to a 64-bit
+ * boundary.
+ */
+#ifdef NETLINK_COMPAT32
+    // Shadow the kernel definition of xfrm_usersa_info with a 64-bit aligned version
+    struct xfrm_usersa_info : ::xfrm_usersa_info {
+    } __attribute__((aligned(8)));
+    // Shadow the kernel's version, using the aligned version of xfrm_usersa_info
+    struct xfrm_userspi_info {
+        struct xfrm_usersa_info info;
+        __u32 min;
+        __u32 max;
+    };
+
+    /*
+     * Anyone who encounters a failure when sending netlink messages should look here
+     * first. Hitting the static_assert() below should be a strong hint that Android
+     * IPsec will probably not work with your current settings.
+     *
+     * Again, experimentally determined, the "flags" field should be the first byte in
+     * the final word of the xfrm_usersa_info struct. The check validates the size of
+     * the padding to be 7.
+     *
+     * This padding is verified to be correct on gcc/x86_64 kernel, and clang/x86 userspace.
+     */
+    static_assert(sizeof(::xfrm_usersa_info) % 8 != 0, "struct xfrm_usersa_info has changed "
+                                                       "alignment. Please consider whether this "
+                                                       "patch is needed.");
+    static_assert(sizeof(xfrm_usersa_info) - offsetof(xfrm_usersa_info, flags) == 8,
+                  "struct xfrm_usersa_info probably misaligned with kernel struct.");
+    static_assert(sizeof(xfrm_usersa_info) % 8 == 0, "struct xfrm_usersa_info_t is not 64-bit  "
+                                                     "aligned. Please consider whether this patch "
+                                                     "is needed.");
+    static_assert(sizeof(::xfrm_userspi_info) - sizeof(::xfrm_usersa_info) ==
+                      sizeof(xfrm_userspi_info) - sizeof(xfrm_usersa_info),
+                  "struct xfrm_userspi_info has changed and does not match the kernel struct.");
+#endif
+
+    struct nlattr_algo_crypt {
+        nlattr hdr;
+        xfrm_algo crypt;
+        uint8_t key[MAX_ALGO_LENGTH];
+    };
+
+    struct nlattr_algo_auth {
+        nlattr hdr;
+        xfrm_algo_auth auth;
+        uint8_t key[MAX_ALGO_LENGTH];
+    };
+
+    struct nlattr_user_tmpl {
+        nlattr hdr;
+        xfrm_user_tmpl tmpl;
+    };
+
+    struct nlattr_encap_tmpl {
+        nlattr hdr;
+        xfrm_encap_tmpl tmpl;
+    };
+
+
+    // helper function for filling in the XfrmSaInfo structure
+    static int fillXfrmSaId(int32_t direction, const std::string& localAddress,
+                            const std::string& remoteAddress, int32_t spi, XfrmSaId* xfrmId);
+
+    // Top level functions for managing a Transport Mode Transform
+    static int addTransportModeTransform(const XfrmSaInfo& record);
+    static int removeTransportModeTransform(const XfrmSaInfo& record);
+
+    // TODO(messagerefactor): FACTOR OUT ALL MESSAGE BUILDING CODE BELOW HERE
+    // Shared between SA and SP
+    static void fillTransportModeSelector(const XfrmSaInfo& record, xfrm_selector* selector);
+
+    // Shared between Transport and Tunnel Mode
+    static int fillNlAttrXfrmAlgoEnc(const XfrmAlgo& in_algo, nlattr_algo_crypt* algo);
+    static int fillNlAttrXfrmAlgoAuth(const XfrmAlgo& in_algo, nlattr_algo_auth* algo);
+    static int fillNlAttrXfrmEncapTmpl(const XfrmSaInfo& record, nlattr_encap_tmpl* tmpl);
+
+    // Functions for Creating a Transport Mode SA
+    static int createTransportModeSecurityAssociation(const XfrmSaInfo& record,
+                                                      const XfrmSocket& sock);
+    static int fillUserSaInfo(const XfrmSaInfo& record, xfrm_usersa_info* usersa);
+
+    // Functions for deleting a Transport Mode SA
+    static int deleteSecurityAssociation(const XfrmSaId& record, const XfrmSocket& sock);
+    static int fillUserSaId(const XfrmSaId& record, xfrm_usersa_id* said);
+    static int fillUserTemplate(const XfrmSaInfo& record, xfrm_user_tmpl* tmpl);
+    static int fillTransportModeUserSpInfo(const XfrmSaInfo& record, xfrm_userpolicy_info* usersp);
+
+    static int allocateSpi(const XfrmSaInfo& record, uint32_t minSpi, uint32_t maxSpi,
+                           uint32_t* outSpi, const XfrmSocket& sock);
+
+    // END TODO(messagerefactor)
+};
+
+} // namespace net
+} // namespace android
+
+#endif /* !defined(XFRM_CONTROLLER_H) */
diff --git a/server/binder/android/net/INetd.aidl b/server/binder/android/net/INetd.aidl
index dbfab64..e5bf218 100644
--- a/server/binder/android/net/INetd.aidl
+++ b/server/binder/android/net/INetd.aidl
@@ -199,4 +199,130 @@
      */
     int getMetricsReportingLevel();
     void setMetricsReportingLevel(int level);
+
+   /**
+    * Reserve an SPI from the kernel
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param localAddress InetAddress as string for the local endpoint
+    * @param remoteAddress InetAddress as string for the remote endpoint
+    * @param spi a requested 32-bit unique ID or 0 to request random allocation
+    * @return the SPI that was allocated or 0 if failed
+    */
+    int ipSecAllocateSpi(
+            int transformId,
+            int direction,
+            in @utf8InCpp String localAddress,
+            in @utf8InCpp String remoteAddress,
+            int spi);
+
+   /**
+    * Create an IpSec Security Association describing how ip(v6) traffic will be encrypted
+    * or decrypted.
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param mode either Transport or Tunnel mode
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param localAddress InetAddress as string for the local endpoint
+    * @param remoteAddress InetAddress as string for the remote endpoint
+    * @param underlyingNetworkHandle the networkHandle of the network to which the SA is applied
+    * @param spi a 32-bit unique ID allocated to the user
+    * @param authAlgo a string identifying the authentication algorithm to be used
+    * @param authKey a byte array containing the authentication key
+    * @param authTruncBits the truncation length of the MAC produced by the authentication algorithm
+    * @param cryptAlgo a string identifying the encryption algorithm to be used
+    * @param cryptKey a byte arrray containing the encryption key
+    * @param cryptTruncBits unused parameter
+    * @param encapType encapsulation type used (if any) for the udp encap socket
+    * @param encapLocalPort the port number on the host to be used in encap packets
+    * @param encapRemotePort the port number of the remote to be used for encap packets
+    * @return the spi that was used to create this SA (should match the SPI paramter)
+    */
+    int ipSecAddSecurityAssociation(
+            int transformId,
+            int mode,
+            int direction,
+            in @utf8InCpp String localAddress,
+            in @utf8InCpp String remoteAddress,
+            long underlyingNetworkHandle,
+            int spi,
+            in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits,
+            in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits,
+            int encapType,
+            int encapLocalPort,
+            int encapRemotePort);
+
+   /**
+    * Delete a previously created security association identified by the provided parameters
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param localAddress InetAddress as string for the local endpoint
+    * @param remoteAddress InetAddress as string for the remote endpoint
+    * @param spi a requested 32-bit unique ID allocated to the user
+    */
+    void ipSecDeleteSecurityAssociation(
+            int transformId,
+            int direction,
+            in @utf8InCpp String localAddress,
+            in @utf8InCpp String remoteAddress,
+            int spi);
+
+   /**
+    * Apply a previously created SA to a specified socket, starting IPsec on that socket
+    *
+    * @param socket a user-provided socket that will have IPsec applied
+    * @param transformId a unique identifier for allocated resources
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param localAddress InetAddress as string for the local endpoint
+    * @param remoteAddress InetAddress as string for the remote endpoint
+    * @param spi a 32-bit unique ID allocated to the user (socket owner)
+    */
+    void ipSecApplyTransportModeTransform(
+            in FileDescriptor socket,
+            int transformId,
+            int direction,
+            in @utf8InCpp String localAddress,
+            in @utf8InCpp String remoteAddress,
+            int spi);
+
+   /**
+    * Remove an IPsec SA from a given socket. This will allow unencrypted traffic to flow
+    * on that socket if a transform had been previously applied.
+    *
+    * @param socket a user-provided socket from which to remove any IPsec configuration
+    */
+    void ipSecRemoveTransportModeTransform(
+            in FileDescriptor socket);
+
+   /**
+    * Request notification of wakeup packets arriving on an interface. Notifications will be
+    * delivered to INetdEventListener.onWakeupEvent().
+    *
+    * @param ifName the interface
+    * @param prefix arbitrary string used to identify wakeup sources in onWakeupEvent
+    */
+    void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+
+   /**
+    * Stop notification of wakeup packets arriving on an interface.
+    *
+    * @param ifName the interface
+    * @param prefix arbitrary string used to identify wakeup sources in onWakeupEvent
+    */
+    void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+
+    const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+    const int IPV6_ADDR_GEN_MODE_NONE = 1;
+    const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+    const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+
+    const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+   /**
+    * Set IPv6 address generation mode. IPv6 should be disabled before changing mode.
+    *
+    * @param mode SLAAC address generation mechanism to use
+    */
+    void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
 }
diff --git a/server/binder/android/net/metrics/INetdEventListener.aidl b/server/binder/android/net/metrics/INetdEventListener.aidl
index e966537..7f4a9e4 100644
--- a/server/binder/android/net/metrics/INetdEventListener.aidl
+++ b/server/binder/android/net/metrics/INetdEventListener.aidl
@@ -60,4 +60,14 @@
      * @param uid the UID of the application that performed the connection.
      */
     void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid);
+
+    /**
+     * Logs a single RX packet which caused the main CPU to exit sleep state.
+     * @param prefix arbitrary string provided via wakeupAddInterface()
+     * @param UID of the destination process or -1 if no UID is available.
+     * @param GID of the destination process or -1 if no GID is available.
+     * @param receive timestamp for the offending packet. In units of nanoseconds and
+     *        synchronized to CLOCK_MONOTONIC.
+     */
+    void onWakeupEvent(String prefix, int uid, int gid, long timestampNs);
 }
diff --git a/server/main.cpp b/server/main.cpp
index 4cc5838..27596f7 100644
--- a/server/main.cpp
+++ b/server/main.cpp
@@ -35,15 +35,16 @@
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
 
-#include "Controllers.h"
 #include "CommandListener.h"
+#include "Controllers.h"
+#include "DnsProxyListener.h"
+#include "FwmarkServer.h"
+#include "MDnsSdListener.h"
+#include "NFLogListener.h"
 #include "NetdConstants.h"
 #include "NetdNativeService.h"
 #include "NetlinkManager.h"
 #include "Stopwatch.h"
-#include "DnsProxyListener.h"
-#include "MDnsSdListener.h"
-#include "FwmarkServer.h"
 
 using android::status_t;
 using android::sp;
@@ -55,8 +56,9 @@
 using android::net::FwmarkServer;
 using android::net::NetdNativeService;
 using android::net::NetlinkManager;
+using android::net::NFLogListener;
+using android::net::makeNFLogListener;
 
-static void blockSigpipe();
 static void remove_pid_file();
 static bool write_pid_file();
 
@@ -92,6 +94,21 @@
         exit(1);
     }
 
+    std::unique_ptr<NFLogListener> logListener;
+    {
+        auto result = makeNFLogListener();
+        if (!isOk(result)) {
+            ALOGE("Unable to create NFLogListener: %s", toString(result).c_str());
+            exit(1);
+        }
+        logListener = std::move(result.value());
+        auto status = gCtls->wakeupCtrl.init(logListener.get());
+        if (!isOk(result)) {
+            ALOGE("Unable to init WakeupController: %s", toString(result).c_str());
+            // We can still continue without wakeup packet logging.
+        }
+    }
+
     // Set local DNS mode, to prevent bionic from proxying
     // back to this service, recursively.
     setenv("ANDROID_DNS_MODE", "local", 1);
@@ -113,11 +130,13 @@
         exit(1);
     }
 
+    Stopwatch subTime;
     status_t ret;
     if ((ret = NetdNativeService::start()) != android::OK) {
         ALOGE("Unable to start NetdNativeService: %d", ret);
         exit(1);
     }
+    ALOGI("Registering NetdNativeService: %.1fms", subTime.getTimeAndReset());
 
     /*
      * Now that we're up, we can respond to commands. Starting the listener also tells
@@ -127,6 +146,7 @@
         ALOGE("Unable to start CommandListener (%s)", strerror(errno));
         exit(1);
     }
+    ALOGI("Starting CommandListener: %.1fms", subTime.getTimeAndReset());
 
     write_pid_file();
 
@@ -172,13 +192,3 @@
 static void remove_pid_file() {
     unlink(PID_FILE_PATH);
 }
-
-static void blockSigpipe()
-{
-    sigset_t mask;
-
-    sigemptyset(&mask);
-    sigaddset(&mask, SIGPIPE);
-    if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0)
-        ALOGW("WARNING: SIGPIPE not blocked\n");
-}
diff --git a/server/netd_unit_test.cpp b/server/netd_unit_test.cpp
new file mode 100644
index 0000000..44cd5e9
--- /dev/null
+++ b/server/netd_unit_test.cpp
@@ -0,0 +1,2 @@
+// This file currently exists only so we can do:
+// runtest -x system/netd/server/netd_unit_test.cpp
diff --git a/server/thread_util.h b/server/thread_util.h
new file mode 100644
index 0000000..8a7ea6f
--- /dev/null
+++ b/server/thread_util.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.
+ */
+
+#ifndef NETD_SERVER_THREAD_UTIL_H
+#define NETD_SERVER_THREAD_UTIL_H
+
+#include <pthread.h>
+#include <memory>
+
+namespace android {
+namespace net {
+
+struct scoped_pthread_attr {
+    scoped_pthread_attr() { pthread_attr_init(&attr); }
+    ~scoped_pthread_attr() { pthread_attr_destroy(&attr); }
+
+    int detach() {
+        return pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+    }
+
+    pthread_attr_t attr;
+};
+
+template<typename T>
+inline void* runAndDelete(void* obj) {
+    std::unique_ptr<T> handler(reinterpret_cast<T*>(obj));
+    handler->run();
+    return nullptr;
+}
+
+template<typename T>
+inline int threadLaunch(T* obj) {
+    if (obj == nullptr) { return -EINVAL;}
+
+    scoped_pthread_attr scoped_attr;
+
+    int rval = scoped_attr.detach();
+    if (rval != 0) { return -errno; }
+
+    pthread_t thread;
+    rval = pthread_create(&thread, &scoped_attr.attr, &runAndDelete<T>, obj);
+    if (rval != 0) {
+        ALOGW("pthread_create failed: %d", errno);
+        return -errno;
+    }
+
+    return rval;
+}
+
+}  // namespace net
+}  // namespace android
+
+#endif  // NETD_SERVER_THREAD_UTIL_H
diff --git a/tests/Android.mk b/tests/Android.mk
index 7d47d45..0c12c1c 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -18,13 +18,14 @@
 # APCT build target
 include $(CLEAR_VARS)
 LOCAL_MODULE := netd_integration_test
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter
 # Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
 LOCAL_CFLAGS += -Wno-varargs
 
 EXTRA_LDLIBS := -lpthread
 LOCAL_SHARED_LIBRARIES += libbase libbinder libcutils liblog liblogwrap libnetdaidl libnetd_client \
-                          libnetutils libutils
+                          libnetutils libutils libnetdutils
 LOCAL_STATIC_LIBRARIES += libnetd_test_dnsresponder
 LOCAL_AIDL_INCLUDES := system/netd/server/binder
 LOCAL_C_INCLUDES += system/netd/include system/netd/binder/include \
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
new file mode 100644
index 0000000..be02773
--- /dev/null
+++ b/tests/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for netd_integration_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="netd_integration_test->/data/local/tmp/netd_integration_test" />
+    </target_preparer>
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="netd_integration_test" />
+    </test>
+</configuration>
diff --git a/tests/benchmarks/Android.mk b/tests/benchmarks/Android.mk
index c67d40e..bfcf600 100644
--- a/tests/benchmarks/Android.mk
+++ b/tests/benchmarks/Android.mk
@@ -13,32 +13,40 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-LOCAL_PATH := $(call my-dir)
+#
+# Note: netd benchmark can't build on nyc-mr2-dev, because google-benchmark project is out of date
+#       and won't be backported, and thus the content of this file is commented out to disable it.
+#       In order to run netd benchmark locally you can uncomment the content of this file and follow
+#       instructions in ag/1673408 (checkout that commit and build external/google-benchmark and
+#       system/netd locally and then run the benchmark locally)
+#
+#
+#LOCAL_PATH := $(call my-dir)
+#
+## APCT build target for metrics tests
+#include $(CLEAR_VARS)
+#LOCAL_MODULE := netd_benchmark
+#LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter
+## Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
+#LOCAL_CFLAGS += -Wno-varargs
 
-# APCT build target for metrics tests
-include $(CLEAR_VARS)
-LOCAL_MODULE := netd_benchmark
-LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter
-# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
-LOCAL_CFLAGS += -Wno-varargs
+#EXTRA_LDLIBS := -lpthread
+#LOCAL_SHARED_LIBRARIES += libbase libbinder liblog libnetd_client
+#LOCAL_STATIC_LIBRARIES += libnetd_test_dnsresponder libutils
 
-EXTRA_LDLIBS := -lpthread
-LOCAL_SHARED_LIBRARIES += libbase libbinder liblog libnetd_client
-LOCAL_STATIC_LIBRARIES += libnetd_test_dnsresponder libutils
+#LOCAL_AIDL_INCLUDES := system/netd/server/binder
+#LOCAL_C_INCLUDES += system/netd/include \
+#                    system/netd/client \
+#                    system/netd/server \
+#                    system/netd/server/binder \
+#                    system/netd/tests/dns_responder \
+#                    bionic/libc/dns/include
 
-LOCAL_AIDL_INCLUDES := system/netd/server/binder
-LOCAL_C_INCLUDES += system/netd/include \
-                    system/netd/client \
-                    system/netd/server \
-                    system/netd/server/binder \
-                    system/netd/tests/dns_responder \
-                    bionic/libc/dns/include
+#LOCAL_SRC_FILES := main.cpp \
+#                   connect_benchmark.cpp \
+#                   dns_benchmark.cpp \
+#                   ../../server/binder/android/net/metrics/INetdEventListener.aidl
 
-LOCAL_SRC_FILES := main.cpp \
-                   connect_benchmark.cpp \
-                   dns_benchmark.cpp \
-                   ../../server/binder/android/net/metrics/INetdEventListener.aidl
+#LOCAL_MODULE_TAGS := eng tests
 
-LOCAL_MODULE_TAGS := eng tests
-
-include $(BUILD_NATIVE_BENCHMARK)
+#include $(BUILD_NATIVE_BENCHMARK)
diff --git a/tests/benchmarks/AndroidTest.xml b/tests/benchmarks/AndroidTest.xml
new file mode 100644
index 0000000..7756cc2
--- /dev/null
+++ b/tests/benchmarks/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for netd_benchmark">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="netd_benchmark->/data/local/tmp/netd_benchmark" />
+    </target_preparer>
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="netd_benchmark" />
+    </test>
+</configuration>
diff --git a/tests/benchmarks/connect_benchmark.cpp b/tests/benchmarks/connect_benchmark.cpp
index 384feb1..132844c 100644
--- a/tests/benchmarks/connect_benchmark.cpp
+++ b/tests/benchmarks/connect_benchmark.cpp
@@ -171,7 +171,7 @@
     if (iterations > 0) {
         latencies.resize(iterations);
         sort(latencies.begin(), latencies.end());
-        state.SetLabel(StringPrintf("%lld", (long long) latencies[iterations * 9 / 10]));
+        state.SetLabel(StringPrintf("%lld", (long long) latencies[iterations * 9 / 10]).c_str());
     }
 }
 
@@ -228,7 +228,7 @@
     if (iterations > 0) {
         latencies.resize(iterations);
         sort(latencies.begin(), latencies.end());
-        state.SetLabel(StringPrintf("%lld", (long long) latencies[iterations * 9 / 10]));
+        state.SetLabel(StringPrintf("%lld", (long long) latencies[iterations * 9 / 10]).c_str());
     }
 }
 
diff --git a/tests/binder_test.cpp b/tests/binder_test.cpp
index 1481186..65de0c3 100644
--- a/tests/binder_test.cpp
+++ b/tests/binder_test.cpp
@@ -168,31 +168,31 @@
         mNetd->firewallReplaceUidChain(String16(chainName.c_str()), true, uids, &ret);
     }
     EXPECT_EQ(true, ret);
-    EXPECT_EQ((int) uids.size() + 6, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
-    EXPECT_EQ((int) uids.size() + 12, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
+    EXPECT_EQ((int) uids.size() + 7, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
+    EXPECT_EQ((int) uids.size() + 13, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
     {
         TimedOperation op("Clearing whitelist chain");
         mNetd->firewallReplaceUidChain(String16(chainName.c_str()), false, noUids, &ret);
     }
     EXPECT_EQ(true, ret);
-    EXPECT_EQ(4, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
-    EXPECT_EQ(4, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
+    EXPECT_EQ(5, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
+    EXPECT_EQ(5, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
 
     {
         TimedOperation op(StringPrintf("Programming %d-UID blacklist chain", kNumUids));
         mNetd->firewallReplaceUidChain(String16(chainName.c_str()), false, uids, &ret);
     }
     EXPECT_EQ(true, ret);
-    EXPECT_EQ((int) uids.size() + 4, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
-    EXPECT_EQ((int) uids.size() + 4, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
+    EXPECT_EQ((int) uids.size() + 5, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
+    EXPECT_EQ((int) uids.size() + 5, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
 
     {
         TimedOperation op("Clearing blacklist chain");
         mNetd->firewallReplaceUidChain(String16(chainName.c_str()), false, noUids, &ret);
     }
     EXPECT_EQ(true, ret);
-    EXPECT_EQ(4, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
-    EXPECT_EQ(4, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
+    EXPECT_EQ(5, iptablesRuleLineLength(IPTABLES_PATH, chainName.c_str()));
+    EXPECT_EQ(5, iptablesRuleLineLength(IP6TABLES_PATH, chainName.c_str()));
 
     // Check that the call fails if iptables returns an error.
     std::string veryLongStringName = "netd_binder_test_UnacceptablyLongIptablesChainName";
diff --git a/tests/netd_test.cpp b/tests/netd_test.cpp
index d7b50ff..bed3785 100644
--- a/tests/netd_test.cpp
+++ b/tests/netd_test.cpp
@@ -233,7 +233,7 @@
         auto t0 = std::chrono::steady_clock::now();
         std::vector<std::thread> threads(num_threads);
         for (std::thread& thread : threads) {
-           thread = std::thread([this, &servers, &dns, &mappings, num_queries]() {
+           thread = std::thread([this, &mappings, num_queries]() {
                 for (unsigned i = 0 ; i < num_queries ; ++i) {
                     uint32_t ofs = arc4random_uniform(mappings.size());
                     auto& mapping = mappings[ofs];
@@ -563,7 +563,7 @@
     const std::vector<std::string> servers = { listen_addr0, listen_addr1, listen_addr2 };
     std::vector<std::thread> threads(10);
     for (std::thread& thread : threads) {
-       thread = std::thread([this, &servers, &dns0, &dns1, &dns2]() {
+       thread = std::thread([this, &servers]() {
             unsigned delay = arc4random_uniform(1*1000*1000); // <= 1s
             usleep(delay);
             std::vector<std::string> serverSubset;