Project import
diff --git a/netd/Android.mk b/netd/Android.mk
new file mode 100644
index 0000000..5053e7d
--- /dev/null
+++ b/netd/Android.mk
@@ -0,0 +1 @@
+include $(call all-subdir-makefiles)
diff --git a/netd/MODULE_LICENSE_APACHE2 b/netd/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/netd/MODULE_LICENSE_APACHE2
diff --git a/netd/NOTICE b/netd/NOTICE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/netd/NOTICE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/netd/client/Android.mk b/netd/client/Android.mk
new file mode 100644
index 0000000..e202534
--- /dev/null
+++ b/netd/client/Android.mk
@@ -0,0 +1,25 @@
+# Copyright (C) 2014 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES := bionic/libc/dns/include system/netd/include
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := -std=c++11 -Wall -Werror
+LOCAL_MODULE := libnetd_client
+LOCAL_SRC_FILES := FwmarkClient.cpp NetdClient.cpp
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/netd/client/FwmarkClient.cpp b/netd/client/FwmarkClient.cpp
new file mode 100644
index 0000000..a82f4c2
--- /dev/null
+++ b/netd/client/FwmarkClient.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 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 "FwmarkClient.h"
+
+#include "FwmarkCommand.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+namespace {
+
+const sockaddr_un FWMARK_SERVER_PATH = {AF_UNIX, "/dev/socket/fwmarkd"};
+
+}  // namespace
+
+bool FwmarkClient::shouldSetFwmark(int family) {
+    return (family == AF_INET || family == AF_INET6) && !getenv("ANDROID_NO_USE_FWMARK_CLIENT");
+}
+
+FwmarkClient::FwmarkClient() : mChannel(-1) {
+}
+
+FwmarkClient::~FwmarkClient() {
+    if (mChannel >= 0) {
+        close(mChannel);
+    }
+}
+
+int FwmarkClient::send(FwmarkCommand* data, int fd) {
+    mChannel = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    if (mChannel == -1) {
+        return -errno;
+    }
+
+    if (TEMP_FAILURE_RETRY(connect(mChannel, reinterpret_cast<const sockaddr*>(&FWMARK_SERVER_PATH),
+                                   sizeof(FWMARK_SERVER_PATH))) == -1) {
+        // If we are unable to connect to the fwmark server, assume there's no error. This protects
+        // against future changes if the fwmark server goes away.
+        return 0;
+    }
+
+    iovec iov;
+    iov.iov_base = data;
+    iov.iov_len = sizeof(*data);
+
+    msghdr message;
+    memset(&message, 0, sizeof(message));
+    message.msg_iov = &iov;
+    message.msg_iovlen = 1;
+
+    union {
+        cmsghdr cmh;
+        char cmsg[CMSG_SPACE(sizeof(fd))];
+    } cmsgu;
+
+    if (data->cmdId != FwmarkCommand::QUERY_USER_ACCESS) {
+        memset(cmsgu.cmsg, 0, sizeof(cmsgu.cmsg));
+        message.msg_control = cmsgu.cmsg;
+        message.msg_controllen = sizeof(cmsgu.cmsg);
+
+        cmsghdr* const cmsgh = CMSG_FIRSTHDR(&message);
+        cmsgh->cmsg_len = CMSG_LEN(sizeof(fd));
+        cmsgh->cmsg_level = SOL_SOCKET;
+        cmsgh->cmsg_type = SCM_RIGHTS;
+        memcpy(CMSG_DATA(cmsgh), &fd, sizeof(fd));
+    }
+
+    if (TEMP_FAILURE_RETRY(sendmsg(mChannel, &message, 0)) == -1) {
+        return -errno;
+    }
+
+    int error = 0;
+
+    if (TEMP_FAILURE_RETRY(recv(mChannel, &error, sizeof(error), 0)) == -1) {
+        return -errno;
+    }
+
+    return error;
+}
diff --git a/netd/client/FwmarkClient.h b/netd/client/FwmarkClient.h
new file mode 100644
index 0000000..df7686d
--- /dev/null
+++ b/netd/client/FwmarkClient.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 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_CLIENT_FWMARK_CLIENT_H
+#define NETD_CLIENT_FWMARK_CLIENT_H
+
+#include <sys/socket.h>
+
+struct FwmarkCommand;
+
+class FwmarkClient {
+public:
+    // Returns true if a socket of the given |family| should be sent to the fwmark server to have
+    // its SO_MARK set.
+    static bool shouldSetFwmark(int family);
+
+    FwmarkClient();
+    ~FwmarkClient();
+
+    // Sends |data| to the fwmark server, along with |fd| as ancillary data using cmsg(3).
+    // Returns 0 on success or a negative errno value on failure.
+    int send(FwmarkCommand* data, int fd);
+
+private:
+    int mChannel;
+};
+
+#endif  // NETD_CLIENT_FWMARK_CLIENT_H
diff --git a/netd/client/NetdClient.cpp b/netd/client/NetdClient.cpp
new file mode 100644
index 0000000..392b0af
--- /dev/null
+++ b/netd/client/NetdClient.cpp
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2014 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 "NetdClient.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <atomic>
+
+#include "Fwmark.h"
+#include "FwmarkClient.h"
+#include "FwmarkCommand.h"
+#include "resolv_netid.h"
+
+namespace {
+
+std::atomic_uint netIdForProcess(NETID_UNSET);
+std::atomic_uint netIdForResolv(NETID_UNSET);
+
+typedef int (*Accept4FunctionType)(int, sockaddr*, socklen_t*, int);
+typedef int (*ConnectFunctionType)(int, const sockaddr*, socklen_t);
+typedef int (*SocketFunctionType)(int, int, int);
+typedef unsigned (*NetIdForResolvFunctionType)(unsigned);
+
+// These variables are only modified at startup (when libc.so is loaded) and never afterwards, so
+// it's okay that they are read later at runtime without a lock.
+Accept4FunctionType libcAccept4 = 0;
+ConnectFunctionType libcConnect = 0;
+SocketFunctionType libcSocket = 0;
+
+int closeFdAndSetErrno(int fd, int error) {
+    close(fd);
+    errno = -error;
+    return -1;
+}
+
+int netdClientAccept4(int sockfd, sockaddr* addr, socklen_t* addrlen, int flags) {
+    int acceptedSocket = libcAccept4(sockfd, addr, addrlen, flags);
+    if (acceptedSocket == -1) {
+        return -1;
+    }
+    int family;
+    if (addr) {
+        family = addr->sa_family;
+    } else {
+        socklen_t familyLen = sizeof(family);
+        if (getsockopt(acceptedSocket, SOL_SOCKET, SO_DOMAIN, &family, &familyLen) == -1) {
+            return closeFdAndSetErrno(acceptedSocket, -errno);
+        }
+    }
+    if (FwmarkClient::shouldSetFwmark(family)) {
+        FwmarkCommand command = {FwmarkCommand::ON_ACCEPT, 0, 0};
+        if (int error = FwmarkClient().send(&command, acceptedSocket)) {
+            return closeFdAndSetErrno(acceptedSocket, error);
+        }
+    }
+    return acceptedSocket;
+}
+
+int netdClientConnect(int sockfd, const sockaddr* addr, socklen_t addrlen) {
+    if (sockfd >= 0 && addr && FwmarkClient::shouldSetFwmark(addr->sa_family)) {
+        FwmarkCommand command = {FwmarkCommand::ON_CONNECT, 0, 0};
+        if (int error = FwmarkClient().send(&command, sockfd)) {
+            errno = -error;
+            return -1;
+        }
+    }
+    return libcConnect(sockfd, addr, addrlen);
+}
+
+int netdClientSocket(int domain, int type, int protocol) {
+    int socketFd = libcSocket(domain, type, protocol);
+    if (socketFd == -1) {
+        return -1;
+    }
+    unsigned netId = netIdForProcess;
+    if (netId != NETID_UNSET && FwmarkClient::shouldSetFwmark(domain)) {
+        if (int error = setNetworkForSocket(netId, socketFd)) {
+            return closeFdAndSetErrno(socketFd, error);
+        }
+    }
+    return socketFd;
+}
+
+unsigned getNetworkForResolv(unsigned netId) {
+    if (netId != NETID_UNSET) {
+        return netId;
+    }
+    netId = netIdForProcess;
+    if (netId != NETID_UNSET) {
+        return netId;
+    }
+    return netIdForResolv;
+}
+
+int setNetworkForTarget(unsigned netId, std::atomic_uint* target) {
+    if (netId == NETID_UNSET) {
+        *target = netId;
+        return 0;
+    }
+    // Verify that we are allowed to use |netId|, by creating a socket and trying to have it marked
+    // with the netId. Call libcSocket() directly; else the socket creation (via netdClientSocket())
+    // might itself cause another check with the fwmark server, which would be wasteful.
+    int socketFd;
+    if (libcSocket) {
+        socketFd = libcSocket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    } else {
+        socketFd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    }
+    if (socketFd < 0) {
+        return -errno;
+    }
+    int error = setNetworkForSocket(netId, socketFd);
+    if (!error) {
+        *target = netId;
+    }
+    close(socketFd);
+    return error;
+}
+
+}  // namespace
+
+// accept() just calls accept4(..., 0), so there's no need to handle accept() separately.
+extern "C" void netdClientInitAccept4(Accept4FunctionType* function) {
+    if (function && *function) {
+        libcAccept4 = *function;
+        *function = netdClientAccept4;
+    }
+}
+
+extern "C" void netdClientInitConnect(ConnectFunctionType* function) {
+    if (function && *function) {
+        libcConnect = *function;
+        *function = netdClientConnect;
+    }
+}
+
+extern "C" void netdClientInitSocket(SocketFunctionType* function) {
+    if (function && *function) {
+        libcSocket = *function;
+        *function = netdClientSocket;
+    }
+}
+
+extern "C" void netdClientInitNetIdForResolv(NetIdForResolvFunctionType* function) {
+    if (function) {
+        *function = getNetworkForResolv;
+    }
+}
+
+extern "C" int getNetworkForSocket(unsigned* netId, int socketFd) {
+    if (!netId || socketFd < 0) {
+        return -EBADF;
+    }
+    Fwmark fwmark;
+    socklen_t fwmarkLen = sizeof(fwmark.intValue);
+    if (getsockopt(socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue, &fwmarkLen) == -1) {
+        return -errno;
+    }
+    *netId = fwmark.netId;
+    return 0;
+}
+
+extern "C" unsigned getNetworkForProcess() {
+    return netIdForProcess;
+}
+
+extern "C" int setNetworkForSocket(unsigned netId, int socketFd) {
+    if (socketFd < 0) {
+        return -EBADF;
+    }
+    FwmarkCommand command = {FwmarkCommand::SELECT_NETWORK, netId, 0};
+    return FwmarkClient().send(&command, socketFd);
+}
+
+extern "C" int setNetworkForProcess(unsigned netId) {
+    return setNetworkForTarget(netId, &netIdForProcess);
+}
+
+extern "C" int setNetworkForResolv(unsigned netId) {
+    return setNetworkForTarget(netId, &netIdForResolv);
+}
+
+extern "C" int protectFromVpn(int socketFd) {
+    if (socketFd < 0) {
+        return -EBADF;
+    }
+    FwmarkCommand command = {FwmarkCommand::PROTECT_FROM_VPN, 0, 0};
+    return FwmarkClient().send(&command, socketFd);
+}
+
+extern "C" int setNetworkForUser(uid_t uid, int socketFd) {
+    if (socketFd < 0) {
+        return -EBADF;
+    }
+    FwmarkCommand command = {FwmarkCommand::SELECT_FOR_USER, 0, uid};
+    return FwmarkClient().send(&command, socketFd);
+}
+
+extern "C" int queryUserAccess(uid_t uid, unsigned netId) {
+    FwmarkCommand command = {FwmarkCommand::QUERY_USER_ACCESS, netId, uid};
+    return FwmarkClient().send(&command, -1);
+}
diff --git a/netd/include/Fwmark.h b/netd/include/Fwmark.h
new file mode 100644
index 0000000..178e175
--- /dev/null
+++ b/netd/include/Fwmark.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 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_INCLUDE_FWMARK_H
+#define NETD_INCLUDE_FWMARK_H
+
+#include "Permission.h"
+
+#include <stdint.h>
+
+union Fwmark {
+    uint32_t intValue;
+    struct {
+        unsigned netId          : 16;
+        bool explicitlySelected :  1;
+        bool protectedFromVpn   :  1;
+        Permission permission   :  2;
+    };
+    Fwmark() : intValue(0) {}
+};
+
+static const unsigned FWMARK_NET_ID_MASK = 0xffff;
+
+static_assert(sizeof(Fwmark) == sizeof(uint32_t), "The entire fwmark must fit into 32 bits");
+
+#endif  // NETD_INCLUDE_FWMARK_H
diff --git a/netd/include/FwmarkCommand.h b/netd/include/FwmarkCommand.h
new file mode 100644
index 0000000..be06899
--- /dev/null
+++ b/netd/include/FwmarkCommand.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 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_INCLUDE_FWMARK_COMMAND_H
+#define NETD_INCLUDE_FWMARK_COMMAND_H
+
+#include <sys/types.h>
+
+// Commands sent from clients to the fwmark server to mark sockets (i.e., set their SO_MARK).
+struct FwmarkCommand {
+    enum {
+        ON_ACCEPT,
+        ON_CONNECT,
+        SELECT_NETWORK,
+        PROTECT_FROM_VPN,
+        SELECT_FOR_USER,
+        QUERY_USER_ACCESS,
+    } cmdId;
+    unsigned netId;  // used only in the SELECT_NETWORK command; ignored otherwise.
+    uid_t uid;  // used only in the SELECT_FOR_USER and QUERY_USER_ACCESS commands;
+                // ignored otherwise.
+};
+
+#endif  // NETD_INCLUDE_FWMARK_COMMAND_H
diff --git a/netd/include/NetdClient.h b/netd/include/NetdClient.h
new file mode 100644
index 0000000..7db0906
--- /dev/null
+++ b/netd/include/NetdClient.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 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_INCLUDE_NETD_CLIENT_H
+#define NETD_INCLUDE_NETD_CLIENT_H
+
+#include <stdbool.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+// All functions below that return an int return 0 on success or a negative errno value on failure.
+
+int getNetworkForSocket(unsigned* netId, int socketFd);
+int setNetworkForSocket(unsigned netId, int socketFd);
+
+unsigned getNetworkForProcess(void);
+int setNetworkForProcess(unsigned netId);
+
+int setNetworkForResolv(unsigned netId);
+
+int protectFromVpn(int socketFd);
+
+int setNetworkForUser(uid_t uid, int socketFd);
+
+int queryUserAccess(uid_t uid, unsigned netId);
+
+__END_DECLS
+
+#endif  // NETD_INCLUDE_NETD_CLIENT_H
diff --git a/netd/include/Permission.h b/netd/include/Permission.h
new file mode 100644
index 0000000..aa5ae81
--- /dev/null
+++ b/netd/include/Permission.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 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_INCLUDE_PERMISSION_H
+#define NETD_INCLUDE_PERMISSION_H
+
+// This enum represents the permissions we care about for networking. When applied to an app, it's
+// the permission the app (UID) has been granted. When applied to a network, it's the permission an
+// app must hold to be allowed to use the network. PERMISSION_NONE means "no special permission is
+// held by the app" or "no special permission is required to use the network".
+//
+// Permissions are flags that can be OR'ed together to represent combinations of permissions.
+//
+// PERMISSION_NONE is used for regular networks and apps, such as those that hold the
+// android.permission.INTERNET framework permission.
+//
+// PERMISSION_NETWORK is used for privileged networks and apps that can manipulate or access them,
+// such as those that hold the android.permission.CHANGE_NETWORK_STATE framework permission.
+//
+// PERMISSION_SYSTEM is used for system apps, such as those that are installed on the system
+// partition, those that hold the android.permission.CONNECTIVITY_INTERNAL framework permission and
+// those whose UID is less than FIRST_APPLICATION_UID.
+enum Permission {
+    PERMISSION_NONE    = 0x0,
+    PERMISSION_NETWORK = 0x1,
+    PERMISSION_SYSTEM  = 0x3,  // Includes PERMISSION_NETWORK.
+};
+
+#endif  // NETD_INCLUDE_PERMISSION_H
diff --git a/netd/server/Android.mk b/netd/server/Android.mk
new file mode 100644
index 0000000..1ceda42
--- /dev/null
+++ b/netd/server/Android.mk
@@ -0,0 +1,93 @@
+# Copyright (C) 2014 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES := \
+        $(call include-path-for, libhardware_legacy)/hardware_legacy \
+        bionic/libc/dns/include \
+        external/mdnsresponder/mDNSShared \
+        system/netd/include \
+
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := -std=c++11 -Wall -Werror
+LOCAL_MODULE := netd
+
+# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
+LOCAL_CPPFLAGS +=  -Wno-varargs
+
+LOCAL_INIT_RC := netd.rc
+
+LOCAL_SHARED_LIBRARIES := \
+        libcrypto \
+        libcutils \
+        libdl \
+        libhardware_legacy \
+        liblog \
+        liblogwrap \
+        libmdnssd \
+        libnetutils \
+        libnl \
+        libsysutils \
+        libbase \
+        libutils \
+
+LOCAL_STATIC_LIBRARIES := \
+        libpcap \
+
+LOCAL_SRC_FILES := \
+        BandwidthController.cpp \
+        ClatdController.cpp \
+        CommandListener.cpp \
+        DnsProxyListener.cpp \
+        DummyNetwork.cpp \
+        FirewallController.cpp \
+        FwmarkServer.cpp \
+        IdletimerController.cpp \
+        InterfaceController.cpp \
+        LocalNetwork.cpp \
+        MDnsSdListener.cpp \
+        NatController.cpp \
+        NetdCommand.cpp \
+        NetdConstants.cpp \
+        NetlinkHandler.cpp \
+        NetlinkManager.cpp \
+        Network.cpp \
+        NetworkController.cpp \
+        PhysicalNetwork.cpp \
+        PppController.cpp \
+        ResolverController.cpp \
+        RouteController.cpp \
+        SockDiag.cpp \
+        SoftapController.cpp \
+        StrictController.cpp \
+        TetherController.cpp \
+        UidRanges.cpp \
+        VirtualNetwork.cpp \
+        main.cpp \
+        oem_iptables_hook.cpp \
+
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS := -Wall -Werror
+LOCAL_CLANG := true
+LOCAL_MODULE := ndc
+LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_SRC_FILES := ndc.cpp
+
+include $(BUILD_EXECUTABLE)
diff --git a/netd/server/BandwidthController.cpp b/netd/server/BandwidthController.cpp
new file mode 100644
index 0000000..d71ce3b
--- /dev/null
+++ b/netd/server/BandwidthController.cpp
@@ -0,0 +1,1311 @@
+/*
+ * Copyright (C) 2011 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_NDEBUG 0
+
+/*
+ * The CommandListener, FrameworkListener don't allow for
+ * multiple calls in parallel to reach the BandwidthController.
+ * If they ever were to allow it, then netd/ would need some tweaking.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/pkt_sched.h>
+
+#define LOG_TAG "BandwidthController"
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <logwrap/logwrap.h>
+
+#include "NetdConstants.h"
+#include "BandwidthController.h"
+#include "NatController.h"  /* For LOCAL_TETHER_COUNTERS_CHAIN */
+#include "ResponseCode.h"
+
+/* Alphabetical */
+#define ALERT_IPT_TEMPLATE "%s %s -m quota2 ! --quota %" PRId64" --name %s"
+const char BandwidthController::ALERT_GLOBAL_NAME[] = "globalAlert";
+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";
+const int  BandwidthController::MAX_CMD_ARGS = 32;
+const int  BandwidthController::MAX_CMD_LEN = 1024;
+const int  BandwidthController::MAX_IFACENAME_LEN = 64;
+const int  BandwidthController::MAX_IPT_OUTPUT_LINE_LEN = 256;
+
+/**
+ * Some comments about the rules:
+ *  * Ordering
+ *    - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains.
+ *      E.g. "-I bw_INPUT -i rmnet0 --jump costly"
+ *    - quota'd rules in the costly chain should be before bw_penalty_box lookups.
+ *    - bw_happy_box rejects everything by default.
+ *    - the qtaguid counting is done at the end of the bw_INPUT/bw_OUTPUT user chains.
+ *
+ * * global quota vs per interface quota
+ *   - global quota for all costly interfaces uses a single costly chain:
+ *    . initial rules
+ *      iptables -N bw_costly_shared
+ *      iptables -I bw_INPUT -i iface0 --jump bw_costly_shared
+ *      iptables -I bw_OUTPUT -o iface0 --jump bw_costly_shared
+ *      iptables -I bw_costly_shared -m quota \! --quota 500000 \
+ *          --jump REJECT --reject-with icmp-net-prohibited
+ *      iptables -A bw_costly_shared --jump bw_penalty_box
+ *      If the happy box is enabled,
+ *        iptables -A bw_penalty_box --jump bw_happy_box
+ *
+ *    . adding a new iface to this, E.g.:
+ *      iptables -I bw_INPUT -i iface1 --jump bw_costly_shared
+ *      iptables -I bw_OUTPUT -o iface1 --jump bw_costly_shared
+ *
+ *   - quota per interface. This is achieve by having "costly" chains per quota.
+ *     E.g. adding a new costly interface iface0 with its own quota:
+ *      iptables -N bw_costly_iface0
+ *      iptables -I bw_INPUT -i iface0 --jump bw_costly_iface0
+ *      iptables -I bw_OUTPUT -o iface0 --jump bw_costly_iface0
+ *      iptables -A bw_costly_iface0 -m quota \! --quota 500000 \
+ *          --jump REJECT --reject-with icmp-port-unreachable
+ *      iptables -A bw_costly_iface0 --jump bw_penalty_box
+ *
+ * * bw_penalty_box handling:
+ *  - only one bw_penalty_box for all interfaces
+ *   E.g  Adding an app, it has to preserve the appened bw_happy_box, so "-I":
+ *    iptables -I bw_penalty_box -m owner --uid-owner app_3 \
+ *        --jump REJECT --reject-with icmp-port-unreachable
+ *
+ * * bw_happy_box handling:
+ *  - The bw_happy_box goes at the end of the penalty box.
+ *   E.g  Adding a happy app,
+ *    iptables -I bw_happy_box -m owner --uid-owner app_3 \
+ *        --jump RETURN
+ */
+const char *BandwidthController::IPT_FLUSH_COMMANDS[] = {
+    /*
+     * Cleanup rules.
+     * Should normally include bw_costly_<iface>, but we rely on the way they are setup
+     * to allow coexistance.
+     */
+    "-F bw_INPUT",
+    "-F bw_OUTPUT",
+    "-F bw_FORWARD",
+    "-F bw_happy_box",
+    "-F bw_penalty_box",
+    "-F bw_costly_shared",
+
+    "-t raw -F bw_raw_PREROUTING",
+    "-t mangle -F bw_mangle_POSTROUTING",
+};
+
+/* The cleanup commands assume flushing has been done. */
+const char *BandwidthController::IPT_CLEANUP_COMMANDS[] = {
+    "-X bw_happy_box",
+    "-X bw_penalty_box",
+    "-X bw_costly_shared",
+};
+
+const char *BandwidthController::IPT_SETUP_COMMANDS[] = {
+    "-N bw_happy_box",
+    "-N bw_penalty_box",
+    "-N bw_costly_shared",
+};
+
+const char *BandwidthController::IPT_BASIC_ACCOUNTING_COMMANDS[] = {
+    "-A bw_INPUT -m owner --socket-exists", /* This is a tracking rule. */
+
+    "-A bw_OUTPUT -m owner --socket-exists", /* This is a tracking rule. */
+
+    "-A bw_costly_shared --jump bw_penalty_box",
+
+    "-t raw -A bw_raw_PREROUTING -m owner --socket-exists", /* This is a tracking rule. */
+    "-t mangle -A bw_mangle_POSTROUTING -m owner --socket-exists", /* This is a tracking rule. */
+};
+
+BandwidthController::BandwidthController(void) {
+}
+
+int BandwidthController::runIpxtablesCmd(const char *cmd, IptJumpOp jumpHandling,
+                                         IptFailureLog failureHandling) {
+    int res = 0;
+
+    ALOGV("runIpxtablesCmd(cmd=%s)", cmd);
+    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) {
+
+    memset(buffer, '\0', buffSize);  // strncpy() is not filling leftover with '\0'
+    strncpy(buffer, src, buffSize);
+    return buffer[buffSize - 1];
+}
+
+int BandwidthController::runIptablesCmd(const char *cmd, IptJumpOp jumpHandling,
+                                        IptIpVer iptVer, IptFailureLog failureHandling) {
+    char buffer[MAX_CMD_LEN];
+    const char *argv[MAX_CMD_ARGS];
+    int argc = 0;
+    char *next = buffer;
+    char *tmp;
+    int res;
+    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.insert(0, " -w ");
+    fullCmd.insert(0, iptVer == IptIpV4 ? IPTABLES_PATH : IP6TABLES_PATH);
+
+    if (StrncpyAndCheck(buffer, fullCmd.c_str(), sizeof(buffer))) {
+        ALOGE("iptables command too long");
+        return -1;
+    }
+
+    argc = 0;
+    while ((tmp = strsep(&next, " "))) {
+        argv[argc++] = tmp;
+        if (argc >= MAX_CMD_ARGS) {
+            ALOGE("iptables argument overflow");
+            return -1;
+        }
+    }
+
+    argv[argc] = NULL;
+    res = android_fork_execvp(argc, (char **)argv, &status, false,
+            failureHandling == IptFailShow);
+    res = res || !WIFEXITED(status) || WEXITSTATUS(status);
+    if (res && failureHandling == IptFailShow) {
+      ALOGE("runIptablesCmd(): res=%d status=%d failed %s", res, status,
+            fullCmd.c_str());
+    }
+    return res;
+}
+
+void BandwidthController::flushCleanTables(bool doClean) {
+    /* Flush and remove the bw_costly_<iface> tables */
+    flushExistingCostlyTables(doClean);
+
+    /* Some of the initialCommands are allowed to fail */
+    runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*),
+            IPT_FLUSH_COMMANDS, RunCmdFailureOk);
+
+    if (doClean) {
+        runCommands(sizeof(IPT_CLEANUP_COMMANDS) / sizeof(char*),
+                IPT_CLEANUP_COMMANDS, RunCmdFailureOk);
+    }
+}
+
+int BandwidthController::setupIptablesHooks(void) {
+
+    /* flush+clean is allowed to fail */
+    flushCleanTables(true);
+    runCommands(sizeof(IPT_SETUP_COMMANDS) / sizeof(char*),
+            IPT_SETUP_COMMANDS, RunCmdFailureBad);
+
+    return 0;
+}
+
+int BandwidthController::enableBandwidthControl(bool force) {
+    int res;
+    char value[PROPERTY_VALUE_MAX];
+
+    if (!force) {
+            property_get("persist.bandwidth.enable", value, "1");
+            if (!strcmp(value, "0"))
+                    return 0;
+    }
+
+    /* Let's pretend we started from scratch ... */
+    sharedQuotaIfaces.clear();
+    quotaIfaces.clear();
+    naughtyAppUids.clear();
+    niceAppUids.clear();
+    globalAlertBytes = 0;
+    globalAlertTetherCount = 0;
+    sharedQuotaBytes = sharedAlertBytes = 0;
+
+    flushCleanTables(false);
+    res = runCommands(sizeof(IPT_BASIC_ACCOUNTING_COMMANDS) / sizeof(char*),
+            IPT_BASIC_ACCOUNTING_COMMANDS, RunCmdFailureBad);
+
+    return res;
+
+}
+
+int BandwidthController::disableBandwidthControl(void) {
+
+    flushCleanTables(false);
+    return 0;
+}
+
+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;
+}
+
+int BandwidthController::enableHappyBox(void) {
+    char cmd[MAX_CMD_LEN];
+    int res = 0;
+
+    /*
+     * We tentatively delete before adding, which helps recovering
+     * from bad states (e.g. netd died).
+     */
+
+    /* Should not exist, but ignore result if already there. */
+    snprintf(cmd, sizeof(cmd), "-N bw_happy_box");
+    runIpxtablesCmd(cmd, IptJumpNoAdd);
+
+    /* Should be empty, but clear in case something was wrong. */
+    niceAppUids.clear();
+    snprintf(cmd, sizeof(cmd), "-F bw_happy_box");
+    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+
+    snprintf(cmd, sizeof(cmd), "-D bw_penalty_box -j bw_happy_box");
+    runIpxtablesCmd(cmd, IptJumpNoAdd);
+    snprintf(cmd, sizeof(cmd), "-A bw_penalty_box -j bw_happy_box");
+    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+
+    /* Reject. Defaulting to prot-unreachable */
+    snprintf(cmd, sizeof(cmd), "-D bw_happy_box -j REJECT");
+    runIpxtablesCmd(cmd, IptJumpNoAdd);
+    snprintf(cmd, sizeof(cmd), "-A bw_happy_box -j REJECT");
+    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+
+    return res;
+}
+
+int BandwidthController::disableHappyBox(void) {
+    char cmd[MAX_CMD_LEN];
+
+    /* Best effort */
+    snprintf(cmd, sizeof(cmd), "-D bw_penalty_box -j bw_happy_box");
+    runIpxtablesCmd(cmd, IptJumpNoAdd);
+    niceAppUids.clear();
+    snprintf(cmd, sizeof(cmd), "-F bw_happy_box");
+    runIpxtablesCmd(cmd, IptJumpNoAdd);
+    snprintf(cmd, sizeof(cmd), "-X bw_happy_box");
+    runIpxtablesCmd(cmd, IptJumpNoAdd);
+
+    return 0;
+}
+
+int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) {
+    return manipulateNaughtyApps(numUids, appUids, SpecialAppOpAdd);
+}
+
+int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) {
+    return manipulateNaughtyApps(numUids, appUids, SpecialAppOpRemove);
+}
+
+int BandwidthController::addNiceApps(int numUids, char *appUids[]) {
+    return manipulateNiceApps(numUids, appUids, SpecialAppOpAdd);
+}
+
+int BandwidthController::removeNiceApps(int numUids, char *appUids[]) {
+    return manipulateNiceApps(numUids, appUids, SpecialAppOpRemove);
+}
+
+int BandwidthController::manipulateNaughtyApps(int numUids, char *appStrUids[], SpecialAppOp appOp) {
+    return manipulateSpecialApps(numUids, appStrUids, "bw_penalty_box", naughtyAppUids, IptJumpReject, appOp);
+}
+
+int BandwidthController::manipulateNiceApps(int numUids, char *appStrUids[], SpecialAppOp appOp) {
+    return manipulateSpecialApps(numUids, appStrUids, "bw_happy_box", niceAppUids, IptJumpReturn, appOp);
+}
+
+
+int BandwidthController::manipulateSpecialApps(int numUids, char *appStrUids[],
+                                               const char *chain,
+                                               std::list<int /*appUid*/> &specialAppUids,
+                                               IptJumpOp jumpHandling, SpecialAppOp appOp) {
+
+    int uidNum;
+    const char *failLogTemplate;
+    IptOp op;
+    int appUids[numUids];
+    std::string iptCmd;
+    std::list<int /*uid*/>::iterator it;
+
+    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;
+    }
+
+    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];
+        for (it = specialAppUids.begin(); it != specialAppUids.end(); it++) {
+            if (*it == uid)
+                break;
+        }
+        bool found = (it != specialAppUids.end());
+
+        if (appOp == SpecialAppOpRemove) {
+            if (!found) {
+                ALOGE("No such appUid %d to remove", uid);
+                return -1;
+            }
+            specialAppUids.erase(it);
+        } else {
+            if (found) {
+                ALOGE("appUid %d exists already", uid);
+                return -1;
+            }
+            specialAppUids.push_front(uid);
+        }
+
+        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;
+}
+
+std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota) {
+    std::string res;
+    char *buff;
+    const char *opFlag;
+
+    ALOGV("makeIptablesQuotaCmd(%d, %" PRId64")", op, quota);
+
+    switch (op) {
+    case IptOpInsert:
+        opFlag = "-I";
+        break;
+    case IptOpAppend:
+        opFlag = "-A";
+        break;
+    case IptOpReplace:
+        opFlag = "-R";
+        break;
+    default:
+    case IptOpDelete:
+        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);
+    return res;
+}
+
+int BandwidthController::prepCostlyIface(const char *ifn, QuotaType quotaType) {
+    char cmd[MAX_CMD_LEN];
+    int res = 0, res1, res2;
+    int ruleInsertPos = 1;
+    std::string costString;
+    const char *costCString;
+
+    /* The "-N costly" is created upfront, no need to handle it here. */
+    switch (quotaType) {
+    case QuotaUnique:
+        costString = "bw_costly_";
+        costString += ifn;
+        costCString = costString.c_str();
+        /*
+         * Flush the bw_costly_<iface> is allowed to fail in case it didn't exist.
+         * Creating a new one is allowed to fail in case it existed.
+         * This helps with netd restarts.
+         */
+        snprintf(cmd, sizeof(cmd), "-F %s", costCString);
+        res1 = runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
+        snprintf(cmd, sizeof(cmd), "-N %s", costCString);
+        res2 = runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
+        res = (res1 && res2) || (!res1 && !res2);
+
+        snprintf(cmd, sizeof(cmd), "-A %s -j bw_penalty_box", costCString);
+        res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+        break;
+    case QuotaShared:
+        costCString = "bw_costly_shared";
+        break;
+    default:
+        ALOGE("Unexpected quotatype %d", quotaType);
+        return -1;
+    }
+
+    if (globalAlertBytes) {
+        /* The alert rule comes 1st */
+        ruleInsertPos = 2;
+    }
+
+    snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString);
+    runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
+
+    snprintf(cmd, sizeof(cmd), "-I bw_INPUT %d -i %s --jump %s", ruleInsertPos, ifn, costCString);
+    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+
+    snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString);
+    runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
+
+    snprintf(cmd, sizeof(cmd), "-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, ifn, costCString);
+    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+    return res;
+}
+
+int BandwidthController::cleanupCostlyIface(const char *ifn, QuotaType quotaType) {
+    char cmd[MAX_CMD_LEN];
+    int res = 0;
+    std::string costString;
+    const char *costCString;
+
+    switch (quotaType) {
+    case QuotaUnique:
+        costString = "bw_costly_";
+        costString += ifn;
+        costCString = costString.c_str();
+        break;
+    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);
+    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+    snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString);
+    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+
+    /* The "-N bw_costly_shared" is created upfront, no need to handle it here. */
+    if (quotaType == QuotaUnique) {
+        snprintf(cmd, sizeof(cmd), "-F %s", costCString);
+        res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+        snprintf(cmd, sizeof(cmd), "-X %s", costCString);
+        res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+    }
+    return res;
+}
+
+int BandwidthController::setInterfaceSharedQuota(const char *iface, int64_t maxBytes) {
+    char ifn[MAX_IFACENAME_LEN];
+    int res = 0;
+    std::string quotaCmd;
+    std::string ifaceName;
+    ;
+    const char *costName = "shared";
+    std::list<std::string>::iterator it;
+
+    if (!maxBytes) {
+        /* Don't talk about -1, deprecate it. */
+        ALOGE("Invalid bytes value. 1..max_int64.");
+        return -1;
+    }
+    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);
+    }
+
+    /* Insert ingress quota. */
+    for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) {
+        if (*it == ifaceName)
+            break;
+    }
+
+    if (it == sharedQuotaIfaces.end()) {
+        res |= prepCostlyIface(ifn, QuotaShared);
+        if (sharedQuotaIfaces.empty()) {
+            quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes);
+            res |= runIpxtablesCmd(quotaCmd.c_str(), IptJumpReject);
+            if (res) {
+                ALOGE("Failed set quota rule");
+                goto fail;
+            }
+            sharedQuotaBytes = maxBytes;
+        }
+        sharedQuotaIfaces.push_front(ifaceName);
+
+    }
+
+    if (maxBytes != sharedQuotaBytes) {
+        res |= updateQuota(costName, maxBytes);
+        if (res) {
+            ALOGE("Failed update quota for %s", costName);
+            goto fail;
+        }
+        sharedQuotaBytes = maxBytes;
+    }
+    return 0;
+
+    fail:
+    /*
+     * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse
+     * rules in the kernel to see which ones need cleaning up.
+     * For now callers needs to choose if they want to "ndc bandwidth enable"
+     * which resets everything.
+     */
+    removeInterfaceSharedQuota(ifn);
+    return -1;
+}
+
+/* It will also cleanup any shared alerts */
+int BandwidthController::removeInterfaceSharedQuota(const char *iface) {
+    char ifn[MAX_IFACENAME_LEN];
+    int res = 0;
+    std::string ifaceName;
+    std::list<std::string>::iterator it;
+    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)
+            break;
+    }
+    if (it == sharedQuotaIfaces.end()) {
+        ALOGE("No such iface %s to delete", ifn);
+        return -1;
+    }
+
+    res |= cleanupCostlyIface(ifn, QuotaShared);
+    sharedQuotaIfaces.erase(it);
+
+    if (sharedQuotaIfaces.empty()) {
+        std::string quotaCmd;
+        quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes);
+        res |= runIpxtablesCmd(quotaCmd.c_str(), IptJumpReject);
+        sharedQuotaBytes = 0;
+        if (sharedAlertBytes) {
+            removeSharedAlert();
+            sharedAlertBytes = 0;
+        }
+    }
+    return res;
+}
+
+int BandwidthController::setInterfaceQuota(const char *iface, int64_t maxBytes) {
+    char ifn[MAX_IFACENAME_LEN];
+    int res = 0;
+    std::string ifaceName;
+    const char *costName;
+    std::list<QuotaInfo>::iterator it;
+    std::string quotaCmd;
+
+    if (!isIfaceName(iface))
+        return -1;
+
+    if (!maxBytes) {
+        /* Don't talk about -1, deprecate it. */
+        ALOGE("Invalid bytes value. 1..max_int64.");
+        return -1;
+    }
+    if (maxBytes == -1) {
+        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)
+            break;
+    }
+
+    if (it == quotaIfaces.end()) {
+        /* Preparing the iface adds a penalty/happy box check */
+        res |= prepCostlyIface(ifn, 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);
+        res |= runIpxtablesCmd(quotaCmd.c_str(), IptJumpReject);
+        if (res) {
+            ALOGE("Failed set quota rule");
+            goto fail;
+        }
+
+        quotaIfaces.push_front(QuotaInfo(ifaceName, maxBytes, 0));
+
+    } else {
+        res |= updateQuota(costName, maxBytes);
+        if (res) {
+            ALOGE("Failed update quota for %s", iface);
+            goto fail;
+        }
+        it->quota = maxBytes;
+    }
+    return 0;
+
+    fail:
+    /*
+     * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse
+     * rules in the kernel to see which ones need cleaning up.
+     * For now callers needs to choose if they want to "ndc bandwidth enable"
+     * which resets everything.
+     */
+    removeInterfaceSharedQuota(ifn);
+    return -1;
+}
+
+int BandwidthController::getInterfaceSharedQuota(int64_t *bytes) {
+    return getInterfaceQuota("shared", bytes);
+}
+
+int BandwidthController::getInterfaceQuota(const char *costName, int64_t *bytes) {
+    FILE *fp;
+    char *fname;
+    int scanRes;
+
+    if (!isIfaceName(costName))
+        return -1;
+
+    asprintf(&fname, "/proc/net/xt_quota/%s", costName);
+    fp = fopen(fname, "re");
+    free(fname);
+    if (!fp) {
+        ALOGE("Reading quota %s failed (%s)", costName, strerror(errno));
+        return -1;
+    }
+    scanRes = fscanf(fp, "%" SCNd64, bytes);
+    ALOGV("Read quota res=%d bytes=%" PRId64, scanRes, *bytes);
+    fclose(fp);
+    return scanRes == 1 ? 0 : -1;
+}
+
+int BandwidthController::removeInterfaceQuota(const char *iface) {
+
+    char ifn[MAX_IFACENAME_LEN];
+    int res = 0;
+    std::string ifaceName;
+    const char *costName;
+    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;
+    costName = iface;
+
+    for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
+        if (it->ifaceName == ifaceName)
+            break;
+    }
+
+    if (it == quotaIfaces.end()) {
+        ALOGE("No such iface %s to delete", ifn);
+        return -1;
+    }
+
+    /* This also removes the quota command of CostlyIface chain. */
+    res |= cleanupCostlyIface(ifn, QuotaUnique);
+
+    quotaIfaces.erase(it);
+
+    return res;
+}
+
+int BandwidthController::updateQuota(const char *quotaName, int64_t bytes) {
+    FILE *fp;
+    char *fname;
+
+    if (!isIfaceName(quotaName)) {
+        ALOGE("updateQuota: Invalid quotaName \"%s\"", quotaName);
+        return -1;
+    }
+
+    asprintf(&fname, "/proc/net/xt_quota/%s", quotaName);
+    fp = fopen(fname, "we");
+    free(fname);
+    if (!fp) {
+        ALOGE("Updating quota %s failed (%s)", quotaName, strerror(errno));
+        return -1;
+    }
+    fprintf(fp, "%" PRId64"\n", bytes);
+    fclose(fp);
+    return 0;
+}
+
+int BandwidthController::runIptablesAlertCmd(IptOp op, const char *alertName, int64_t bytes) {
+    int res = 0;
+    const char *opFlag;
+    char *alertQuotaCmd;
+
+    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_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;
+}
+
+int BandwidthController::runIptablesAlertFwdCmd(IptOp op, const char *alertName, int64_t bytes) {
+    int res = 0;
+    const char *opFlag;
+    char *alertQuotaCmd;
+
+    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;
+}
+
+int BandwidthController::setGlobalAlert(int64_t bytes) {
+    const char *alertName = ALERT_GLOBAL_NAME;
+    int res = 0;
+
+    if (!bytes) {
+        ALOGE("Invalid bytes value. 1..max_int64.");
+        return -1;
+    }
+    if (globalAlertBytes) {
+        res = updateQuota(alertName, bytes);
+    } else {
+        res = runIptablesAlertCmd(IptOpInsert, alertName, bytes);
+        if (globalAlertTetherCount) {
+            ALOGV("setGlobalAlert for %d tether", globalAlertTetherCount);
+            res |= runIptablesAlertFwdCmd(IptOpInsert, alertName, bytes);
+        }
+    }
+    globalAlertBytes = bytes;
+    return res;
+}
+
+int BandwidthController::setGlobalAlertInForwardChain(void) {
+    const char *alertName = ALERT_GLOBAL_NAME;
+    int res = 0;
+
+    globalAlertTetherCount++;
+    ALOGV("setGlobalAlertInForwardChain(): %d tether", globalAlertTetherCount);
+
+    /*
+     * If there is no globalAlert active we are done.
+     * If there is an active globalAlert but this is not the 1st
+     * tether, we are also done.
+     */
+    if (!globalAlertBytes || globalAlertTetherCount != 1) {
+        return 0;
+    }
+
+    /* We only add the rule if this was the 1st tether added. */
+    res = runIptablesAlertFwdCmd(IptOpInsert, alertName, globalAlertBytes);
+    return res;
+}
+
+int BandwidthController::removeGlobalAlert(void) {
+
+    const char *alertName = ALERT_GLOBAL_NAME;
+    int res = 0;
+
+    if (!globalAlertBytes) {
+        ALOGE("No prior alert set");
+        return -1;
+    }
+    res = runIptablesAlertCmd(IptOpDelete, alertName, globalAlertBytes);
+    if (globalAlertTetherCount) {
+        res |= runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes);
+    }
+    globalAlertBytes = 0;
+    return res;
+}
+
+int BandwidthController::removeGlobalAlertInForwardChain(void) {
+    int res = 0;
+    const char *alertName = ALERT_GLOBAL_NAME;
+
+    if (!globalAlertTetherCount) {
+        ALOGE("No prior alert set");
+        return -1;
+    }
+
+    globalAlertTetherCount--;
+    /*
+     * If there is no globalAlert active we are done.
+     * If there is an active globalAlert but there are more
+     * tethers, we are also done.
+     */
+    if (!globalAlertBytes || globalAlertTetherCount >= 1) {
+        return 0;
+    }
+
+    /* We only detete the rule if this was the last tether removed. */
+    res = runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes);
+    return res;
+}
+
+int BandwidthController::setSharedAlert(int64_t bytes) {
+    if (!sharedQuotaBytes) {
+        ALOGE("Need to have a prior shared quota set to set an alert");
+        return -1;
+    }
+    if (!bytes) {
+        ALOGE("Invalid bytes value. 1..max_int64.");
+        return -1;
+    }
+    return setCostlyAlert("shared", bytes, &sharedAlertBytes);
+}
+
+int BandwidthController::removeSharedAlert(void) {
+    return removeCostlyAlert("shared", &sharedAlertBytes);
+}
+
+int BandwidthController::setInterfaceAlert(const char *iface, int64_t bytes) {
+    std::list<QuotaInfo>::iterator it;
+
+    if (!isIfaceName(iface)) {
+        ALOGE("setInterfaceAlert: Invalid iface \"%s\"", iface);
+        return -1;
+    }
+
+    if (!bytes) {
+        ALOGE("Invalid bytes value. 1..max_int64.");
+        return -1;
+    }
+    for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
+        if (it->ifaceName == iface)
+            break;
+    }
+
+    if (it == quotaIfaces.end()) {
+        ALOGE("Need to have a prior interface quota set to set an alert");
+        return -1;
+    }
+
+    return setCostlyAlert(iface, bytes, &it->alert);
+}
+
+int BandwidthController::removeInterfaceAlert(const char *iface) {
+    std::list<QuotaInfo>::iterator it;
+
+    if (!isIfaceName(iface)) {
+        ALOGE("removeInterfaceAlert: Invalid iface \"%s\"", iface);
+        return -1;
+    }
+
+    for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
+        if (it->ifaceName == iface)
+            break;
+    }
+
+    if (it == quotaIfaces.end()) {
+        ALOGE("No prior alert set for interface %s", iface);
+        return -1;
+    }
+
+    return removeCostlyAlert(iface, &it->alert);
+}
+
+int BandwidthController::setCostlyAlert(const char *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);
+        return -1;
+    }
+
+    if (!bytes) {
+        ALOGE("Invalid bytes value. 1..max_int64.");
+        return -1;
+    }
+    asprintf(&alertName, "%sAlert", costName);
+    if (*alertBytes) {
+        res = updateQuota(alertName, *alertBytes);
+    } else {
+        asprintf(&chainName, "bw_costly_%s", costName);
+        asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "-A", chainName, bytes, alertName);
+        res |= runIpxtablesCmd(alertQuotaCmd, IptJumpNoAdd);
+        free(alertQuotaCmd);
+        free(chainName);
+    }
+    *alertBytes = bytes;
+    free(alertName);
+    return res;
+}
+
+int BandwidthController::removeCostlyAlert(const char *costName, int64_t *alertBytes) {
+    char *alertQuotaCmd;
+    char *chainName;
+    char *alertName;
+    int res = 0;
+
+    if (!isIfaceName(costName)) {
+        ALOGE("removeCostlyAlert: Invalid costName \"%s\"", costName);
+        return -1;
+    }
+
+    if (!*alertBytes) {
+        ALOGE("No prior alert set for %s alert", costName);
+        return -1;
+    }
+
+    asprintf(&alertName, "%sAlert", costName);
+    asprintf(&chainName, "bw_costly_%s", costName);
+    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "-D", chainName, *alertBytes, alertName);
+    res |= runIpxtablesCmd(alertQuotaCmd, IptJumpNoAdd);
+    free(alertQuotaCmd);
+    free(chainName);
+
+    *alertBytes = 0;
+    free(alertName);
+    return res;
+}
+
+/*
+ * Parse the ptks and bytes out of:
+ *   Chain natctrl_tether_counters (4 references)
+ *       pkts      bytes target     prot opt in     out     source               destination
+ *         26     2373 RETURN     all  --  wlan0  rmnet0  0.0.0.0/0            0.0.0.0/0
+ *         27     2002 RETURN     all  --  rmnet0 wlan0   0.0.0.0/0            0.0.0.0/0
+ *       1040   107471 RETURN     all  --  bt-pan rmnet0  0.0.0.0/0            0.0.0.0/0
+ *       1450  1708806 RETURN     all  --  rmnet0 bt-pan  0.0.0.0/0            0.0.0.0/0
+ * It results in an error if invoked and no tethering counter rules exist. The constraint
+ * helps detect complete parsing failure.
+ */
+int BandwidthController::parseForwardChainStats(SocketClient *cli, const TetherStats filter,
+                                                FILE *fp, std::string &extraProcessingInfo) {
+    int res;
+    char lineBuffer[MAX_IPT_OUTPUT_LINE_LEN];
+    char iface0[MAX_IPT_OUTPUT_LINE_LEN];
+    char iface1[MAX_IPT_OUTPUT_LINE_LEN];
+    char rest[MAX_IPT_OUTPUT_LINE_LEN];
+
+    TetherStats stats;
+    char *buffPtr;
+    int64_t packets, bytes;
+    int statsFound = 0;
+
+    bool filterPair = filter.intIface[0] && filter.extIface[0];
+
+    char *filterMsg = filter.getStatsLine();
+    ALOGV("filter: %s",  filterMsg);
+    free(filterMsg);
+
+    stats = filter;
+
+    while (NULL != (buffPtr = fgets(lineBuffer, MAX_IPT_OUTPUT_LINE_LEN, fp))) {
+        /* Clean up, so a failed parse can still print info */
+        iface0[0] = iface1[0] = rest[0] = packets = bytes = 0;
+        res = sscanf(buffPtr, "%" SCNd64" %" SCNd64" RETURN all -- %s %s 0.%s",
+                &packets, &bytes, iface0, iface1, rest);
+        ALOGV("parse res=%d iface0=<%s> iface1=<%s> pkts=%" PRId64" bytes=%" PRId64" rest=<%s> orig line=<%s>", res,
+             iface0, iface1, packets, bytes, rest, buffPtr);
+        extraProcessingInfo += buffPtr;
+
+        if (res != 5) {
+            continue;
+        }
+        /*
+         * The following assumes that the 1st rule has in:extIface out:intIface,
+         * which is what NatController sets up.
+         * If not filtering, the 1st match rx, and sets up the pair for the tx side.
+         */
+        if (filter.intIface[0] && filter.extIface[0]) {
+            if (filter.intIface == iface0 && filter.extIface == iface1) {
+                ALOGV("2Filter RX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
+                stats.rxPackets = packets;
+                stats.rxBytes = bytes;
+            } else if (filter.intIface == iface1 && filter.extIface == iface0) {
+                ALOGV("2Filter TX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
+                stats.txPackets = packets;
+                stats.txBytes = bytes;
+            }
+        } else if (filter.intIface[0] || filter.extIface[0]) {
+            if (filter.intIface == iface0 || filter.extIface == iface1) {
+                ALOGV("1Filter RX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
+                stats.intIface = iface0;
+                stats.extIface = iface1;
+                stats.rxPackets = packets;
+                stats.rxBytes = bytes;
+            } else if (filter.intIface == iface1 || filter.extIface == iface0) {
+                ALOGV("1Filter TX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
+                stats.intIface = iface1;
+                stats.extIface = iface0;
+                stats.txPackets = packets;
+                stats.txBytes = bytes;
+            }
+        } else /* if (!filter.intFace[0] && !filter.extIface[0]) */ {
+            if (!stats.intIface[0]) {
+                ALOGV("0Filter RX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
+                stats.intIface = iface0;
+                stats.extIface = iface1;
+                stats.rxPackets = packets;
+                stats.rxBytes = bytes;
+            } else if (stats.intIface == iface1 && stats.extIface == iface0) {
+                ALOGV("0Filter TX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
+                stats.txPackets = packets;
+                stats.txBytes = bytes;
+            }
+        }
+        if (stats.rxBytes != -1 && stats.txBytes != -1) {
+            ALOGV("rx_bytes=%" PRId64" tx_bytes=%" PRId64" filterPair=%d", stats.rxBytes, stats.txBytes, filterPair);
+            /* Send out stats, and prep for the next if needed. */
+            char *msg = stats.getStatsLine();
+            if (filterPair) {
+                cli->sendMsg(ResponseCode::TetheringStatsResult, msg, false);
+                return 0;
+            } else {
+                cli->sendMsg(ResponseCode::TetheringStatsListResult, msg, false);
+                stats = filter;
+            }
+            free(msg);
+            statsFound++;
+        }
+    }
+
+    /* It is always an error to find only one side of the stats. */
+    /* It is an error to find nothing when not filtering. */
+    if (((stats.rxBytes == -1) != (stats.txBytes == -1)) ||
+        (!statsFound && !filterPair)) {
+        return -1;
+    }
+    cli->sendMsg(ResponseCode::CommandOkay, "Tethering stats list completed", false);
+    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);
+    return msg;
+}
+
+int BandwidthController::getTetherStats(SocketClient *cli, TetherStats &stats, std::string &extraProcessingInfo) {
+    int res;
+    std::string fullCmd;
+    FILE *iptOutput;
+
+    /*
+     * Why not use some kind of lib to talk to iptables?
+     * Because the only libs are libiptc and libip6tc in iptables, and they are
+     * not easy to use. They require the known iptables match modules to be
+     * preloaded/linked, and require apparently a lot of wrapper code to get
+     * the wanted info.
+     */
+    fullCmd = IPTABLES_PATH;
+    fullCmd += " -nvx -w -L ";
+    fullCmd += NatController::LOCAL_TETHER_COUNTERS_CHAIN;
+    iptOutput = popen(fullCmd.c_str(), "r");
+    if (!iptOutput) {
+            ALOGE("Failed to run %s err=%s", fullCmd.c_str(), strerror(errno));
+            extraProcessingInfo += "Failed to run iptables.";
+        return -1;
+    }
+    res = parseForwardChainStats(cli, stats, iptOutput, extraProcessingInfo);
+    pclose(iptOutput);
+
+    /* Currently NatController doesn't do ipv6 tethering, so we are done. */
+    return res;
+}
+
+void BandwidthController::flushExistingCostlyTables(bool doClean) {
+    std::string fullCmd;
+    FILE *iptOutput;
+
+    /* Only lookup ip4 table names as ip6 will have the same tables ... */
+    fullCmd = IPTABLES_PATH;
+    fullCmd += " -w -S";
+    iptOutput = popen(fullCmd.c_str(), "r");
+    if (!iptOutput) {
+            ALOGE("Failed to run %s err=%s", fullCmd.c_str(), strerror(errno));
+        return;
+    }
+    /* ... then flush/clean both ip4 and ip6 iptables. */
+    parseAndFlushCostlyTables(iptOutput, doClean);
+    pclose(iptOutput);
+}
+
+void BandwidthController::parseAndFlushCostlyTables(FILE *fp, bool doRemove) {
+    int res;
+    char lineBuffer[MAX_IPT_OUTPUT_LINE_LEN];
+    char costlyIfaceName[MAX_IPT_OUTPUT_LINE_LEN];
+    char cmd[MAX_CMD_LEN];
+    char *buffPtr;
+
+    while (NULL != (buffPtr = fgets(lineBuffer, MAX_IPT_OUTPUT_LINE_LEN, fp))) {
+        costlyIfaceName[0] = '\0';   /* So that debugging output always works */
+        res = sscanf(buffPtr, "-N bw_costly_%s", costlyIfaceName);
+        ALOGV("parse res=%d costly=<%s> orig line=<%s>", res,
+            costlyIfaceName, buffPtr);
+        if (res != 1) {
+            continue;
+        }
+        /* Exclusions: "shared" is not an ifacename */
+        if (!strcmp(costlyIfaceName, "shared")) {
+            continue;
+        }
+
+        snprintf(cmd, sizeof(cmd), "-F bw_costly_%s", costlyIfaceName);
+        runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
+        if (doRemove) {
+            snprintf(cmd, sizeof(cmd), "-X bw_costly_%s", costlyIfaceName);
+            runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
+        }
+    }
+}
diff --git a/netd/server/BandwidthController.h b/netd/server/BandwidthController.h
new file mode 100644
index 0000000..2aca2cd
--- /dev/null
+++ b/netd/server/BandwidthController.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2011 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 _BANDWIDTH_CONTROLLER_H
+#define _BANDWIDTH_CONTROLLER_H
+
+#include <list>
+#include <string>
+#include <utility>  // for pair
+
+#include <sysutils/SocketClient.h>
+
+class BandwidthController {
+public:
+    class TetherStats {
+    public:
+        TetherStats(void)
+                : rxBytes(-1), rxPackets(-1),
+                    txBytes(-1), txPackets(-1) {};
+        TetherStats(std::string intIfn, std::string extIfn,
+                int64_t rxB, int64_t rxP,
+                int64_t txB, int64_t txP)
+                        : intIface(intIfn), extIface(extIfn),
+                            rxBytes(rxB), rxPackets(rxP),
+                            txBytes(txB), txPackets(txP) {};
+        /* Internal interface. Same as NatController's notion. */
+        std::string intIface;
+        /* External interface. Same as NatController's notion. */
+        std::string extIface;
+        int64_t rxBytes, rxPackets;
+        int64_t txBytes, txPackets;
+        /*
+         * 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;
+    };
+
+    BandwidthController();
+
+    int setupIptablesHooks(void);
+
+    int enableBandwidthControl(bool force);
+    int disableBandwidthControl(void);
+
+    int setInterfaceSharedQuota(const char *iface, int64_t bytes);
+    int getInterfaceSharedQuota(int64_t *bytes);
+    int removeInterfaceSharedQuota(const char *iface);
+
+    int setInterfaceQuota(const char *iface, int64_t bytes);
+    int getInterfaceQuota(const char *iface, int64_t *bytes);
+    int removeInterfaceQuota(const char *iface);
+
+    int enableHappyBox(void);
+    int disableHappyBox(void);
+    int addNaughtyApps(int numUids, char *appUids[]);
+    int removeNaughtyApps(int numUids, char *appUids[]);
+    int addNiceApps(int numUids, char *appUids[]);
+    int removeNiceApps(int numUids, char *appUids[]);
+
+    int setGlobalAlert(int64_t bytes);
+    int removeGlobalAlert(void);
+    int setGlobalAlertInForwardChain(void);
+    int removeGlobalAlertInForwardChain(void);
+
+    int setSharedAlert(int64_t bytes);
+    int removeSharedAlert(void);
+
+    int setInterfaceAlert(const char *iface, int64_t bytes);
+    int removeInterfaceAlert(const char *iface);
+
+    /*
+     * For single pair of ifaces, stats should have ifaceIn and ifaceOut initialized.
+     * For all pairs, stats should have ifaceIn=ifaceOut="".
+     * Sends out to the cli the single stat (TetheringStatsReluts) or a list of stats
+     * (TetheringStatsListResult+CommandOkay).
+     * Error is to be handled on the outside.
+     * It results in an error if invoked and no tethering counter rules exist.
+     */
+    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;
+
+protected:
+    class QuotaInfo {
+    public:
+      QuotaInfo(std::string ifn, int64_t q, int64_t a)
+              : ifaceName(ifn), quota(q), alert(a) {};
+        std::string ifaceName;
+        int64_t quota;
+        int64_t alert;
+    };
+
+    enum IptIpVer { IptIpV4, IptIpV6 };
+    enum IptOp { IptOpInsert, IptOpReplace, IptOpDelete, IptOpAppend };
+    enum IptJumpOp { IptJumpReject, IptJumpReturn, IptJumpNoAdd };
+    enum SpecialAppOp { SpecialAppOpAdd, SpecialAppOpRemove };
+    enum QuotaType { QuotaUnique, QuotaShared };
+    enum RunCmdErrHandling { RunCmdFailureBad, RunCmdFailureOk };
+#if LOG_NDEBUG
+    enum IptFailureLog { IptFailShow, IptFailHide };
+#else
+    enum IptFailureLog { IptFailShow, IptFailHide = IptFailShow };
+#endif
+
+    int manipulateSpecialApps(int numUids, char *appStrUids[],
+                               const char *chain,
+                               std::list<int /*appUid*/> &specialAppUids,
+                               IptJumpOp jumpHandling, SpecialAppOp appOp);
+    int manipulateNaughtyApps(int numUids, char *appStrUids[], SpecialAppOp appOp);
+    int manipulateNiceApps(int numUids, char *appStrUids[], SpecialAppOp appOp);
+
+    int prepCostlyIface(const char *ifn, QuotaType quotaType);
+    int cleanupCostlyIface(const char *ifn, QuotaType quotaType);
+
+    std::string makeIptablesSpecialAppCmd(IptOp op, int uid, const char *chain);
+    std::string makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota);
+
+    int runIptablesAlertCmd(IptOp op, const char *alertName, int64_t bytes);
+    int runIptablesAlertFwdCmd(IptOp op, const char *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,
+                               IptFailureLog failureHandling = IptFailShow);
+    static int runIptablesCmd(const char *cmd, IptJumpOp jumpHandling, IptIpVer iptIpVer,
+                              IptFailureLog failureHandling = IptFailShow);
+
+
+    // Provides strncpy() + check overflow.
+    static int StrncpyAndCheck(char *buffer, const char *src, size_t buffSize);
+
+    int updateQuota(const char *alertName, int64_t bytes);
+
+    int setCostlyAlert(const char *costName, int64_t bytes, int64_t *alertBytes);
+    int removeCostlyAlert(const char *costName, int64_t *alertBytes);
+
+    /*
+     * stats should never have only intIface initialized. Other 3 combos are ok.
+     * fp should be a file to the apropriate FORWARD chain of iptables rules.
+     * extraProcessingInfo: contains raw parsed data, and error info.
+     * This strongly requires that setup of the rules is in a specific order:
+     *  in:intIface out:extIface
+     *  in:extIface out:intIface
+     * and the rules are grouped in pairs when more that one tethering was setup.
+     */
+    static int parseForwardChainStats(SocketClient *cli, const TetherStats filter, FILE *fp,
+                                      std::string &extraProcessingInfo);
+
+    /*
+     * Attempt to find the bw_costly_* tables that need flushing,
+     * and flush them.
+     * If doClean then remove the tables also.
+     * Deals with both ip4 and ip6 tables.
+     */
+    void flushExistingCostlyTables(bool doClean);
+    static void parseAndFlushCostlyTables(FILE *fp, bool doRemove);
+
+    /*
+     * Attempt to flush our tables.
+     * If doClean then remove them also.
+     * Deals with both ip4 and ip6 tables.
+     */
+    void flushCleanTables(bool doClean);
+
+    /*------------------*/
+
+    std::list<std::string> sharedQuotaIfaces;
+    int64_t sharedQuotaBytes;
+    int64_t sharedAlertBytes;
+    int64_t globalAlertBytes;
+    /*
+     * This tracks the number of tethers setup.
+     * The FORWARD chain is updated in the following cases:
+     *  - The 1st time a globalAlert is setup and there are tethers setup.
+     *  - Anytime a globalAlert is removed and there are tethers setup.
+     *  - The 1st tether is setup and there is a globalAlert active.
+     *  - The last tether is removed and there is a globalAlert active.
+     */
+    int globalAlertTetherCount;
+
+    std::list<QuotaInfo> quotaIfaces;
+    std::list<int /*appUid*/> naughtyAppUids;
+    std::list<int /*appUid*/> niceAppUids;
+
+private:
+    static const char *IPT_FLUSH_COMMANDS[];
+    static const char *IPT_CLEANUP_COMMANDS[];
+    static const char *IPT_SETUP_COMMANDS[];
+    static const char *IPT_BASIC_ACCOUNTING_COMMANDS[];
+
+    /* Alphabetical */
+    static const char ALERT_GLOBAL_NAME[];
+    static const int  MAX_CMD_ARGS;
+    static const int  MAX_CMD_LEN;
+    static const int  MAX_IFACENAME_LEN;
+    static const int  MAX_IPT_OUTPUT_LINE_LEN;
+};
+
+#endif
diff --git a/netd/server/ClatdController.cpp b/netd/server/ClatdController.cpp
new file mode 100644
index 0000000..348a0dd
--- /dev/null
+++ b/netd/server/ClatdController.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2008 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 <map>
+#include <string>
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#define LOG_TAG "ClatdController"
+#include <cutils/log.h>
+
+#include <resolv_netid.h>
+
+#include "NetdConstants.h"
+#include "ClatdController.h"
+#include "Fwmark.h"
+#include "NetdConstants.h"
+#include "NetworkController.h"
+
+static const char* kClatdPath = "/system/bin/clatd";
+
+ClatdController::ClatdController(NetworkController* controller)
+        : mNetCtrl(controller) {
+}
+
+ClatdController::~ClatdController() {
+}
+
+// Returns the PID of the clatd running on interface |interface|, or 0 if clatd is not running on
+// |interface|.
+pid_t ClatdController::getClatdPid(char* interface) {
+    auto it = mClatdPids.find(interface);
+    return (it == mClatdPids.end() ? 0 : it->second);
+}
+
+int ClatdController::startClatd(char* interface) {
+    pid_t pid = getClatdPid(interface);
+
+    if (pid != 0) {
+        ALOGE("clatd pid=%d already started on %s", pid, interface);
+        errno = EBUSY;
+        return -1;
+    }
+
+    // Pass in the interface, a netid to use for DNS lookups, and a fwmark for outgoing packets.
+    unsigned netId = mNetCtrl->getNetworkForInterface(interface);
+    if (netId == NETID_UNSET) {
+        ALOGE("interface %s not assigned to any netId", interface);
+        errno = ENODEV;
+        return -1;
+    }
+
+    char netIdString[UINT32_STRLEN];
+    snprintf(netIdString, sizeof(netIdString), "%u", netId);
+
+    Fwmark fwmark;
+    fwmark.netId = netId;
+    fwmark.explicitlySelected = true;
+    fwmark.protectedFromVpn = true;
+    fwmark.permission = PERMISSION_SYSTEM;
+
+    char fwmarkString[UINT32_HEX_STRLEN];
+    snprintf(fwmarkString, sizeof(fwmarkString), "0x%x", fwmark.intValue);
+
+    ALOGD("starting clatd on %s", interface);
+
+    std::string progname("clatd-");
+    progname += interface;
+
+    if ((pid = fork()) < 0) {
+        ALOGE("fork failed (%s)", strerror(errno));
+        return -1;
+    }
+
+    if (!pid) {
+        char *args[] = {
+            (char *) progname.c_str(),
+            (char *) "-i",
+            interface,
+            (char *) "-n",
+            netIdString,
+            (char *) "-m",
+            fwmarkString,
+            NULL
+        };
+
+        if (execv(kClatdPath, args)) {
+            ALOGE("execv failed (%s)", strerror(errno));
+            _exit(1);
+        }
+        ALOGE("Should never get here!");
+        _exit(1);
+    } else {
+        mClatdPids[interface] = pid;
+        ALOGD("clatd started on %s", interface);
+    }
+
+    return 0;
+}
+
+int ClatdController::stopClatd(char* interface) {
+    pid_t pid = getClatdPid(interface);
+
+    if (pid == 0) {
+        ALOGE("clatd already stopped");
+        return -1;
+    }
+
+    ALOGD("Stopping clatd pid=%d on %s", pid, interface);
+
+    kill(pid, SIGTERM);
+    waitpid(pid, NULL, 0);
+    mClatdPids.erase(interface);
+
+    ALOGD("clatd on %s stopped", interface);
+
+    return 0;
+}
+
+bool ClatdController::isClatdStarted(char* interface) {
+    pid_t waitpid_status;
+    pid_t pid = getClatdPid(interface);
+    if (pid == 0) {
+        return false;
+    }
+    waitpid_status = waitpid(pid, NULL, WNOHANG);
+    if (waitpid_status != 0) {
+        mClatdPids.erase(interface);  // child exited, don't call waitpid on it again
+    }
+    return waitpid_status == 0; // 0 while child is running
+}
diff --git a/netd/server/ClatdController.h b/netd/server/ClatdController.h
new file mode 100644
index 0000000..1985836
--- /dev/null
+++ b/netd/server/ClatdController.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2008 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 _CLATD_CONTROLLER_H
+#define _CLATD_CONTROLLER_H
+
+#include <map>
+
+class NetworkController;
+
+class ClatdController {
+public:
+    explicit ClatdController(NetworkController* controller);
+    virtual ~ClatdController();
+
+    int startClatd(char *interface);
+    int stopClatd(char* interface);
+    bool isClatdStarted(char* interface);
+
+private:
+    NetworkController* const mNetCtrl;
+    std::map<std::string, pid_t> mClatdPids;
+    pid_t getClatdPid(char* interface);
+};
+
+#endif
diff --git a/netd/server/CleanSpec.mk b/netd/server/CleanSpec.mk
new file mode 100644
index 0000000..b84e1b6
--- /dev/null
+++ b/netd/server/CleanSpec.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2007 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/netd/server/CommandListener.cpp b/netd/server/CommandListener.cpp
new file mode 100644
index 0000000..47edd86
--- /dev/null
+++ b/netd/server/CommandListener.cpp
@@ -0,0 +1,1830 @@
+/*
+ * Copyright (C) 2008 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_NDEBUG 0
+
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <linux/if.h>
+#include <resolv_netid.h>
+#include <resolv_params.h>
+
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#define LOG_TAG "CommandListener"
+
+#include <cutils/log.h>
+#include <netutils/ifc.h>
+#include <sysutils/SocketClient.h>
+
+#include "CommandListener.h"
+#include "ResponseCode.h"
+#include "BandwidthController.h"
+#include "IdletimerController.h"
+#include "oem_iptables_hook.h"
+#include "NetdConstants.h"
+#include "FirewallController.h"
+#include "RouteController.h"
+#include "UidRanges.h"
+
+#include <string>
+#include <vector>
+
+namespace {
+
+const unsigned NUM_OEM_IDS = NetworkController::MAX_OEM_ID - NetworkController::MIN_OEM_ID + 1;
+
+Permission stringToPermission(const char* arg) {
+    if (!strcmp(arg, "NETWORK")) {
+        return PERMISSION_NETWORK;
+    }
+    if (!strcmp(arg, "SYSTEM")) {
+        return PERMISSION_SYSTEM;
+    }
+    return PERMISSION_NONE;
+}
+
+unsigned stringToNetId(const char* arg) {
+    if (!strcmp(arg, "local")) {
+        return NetworkController::LOCAL_NET_ID;
+    }
+    // OEM NetIds are "oem1", "oem2", .., "oem50".
+    if (!strncmp(arg, "oem", 3)) {
+        unsigned n = strtoul(arg + 3, NULL, 0);
+        if (1 <= n && n <= NUM_OEM_IDS) {
+            return NetworkController::MIN_OEM_ID + n;
+        }
+        return NETID_UNSET;
+    }
+    // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
+    return strtoul(arg, NULL, 0);
+}
+
+}  // namespace
+
+NetworkController *CommandListener::sNetCtrl = NULL;
+TetherController *CommandListener::sTetherCtrl = NULL;
+NatController *CommandListener::sNatCtrl = NULL;
+PppController *CommandListener::sPppCtrl = NULL;
+SoftapController *CommandListener::sSoftapCtrl = NULL;
+BandwidthController * CommandListener::sBandwidthCtrl = NULL;
+IdletimerController * CommandListener::sIdletimerCtrl = NULL;
+InterfaceController *CommandListener::sInterfaceCtrl = NULL;
+ResolverController *CommandListener::sResolverCtrl = NULL;
+FirewallController *CommandListener::sFirewallCtrl = NULL;
+ClatdController *CommandListener::sClatdCtrl = NULL;
+StrictController *CommandListener::sStrictCtrl = NULL;
+
+/**
+ * List of module chains to be created, along with explicit ordering. ORDERING
+ * IS CRITICAL, AND SHOULD BE TRIPLE-CHECKED WITH EACH CHANGE.
+ */
+static const char* FILTER_INPUT[] = {
+        // Bandwidth should always be early in input chain, to make sure we
+        // correctly count incoming traffic against data plan.
+        BandwidthController::LOCAL_INPUT,
+        FirewallController::LOCAL_INPUT,
+        NULL,
+};
+
+static const char* FILTER_FORWARD[] = {
+        OEM_IPTABLES_FILTER_FORWARD,
+        FirewallController::LOCAL_FORWARD,
+        BandwidthController::LOCAL_FORWARD,
+        NatController::LOCAL_FORWARD,
+        NULL,
+};
+
+static const char* FILTER_OUTPUT[] = {
+        OEM_IPTABLES_FILTER_OUTPUT,
+        FirewallController::LOCAL_OUTPUT,
+        StrictController::LOCAL_OUTPUT,
+        BandwidthController::LOCAL_OUTPUT,
+        NULL,
+};
+
+static const char* RAW_PREROUTING[] = {
+        BandwidthController::LOCAL_RAW_PREROUTING,
+        IdletimerController::LOCAL_RAW_PREROUTING,
+        NULL,
+};
+
+static const char* MANGLE_POSTROUTING[] = {
+        BandwidthController::LOCAL_MANGLE_POSTROUTING,
+        IdletimerController::LOCAL_MANGLE_POSTROUTING,
+        NULL,
+};
+
+static const char* MANGLE_FORWARD[] = {
+        NatController::LOCAL_MANGLE_FORWARD,
+        NULL,
+};
+
+static const char* NAT_PREROUTING[] = {
+        OEM_IPTABLES_NAT_PREROUTING,
+        NULL,
+};
+
+static const char* NAT_POSTROUTING[] = {
+        NatController::LOCAL_NAT_POSTROUTING,
+        NULL,
+};
+
+static void createChildChains(IptablesTarget target, const char* table, const char* parentChain,
+        const char** childChains) {
+    const char** childChain = childChains;
+    do {
+        // Order is important:
+        // -D to delete any pre-existing jump rule (removes references
+        //    that would prevent -X from working)
+        // -F to flush any existing chain
+        // -X to delete any existing chain
+        // -N to create the chain
+        // -A to append the chain to parent
+
+        execIptablesSilently(target, "-t", table, "-D", parentChain, "-j", *childChain, NULL);
+        execIptablesSilently(target, "-t", table, "-F", *childChain, NULL);
+        execIptablesSilently(target, "-t", table, "-X", *childChain, NULL);
+        execIptables(target, "-t", table, "-N", *childChain, NULL);
+        execIptables(target, "-t", table, "-A", parentChain, "-j", *childChain, NULL);
+    } while (*(++childChain) != NULL);
+}
+
+CommandListener::CommandListener() :
+                 FrameworkListener("netd", true) {
+    registerCmd(new InterfaceCmd());
+    registerCmd(new IpFwdCmd());
+    registerCmd(new TetherCmd());
+    registerCmd(new NatCmd());
+    registerCmd(new ListTtysCmd());
+    registerCmd(new PppdCmd());
+    registerCmd(new SoftapCmd());
+    registerCmd(new BandwidthControlCmd());
+    registerCmd(new IdletimerControlCmd());
+    registerCmd(new ResolverCmd());
+    registerCmd(new FirewallCmd());
+    registerCmd(new ClatdCmd());
+    registerCmd(new NetworkCommand());
+    registerCmd(new StrictCmd());
+
+    if (!sNetCtrl)
+        sNetCtrl = new NetworkController();
+    if (!sTetherCtrl)
+        sTetherCtrl = new TetherController();
+    if (!sNatCtrl)
+        sNatCtrl = new NatController();
+    if (!sPppCtrl)
+        sPppCtrl = new PppController();
+    if (!sSoftapCtrl)
+        sSoftapCtrl = new SoftapController();
+    if (!sBandwidthCtrl)
+        sBandwidthCtrl = new BandwidthController();
+    if (!sIdletimerCtrl)
+        sIdletimerCtrl = new IdletimerController();
+    if (!sResolverCtrl)
+        sResolverCtrl = new ResolverController();
+    if (!sFirewallCtrl)
+        sFirewallCtrl = new FirewallController();
+    if (!sInterfaceCtrl)
+        sInterfaceCtrl = new InterfaceController();
+    if (!sClatdCtrl)
+        sClatdCtrl = new ClatdController(sNetCtrl);
+    if (!sStrictCtrl)
+        sStrictCtrl = new StrictController();
+
+    /*
+     * This is the only time we touch top-level chains in iptables; controllers
+     * should only mutate rules inside of their children chains, as created by
+     * the constants above.
+     *
+     * Modules should never ACCEPT packets (except in well-justified cases);
+     * they should instead defer to any remaining modules using RETURN, or
+     * otherwise DROP/REJECT.
+     */
+
+    // Create chains for children modules
+    createChildChains(V4V6, "filter", "INPUT", FILTER_INPUT);
+    createChildChains(V4V6, "filter", "FORWARD", FILTER_FORWARD);
+    createChildChains(V4V6, "filter", "OUTPUT", FILTER_OUTPUT);
+    createChildChains(V4V6, "raw", "PREROUTING", RAW_PREROUTING);
+    createChildChains(V4V6, "mangle", "POSTROUTING", MANGLE_POSTROUTING);
+    createChildChains(V4, "mangle", "FORWARD", MANGLE_FORWARD);
+    createChildChains(V4, "nat", "PREROUTING", NAT_PREROUTING);
+    createChildChains(V4, "nat", "POSTROUTING", NAT_POSTROUTING);
+
+    // Let each module setup their child chains
+    setupOemIptablesHook();
+
+    /* When enabled, DROPs all packets except those matching rules. */
+    sFirewallCtrl->setupIptablesHooks();
+
+    /* Does DROPs in FORWARD by default */
+    sNatCtrl->setupIptablesHooks();
+    /*
+     * Does REJECT in INPUT, OUTPUT. Does counting also.
+     * No DROP/REJECT allowed later in netfilter-flow hook order.
+     */
+    sBandwidthCtrl->setupIptablesHooks();
+    /*
+     * Counts in nat: PREROUTING, POSTROUTING.
+     * No DROP/REJECT allowed later in netfilter-flow hook order.
+     */
+    sIdletimerCtrl->setupIptablesHooks();
+
+    sBandwidthCtrl->enableBandwidthControl(false);
+
+    if (int ret = RouteController::Init(NetworkController::LOCAL_NET_ID)) {
+        ALOGE("failed to initialize RouteController (%s)", strerror(-ret));
+    }
+}
+
+CommandListener::InterfaceCmd::InterfaceCmd() :
+                 NetdCommand("interface") {
+}
+
+int CommandListener::InterfaceCmd::runCommand(SocketClient *cli,
+                                                      int argc, char **argv) {
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "list")) {
+        DIR *d;
+        struct dirent *de;
+
+        if (!(d = opendir("/sys/class/net"))) {
+            cli->sendMsg(ResponseCode::OperationFailed, "Failed to open sysfs dir", true);
+            return 0;
+        }
+
+        while((de = readdir(d))) {
+            if (de->d_name[0] == '.')
+                continue;
+            cli->sendMsg(ResponseCode::InterfaceListResult, de->d_name, false);
+        }
+        closedir(d);
+        cli->sendMsg(ResponseCode::CommandOkay, "Interface list completed", false);
+        return 0;
+    } else {
+        /*
+         * These commands take a minimum of 3 arguments
+         */
+        if (argc < 3) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+            return 0;
+        }
+
+        if (!strcmp(argv[1], "getcfg")) {
+            struct in_addr addr;
+            int prefixLength;
+            unsigned char hwaddr[6];
+            unsigned flags = 0;
+
+            ifc_init();
+            memset(hwaddr, 0, sizeof(hwaddr));
+
+            if (ifc_get_info(argv[2], &addr.s_addr, &prefixLength, &flags)) {
+                cli->sendMsg(ResponseCode::OperationFailed, "Interface not found", true);
+                ifc_close();
+                return 0;
+            }
+
+            if (ifc_get_hwaddr(argv[2], (void *) hwaddr)) {
+                ALOGW("Failed to retrieve HW addr for %s (%s)", argv[2], strerror(errno));
+            }
+
+            char *addr_s = strdup(inet_ntoa(addr));
+            const char *updown, *brdcst, *loopbk, *ppp, *running, *multi;
+
+            updown =  (flags & IFF_UP)           ? "up" : "down";
+            brdcst =  (flags & IFF_BROADCAST)    ? " broadcast" : "";
+            loopbk =  (flags & IFF_LOOPBACK)     ? " loopback" : "";
+            ppp =     (flags & IFF_POINTOPOINT)  ? " point-to-point" : "";
+            running = (flags & IFF_RUNNING)      ? " running" : "";
+            multi =   (flags & IFF_MULTICAST)    ? " multicast" : "";
+
+            char *flag_s;
+
+            asprintf(&flag_s, "%s%s%s%s%s%s", updown, brdcst, loopbk, ppp, running, multi);
+
+            char *msg = NULL;
+            asprintf(&msg, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x %s %d %s",
+                     hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5],
+                     addr_s, prefixLength, flag_s);
+
+            cli->sendMsg(ResponseCode::InterfaceGetCfgResult, msg, false);
+
+            free(addr_s);
+            free(flag_s);
+            free(msg);
+
+            ifc_close();
+            return 0;
+        } else if (!strcmp(argv[1], "setcfg")) {
+            // arglist: iface [addr prefixLength] flags
+            if (argc < 4) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+                return 0;
+            }
+            ALOGD("Setting iface cfg");
+
+            struct in_addr addr;
+            int index = 5;
+
+            ifc_init();
+
+            if (!inet_aton(argv[3], &addr)) {
+                // Handle flags only case
+                index = 3;
+            } else {
+                if (ifc_set_addr(argv[2], addr.s_addr)) {
+                    cli->sendMsg(ResponseCode::OperationFailed, "Failed to set address", true);
+                    ifc_close();
+                    return 0;
+                }
+
+                // Set prefix length on a non zero address
+                if (addr.s_addr != 0 && ifc_set_prefixLength(argv[2], atoi(argv[4]))) {
+                   cli->sendMsg(ResponseCode::OperationFailed, "Failed to set prefixLength", true);
+                   ifc_close();
+                   return 0;
+               }
+            }
+
+            /* Process flags */
+            for (int i = index; i < argc; i++) {
+                char *flag = argv[i];
+                if (!strcmp(flag, "up")) {
+                    ALOGD("Trying to bring up %s", argv[2]);
+                    if (ifc_up(argv[2])) {
+                        ALOGE("Error upping interface");
+                        cli->sendMsg(ResponseCode::OperationFailed, "Failed to up interface", true);
+                        ifc_close();
+                        return 0;
+                    }
+                } else if (!strcmp(flag, "down")) {
+                    ALOGD("Trying to bring down %s", argv[2]);
+                    if (ifc_down(argv[2])) {
+                        ALOGE("Error downing interface");
+                        cli->sendMsg(ResponseCode::OperationFailed, "Failed to down interface", true);
+                        ifc_close();
+                        return 0;
+                    }
+                } else if (!strcmp(flag, "broadcast")) {
+                    // currently ignored
+                } else if (!strcmp(flag, "multicast")) {
+                    // currently ignored
+                } else if (!strcmp(flag, "running")) {
+                    // currently ignored
+                } else if (!strcmp(flag, "loopback")) {
+                    // currently ignored
+                } else if (!strcmp(flag, "point-to-point")) {
+                    // currently ignored
+                } else {
+                    cli->sendMsg(ResponseCode::CommandParameterError, "Flag unsupported", false);
+                    ifc_close();
+                    return 0;
+                }
+            }
+
+            cli->sendMsg(ResponseCode::CommandOkay, "Interface configuration set", false);
+            ifc_close();
+            return 0;
+        } else if (!strcmp(argv[1], "clearaddrs")) {
+            // arglist: iface
+            ALOGD("Clearing all IP addresses on %s", argv[2]);
+
+            ifc_clear_addresses(argv[2]);
+
+            cli->sendMsg(ResponseCode::CommandOkay, "Interface IP addresses cleared", false);
+            return 0;
+        } else if (!strcmp(argv[1], "ipv6privacyextensions")) {
+            if (argc != 4) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError,
+                        "Usage: interface ipv6privacyextensions <interface> <enable|disable>",
+                        false);
+                return 0;
+            }
+            int enable = !strncmp(argv[3], "enable", 7);
+            if (sInterfaceCtrl->setIPv6PrivacyExtensions(argv[2], enable) == 0) {
+                cli->sendMsg(ResponseCode::CommandOkay, "IPv6 privacy extensions changed", false);
+            } else {
+                cli->sendMsg(ResponseCode::OperationFailed,
+                        "Failed to set ipv6 privacy extensions", true);
+            }
+            return 0;
+        } else if (!strcmp(argv[1], "ipv6")) {
+            if (argc != 4) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError,
+                        "Usage: interface ipv6 <interface> <enable|disable>",
+                        false);
+                return 0;
+            }
+
+            int enable = !strncmp(argv[3], "enable", 7);
+            if (sInterfaceCtrl->setEnableIPv6(argv[2], enable) == 0) {
+                cli->sendMsg(ResponseCode::CommandOkay, "IPv6 state changed", false);
+            } else {
+                cli->sendMsg(ResponseCode::OperationFailed,
+                        "Failed to change IPv6 state", true);
+            }
+            return 0;
+        } else if (!strcmp(argv[1], "ipv6ndoffload")) {
+            if (argc != 4) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError,
+                        "Usage: interface ipv6ndoffload <interface> <enable|disable>",
+                        false);
+                return 0;
+            }
+            int enable = !strncmp(argv[3], "enable", 7);
+            if (sInterfaceCtrl->setIPv6NdOffload(argv[2], enable) == 0) {
+                cli->sendMsg(ResponseCode::CommandOkay, "IPv6 ND offload changed", false);
+            } else {
+                cli->sendMsg(ResponseCode::OperationFailed,
+                        "Failed to change IPv6 ND offload state", true);
+            }
+            return 0;
+        } else if (!strcmp(argv[1], "setmtu")) {
+            if (argc != 4) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError,
+                        "Usage: interface setmtu <interface> <val>", false);
+                return 0;
+            }
+            if (sInterfaceCtrl->setMtu(argv[2], argv[3]) == 0) {
+                cli->sendMsg(ResponseCode::CommandOkay, "MTU changed", false);
+            } else {
+                cli->sendMsg(ResponseCode::OperationFailed,
+                        "Failed to set MTU", true);
+            }
+            return 0;
+        } else {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown interface cmd", false);
+            return 0;
+        }
+    }
+    return 0;
+}
+
+
+CommandListener::ListTtysCmd::ListTtysCmd() :
+                 NetdCommand("list_ttys") {
+}
+
+int CommandListener::ListTtysCmd::runCommand(SocketClient *cli,
+                                             int /* argc */, char ** /* argv */) {
+    TtyCollection *tlist = sPppCtrl->getTtyList();
+    TtyCollection::iterator it;
+
+    for (it = tlist->begin(); it != tlist->end(); ++it) {
+        cli->sendMsg(ResponseCode::TtyListResult, *it, false);
+    }
+
+    cli->sendMsg(ResponseCode::CommandOkay, "Ttys listed.", false);
+    return 0;
+}
+
+CommandListener::IpFwdCmd::IpFwdCmd() :
+                 NetdCommand("ipfwd") {
+}
+
+int CommandListener::IpFwdCmd::runCommand(SocketClient *cli, int argc, char **argv) {
+    bool matched = false;
+    bool success;
+
+    if (argc == 2) {
+        //   0     1
+        // ipfwd status
+        if (!strcmp(argv[1], "status")) {
+            char *tmp = NULL;
+
+            asprintf(&tmp, "Forwarding %s",
+                     ((sTetherCtrl->forwardingRequestCount() > 0) ? "enabled" : "disabled"));
+            cli->sendMsg(ResponseCode::IpFwdStatusResult, tmp, false);
+            free(tmp);
+            return 0;
+        }
+    } else if (argc == 3) {
+        //  0      1         2
+        // ipfwd enable  <requester>
+        // ipfwd disable <requester>
+        if (!strcmp(argv[1], "enable")) {
+            matched = true;
+            success = sTetherCtrl->enableForwarding(argv[2]);
+        } else if (!strcmp(argv[1], "disable")) {
+            matched = true;
+            success = sTetherCtrl->disableForwarding(argv[2]);
+        }
+    } else if (argc == 4) {
+        //  0      1      2     3
+        // ipfwd  add   wlan0 dummy0
+        // ipfwd remove wlan0 dummy0
+        int ret = 0;
+        if (!strcmp(argv[1], "add")) {
+            matched = true;
+            ret = RouteController::enableTethering(argv[2], argv[3]);
+        } else if (!strcmp(argv[1], "remove")) {
+            matched = true;
+            ret = RouteController::disableTethering(argv[2], argv[3]);
+        }
+        success = (ret == 0);
+        errno = -ret;
+    }
+
+    if (!matched) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown ipfwd cmd", false);
+        return 0;
+    }
+
+    if (success) {
+        cli->sendMsg(ResponseCode::CommandOkay, "ipfwd operation succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "ipfwd operation failed", true);
+    }
+    return 0;
+}
+
+CommandListener::TetherCmd::TetherCmd() :
+                 NetdCommand("tether") {
+}
+
+int CommandListener::TetherCmd::runCommand(SocketClient *cli,
+                                                      int argc, char **argv) {
+    int rc = 0;
+
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "stop")) {
+        rc = sTetherCtrl->stopTethering();
+    } else if (!strcmp(argv[1], "status")) {
+        char *tmp = NULL;
+
+        asprintf(&tmp, "Tethering services %s",
+                 (sTetherCtrl->isTetheringStarted() ? "started" : "stopped"));
+        cli->sendMsg(ResponseCode::TetherStatusResult, tmp, false);
+        free(tmp);
+        return 0;
+    } else if (argc == 3) {
+        if (!strcmp(argv[1], "interface") && !strcmp(argv[2], "list")) {
+            InterfaceCollection *ilist = sTetherCtrl->getTetheredInterfaceList();
+            InterfaceCollection::iterator it;
+            for (it = ilist->begin(); it != ilist->end(); ++it) {
+                cli->sendMsg(ResponseCode::TetherInterfaceListResult, *it, false);
+            }
+        } else if (!strcmp(argv[1], "dns") && !strcmp(argv[2], "list")) {
+            char netIdStr[UINT32_STRLEN];
+            snprintf(netIdStr, sizeof(netIdStr), "%u", sTetherCtrl->getDnsNetId());
+            cli->sendMsg(ResponseCode::TetherDnsFwdNetIdResult, netIdStr, false);
+
+            NetAddressCollection *dlist = sTetherCtrl->getDnsForwarders();
+            NetAddressCollection::iterator it;
+
+            for (it = dlist->begin(); it != dlist->end(); ++it) {
+                cli->sendMsg(ResponseCode::TetherDnsFwdTgtListResult, inet_ntoa(*it), false);
+            }
+        }
+    } else {
+        /*
+         * These commands take a minimum of 4 arguments
+         */
+        if (argc < 4) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+            return 0;
+        }
+
+        if (!strcmp(argv[1], "start")) {
+            if (argc % 2 == 1) {
+                cli->sendMsg(ResponseCode::CommandSyntaxError, "Bad number of arguments", false);
+                return 0;
+            }
+
+            int num_addrs = argc - 2;
+            int arg_index = 2;
+            int array_index = 0;
+            in_addr *addrs = (in_addr *)malloc(sizeof(in_addr) * num_addrs);
+            while (array_index < num_addrs) {
+                if (!inet_aton(argv[arg_index++], &(addrs[array_index++]))) {
+                    cli->sendMsg(ResponseCode::CommandParameterError, "Invalid address", false);
+                    free(addrs);
+                    return 0;
+                }
+            }
+            rc = sTetherCtrl->startTethering(num_addrs, addrs);
+            free(addrs);
+        } else if (!strcmp(argv[1], "interface")) {
+            if (!strcmp(argv[2], "add")) {
+                rc = sTetherCtrl->tetherInterface(argv[3]);
+            } else if (!strcmp(argv[2], "remove")) {
+                rc = sTetherCtrl->untetherInterface(argv[3]);
+            /* else if (!strcmp(argv[2], "list")) handled above */
+            } else {
+                cli->sendMsg(ResponseCode::CommandParameterError,
+                             "Unknown tether interface operation", false);
+                return 0;
+            }
+        } else if (!strcmp(argv[1], "dns")) {
+            if (!strcmp(argv[2], "set")) {
+                if (argc < 5) {
+                    cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+                    return 0;
+                }
+                unsigned netId = stringToNetId(argv[3]);
+                rc = sTetherCtrl->setDnsForwarders(netId, &argv[4], argc - 4);
+            /* else if (!strcmp(argv[2], "list")) handled above */
+            } else {
+                cli->sendMsg(ResponseCode::CommandParameterError,
+                             "Unknown tether interface operation", false);
+                return 0;
+            }
+        } else {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown tether cmd", false);
+            return 0;
+        }
+    }
+
+    if (!rc) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Tether operation succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Tether operation failed", true);
+    }
+
+    return 0;
+}
+
+CommandListener::NatCmd::NatCmd() :
+                 NetdCommand("nat") {
+}
+
+int CommandListener::NatCmd::runCommand(SocketClient *cli,
+                                                      int argc, char **argv) {
+    int rc = 0;
+
+    if (argc < 5) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    //  0     1       2        3
+    // nat  enable intiface extiface
+    // nat disable intiface extiface
+    if (!strcmp(argv[1], "enable") && argc >= 4) {
+        rc = sNatCtrl->enableNat(argv[2], argv[3]);
+        if(!rc) {
+            /* Ignore ifaces for now. */
+            rc = sBandwidthCtrl->setGlobalAlertInForwardChain();
+        }
+    } else if (!strcmp(argv[1], "disable") && argc >= 4) {
+        /* Ignore ifaces for now. */
+        rc = sBandwidthCtrl->removeGlobalAlertInForwardChain();
+        rc |= sNatCtrl->disableNat(argv[2], argv[3]);
+    } else {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown nat cmd", false);
+        return 0;
+    }
+
+    if (!rc) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Nat operation succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Nat operation failed", true);
+    }
+
+    return 0;
+}
+
+CommandListener::PppdCmd::PppdCmd() :
+                 NetdCommand("pppd") {
+}
+
+int CommandListener::PppdCmd::runCommand(SocketClient *cli,
+                                                      int argc, char **argv) {
+    int rc = 0;
+
+    if (argc < 3) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "attach")) {
+        struct in_addr l, r, dns1, dns2;
+
+        memset(&dns1, 0, sizeof(struct in_addr));
+        memset(&dns2, 0, sizeof(struct in_addr));
+
+        if (!inet_aton(argv[3], &l)) {
+            cli->sendMsg(ResponseCode::CommandParameterError, "Invalid local address", false);
+            return 0;
+        }
+        if (!inet_aton(argv[4], &r)) {
+            cli->sendMsg(ResponseCode::CommandParameterError, "Invalid remote address", false);
+            return 0;
+        }
+        if ((argc > 3) && (!inet_aton(argv[5], &dns1))) {
+            cli->sendMsg(ResponseCode::CommandParameterError, "Invalid dns1 address", false);
+            return 0;
+        }
+        if ((argc > 4) && (!inet_aton(argv[6], &dns2))) {
+            cli->sendMsg(ResponseCode::CommandParameterError, "Invalid dns2 address", false);
+            return 0;
+        }
+        rc = sPppCtrl->attachPppd(argv[2], l, r, dns1, dns2);
+    } else if (!strcmp(argv[1], "detach")) {
+        rc = sPppCtrl->detachPppd(argv[2]);
+    } else {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown pppd cmd", false);
+        return 0;
+    }
+
+    if (!rc) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Pppd operation succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Pppd operation failed", true);
+    }
+
+    return 0;
+}
+
+CommandListener::SoftapCmd::SoftapCmd() :
+                 NetdCommand("softap") {
+}
+
+int CommandListener::SoftapCmd::runCommand(SocketClient *cli,
+                                        int argc, char **argv) {
+    int rc = ResponseCode::SoftapStatusResult;
+    char *retbuf = NULL;
+
+    if (sSoftapCtrl == NULL) {
+      cli->sendMsg(ResponseCode::ServiceStartFailed, "SoftAP is not available", false);
+      return -1;
+    }
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError,
+                     "Missing argument in a SoftAP command", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "startap")) {
+        rc = sSoftapCtrl->startSoftap();
+    } else if (!strcmp(argv[1], "stopap")) {
+        rc = sSoftapCtrl->stopSoftap();
+    } else if (!strcmp(argv[1], "fwreload")) {
+        rc = sSoftapCtrl->fwReloadSoftap(argc, argv);
+    } else if (!strcmp(argv[1], "status")) {
+        asprintf(&retbuf, "Softap service %s running",
+                 (sSoftapCtrl->isSoftapStarted() ? "is" : "is not"));
+        cli->sendMsg(rc, retbuf, false);
+        free(retbuf);
+        return 0;
+    } else if (!strcmp(argv[1], "set")) {
+        rc = sSoftapCtrl->setSoftap(argc, argv);
+    } else {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unrecognized SoftAP command", false);
+        return 0;
+    }
+
+    if (rc >= 400 && rc < 600)
+      cli->sendMsg(rc, "SoftAP command has failed", false);
+    else
+      cli->sendMsg(rc, "Ok", false);
+
+    return 0;
+}
+
+CommandListener::ResolverCmd::ResolverCmd() :
+        NetdCommand("resolver") {
+}
+
+int CommandListener::ResolverCmd::runCommand(SocketClient *cli, int argc, char **margv) {
+    int rc = 0;
+    const char **argv = const_cast<const char **>(margv);
+
+    if (argc < 3) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Resolver missing arguments", false);
+        return 0;
+    }
+
+    unsigned netId = stringToNetId(argv[2]);
+    // TODO: Consider making NetworkController.isValidNetwork() public
+    // and making that check here.
+
+    if (!strcmp(argv[1], "setnetdns")) {
+        // "resolver setnetdns <netId> <domains> <dns servers> [<params>]"
+        if (!parseAndExecuteSetNetDns(netId, argc, argv)) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                    "Wrong number of or invalid arguments to resolver setnetdns", false);
+            return 0;
+        }
+    } else if (!strcmp(argv[1], "clearnetdns")) { // "resolver clearnetdns <netId>"
+        if (argc == 3) {
+            rc = sResolverCtrl->clearDnsServers(netId);
+        } else {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                    "Wrong number of arguments to resolver clearnetdns", false);
+            return 0;
+        }
+    } else if (!strcmp(argv[1], "flushnet")) { // "resolver flushnet <netId>"
+        if (argc == 3) {
+            rc = sResolverCtrl->flushDnsCache(netId);
+        } else {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                    "Wrong number of arguments to resolver flushnet", false);
+            return 0;
+        }
+    } else {
+        cli->sendMsg(ResponseCode::CommandSyntaxError,"Resolver unknown command", false);
+        return 0;
+    }
+
+    if (!rc) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Resolver command succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Resolver command failed", true);
+    }
+
+    return 0;
+}
+
+bool CommandListener::ResolverCmd::parseAndExecuteSetNetDns(int netId, int argc,
+        const char** argv) {
+    // "resolver setnetdns <netId> <domains> <dns1> [<dns2> ...] [--params <params>]"
+    // TODO: This code has to be replaced by a Binder call ASAP
+    if (argc < 5) {
+        return false;
+    }
+    int end = argc;
+    __res_params params;
+    const __res_params* paramsPtr = nullptr;
+    if (end > 6 && !strcmp(argv[end - 2], "--params")) {
+        const char* paramsStr = argv[end - 1];
+        end -= 2;
+        if (sscanf(paramsStr, "%hu %hhu %hhu %hhu", &params.sample_validity,
+                &params.success_threshold, &params.min_samples, &params.max_samples) != 4) {
+            return false;
+        }
+        paramsPtr = &params;
+    }
+    return sResolverCtrl->setDnsServers(netId, argv[3], &argv[4], end - 4, paramsPtr) == 0;
+}
+
+CommandListener::BandwidthControlCmd::BandwidthControlCmd() :
+    NetdCommand("bandwidth") {
+}
+
+void CommandListener::BandwidthControlCmd::sendGenericSyntaxError(SocketClient *cli, const char *usageMsg) {
+    char *msg;
+    asprintf(&msg, "Usage: bandwidth %s", usageMsg);
+    cli->sendMsg(ResponseCode::CommandSyntaxError, msg, false);
+    free(msg);
+}
+
+void CommandListener::BandwidthControlCmd::sendGenericOkFail(SocketClient *cli, int cond) {
+    if (!cond) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Bandwidth command succeeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Bandwidth command failed", false);
+    }
+}
+
+void CommandListener::BandwidthControlCmd::sendGenericOpFailed(SocketClient *cli, const char *errMsg) {
+    cli->sendMsg(ResponseCode::OperationFailed, errMsg, false);
+}
+
+int CommandListener::BandwidthControlCmd::runCommand(SocketClient *cli, int argc, char **argv) {
+    if (argc < 2) {
+        sendGenericSyntaxError(cli, "<cmds> <args...>");
+        return 0;
+    }
+
+    ALOGV("bwctrlcmd: argc=%d %s %s ...", argc, argv[0], argv[1]);
+
+    if (!strcmp(argv[1], "enable")) {
+        int rc = sBandwidthCtrl->enableBandwidthControl(true);
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "disable")) {
+        int rc = sBandwidthCtrl->disableBandwidthControl();
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "removequota") || !strcmp(argv[1], "rq")) {
+        if (argc != 3) {
+            sendGenericSyntaxError(cli, "removequota <interface>");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->removeInterfaceSharedQuota(argv[2]);
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "getquota") || !strcmp(argv[1], "gq")) {
+        int64_t bytes;
+        if (argc != 2) {
+            sendGenericSyntaxError(cli, "getquota");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->getInterfaceSharedQuota(&bytes);
+        if (rc) {
+            sendGenericOpFailed(cli, "Failed to get quota");
+            return 0;
+        }
+
+        char *msg;
+        asprintf(&msg, "%" PRId64, bytes);
+        cli->sendMsg(ResponseCode::QuotaCounterResult, msg, false);
+        free(msg);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "getiquota") || !strcmp(argv[1], "giq")) {
+        int64_t bytes;
+        if (argc != 3) {
+            sendGenericSyntaxError(cli, "getiquota <iface>");
+            return 0;
+        }
+
+        int rc = sBandwidthCtrl->getInterfaceQuota(argv[2], &bytes);
+        if (rc) {
+            sendGenericOpFailed(cli, "Failed to get quota");
+            return 0;
+        }
+        char *msg;
+        asprintf(&msg, "%" PRId64, bytes);
+        cli->sendMsg(ResponseCode::QuotaCounterResult, msg, false);
+        free(msg);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "setquota") || !strcmp(argv[1], "sq")) {
+        if (argc != 4) {
+            sendGenericSyntaxError(cli, "setquota <interface> <bytes>");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->setInterfaceSharedQuota(argv[2], atoll(argv[3]));
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "setquotas") || !strcmp(argv[1], "sqs")) {
+        int rc;
+        if (argc < 4) {
+            sendGenericSyntaxError(cli, "setquotas <bytes> <interface> ...");
+            return 0;
+        }
+
+        for (int q = 3; argc >= 4; q++, argc--) {
+            rc = sBandwidthCtrl->setInterfaceSharedQuota(argv[q], atoll(argv[2]));
+            if (rc) {
+                char *msg;
+                asprintf(&msg, "bandwidth setquotas %s %s failed", argv[2], argv[q]);
+                cli->sendMsg(ResponseCode::OperationFailed,
+                             msg, false);
+                free(msg);
+                return 0;
+            }
+        }
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "removequotas") || !strcmp(argv[1], "rqs")) {
+        int rc;
+        if (argc < 3) {
+            sendGenericSyntaxError(cli, "removequotas <interface> ...");
+            return 0;
+        }
+
+        for (int q = 2; argc >= 3; q++, argc--) {
+            rc = sBandwidthCtrl->removeInterfaceSharedQuota(argv[q]);
+            if (rc) {
+                char *msg;
+                asprintf(&msg, "bandwidth removequotas %s failed", argv[q]);
+                cli->sendMsg(ResponseCode::OperationFailed,
+                             msg, false);
+                free(msg);
+                return 0;
+            }
+        }
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "removeiquota") || !strcmp(argv[1], "riq")) {
+        if (argc != 3) {
+            sendGenericSyntaxError(cli, "removeiquota <interface>");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->removeInterfaceQuota(argv[2]);
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "setiquota") || !strcmp(argv[1], "siq")) {
+        if (argc != 4) {
+            sendGenericSyntaxError(cli, "setiquota <interface> <bytes>");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->setInterfaceQuota(argv[2], atoll(argv[3]));
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "addnaughtyapps") || !strcmp(argv[1], "ana")) {
+        if (argc < 3) {
+            sendGenericSyntaxError(cli, "addnaughtyapps <appUid> ...");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->addNaughtyApps(argc - 2, argv + 2);
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+
+    }
+    if (!strcmp(argv[1], "removenaughtyapps") || !strcmp(argv[1], "rna")) {
+        if (argc < 3) {
+            sendGenericSyntaxError(cli, "removenaughtyapps <appUid> ...");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->removeNaughtyApps(argc - 2, argv + 2);
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "happybox")) {
+        if (argc < 3) {
+            sendGenericSyntaxError(cli, "happybox (enable | disable)");
+            return 0;
+        }
+        if (!strcmp(argv[2], "enable")) {
+            int rc = sBandwidthCtrl->enableHappyBox();
+            sendGenericOkFail(cli, rc);
+            return 0;
+
+        }
+        if (!strcmp(argv[2], "disable")) {
+            int rc = sBandwidthCtrl->disableHappyBox();
+            sendGenericOkFail(cli, rc);
+            return 0;
+        }
+        sendGenericSyntaxError(cli, "happybox (enable | disable)");
+        return 0;
+    }
+    if (!strcmp(argv[1], "addniceapps") || !strcmp(argv[1], "aha")) {
+        if (argc < 3) {
+            sendGenericSyntaxError(cli, "addniceapps <appUid> ...");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->addNiceApps(argc - 2, argv + 2);
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "removeniceapps") || !strcmp(argv[1], "rha")) {
+        if (argc < 3) {
+            sendGenericSyntaxError(cli, "removeniceapps <appUid> ...");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->removeNiceApps(argc - 2, argv + 2);
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "setglobalalert") || !strcmp(argv[1], "sga")) {
+        if (argc != 3) {
+            sendGenericSyntaxError(cli, "setglobalalert <bytes>");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->setGlobalAlert(atoll(argv[2]));
+        sendGenericOkFail(cli, rc);
+        return 0;
+    }
+    if (!strcmp(argv[1], "debugsettetherglobalalert") || !strcmp(argv[1], "dstga")) {
+        if (argc != 4) {
+            sendGenericSyntaxError(cli, "debugsettetherglobalalert <interface0> <interface1>");
+            return 0;
+        }
+        /* We ignore the interfaces for now. */
+        int rc = sBandwidthCtrl->setGlobalAlertInForwardChain();
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "removeglobalalert") || !strcmp(argv[1], "rga")) {
+        if (argc != 2) {
+            sendGenericSyntaxError(cli, "removeglobalalert");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->removeGlobalAlert();
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "debugremovetetherglobalalert") || !strcmp(argv[1], "drtga")) {
+        if (argc != 4) {
+            sendGenericSyntaxError(cli, "debugremovetetherglobalalert <interface0> <interface1>");
+            return 0;
+        }
+        /* We ignore the interfaces for now. */
+        int rc = sBandwidthCtrl->removeGlobalAlertInForwardChain();
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "setsharedalert") || !strcmp(argv[1], "ssa")) {
+        if (argc != 3) {
+            sendGenericSyntaxError(cli, "setsharedalert <bytes>");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->setSharedAlert(atoll(argv[2]));
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "removesharedalert") || !strcmp(argv[1], "rsa")) {
+        if (argc != 2) {
+            sendGenericSyntaxError(cli, "removesharedalert");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->removeSharedAlert();
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "setinterfacealert") || !strcmp(argv[1], "sia")) {
+        if (argc != 4) {
+            sendGenericSyntaxError(cli, "setinterfacealert <interface> <bytes>");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->setInterfaceAlert(argv[2], atoll(argv[3]));
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "removeinterfacealert") || !strcmp(argv[1], "ria")) {
+        if (argc != 3) {
+            sendGenericSyntaxError(cli, "removeinterfacealert <interface>");
+            return 0;
+        }
+        int rc = sBandwidthCtrl->removeInterfaceAlert(argv[2]);
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
+    if (!strcmp(argv[1], "gettetherstats") || !strcmp(argv[1], "gts")) {
+        BandwidthController::TetherStats tetherStats;
+        std::string extraProcessingInfo = "";
+        if (argc < 2 || argc > 4) {
+            sendGenericSyntaxError(cli, "gettetherstats [<intInterface> <extInterface>]");
+            return 0;
+        }
+        tetherStats.intIface = argc > 2 ? argv[2] : "";
+        tetherStats.extIface = argc > 3 ? argv[3] : "";
+        // No filtering requested and there are no interface pairs to lookup.
+        if (argc <= 2 && sNatCtrl->ifacePairList.empty()) {
+            cli->sendMsg(ResponseCode::CommandOkay, "Tethering stats list completed", false);
+            return 0;
+        }
+        int rc = sBandwidthCtrl->getTetherStats(cli, tetherStats, extraProcessingInfo);
+        if (rc) {
+                extraProcessingInfo.insert(0, "Failed to get tethering stats.\n");
+                sendGenericOpFailed(cli, extraProcessingInfo.c_str());
+                return 0;
+        }
+        return 0;
+
+    }
+
+    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown bandwidth cmd", false);
+    return 0;
+}
+
+CommandListener::IdletimerControlCmd::IdletimerControlCmd() :
+    NetdCommand("idletimer") {
+}
+
+int CommandListener::IdletimerControlCmd::runCommand(SocketClient *cli, int argc, char **argv) {
+  // TODO(ashish): Change the error statements
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    ALOGV("idletimerctrlcmd: argc=%d %s %s ...", argc, argv[0], argv[1]);
+
+    if (!strcmp(argv[1], "enable")) {
+      if (0 != sIdletimerCtrl->enableIdletimerControl()) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+      } else {
+        cli->sendMsg(ResponseCode::CommandOkay, "Enable success", false);
+      }
+      return 0;
+
+    }
+    if (!strcmp(argv[1], "disable")) {
+      if (0 != sIdletimerCtrl->disableIdletimerControl()) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+      } else {
+        cli->sendMsg(ResponseCode::CommandOkay, "Disable success", false);
+      }
+      return 0;
+    }
+    if (!strcmp(argv[1], "add")) {
+        if (argc != 5) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+            return 0;
+        }
+        if(0 != sIdletimerCtrl->addInterfaceIdletimer(
+                                        argv[2], atoi(argv[3]), argv[4])) {
+          cli->sendMsg(ResponseCode::OperationFailed, "Failed to add interface", false);
+        } else {
+          cli->sendMsg(ResponseCode::CommandOkay,  "Add success", false);
+        }
+        return 0;
+    }
+    if (!strcmp(argv[1], "remove")) {
+        if (argc != 5) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+            return 0;
+        }
+        // ashish: fixme timeout
+        if (0 != sIdletimerCtrl->removeInterfaceIdletimer(
+                                        argv[2], atoi(argv[3]), argv[4])) {
+          cli->sendMsg(ResponseCode::OperationFailed, "Failed to remove interface", false);
+        } else {
+          cli->sendMsg(ResponseCode::CommandOkay, "Remove success", false);
+        }
+        return 0;
+    }
+
+    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown idletimer cmd", false);
+    return 0;
+}
+
+CommandListener::FirewallCmd::FirewallCmd() :
+    NetdCommand("firewall") {
+}
+
+int CommandListener::FirewallCmd::sendGenericOkFail(SocketClient *cli, int cond) {
+    if (!cond) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Firewall command succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Firewall command failed", false);
+    }
+    return 0;
+}
+
+FirewallRule CommandListener::FirewallCmd::parseRule(const char* arg) {
+    if (!strcmp(arg, "allow")) {
+        return ALLOW;
+    } else if (!strcmp(arg, "deny")) {
+        return DENY;
+    } else {
+        ALOGE("failed to parse uid rule (%s)", arg);
+        return ALLOW;
+    }
+}
+
+FirewallType CommandListener::FirewallCmd::parseFirewallType(const char* arg) {
+    if (!strcmp(arg, "whitelist")) {
+        return WHITELIST;
+    } else if (!strcmp(arg, "blacklist")) {
+        return BLACKLIST;
+    } else {
+        ALOGE("failed to parse firewall type (%s)", arg);
+        return BLACKLIST;
+    }
+}
+
+ChildChain CommandListener::FirewallCmd::parseChildChain(const char* arg) {
+    if (!strcmp(arg, "dozable")) {
+        return DOZABLE;
+    } else if (!strcmp(arg, "standby")) {
+        return STANDBY;
+    } else if (!strcmp(arg, "none")) {
+        return NONE;
+    } else {
+        ALOGE("failed to parse child firewall chain (%s)", arg);
+        return INVALID_CHAIN;
+    }
+}
+
+int CommandListener::FirewallCmd::runCommand(SocketClient *cli, int argc,
+        char **argv) {
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing command", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "enable")) {
+        if (argc != 3) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                        "Usage: firewall enable <whitelist|blacklist>", false);
+            return 0;
+        }
+        FirewallType firewallType = parseFirewallType(argv[2]);
+
+        int res = sFirewallCtrl->enableFirewall(firewallType);
+        return sendGenericOkFail(cli, res);
+    }
+    if (!strcmp(argv[1], "disable")) {
+        int res = sFirewallCtrl->disableFirewall();
+        return sendGenericOkFail(cli, res);
+    }
+    if (!strcmp(argv[1], "is_enabled")) {
+        int res = sFirewallCtrl->isFirewallEnabled();
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "set_interface_rule")) {
+        if (argc != 4) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall set_interface_rule <rmnet0> <allow|deny>", false);
+            return 0;
+        }
+
+        const char* iface = argv[2];
+        FirewallRule rule = parseRule(argv[3]);
+
+        int res = sFirewallCtrl->setInterfaceRule(iface, rule);
+        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 = sFirewallCtrl->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 |= sFirewallCtrl->setEgressDestRule(addr, PROTOCOL_TCP, port, rule);
+        res |= sFirewallCtrl->setEgressDestRule(addr, PROTOCOL_UDP, port, rule);
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "set_uid_rule")) {
+        if (argc != 5) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall set_uid_rule <dozable|standby|none> <1000> <allow|deny>",
+                         false);
+            return 0;
+        }
+
+        ChildChain childChain = parseChildChain(argv[2]);
+        if (childChain == INVALID_CHAIN) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Invalid chain name. Valid names are: <dozable|standby|none>",
+                         false);
+            return 0;
+        }
+        int uid = atoi(argv[3]);
+        FirewallRule rule = parseRule(argv[4]);
+        int res = sFirewallCtrl->setUidRule(childChain, uid, rule);
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "enable_chain")) {
+        if (argc != 3) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall enable_chain <dozable|standby>",
+                         false);
+            return 0;
+        }
+
+        ChildChain childChain = parseChildChain(argv[2]);
+        int res = sFirewallCtrl->enableChildChains(childChain, true);
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "disable_chain")) {
+        if (argc != 3) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall disable_chain <dozable|standby>",
+                         false);
+            return 0;
+        }
+
+        ChildChain childChain = parseChildChain(argv[2]);
+        int res = sFirewallCtrl->enableChildChains(childChain, false);
+        return sendGenericOkFail(cli, res);
+    }
+
+    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown command", false);
+    return 0;
+}
+
+CommandListener::ClatdCmd::ClatdCmd() : NetdCommand("clatd") {
+}
+
+int CommandListener::ClatdCmd::runCommand(SocketClient *cli, int argc,
+                                                            char **argv) {
+    int rc = 0;
+    if (argc < 3) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "stop")) {
+        rc = sClatdCtrl->stopClatd(argv[2]);
+    } else if (!strcmp(argv[1], "status")) {
+        char *tmp = NULL;
+        asprintf(&tmp, "Clatd status: %s", (sClatdCtrl->isClatdStarted(argv[2]) ?
+                                            "started" : "stopped"));
+        cli->sendMsg(ResponseCode::ClatdStatusResult, tmp, false);
+        free(tmp);
+        return 0;
+    } else if (!strcmp(argv[1], "start")) {
+        rc = sClatdCtrl->startClatd(argv[2]);
+    } else {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown clatd cmd", false);
+        return 0;
+    }
+
+    if (!rc) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Clatd operation succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Clatd operation failed", false);
+    }
+
+    return 0;
+}
+
+CommandListener::StrictCmd::StrictCmd() :
+    NetdCommand("strict") {
+}
+
+int CommandListener::StrictCmd::sendGenericOkFail(SocketClient *cli, int cond) {
+    if (!cond) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Strict command succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Strict command failed", false);
+    }
+    return 0;
+}
+
+StrictPenalty CommandListener::StrictCmd::parsePenalty(const char* arg) {
+    if (!strcmp(arg, "reject")) {
+        return REJECT;
+    } else if (!strcmp(arg, "log")) {
+        return LOG;
+    } else if (!strcmp(arg, "accept")) {
+        return ACCEPT;
+    } else {
+        return INVALID;
+    }
+}
+
+int CommandListener::StrictCmd::runCommand(SocketClient *cli, int argc,
+        char **argv) {
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing command", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "enable")) {
+        int res = sStrictCtrl->enableStrict();
+        return sendGenericOkFail(cli, res);
+    }
+    if (!strcmp(argv[1], "disable")) {
+        int res = sStrictCtrl->disableStrict();
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "set_uid_cleartext_policy")) {
+        if (argc != 4) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: strict set_uid_cleartext_policy <uid> <accept|log|reject>",
+                         false);
+            return 0;
+        }
+
+        errno = 0;
+        unsigned long int uid = strtoul(argv[2], NULL, 0);
+        if (errno || uid > UID_MAX) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Invalid UID", false);
+            return 0;
+        }
+
+        StrictPenalty penalty = parsePenalty(argv[3]);
+        if (penalty == INVALID) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Invalid penalty argument", false);
+            return 0;
+        }
+
+        int res = sStrictCtrl->setUidCleartextPenalty((uid_t) uid, penalty);
+        return sendGenericOkFail(cli, res);
+    }
+
+    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown command", false);
+    return 0;
+}
+
+CommandListener::NetworkCommand::NetworkCommand() : NetdCommand("network") {
+}
+
+int CommandListener::NetworkCommand::syntaxError(SocketClient* client, const char* message) {
+    client->sendMsg(ResponseCode::CommandSyntaxError, message, false);
+    return 0;
+}
+
+int CommandListener::NetworkCommand::operationError(SocketClient* client, const char* message,
+                                                    int ret) {
+    errno = -ret;
+    client->sendMsg(ResponseCode::OperationFailed, message, true);
+    return 0;
+}
+
+int CommandListener::NetworkCommand::success(SocketClient* client) {
+    client->sendMsg(ResponseCode::CommandOkay, "success", false);
+    return 0;
+}
+
+int CommandListener::NetworkCommand::runCommand(SocketClient* client, int argc, char** argv) {
+    if (argc < 2) {
+        return syntaxError(client, "Missing argument");
+    }
+
+    //    0      1      2      3      4       5         6            7           8
+    // network route [legacy <uid>]  add   <netId> <interface> <destination> [nexthop]
+    // network route [legacy <uid>] remove <netId> <interface> <destination> [nexthop]
+    //
+    // nexthop may be either an IPv4/IPv6 address or one of "unreachable" or "throw".
+    if (!strcmp(argv[1], "route")) {
+        if (argc < 6 || argc > 9) {
+            return syntaxError(client, "Incorrect number of arguments");
+        }
+
+        int nextArg = 2;
+        bool legacy = false;
+        uid_t uid = 0;
+        if (!strcmp(argv[nextArg], "legacy")) {
+            ++nextArg;
+            legacy = true;
+            uid = strtoul(argv[nextArg++], NULL, 0);
+        }
+
+        bool add = false;
+        if (!strcmp(argv[nextArg], "add")) {
+            add = true;
+        } else if (strcmp(argv[nextArg], "remove")) {
+            return syntaxError(client, "Unknown argument");
+        }
+        ++nextArg;
+
+        if (argc < nextArg + 3 || argc > nextArg + 4) {
+            return syntaxError(client, "Incorrect number of arguments");
+        }
+
+        unsigned netId = stringToNetId(argv[nextArg++]);
+        const char* interface = argv[nextArg++];
+        const char* destination = argv[nextArg++];
+        const char* nexthop = argc > nextArg ? argv[nextArg] : NULL;
+
+        int ret;
+        if (add) {
+            ret = sNetCtrl->addRoute(netId, interface, destination, nexthop, legacy, uid);
+        } else {
+            ret = sNetCtrl->removeRoute(netId, interface, destination, nexthop, legacy, uid);
+        }
+        if (ret) {
+            return operationError(client, add ? "addRoute() failed" : "removeRoute() failed", ret);
+        }
+
+        return success(client);
+    }
+
+    //    0        1       2       3         4
+    // network interface  add   <netId> <interface>
+    // network interface remove <netId> <interface>
+    if (!strcmp(argv[1], "interface")) {
+        if (argc != 5) {
+            return syntaxError(client, "Missing argument");
+        }
+        unsigned netId = stringToNetId(argv[3]);
+        if (!strcmp(argv[2], "add")) {
+            if (int ret = sNetCtrl->addInterfaceToNetwork(netId, argv[4])) {
+                return operationError(client, "addInterfaceToNetwork() failed", ret);
+            }
+        } else if (!strcmp(argv[2], "remove")) {
+            if (int ret = sNetCtrl->removeInterfaceFromNetwork(netId, argv[4])) {
+                return operationError(client, "removeInterfaceFromNetwork() failed", ret);
+            }
+        } else {
+            return syntaxError(client, "Unknown argument");
+        }
+        return success(client);
+    }
+
+    //    0      1       2         3
+    // network create <netId> [permission]
+    //
+    //    0      1       2     3     4        5
+    // network create <netId> vpn <hasDns> <secure>
+    if (!strcmp(argv[1], "create")) {
+        if (argc < 3) {
+            return syntaxError(client, "Missing argument");
+        }
+        unsigned netId = stringToNetId(argv[2]);
+        if (argc == 6 && !strcmp(argv[3], "vpn")) {
+            bool hasDns = atoi(argv[4]);
+            bool secure = atoi(argv[5]);
+            if (int ret = sNetCtrl->createVirtualNetwork(netId, hasDns, secure)) {
+                return operationError(client, "createVirtualNetwork() failed", ret);
+            }
+        } else if (argc > 4) {
+            return syntaxError(client, "Unknown trailing argument(s)");
+        } else {
+            Permission permission = PERMISSION_NONE;
+            if (argc == 4) {
+                permission = stringToPermission(argv[3]);
+                if (permission == PERMISSION_NONE) {
+                    return syntaxError(client, "Unknown permission");
+                }
+            }
+            if (int ret = sNetCtrl->createPhysicalNetwork(netId, permission)) {
+                return operationError(client, "createPhysicalNetwork() failed", ret);
+            }
+        }
+        return success(client);
+    }
+
+    //    0       1       2
+    // network destroy <netId>
+    if (!strcmp(argv[1], "destroy")) {
+        if (argc != 3) {
+            return syntaxError(client, "Incorrect number of arguments");
+        }
+        unsigned netId = stringToNetId(argv[2]);
+        if (int ret = sNetCtrl->destroyNetwork(netId)) {
+            return operationError(client, "destroyNetwork() failed", ret);
+        }
+        return success(client);
+    }
+
+    //    0       1      2      3
+    // network default  set  <netId>
+    // network default clear
+    if (!strcmp(argv[1], "default")) {
+        if (argc < 3) {
+            return syntaxError(client, "Missing argument");
+        }
+        unsigned netId = NETID_UNSET;
+        if (!strcmp(argv[2], "set")) {
+            if (argc < 4) {
+                return syntaxError(client, "Missing netId");
+            }
+            netId = stringToNetId(argv[3]);
+        } else if (strcmp(argv[2], "clear")) {
+            return syntaxError(client, "Unknown argument");
+        }
+        if (int ret = sNetCtrl->setDefaultNetwork(netId)) {
+            return operationError(client, "setDefaultNetwork() failed", ret);
+        }
+        return success(client);
+    }
+
+    //    0        1         2      3        4          5
+    // network permission   user   set  <permission>  <uid> ...
+    // network permission   user  clear    <uid> ...
+    // network permission network  set  <permission> <netId> ...
+    // network permission network clear   <netId> ...
+    if (!strcmp(argv[1], "permission")) {
+        if (argc < 5) {
+            return syntaxError(client, "Missing argument");
+        }
+        int nextArg = 4;
+        Permission permission = PERMISSION_NONE;
+        if (!strcmp(argv[3], "set")) {
+            permission = stringToPermission(argv[4]);
+            if (permission == PERMISSION_NONE) {
+                return syntaxError(client, "Unknown permission");
+            }
+            nextArg = 5;
+        } else if (strcmp(argv[3], "clear")) {
+            return syntaxError(client, "Unknown argument");
+        }
+        if (nextArg == argc) {
+            return syntaxError(client, "Missing id");
+        }
+
+        bool userPermissions = !strcmp(argv[2], "user");
+        bool networkPermissions = !strcmp(argv[2], "network");
+        if (!userPermissions && !networkPermissions) {
+            return syntaxError(client, "Unknown argument");
+        }
+
+        std::vector<unsigned> ids;
+        for (; nextArg < argc; ++nextArg) {
+            if (userPermissions) {
+                char* endPtr;
+                unsigned id = strtoul(argv[nextArg], &endPtr, 0);
+                if (!*argv[nextArg] || *endPtr) {
+                    return syntaxError(client, "Invalid id");
+                }
+                ids.push_back(id);
+            } else {
+                // networkPermissions
+                ids.push_back(stringToNetId(argv[nextArg]));
+            }
+        }
+        if (userPermissions) {
+            sNetCtrl->setPermissionForUsers(permission, ids);
+        } else {
+            // networkPermissions
+            if (int ret = sNetCtrl->setPermissionForNetworks(permission, ids)) {
+                return operationError(client, "setPermissionForNetworks() failed", ret);
+            }
+        }
+
+        return success(client);
+    }
+
+    //    0      1     2       3           4
+    // network users  add   <netId> [<uid>[-<uid>]] ...
+    // network users remove <netId> [<uid>[-<uid>]] ...
+    if (!strcmp(argv[1], "users")) {
+        if (argc < 4) {
+            return syntaxError(client, "Missing argument");
+        }
+        unsigned netId = stringToNetId(argv[3]);
+        UidRanges uidRanges;
+        if (!uidRanges.parseFrom(argc - 4, argv + 4)) {
+            return syntaxError(client, "Invalid UIDs");
+        }
+        if (!strcmp(argv[2], "add")) {
+            if (int ret = sNetCtrl->addUsersToNetwork(netId, uidRanges)) {
+                return operationError(client, "addUsersToNetwork() failed", ret);
+            }
+        } else if (!strcmp(argv[2], "remove")) {
+            if (int ret = sNetCtrl->removeUsersFromNetwork(netId, uidRanges)) {
+                return operationError(client, "removeUsersFromNetwork() failed", ret);
+            }
+        } else {
+            return syntaxError(client, "Unknown argument");
+        }
+        return success(client);
+    }
+
+    //    0       1      2     3
+    // network protect allow <uid> ...
+    // network protect  deny <uid> ...
+    if (!strcmp(argv[1], "protect")) {
+        if (argc < 4) {
+            return syntaxError(client, "Missing argument");
+        }
+        std::vector<uid_t> uids;
+        for (int i = 3; i < argc; ++i) {
+            uids.push_back(strtoul(argv[i], NULL, 0));
+        }
+        if (!strcmp(argv[2], "allow")) {
+            sNetCtrl->allowProtect(uids);
+        } else if (!strcmp(argv[2], "deny")) {
+            sNetCtrl->denyProtect(uids);
+        } else {
+            return syntaxError(client, "Unknown argument");
+        }
+        return success(client);
+    }
+
+    return syntaxError(client, "Unknown argument");
+}
diff --git a/netd/server/CommandListener.h b/netd/server/CommandListener.h
new file mode 100644
index 0000000..6846323
--- /dev/null
+++ b/netd/server/CommandListener.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2008 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 _COMMANDLISTENER_H__
+#define _COMMANDLISTENER_H__
+
+#include <sysutils/FrameworkListener.h>
+
+#include "NetdCommand.h"
+#include "NetworkController.h"
+#include "TetherController.h"
+#include "NatController.h"
+#include "PppController.h"
+#include "SoftapController.h"
+#include "BandwidthController.h"
+#include "IdletimerController.h"
+#include "InterfaceController.h"
+#include "ResolverController.h"
+#include "FirewallController.h"
+#include "ClatdController.h"
+#include "StrictController.h"
+
+class CommandListener : public FrameworkListener {
+    static TetherController *sTetherCtrl;
+    static NatController *sNatCtrl;
+    static PppController *sPppCtrl;
+    static SoftapController *sSoftapCtrl;
+    static BandwidthController *sBandwidthCtrl;
+    static IdletimerController *sIdletimerCtrl;
+    static InterfaceController *sInterfaceCtrl;
+    static ResolverController *sResolverCtrl;
+    static FirewallController *sFirewallCtrl;
+    static ClatdController *sClatdCtrl;
+    static StrictController *sStrictCtrl;
+
+public:
+    static NetworkController *sNetCtrl;
+
+    CommandListener();
+    virtual ~CommandListener() {}
+
+private:
+
+    class SoftapCmd : public NetdCommand {
+    public:
+        SoftapCmd();
+        virtual ~SoftapCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
+    class InterfaceCmd : public NetdCommand {
+    public:
+        InterfaceCmd();
+        virtual ~InterfaceCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
+    class IpFwdCmd : public NetdCommand {
+    public:
+        IpFwdCmd();
+        virtual ~IpFwdCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
+    class TetherCmd : public NetdCommand {
+    public:
+        TetherCmd();
+        virtual ~TetherCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
+    class NatCmd : public NetdCommand {
+    public:
+        NatCmd();
+        virtual ~NatCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
+    class ListTtysCmd : public NetdCommand {
+    public:
+        ListTtysCmd();
+        virtual ~ListTtysCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
+    class PppdCmd : public NetdCommand {
+    public:
+        PppdCmd();
+        virtual ~PppdCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
+    class BandwidthControlCmd : public NetdCommand {
+    public:
+        BandwidthControlCmd();
+        virtual ~BandwidthControlCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    protected:
+        void sendGenericOkFail(SocketClient *cli, int cond);
+        void sendGenericOpFailed(SocketClient *cli, const char *errMsg);
+        void sendGenericSyntaxError(SocketClient *cli, const char *usageMsg);
+    };
+
+    class IdletimerControlCmd : public NetdCommand {
+    public:
+        IdletimerControlCmd();
+        virtual ~IdletimerControlCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
+    class ResolverCmd : public NetdCommand {
+    public:
+        ResolverCmd();
+        virtual ~ResolverCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+
+    private:
+        bool parseAndExecuteSetNetDns(int netId, int argc, const char** argv);
+    };
+
+    class FirewallCmd: public NetdCommand {
+    public:
+        FirewallCmd();
+        virtual ~FirewallCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    protected:
+        int sendGenericOkFail(SocketClient *cli, int cond);
+        static FirewallRule parseRule(const char* arg);
+        static FirewallType parseFirewallType(const char* arg);
+        static ChildChain parseChildChain(const char* arg);
+    };
+
+    class ClatdCmd : public NetdCommand {
+    public:
+        ClatdCmd();
+        virtual ~ClatdCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
+
+    class StrictCmd : public NetdCommand {
+    public:
+        StrictCmd();
+        virtual ~StrictCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    protected:
+        int sendGenericOkFail(SocketClient *cli, int cond);
+        static StrictPenalty parsePenalty(const char* arg);
+    };
+
+    class NetworkCommand : public NetdCommand {
+    public:
+        NetworkCommand();
+        virtual ~NetworkCommand() {}
+        int runCommand(SocketClient* client, int argc, char** argv);
+    private:
+        int syntaxError(SocketClient* cli, const char* message);
+        int operationError(SocketClient* cli, const char* message, int ret);
+        int success(SocketClient* cli);
+    };
+};
+
+#endif
diff --git a/netd/server/ConnmarkFlags.h b/netd/server/ConnmarkFlags.h
new file mode 100644
index 0000000..2bbefc0
--- /dev/null
+++ b/netd/server/ConnmarkFlags.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 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 _CONNMARK_FLAGS_H
+#define _CONNMARK_FLAGS_H
+
+/*
+ * iptables CONNMARK flag values used by various controllers. These values
+ * need to be stored in one place to avoid clashes.
+ */
+class ConnmarkFlags {
+public:
+    static const unsigned int STRICT_RESOLVED_ACCEPT = 0x01000000;
+    static const unsigned int STRICT_RESOLVED_REJECT = 0x02000000;
+};
+
+#endif
diff --git a/netd/server/DnsProxyListener.cpp b/netd/server/DnsProxyListener.cpp
new file mode 100644
index 0000000..6c71c5b
--- /dev/null
+++ b/netd/server/DnsProxyListener.cpp
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2010 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 <dirent.h>
+#include <errno.h>
+#include <linux/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <string.h>
+#include <pthread.h>
+#include <resolv_netid.h>
+#include <net/if.h>
+
+#define LOG_TAG "DnsProxyListener"
+#define DBG 0
+#define VDBG 0
+
+#include <cutils/log.h>
+#include <sysutils/SocketClient.h>
+
+#include "Fwmark.h"
+#include "DnsProxyListener.h"
+#include "NetdConstants.h"
+#include "NetworkController.h"
+#include "ResponseCode.h"
+
+DnsProxyListener::DnsProxyListener(const NetworkController* netCtrl) :
+        FrameworkListener("dnsproxyd"), mNetCtrl(netCtrl) {
+    registerCmd(new GetAddrInfoCmd(this));
+    registerCmd(new GetHostByAddrCmd(this));
+    registerCmd(new GetHostByNameCmd(this));
+}
+
+DnsProxyListener::GetAddrInfoHandler::GetAddrInfoHandler(
+        SocketClient *c, char* host, char* service, struct addrinfo* hints,
+        const struct android_net_context& netcontext)
+        : mClient(c),
+          mHost(host),
+          mService(service),
+          mHints(hints),
+          mNetContext(netcontext) {
+}
+
+DnsProxyListener::GetAddrInfoHandler::~GetAddrInfoHandler() {
+    free(mHost);
+    free(mService);
+    free(mHints);
+}
+
+void DnsProxyListener::GetAddrInfoHandler::start() {
+    pthread_t thread;
+    pthread_create(&thread, NULL,
+                   DnsProxyListener::GetAddrInfoHandler::threadStart, this);
+    pthread_detach(thread);
+}
+
+void* DnsProxyListener::GetAddrInfoHandler::threadStart(void* obj) {
+    GetAddrInfoHandler* handler = reinterpret_cast<GetAddrInfoHandler*>(obj);
+    handler->run();
+    delete handler;
+    pthread_exit(NULL);
+    return NULL;
+}
+
+static bool sendBE32(SocketClient* c, uint32_t data) {
+    uint32_t be_data = htonl(data);
+    return c->sendData(&be_data, sizeof(be_data)) == 0;
+}
+
+// Sends 4 bytes of big-endian length, followed by the data.
+// Returns true on success.
+static bool sendLenAndData(SocketClient* c, const int len, const void* data) {
+    return sendBE32(c, len) && (len == 0 || c->sendData(data, len) == 0);
+}
+
+// Returns true on success
+static bool sendhostent(SocketClient *c, struct hostent *hp) {
+    bool success = true;
+    int i;
+    if (hp->h_name != NULL) {
+        success &= sendLenAndData(c, strlen(hp->h_name)+1, hp->h_name);
+    } else {
+        success &= sendLenAndData(c, 0, "") == 0;
+    }
+
+    for (i=0; hp->h_aliases[i] != NULL; i++) {
+        success &= sendLenAndData(c, strlen(hp->h_aliases[i])+1, hp->h_aliases[i]);
+    }
+    success &= sendLenAndData(c, 0, ""); // null to indicate we're done
+
+    uint32_t buf = htonl(hp->h_addrtype);
+    success &= c->sendData(&buf, sizeof(buf)) == 0;
+
+    buf = htonl(hp->h_length);
+    success &= c->sendData(&buf, sizeof(buf)) == 0;
+
+    for (i=0; hp->h_addr_list[i] != NULL; i++) {
+        success &= sendLenAndData(c, 16, hp->h_addr_list[i]);
+    }
+    success &= sendLenAndData(c, 0, ""); // null to indicate we're done
+    return success;
+}
+
+static bool sendaddrinfo(SocketClient* c, struct addrinfo* ai) {
+    // struct addrinfo {
+    //      int     ai_flags;       /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
+    //      int     ai_family;      /* PF_xxx */
+    //      int     ai_socktype;    /* SOCK_xxx */
+    //      int     ai_protocol;    /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
+    //      socklen_t ai_addrlen;   /* length of ai_addr */
+    //      char    *ai_canonname;  /* canonical name for hostname */
+    //      struct  sockaddr *ai_addr;      /* binary address */
+    //      struct  addrinfo *ai_next;      /* next structure in linked list */
+    // };
+
+    // Write the struct piece by piece because we might be a 64-bit netd
+    // talking to a 32-bit process.
+    bool success =
+            sendBE32(c, ai->ai_flags) &&
+            sendBE32(c, ai->ai_family) &&
+            sendBE32(c, ai->ai_socktype) &&
+            sendBE32(c, ai->ai_protocol);
+    if (!success) {
+        return false;
+    }
+
+    // ai_addrlen and ai_addr.
+    if (!sendLenAndData(c, ai->ai_addrlen, ai->ai_addr)) {
+        return false;
+    }
+
+    // strlen(ai_canonname) and ai_canonname.
+    if (!sendLenAndData(c, ai->ai_canonname ? strlen(ai->ai_canonname) + 1 : 0, ai->ai_canonname)) {
+        return false;
+    }
+
+    return true;
+}
+
+void DnsProxyListener::GetAddrInfoHandler::run() {
+    if (DBG) {
+        ALOGD("GetAddrInfoHandler, now for %s / %s / {%u,%u,%u,%u,%u}", mHost, mService,
+                mNetContext.app_netid, mNetContext.app_mark,
+                mNetContext.dns_netid, mNetContext.dns_mark,
+                mNetContext.uid);
+    }
+
+    struct addrinfo* result = NULL;
+    uint32_t rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, &result);
+    if (rv) {
+        // getaddrinfo failed
+        mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, &rv, sizeof(rv));
+    } else {
+        bool success = !mClient->sendCode(ResponseCode::DnsProxyQueryResult);
+        struct addrinfo* ai = result;
+        while (ai && success) {
+            success = sendBE32(mClient, 1) && sendaddrinfo(mClient, ai);
+            ai = ai->ai_next;
+        }
+        success = success && sendBE32(mClient, 0);
+        if (!success) {
+            ALOGW("Error writing DNS result to client");
+        }
+    }
+    if (result) {
+        freeaddrinfo(result);
+    }
+    mClient->decRef();
+}
+
+DnsProxyListener::GetAddrInfoCmd::GetAddrInfoCmd(const DnsProxyListener* dnsProxyListener) :
+    NetdCommand("getaddrinfo"),
+    mDnsProxyListener(dnsProxyListener) {
+}
+
+int DnsProxyListener::GetAddrInfoCmd::runCommand(SocketClient *cli,
+                                            int argc, char **argv) {
+    if (DBG) {
+        for (int i = 0; i < argc; i++) {
+            ALOGD("argv[%i]=%s", i, argv[i]);
+        }
+    }
+    if (argc != 8) {
+        char* msg = NULL;
+        asprintf( &msg, "Invalid number of arguments to getaddrinfo: %i", argc);
+        ALOGW("%s", msg);
+        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
+        free(msg);
+        return -1;
+    }
+
+    char* name = argv[1];
+    if (strcmp("^", name) == 0) {
+        name = NULL;
+    } else {
+        name = strdup(name);
+    }
+
+    char* service = argv[2];
+    if (strcmp("^", service) == 0) {
+        service = NULL;
+    } else {
+        service = strdup(service);
+    }
+
+    struct addrinfo* hints = NULL;
+    int ai_flags = atoi(argv[3]);
+    int ai_family = atoi(argv[4]);
+    int ai_socktype = atoi(argv[5]);
+    int ai_protocol = atoi(argv[6]);
+    unsigned netId = strtoul(argv[7], NULL, 10);
+    uid_t uid = cli->getUid();
+
+    struct android_net_context netcontext;
+    mDnsProxyListener->mNetCtrl->getNetworkContext(netId, uid, &netcontext);
+
+    if (ai_flags != -1 || ai_family != -1 ||
+        ai_socktype != -1 || ai_protocol != -1) {
+        hints = (struct addrinfo*) calloc(1, sizeof(struct addrinfo));
+        hints->ai_flags = ai_flags;
+        hints->ai_family = ai_family;
+        hints->ai_socktype = ai_socktype;
+        hints->ai_protocol = ai_protocol;
+
+        // Only implement AI_ADDRCONFIG if application is using default network since our
+        // implementation only works on the default network.
+        if ((hints->ai_flags & AI_ADDRCONFIG) &&
+                netcontext.dns_netid != mDnsProxyListener->mNetCtrl->getDefaultNetwork()) {
+            hints->ai_flags &= ~AI_ADDRCONFIG;
+        }
+    }
+
+    if (DBG) {
+        ALOGD("GetAddrInfoHandler for %s / %s / {%u,%u,%u,%u,%u}",
+             name ? name : "[nullhost]",
+             service ? service : "[nullservice]",
+             netcontext.app_netid, netcontext.app_mark,
+             netcontext.dns_netid, netcontext.dns_mark,
+             netcontext.uid);
+    }
+
+    cli->incRef();
+    DnsProxyListener::GetAddrInfoHandler* handler =
+            new DnsProxyListener::GetAddrInfoHandler(cli, name, service, hints, netcontext);
+    handler->start();
+
+    return 0;
+}
+
+/*******************************************************
+ *                  GetHostByName                      *
+ *******************************************************/
+DnsProxyListener::GetHostByNameCmd::GetHostByNameCmd(const DnsProxyListener* dnsProxyListener) :
+      NetdCommand("gethostbyname"),
+      mDnsProxyListener(dnsProxyListener) {
+}
+
+int DnsProxyListener::GetHostByNameCmd::runCommand(SocketClient *cli,
+                                            int argc, char **argv) {
+    if (DBG) {
+        for (int i = 0; i < argc; i++) {
+            ALOGD("argv[%i]=%s", i, argv[i]);
+        }
+    }
+    if (argc != 4) {
+        char* msg = NULL;
+        asprintf(&msg, "Invalid number of arguments to gethostbyname: %i", argc);
+        ALOGW("%s", msg);
+        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
+        free(msg);
+        return -1;
+    }
+
+    uid_t uid = cli->getUid();
+    unsigned netId = strtoul(argv[1], NULL, 10);
+    char* name = argv[2];
+    int af = atoi(argv[3]);
+
+    if (strcmp(name, "^") == 0) {
+        name = NULL;
+    } else {
+        name = strdup(name);
+    }
+
+    uint32_t mark = mDnsProxyListener->mNetCtrl->getNetworkForDns(&netId, uid);
+
+    cli->incRef();
+    DnsProxyListener::GetHostByNameHandler* handler =
+            new DnsProxyListener::GetHostByNameHandler(cli, name, af, netId, mark);
+    handler->start();
+
+    return 0;
+}
+
+DnsProxyListener::GetHostByNameHandler::GetHostByNameHandler(SocketClient* c,
+                                                             char* name,
+                                                             int af,
+                                                             unsigned netId,
+                                                             uint32_t mark)
+        : mClient(c),
+          mName(name),
+          mAf(af),
+          mNetId(netId),
+          mMark(mark) {
+}
+
+DnsProxyListener::GetHostByNameHandler::~GetHostByNameHandler() {
+    free(mName);
+}
+
+void DnsProxyListener::GetHostByNameHandler::start() {
+    pthread_t thread;
+    pthread_create(&thread, NULL,
+            DnsProxyListener::GetHostByNameHandler::threadStart, this);
+    pthread_detach(thread);
+}
+
+void* DnsProxyListener::GetHostByNameHandler::threadStart(void* obj) {
+    GetHostByNameHandler* handler = reinterpret_cast<GetHostByNameHandler*>(obj);
+    handler->run();
+    delete handler;
+    pthread_exit(NULL);
+    return NULL;
+}
+
+void DnsProxyListener::GetHostByNameHandler::run() {
+    if (DBG) {
+        ALOGD("DnsProxyListener::GetHostByNameHandler::run\n");
+    }
+
+    struct hostent* hp;
+
+    hp = android_gethostbynamefornet(mName, mAf, mNetId, mMark);
+
+    if (DBG) {
+        ALOGD("GetHostByNameHandler::run gethostbyname errno: %s hp->h_name = %s, name_len = %zu\n",
+                hp ? "success" : strerror(errno),
+                (hp && hp->h_name) ? hp->h_name : "null",
+                (hp && hp->h_name) ? strlen(hp->h_name) + 1 : 0);
+    }
+
+    bool success = true;
+    if (hp) {
+        success = mClient->sendCode(ResponseCode::DnsProxyQueryResult) == 0;
+        success &= sendhostent(mClient, hp);
+    } else {
+        success = mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, NULL, 0) == 0;
+    }
+
+    if (!success) {
+        ALOGW("GetHostByNameHandler: Error writing DNS result to client\n");
+    }
+    mClient->decRef();
+}
+
+
+/*******************************************************
+ *                  GetHostByAddr                      *
+ *******************************************************/
+DnsProxyListener::GetHostByAddrCmd::GetHostByAddrCmd(const DnsProxyListener* dnsProxyListener) :
+        NetdCommand("gethostbyaddr"),
+        mDnsProxyListener(dnsProxyListener) {
+}
+
+int DnsProxyListener::GetHostByAddrCmd::runCommand(SocketClient *cli,
+                                            int argc, char **argv) {
+    if (DBG) {
+        for (int i = 0; i < argc; i++) {
+            ALOGD("argv[%i]=%s", i, argv[i]);
+        }
+    }
+    if (argc != 5) {
+        char* msg = NULL;
+        asprintf(&msg, "Invalid number of arguments to gethostbyaddr: %i", argc);
+        ALOGW("%s", msg);
+        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
+        free(msg);
+        return -1;
+    }
+
+    char* addrStr = argv[1];
+    int addrLen = atoi(argv[2]);
+    int addrFamily = atoi(argv[3]);
+    uid_t uid = cli->getUid();
+    unsigned netId = strtoul(argv[4], NULL, 10);
+
+    void* addr = malloc(sizeof(struct in6_addr));
+    errno = 0;
+    int result = inet_pton(addrFamily, addrStr, addr);
+    if (result <= 0) {
+        char* msg = NULL;
+        asprintf(&msg, "inet_pton(\"%s\") failed %s", addrStr, strerror(errno));
+        ALOGW("%s", msg);
+        cli->sendMsg(ResponseCode::OperationFailed, msg, false);
+        free(addr);
+        free(msg);
+        return -1;
+    }
+
+    uint32_t mark = mDnsProxyListener->mNetCtrl->getNetworkForDns(&netId, uid);
+
+    cli->incRef();
+    DnsProxyListener::GetHostByAddrHandler* handler =
+            new DnsProxyListener::GetHostByAddrHandler(cli, addr, addrLen, addrFamily, netId, mark);
+    handler->start();
+
+    return 0;
+}
+
+DnsProxyListener::GetHostByAddrHandler::GetHostByAddrHandler(SocketClient* c,
+                                                             void* address,
+                                                             int   addressLen,
+                                                             int   addressFamily,
+                                                             unsigned netId,
+                                                             uint32_t mark)
+        : mClient(c),
+          mAddress(address),
+          mAddressLen(addressLen),
+          mAddressFamily(addressFamily),
+          mNetId(netId),
+          mMark(mark) {
+}
+
+DnsProxyListener::GetHostByAddrHandler::~GetHostByAddrHandler() {
+    free(mAddress);
+}
+
+void DnsProxyListener::GetHostByAddrHandler::start() {
+    pthread_t thread;
+    pthread_create(&thread, NULL,
+                   DnsProxyListener::GetHostByAddrHandler::threadStart, this);
+    pthread_detach(thread);
+}
+
+void* DnsProxyListener::GetHostByAddrHandler::threadStart(void* obj) {
+    GetHostByAddrHandler* handler = reinterpret_cast<GetHostByAddrHandler*>(obj);
+    handler->run();
+    delete handler;
+    pthread_exit(NULL);
+    return NULL;
+}
+
+void DnsProxyListener::GetHostByAddrHandler::run() {
+    if (DBG) {
+        ALOGD("DnsProxyListener::GetHostByAddrHandler::run\n");
+    }
+    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);
+
+    if (DBG) {
+        ALOGD("GetHostByAddrHandler::run gethostbyaddr errno: %s hp->h_name = %s, name_len = %zu\n",
+                hp ? "success" : strerror(errno),
+                (hp && hp->h_name) ? hp->h_name : "null",
+                (hp && hp->h_name) ? strlen(hp->h_name) + 1 : 0);
+    }
+
+    bool success = true;
+    if (hp) {
+        success = mClient->sendCode(ResponseCode::DnsProxyQueryResult) == 0;
+        success &= sendhostent(mClient, hp);
+    } else {
+        success = mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, NULL, 0) == 0;
+    }
+
+    if (!success) {
+        ALOGW("GetHostByAddrHandler: Error writing DNS result to client\n");
+    }
+    mClient->decRef();
+}
diff --git a/netd/server/DnsProxyListener.h b/netd/server/DnsProxyListener.h
new file mode 100644
index 0000000..b67ad5c
--- /dev/null
+++ b/netd/server/DnsProxyListener.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 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 _DNSPROXYLISTENER_H__
+#define _DNSPROXYLISTENER_H__
+
+#include <resolv_netid.h>  // struct android_net_context
+#include <sysutils/FrameworkListener.h>
+
+#include "NetdCommand.h"
+
+class NetworkController;
+
+class DnsProxyListener : public FrameworkListener {
+public:
+    explicit DnsProxyListener(const NetworkController* netCtrl);
+    virtual ~DnsProxyListener() {}
+
+private:
+    const NetworkController *mNetCtrl;
+    class GetAddrInfoCmd : public NetdCommand {
+    public:
+        explicit GetAddrInfoCmd(const DnsProxyListener* dnsProxyListener);
+        virtual ~GetAddrInfoCmd() {}
+        int runCommand(SocketClient *c, int argc, char** argv);
+    private:
+        const DnsProxyListener* mDnsProxyListener;
+    };
+
+    class GetAddrInfoHandler {
+    public:
+        // Note: All of host, service, and hints may be NULL
+        GetAddrInfoHandler(SocketClient *c,
+                           char* host,
+                           char* service,
+                           struct addrinfo* hints,
+                           const struct android_net_context& netcontext);
+        ~GetAddrInfoHandler();
+
+        static void* threadStart(void* handler);
+        void start();
+
+    private:
+        void run();
+        SocketClient* mClient;  // ref counted
+        char* mHost;    // owned
+        char* mService; // owned
+        struct addrinfo* mHints;  // owned
+        struct android_net_context mNetContext;
+    };
+
+    /* ------ gethostbyname ------*/
+    class GetHostByNameCmd : public NetdCommand {
+    public:
+        explicit GetHostByNameCmd(const DnsProxyListener* dnsProxyListener);
+        virtual ~GetHostByNameCmd() {}
+        int runCommand(SocketClient *c, int argc, char** argv);
+    private:
+        const DnsProxyListener* mDnsProxyListener;
+    };
+
+    class GetHostByNameHandler {
+    public:
+        GetHostByNameHandler(SocketClient *c,
+                            char *name,
+                            int af,
+                            unsigned netId,
+                            uint32_t mark);
+        ~GetHostByNameHandler();
+        static void* threadStart(void* handler);
+        void start();
+    private:
+        void run();
+        SocketClient* mClient; //ref counted
+        char* mName; // owned
+        int mAf;
+        unsigned mNetId;
+        uint32_t mMark;
+    };
+
+    /* ------ gethostbyaddr ------*/
+    class GetHostByAddrCmd : public NetdCommand {
+    public:
+        explicit GetHostByAddrCmd(const DnsProxyListener* dnsProxyListener);
+        virtual ~GetHostByAddrCmd() {}
+        int runCommand(SocketClient *c, int argc, char** argv);
+    private:
+        const DnsProxyListener* mDnsProxyListener;
+    };
+
+    class GetHostByAddrHandler {
+    public:
+        GetHostByAddrHandler(SocketClient *c,
+                            void* address,
+                            int addressLen,
+                            int addressFamily,
+                            unsigned netId,
+                            uint32_t mark);
+        ~GetHostByAddrHandler();
+
+        static void* threadStart(void* handler);
+        void start();
+
+    private:
+        void run();
+        SocketClient* mClient;  // ref counted
+        void* mAddress;    // address to lookup; owned
+        int mAddressLen; // length of address to look up
+        int mAddressFamily;  // address family
+        unsigned mNetId;
+        uint32_t mMark;
+    };
+};
+
+#endif
diff --git a/netd/server/DummyNetwork.cpp b/netd/server/DummyNetwork.cpp
new file mode 100644
index 0000000..a3b3435
--- /dev/null
+++ b/netd/server/DummyNetwork.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 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 "DummyNetwork.h"
+
+#include "RouteController.h"
+
+#define LOG_TAG "Netd"
+#include "log/log.h"
+#include "errno.h"
+
+const char* DummyNetwork::INTERFACE_NAME = "dummy0";
+
+DummyNetwork::DummyNetwork(unsigned netId) : Network(netId) {
+    mInterfaces.insert(INTERFACE_NAME);
+}
+
+DummyNetwork::~DummyNetwork() {
+}
+
+Network::Type DummyNetwork::getType() const {
+    return DUMMY;
+}
+
+int DummyNetwork::addInterface(const std::string& /* interface */) {
+    return -EINVAL;
+}
+
+int DummyNetwork::removeInterface(const std::string& /* interface */) {
+    return -EINVAL;
+}
diff --git a/netd/server/DummyNetwork.h b/netd/server/DummyNetwork.h
new file mode 100644
index 0000000..7bc0d3d
--- /dev/null
+++ b/netd/server/DummyNetwork.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 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_DUMMY_NETWORK_H
+#define NETD_SERVER_DUMMY_NETWORK_H
+
+#include "Network.h"
+
+class DummyNetwork : public Network {
+public:
+    static const char* INTERFACE_NAME;
+    explicit DummyNetwork(unsigned netId);
+    virtual ~DummyNetwork();
+
+private:
+    Type getType() const override;
+    int addInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+    int removeInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+};
+
+#endif  // NETD_SERVER_DUMMY_NETWORK_H
diff --git a/netd/server/FirewallController.cpp b/netd/server/FirewallController.cpp
new file mode 100644
index 0000000..cf5a7de
--- /dev/null
+++ b/netd/server/FirewallController.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2012 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define LOG_TAG "FirewallController"
+#define LOG_NDEBUG 0
+
+#include <cutils/log.h>
+#include <private/android_filesystem_config.h>
+
+#include "NetdConstants.h"
+#include "FirewallController.h"
+
+const char* FirewallController::TABLE = "filter";
+
+const char* FirewallController::LOCAL_INPUT = "fw_INPUT";
+const char* FirewallController::LOCAL_OUTPUT = "fw_OUTPUT";
+const char* FirewallController::LOCAL_FORWARD = "fw_FORWARD";
+
+const char* FirewallController::LOCAL_DOZABLE = "fw_dozable";
+const char* FirewallController::LOCAL_STANDBY = "fw_standby";
+
+// ICMPv6 types that are required for any form of IPv6 connectivity to work. Note that because the
+// fw_dozable chain is called from both INPUT and OUTPUT, this includes both packets that we need
+// to be able to send (e.g., RS, NS), and packets that we need to receive (e.g., RA, NA).
+const char* FirewallController::ICMPV6_TYPES[] = {
+    "packet-too-big",
+    "router-solicitation",
+    "router-advertisement",
+    "neighbour-solicitation",
+    "neighbour-advertisement",
+    "redirect",
+};
+
+FirewallController::FirewallController(void) {
+    // If no rules are set, it's in BLACKLIST mode
+    mFirewallType = BLACKLIST;
+}
+
+int FirewallController::setupIptablesHooks(void) {
+    int res = 0;
+    // child chains are created but not attached, they will be attached explicitly.
+    FirewallType firewallType = getFirewallType(DOZABLE);
+    res |= createChain(LOCAL_DOZABLE, LOCAL_INPUT, firewallType);
+
+    firewallType = getFirewallType(STANDBY);
+    res |= createChain(LOCAL_STANDBY, LOCAL_INPUT, firewallType);
+
+    return res;
+}
+
+int FirewallController::enableFirewall(FirewallType ftype) {
+    int res = 0;
+    if (mFirewallType != ftype) {
+        // flush any existing rules
+        disableFirewall();
+
+        if (ftype == WHITELIST) {
+            // create default rule to drop all traffic
+            res |= execIptables(V4V6, "-A", LOCAL_INPUT, "-j", "DROP", NULL);
+            res |= execIptables(V4V6, "-A", LOCAL_OUTPUT, "-j", "REJECT", NULL);
+            res |= execIptables(V4V6, "-A", LOCAL_FORWARD, "-j", "REJECT", NULL);
+        }
+
+        // Set this after calling disableFirewall(), since it defaults to WHITELIST there
+        mFirewallType = ftype;
+    }
+    return res;
+}
+
+int FirewallController::disableFirewall(void) {
+    int res = 0;
+
+    mFirewallType = WHITELIST;
+
+    // flush any existing rules
+    res |= execIptables(V4V6, "-F", LOCAL_INPUT, NULL);
+    res |= execIptables(V4V6, "-F", LOCAL_OUTPUT, NULL);
+    res |= execIptables(V4V6, "-F", LOCAL_FORWARD, NULL);
+
+    return res;
+}
+
+int FirewallController::enableChildChains(ChildChain chain, bool enable) {
+    int res = 0;
+    const char* name;
+    switch(chain) {
+        case DOZABLE:
+            name = LOCAL_DOZABLE;
+            break;
+        case STANDBY:
+            name = LOCAL_STANDBY;
+            break;
+        default:
+            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);
+    }
+    return res;
+}
+
+int FirewallController::isFirewallEnabled(void) {
+    // TODO: verify that rules are still in place near top
+    return -1;
+}
+
+int FirewallController::setInterfaceRule(const char* iface, FirewallRule rule) {
+    if (mFirewallType == BLACKLIST) {
+        // Unsupported in BLACKLIST mode
+        return -1;
+    }
+
+    if (!isIfaceName(iface)) {
+        errno = ENOENT;
+        return -1;
+    }
+
+    const char* op;
+    if (rule == ALLOW) {
+        op = "-I";
+    } else {
+        op = "-D";
+    }
+
+    int res = 0;
+    res |= execIptables(V4V6, op, LOCAL_INPUT, "-i", iface, "-j", "RETURN", NULL);
+    res |= execIptables(V4V6, op, LOCAL_OUTPUT, "-o", iface, "-j", "RETURN", NULL);
+    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:
+            return WHITELIST;
+        case STANDBY:
+            return BLACKLIST;
+        case NONE:
+            return mFirewallType;
+        default:
+            return BLACKLIST;
+    }
+}
+
+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);
+    if (firewallType == WHITELIST) {
+        target = "RETURN";
+        op = (rule == ALLOW)? "-I" : "-D";
+    } else { // BLACKLIST mode
+        target = "DROP";
+        op = (rule == DENY)? "-I" : "-D";
+    }
+
+    int res = 0;
+    switch(chain) {
+        case DOZABLE:
+            res |= execIptables(V4V6, op, LOCAL_DOZABLE, "-m", "owner", "--uid-owner",
+                    uidStr, "-j", target, NULL);
+            break;
+        case STANDBY:
+            res |= execIptables(V4V6, op, LOCAL_STANDBY, "-m", "owner", "--uid-owner",
+                    uidStr, "-j", target, NULL);
+            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);
+            break;
+        default:
+            ALOGW("Unknown child chain: %d", chain);
+            break;
+    }
+    return res;
+}
+
+int FirewallController::attachChain(const char* childChain, const char* parentChain) {
+    return execIptables(V4V6, "-t", TABLE, "-A", parentChain, "-j", childChain, NULL);
+}
+
+int FirewallController::detachChain(const char* childChain, const char* parentChain) {
+    return execIptables(V4V6, "-t", TABLE, "-D", parentChain, "-j", childChain, NULL);
+}
+
+int FirewallController::createChain(const char* childChain,
+        const char* parentChain, FirewallType type) {
+    // Order is important, otherwise later steps may fail.
+    execIptablesSilently(V4V6, "-t", TABLE, "-D", parentChain, "-j", childChain, NULL);
+    execIptablesSilently(V4V6, "-t", TABLE, "-F", childChain, NULL);
+    execIptablesSilently(V4V6, "-t", TABLE, "-X", childChain, NULL);
+    int res = 0;
+    res |= execIptables(V4V6, "-t", TABLE, "-N", childChain, NULL);
+    if (type == WHITELIST) {
+        // Allow ICMPv6 packets necessary to make IPv6 connectivity work. http://b/23158230 .
+        for (size_t i = 0; i < ARRAY_SIZE(ICMPV6_TYPES); i++) {
+            res |= execIptables(V6, "-A", childChain, "-p", "icmpv6", "--icmpv6-type",
+                    ICMPV6_TYPES[i], "-j", "RETURN", NULL);
+        }
+
+        // create default white list for system uid range
+        char uidStr[16];
+        sprintf(uidStr, "0-%d", AID_APP - 1);
+        res |= execIptables(V4V6, "-A", childChain, "-m", "owner", "--uid-owner",
+                uidStr, "-j", "RETURN", NULL);
+
+        // create default rule to drop all traffic
+        res |= execIptables(V4V6, "-A", childChain, "-j", "DROP", NULL);
+    }
+    return res;
+}
diff --git a/netd/server/FirewallController.h b/netd/server/FirewallController.h
new file mode 100644
index 0000000..34a8b9c
--- /dev/null
+++ b/netd/server/FirewallController.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 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 _FIREWALL_CONTROLLER_H
+#define _FIREWALL_CONTROLLER_H
+
+#include <string>
+
+enum FirewallRule { DENY, ALLOW };
+
+// WHITELIST means the firewall denies all by default, uids must be explicitly ALLOWed
+// BLACKLIST means the firewall allows all by default, uids must be explicitly DENYed
+
+enum FirewallType { WHITELIST, BLACKLIST };
+
+enum ChildChain { NONE, DOZABLE, STANDBY, INVALID_CHAIN };
+
+#define PROTOCOL_TCP 6
+#define PROTOCOL_UDP 17
+
+/*
+ * Simple firewall that drops all packets except those matching explicitly
+ * defined ALLOW rules.
+ */
+class FirewallController {
+public:
+    FirewallController();
+
+    int setupIptablesHooks(void);
+
+    int enableFirewall(FirewallType);
+    int disableFirewall(void);
+    int isFirewallEnabled(void);
+
+    /* 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);
+
+    int enableChildChains(ChildChain, bool);
+
+    static const char* TABLE;
+
+    static const char* LOCAL_INPUT;
+    static const char* LOCAL_OUTPUT;
+    static const char* LOCAL_FORWARD;
+
+    static const char* LOCAL_DOZABLE;
+    static const char* LOCAL_STANDBY;
+
+    static const char* ICMPV6_TYPES[];
+
+private:
+    FirewallType mFirewallType;
+    int attachChain(const char*, const char*);
+    int detachChain(const char*, const char*);
+    int createChain(const char*, const char*, FirewallType);
+    FirewallType getFirewallType(ChildChain);
+};
+
+#endif
diff --git a/netd/server/FwmarkServer.cpp b/netd/server/FwmarkServer.cpp
new file mode 100644
index 0000000..530e96a
--- /dev/null
+++ b/netd/server/FwmarkServer.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2014 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 "FwmarkServer.h"
+
+#include "Fwmark.h"
+#include "FwmarkCommand.h"
+#include "NetworkController.h"
+#include "resolv_netid.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+
+FwmarkServer::FwmarkServer(NetworkController* networkController) :
+        SocketListener("fwmarkd", true), mNetworkController(networkController) {
+}
+
+bool FwmarkServer::onDataAvailable(SocketClient* client) {
+    int socketFd = -1;
+    int error = processClient(client, &socketFd);
+    if (socketFd >= 0) {
+        close(socketFd);
+    }
+
+    // Always send a response even if there were connection errors or read errors, so that we don't
+    // inadvertently cause the client to hang (which always waits for a response).
+    client->sendData(&error, sizeof(error));
+
+    // Always close the client connection (by returning false). This prevents a DoS attack where
+    // the client issues multiple commands on the same connection, never reading the responses,
+    // causing its receive buffer to fill up, and thus causing our client->sendData() to block.
+    return false;
+}
+
+int FwmarkServer::processClient(SocketClient* client, int* socketFd) {
+    FwmarkCommand command;
+
+    iovec iov;
+    iov.iov_base = &command;
+    iov.iov_len = sizeof(command);
+
+    msghdr message;
+    memset(&message, 0, sizeof(message));
+    message.msg_iov = &iov;
+    message.msg_iovlen = 1;
+
+    union {
+        cmsghdr cmh;
+        char cmsg[CMSG_SPACE(sizeof(*socketFd))];
+    } cmsgu;
+
+    memset(cmsgu.cmsg, 0, sizeof(cmsgu.cmsg));
+    message.msg_control = cmsgu.cmsg;
+    message.msg_controllen = sizeof(cmsgu.cmsg);
+
+    int messageLength = TEMP_FAILURE_RETRY(recvmsg(client->getSocket(), &message, 0));
+    if (messageLength <= 0) {
+        return -errno;
+    }
+
+    if (messageLength != sizeof(command)) {
+        return -EBADMSG;
+    }
+
+    Permission permission = mNetworkController->getPermissionForUser(client->getUid());
+
+    if (command.cmdId == FwmarkCommand::QUERY_USER_ACCESS) {
+        if ((permission & PERMISSION_SYSTEM) != PERMISSION_SYSTEM) {
+            return -EPERM;
+        }
+        return mNetworkController->checkUserNetworkAccess(command.uid, command.netId);
+    }
+
+    cmsghdr* const cmsgh = CMSG_FIRSTHDR(&message);
+    if (cmsgh && cmsgh->cmsg_level == SOL_SOCKET && cmsgh->cmsg_type == SCM_RIGHTS &&
+        cmsgh->cmsg_len == CMSG_LEN(sizeof(*socketFd))) {
+        memcpy(socketFd, CMSG_DATA(cmsgh), sizeof(*socketFd));
+    }
+
+    if (*socketFd < 0) {
+        return -EBADF;
+    }
+
+    Fwmark fwmark;
+    socklen_t fwmarkLen = sizeof(fwmark.intValue);
+    if (getsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue, &fwmarkLen) == -1) {
+        return -errno;
+    }
+
+    switch (command.cmdId) {
+        case FwmarkCommand::ON_ACCEPT: {
+            // Called after a socket accept(). The kernel would've marked the NetId and necessary
+            // permissions bits, so we just add the rest of the user's permissions here.
+            permission = static_cast<Permission>(permission | fwmark.permission);
+            break;
+        }
+
+        case FwmarkCommand::ON_CONNECT: {
+            // Called before a socket connect() happens. Set an appropriate NetId into the fwmark so
+            // that the socket routes consistently over that network. Do this even if the socket
+            // already has a NetId, so that calling connect() multiple times still works.
+            //
+            // But if the explicit bit was set, the existing NetId was explicitly preferred (and not
+            // a case of connect() being called multiple times). Don't reset the NetId in that case.
+            //
+            // An "appropriate" NetId is the NetId of a bypassable VPN that applies to the user, or
+            // failing that, the default network. We'll never set the NetId of a secure VPN here.
+            // See the comments in the implementation of getNetworkForConnect() for more details.
+            //
+            // If the protect bit is set, this could be either a system proxy (e.g.: the dns proxy
+            // or the download manager) acting on behalf of another user, or a VPN provider. If it's
+            // a proxy, we shouldn't reset the NetId. If it's a VPN provider, we should set the
+            // default network's NetId.
+            //
+            // There's no easy way to tell the difference between a proxy and a VPN app. We can't
+            // use PERMISSION_SYSTEM to identify the proxy because a VPN app may also have those
+            // permissions. So we use the following heuristic:
+            //
+            // If it's a proxy, but the existing NetId is not a VPN, that means the user (that the
+            // proxy is acting on behalf of) is not subject to a VPN, so the proxy must have picked
+            // the default network's NetId. So, it's okay to replace that with the current default
+            // network's NetId (which in all likelihood is the same).
+            //
+            // Conversely, if it's a VPN provider, the existing NetId cannot be a VPN. The only time
+            // we set a VPN's NetId into a socket without setting the explicit bit is here, in
+            // ON_CONNECT, but we won't do that if the socket has the protect bit set. If the VPN
+            // provider connect()ed (and got the VPN NetId set) and then called protect(), we
+            // would've unset the NetId in PROTECT_FROM_VPN below.
+            //
+            // So, overall (when the explicit bit is not set but the protect bit is set), if the
+            // existing NetId is a VPN, don't reset it. Else, set the default network's NetId.
+            if (!fwmark.explicitlySelected) {
+                if (!fwmark.protectedFromVpn) {
+                    fwmark.netId = mNetworkController->getNetworkForConnect(client->getUid());
+                } else if (!mNetworkController->isVirtualNetwork(fwmark.netId)) {
+                    fwmark.netId = mNetworkController->getDefaultNetwork();
+                }
+            }
+            break;
+        }
+
+        case FwmarkCommand::SELECT_NETWORK: {
+            fwmark.netId = command.netId;
+            if (command.netId == NETID_UNSET) {
+                fwmark.explicitlySelected = false;
+                fwmark.protectedFromVpn = false;
+                permission = PERMISSION_NONE;
+            } else {
+                if (int ret = mNetworkController->checkUserNetworkAccess(client->getUid(),
+                                                                         command.netId)) {
+                    return ret;
+                }
+                fwmark.explicitlySelected = true;
+                fwmark.protectedFromVpn = mNetworkController->canProtect(client->getUid());
+            }
+            break;
+        }
+
+        case FwmarkCommand::PROTECT_FROM_VPN: {
+            if (!mNetworkController->canProtect(client->getUid())) {
+                return -EPERM;
+            }
+            // If a bypassable VPN's provider app calls connect() and then protect(), it will end up
+            // with a socket that looks like that of a system proxy but is not (see comments for
+            // ON_CONNECT above). So, reset the NetId.
+            //
+            // In any case, it's appropriate that if the socket has an implicit VPN NetId mark, the
+            // PROTECT_FROM_VPN command should unset it.
+            if (!fwmark.explicitlySelected && mNetworkController->isVirtualNetwork(fwmark.netId)) {
+                fwmark.netId = mNetworkController->getDefaultNetwork();
+            }
+            fwmark.protectedFromVpn = true;
+            permission = static_cast<Permission>(permission | fwmark.permission);
+            break;
+        }
+
+        case FwmarkCommand::SELECT_FOR_USER: {
+            if ((permission & PERMISSION_SYSTEM) != PERMISSION_SYSTEM) {
+                return -EPERM;
+            }
+            fwmark.netId = mNetworkController->getNetworkForUser(command.uid);
+            fwmark.protectedFromVpn = true;
+            break;
+        }
+
+        default: {
+            // unknown command
+            return -EPROTO;
+        }
+    }
+
+    fwmark.permission = permission;
+
+    if (setsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue,
+                   sizeof(fwmark.intValue)) == -1) {
+        return -errno;
+    }
+
+    return 0;
+}
diff --git a/netd/server/FwmarkServer.h b/netd/server/FwmarkServer.h
new file mode 100644
index 0000000..12096be
--- /dev/null
+++ b/netd/server/FwmarkServer.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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_FWMARK_SERVER_H
+#define NETD_SERVER_FWMARK_SERVER_H
+
+#include "sysutils/SocketListener.h"
+
+class NetworkController;
+
+class FwmarkServer : public SocketListener {
+public:
+    explicit FwmarkServer(NetworkController* networkController);
+
+private:
+    // Overridden from SocketListener:
+    bool onDataAvailable(SocketClient* client);
+
+    // Returns 0 on success or a negative errno value on failure.
+    int processClient(SocketClient* client, int* socketFd);
+
+    NetworkController* const mNetworkController;
+};
+
+#endif  // NETD_SERVER_FWMARK_SERVER_H
diff --git a/netd/server/IdletimerController.cpp b/netd/server/IdletimerController.cpp
new file mode 100644
index 0000000..e6306fd
--- /dev/null
+++ b/netd/server/IdletimerController.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * MODUS OPERANDI
+ * --------------
+ *
+ * IPTABLES command sequence:
+ *
+ * iptables -F
+ *
+ * iptables -t raw -F idletimer_PREROUTING
+ * iptables -t mangle -F idletimer_POSTROUTING
+ *
+ *
+ * iptables -t raw -N idletimer_PREROUTING
+ * iptables -t mangle -N idletimer_POSTROUTING
+ *
+ * iptables -t raw -D PREROUTING -j idletimer_PREROUTING
+ * iptables -t mangle -D POSTROUTING -j idletimer_POSTROUTING
+ *
+ *
+ * iptables -t raw -I PREROUTING -j idletimer_PREROUTING
+ * iptables -t mangle -I POSTROUTING -j idletimer_POSTROUTING
+ *
+ * # For notifications to work the lable name must match the name of a valid interface.
+ * # If the label name does match an interface, the rules will be a no-op.
+ *
+ * iptables -t raw -A idletimer_PREROUTING -i rmnet0 -j IDLETIMER  --timeout 5 --label test-chain --send_nl_msg 1
+ * iptables -t mangle -A idletimer_POSTROUTING -o rmnet0 -j IDLETIMER  --timeout 5 --label test-chain --send_nl_msg 1
+ *
+ * iptables -nxvL -t raw
+ * iptables -nxvL -t mangle
+ *
+ * =================
+ *
+ * ndc command sequence
+ * ------------------
+ * ndc idletimer enable
+ * ndc idletimer add <iface> <timeout> <class label>
+ * ndc idletimer remove <iface> <timeout> <class label>
+ *
+ * Monitor effect on the iptables chains after each step using:
+ *     iptables -nxvL -t raw
+ *     iptables -nxvL -t mangle
+ *
+ * Remember that the timeout value has to be same at the time of the
+ * removal.
+ *
+ * =================
+ *
+ * Verifying the iptables rule
+ * ---------------------------
+ * We want to make sure the iptable rules capture every packet. It can be
+ * verified with tcpdump. First take a note of the pkts count for the two rules:
+ *
+ * adb shell iptables -t mangle -L idletimer_mangle_POSTROUTING -v && adb shell iptables -t raw -L idletimer_raw_PREROUTING -v
+ *
+ * And then, before any network traffics happen on the device, run tcpdump:
+ *
+ * adb shell tcpdump | tee tcpdump.log
+ *
+ * After a while run iptables commands again, you could then count the number
+ * of incoming and outgoing packets captured by tcpdump, and compare that with
+ * the numbers reported by iptables command. There shouldn't be too much
+ * difference on these numbers, i.e., with 2000 packets captured it should
+ * differ by less than 5.
+ *
+ * =================
+ *
+ * Note that currently if the name of the iface is incorrect, iptables
+ * will setup rules without checking if it is the name of a valid
+ * interface (although no notifications will ever be received).  It is
+ * the responsibility of code in Java land to ensure that the interface name
+ * is correct. The benefit of this, is that idletimers can be setup on
+ * interfaces than come and go.
+ *
+ * A remove should be called for each add command issued during cleanup, as duplicate
+ * entries of the rule may exist and will all have to removed.
+ *
+ */
+
+#define LOG_NDEBUG 0
+
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <cutils/properties.h>
+
+#define LOG_TAG "IdletimerController"
+#include <cutils/log.h>
+#include <logwrap/logwrap.h>
+
+#include "IdletimerController.h"
+#include "NetdConstants.h"
+
+const char* IdletimerController::LOCAL_RAW_PREROUTING = "idletimer_raw_PREROUTING";
+const char* IdletimerController::LOCAL_MANGLE_POSTROUTING = "idletimer_mangle_POSTROUTING";
+
+IdletimerController::IdletimerController() {
+}
+
+IdletimerController::~IdletimerController() {
+}
+/* return 0 or non-zero */
+int IdletimerController::runIpxtablesCmd(int argc, const char **argv) {
+    int resIpv4, resIpv6;
+
+    // Running for IPv4
+    argv[0] = IPTABLES_PATH;
+    resIpv4 = android_fork_execvp(argc, (char **)argv, NULL, false, false);
+
+    // Running for IPv6
+    argv[0] = IP6TABLES_PATH;
+    resIpv6 = android_fork_execvp(argc, (char **)argv, NULL, false, false);
+
+#if !LOG_NDEBUG
+    std::string full_cmd = argv[0];
+    argc--; argv++;
+    for (; argc; argc--, argv++) {
+        full_cmd += " ";
+        full_cmd += argv[0];
+    }
+    ALOGV("runCmd(%s) res_ipv4=%d, res_ipv6=%d", full_cmd.c_str(), resIpv4, resIpv6);
+#endif
+
+    return (resIpv4 == 0 && resIpv6 == 0) ? 0 : -1;
+}
+
+bool IdletimerController::setupIptablesHooks() {
+    return true;
+}
+
+int IdletimerController::setDefaults() {
+  int res;
+  const char *cmd1[] = {
+      NULL, // To be filled inside runIpxtablesCmd
+      "-w",
+      "-t",
+      "raw",
+      "-F",
+      LOCAL_RAW_PREROUTING
+  };
+  res = runIpxtablesCmd(ARRAY_SIZE(cmd1), cmd1);
+
+  if (res)
+    return res;
+
+  const char *cmd2[] = {
+      NULL, // To be filled inside runIpxtablesCmd
+      "-w",
+      "-t",
+      "mangle",
+      "-F",
+      LOCAL_MANGLE_POSTROUTING
+  };
+  res = runIpxtablesCmd(ARRAY_SIZE(cmd2), cmd2);
+
+  return res;
+}
+
+int IdletimerController::enableIdletimerControl() {
+    int res = setDefaults();
+    return res;
+}
+
+int IdletimerController::disableIdletimerControl() {
+    int res = setDefaults();
+    return res;
+}
+
+int IdletimerController::modifyInterfaceIdletimer(IptOp op, const char *iface,
+                                                  uint32_t timeout,
+                                                  const char *classLabel) {
+  int res;
+  char timeout_str[11]; //enough to store any 32-bit unsigned decimal
+
+  if (!isIfaceName(iface)) {
+    errno = ENOENT;
+    return -1;
+  }
+
+  snprintf(timeout_str, sizeof(timeout_str), "%u", timeout);
+
+  const char *cmd1[] = {
+      NULL, // To be filled inside runIpxtablesCmd
+      "-w",
+      "-t",
+      "raw",
+      (op == IptOpAdd) ? "-A" : "-D",
+      LOCAL_RAW_PREROUTING,
+      "-i",
+      iface,
+      "-j",
+      "IDLETIMER",
+      "--timeout",
+      timeout_str,
+      "--label",
+      classLabel,
+      "--send_nl_msg",
+      "1"
+  };
+  res = runIpxtablesCmd(ARRAY_SIZE(cmd1), cmd1);
+
+  if (res)
+    return res;
+
+  const char *cmd2[] = {
+      NULL, // To be filled inside runIpxtablesCmd
+      "-w",
+      "-t",
+      "mangle",
+      (op == IptOpAdd) ? "-A" : "-D",
+      LOCAL_MANGLE_POSTROUTING,
+      "-o",
+      iface,
+      "-j",
+      "IDLETIMER",
+      "--timeout",
+      timeout_str,
+      "--label",
+      classLabel,
+      "--send_nl_msg",
+      "1"
+  };
+  res = runIpxtablesCmd(ARRAY_SIZE(cmd2), cmd2);
+
+  return res;
+}
+
+int IdletimerController::addInterfaceIdletimer(const char *iface,
+                                               uint32_t timeout,
+                                               const char *classLabel) {
+  return modifyInterfaceIdletimer(IptOpAdd, iface, timeout, classLabel);
+}
+
+int IdletimerController::removeInterfaceIdletimer(const char *iface,
+                                                  uint32_t timeout,
+                                                  const char *classLabel) {
+  return modifyInterfaceIdletimer(IptOpDelete, iface, timeout, classLabel);
+}
diff --git a/netd/server/IdletimerController.h b/netd/server/IdletimerController.h
new file mode 100644
index 0000000..98a312e
--- /dev/null
+++ b/netd/server/IdletimerController.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2012 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 _IDLETIMER_CONTROLLER_H
+#define _IDLETIMER_CONTROLLER_H
+
+class IdletimerController {
+public:
+
+    IdletimerController();
+    virtual ~IdletimerController();
+
+    int enableIdletimerControl();
+    int disableIdletimerControl();
+    int addInterfaceIdletimer(const char *iface, uint32_t timeout,
+                              const char *classLabel);
+    int removeInterfaceIdletimer(const char *iface, uint32_t timeout,
+                                 const char *classLabel);
+    bool setupIptablesHooks();
+
+    static const char* LOCAL_RAW_PREROUTING;
+    static const char* LOCAL_MANGLE_POSTROUTING;
+
+ private:
+    enum IptOp { IptOpAdd, IptOpDelete };
+    int setDefaults();
+    int runIpxtablesCmd(int argc, const char **cmd);
+    int modifyInterfaceIdletimer(IptOp op, const char *iface, uint32_t timeout,
+                                 const char *classLabel);
+};
+
+#endif
diff --git a/netd/server/InterfaceController.cpp b/netd/server/InterfaceController.cpp
new file mode 100644
index 0000000..c518fe6
--- /dev/null
+++ b/netd/server/InterfaceController.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2012 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 <dirent.h>
+#include <errno.h>
+#include <malloc.h>
+
+#define LOG_TAG "InterfaceController"
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <cutils/log.h>
+#include <logwrap/logwrap.h>
+
+#include "InterfaceController.h"
+#include "RouteController.h"
+
+using android::base::StringPrintf;
+using android::base::WriteStringToFile;
+
+namespace {
+
+const char ipv6_proc_path[] = "/proc/sys/net/ipv6/conf";
+
+const char ipv4_neigh_conf_dir[] = "/proc/sys/net/ipv4/neigh";
+
+const char ipv6_neigh_conf_dir[] = "/proc/sys/net/ipv6/neigh";
+
+const char sys_net_path[] = "/sys/class/net";
+
+const char wl_util_path[] = "/system/xbin/wlutil";
+
+bool isInterfaceName(const char *name) {
+    return strcmp(name, ".") &&
+            strcmp(name, "..") &&
+            strcmp(name, "default") &&
+            strcmp(name, "all");
+}
+
+int writeValueToPath(
+        const char* dirname, const char* subdirname, const char* basename,
+        const char* value) {
+    std::string path(StringPrintf("%s/%s/%s", dirname, subdirname, basename));
+    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);
+    if (!dir) {
+        ALOGE("Can't list %s: %s", dirname, strerror(errno));
+        return;
+    }
+    dirent* d;
+    while ((d = readdir(dir))) {
+        if ((d->d_type != DT_DIR) || !isInterfaceName(d->d_name)) {
+            continue;
+        }
+        writeValueToPath(dirname, d->d_name, basename, value);
+    }
+    closedir(dir);
+}
+
+void setIPv6UseOutgoingInterfaceAddrsOnly(const char *value) {
+    setOnAllInterfaces(ipv6_proc_path, "use_oif_addrs_only", value);
+}
+
+}  // namespace
+
+InterfaceController::InterfaceController() {
+	// Initial IPv6 settings.
+	// By default, accept_ra is set to 1 (accept RAs unless forwarding is on) on all interfaces.
+	// This causes RAs to work or not work based on whether forwarding is on, and causes routes
+	// learned from RAs to go away when forwarding is turned on. Make this behaviour predictable
+	// by always setting accept_ra to 2.
+	setAcceptRA("2");
+
+	setAcceptRARouteTable(-RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX);
+
+	// Enable optimistic DAD for IPv6 addresses on all interfaces.
+	setIPv6OptimisticMode("1");
+
+	// Reduce the ARP/ND base reachable time from the default (30sec) to 15sec.
+	setBaseReachableTimeMs(15 * 1000);
+
+	// When sending traffic via a given interface use only addresses configured
+        // on that interface as possible source addresses.
+	setIPv6UseOutgoingInterfaceAddrsOnly("1");
+}
+
+InterfaceController::~InterfaceController() {
+}
+
+int InterfaceController::setEnableIPv6(const char *interface, const int on) {
+    if (!isIfaceName(interface)) {
+        errno = ENOENT;
+        return -1;
+    }
+    // When disable_ipv6 changes from 1 to 0, the kernel starts autoconf.
+    // When disable_ipv6 changes from 0 to 1, the kernel clears all autoconf
+    // addresses and routes and disables IPv6 on the interface.
+    const char *disable_ipv6 = on ? "0" : "1";
+    return writeValueToPath(ipv6_proc_path, interface, "disable_ipv6", disable_ipv6);
+}
+
+int InterfaceController::setIPv6PrivacyExtensions(const char *interface, const int on) {
+    if (!isIfaceName(interface)) {
+        errno = ENOENT;
+        return -1;
+    }
+    // 0: disable IPv6 privacy addresses
+    // 0: enable IPv6 privacy addresses and prefer them over non-privacy ones.
+    return writeValueToPath(ipv6_proc_path, interface, "use_tempaddr", on ? "2" : "0");
+}
+
+// Enables or disables IPv6 ND offload. This is useful for 464xlat on wifi, IPv6 tethering, and
+// generally implementing IPv6 neighbour discovery and duplicate address detection properly.
+// TODO: This should be implemented in wpa_supplicant via driver commands instead.
+int InterfaceController::setIPv6NdOffload(char* interface, const int on) {
+    // Only supported on Broadcom chipsets via wlutil for now.
+    if (access(wl_util_path, X_OK) == 0) {
+        const char *argv[] = {
+            wl_util_path,
+            "-a",
+            interface,
+            "ndoe",
+            on ? "1" : "0"
+        };
+        int ret = android_fork_execvp(ARRAY_SIZE(argv), const_cast<char**>(argv), NULL,
+                                      false, false);
+        ALOGD("%s ND offload on %s: %d (%s)",
+              (on ? "enabling" : "disabling"), interface, ret, strerror(errno));
+        return ret;
+    } else {
+        return 0;
+    }
+}
+
+void InterfaceController::setAcceptRA(const char *value) {
+    setOnAllInterfaces(ipv6_proc_path, "accept_ra", value);
+}
+
+// |tableOrOffset| is interpreted as:
+//     If == 0: default. Routes go into RT6_TABLE_MAIN.
+//     If > 0: user set. Routes go into the specified table.
+//     If < 0: automatic. The absolute value is intepreted as an offset and added to the interface
+//             ID to get the table. If it's set to -1000, routes from interface ID 5 will go into
+//             table 1005, etc.
+void InterfaceController::setAcceptRARouteTable(int tableOrOffset) {
+    std::string value(StringPrintf("%d", tableOrOffset));
+    setOnAllInterfaces(ipv6_proc_path, "accept_ra_rt_table", value.c_str());
+}
+
+int InterfaceController::setMtu(const char *interface, const char *mtu)
+{
+    if (!isIfaceName(interface)) {
+        errno = ENOENT;
+        return -1;
+    }
+    return writeValueToPath(sys_net_path, interface, "mtu", mtu);
+}
+
+void InterfaceController::setBaseReachableTimeMs(unsigned int millis) {
+    std::string value(StringPrintf("%u", millis));
+    setOnAllInterfaces(ipv4_neigh_conf_dir, "base_reachable_time_ms", value.c_str());
+    setOnAllInterfaces(ipv6_neigh_conf_dir, "base_reachable_time_ms", value.c_str());
+}
+
+void InterfaceController::setIPv6OptimisticMode(const char *value) {
+    setOnAllInterfaces(ipv6_proc_path, "optimistic_dad", value);
+    setOnAllInterfaces(ipv6_proc_path, "use_optimistic", value);
+}
diff --git a/netd/server/InterfaceController.h b/netd/server/InterfaceController.h
new file mode 100644
index 0000000..89728b1
--- /dev/null
+++ b/netd/server/InterfaceController.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 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 _INTERFACE_CONTROLLER_H
+#define _INTERFACE_CONTROLLER_H
+
+class InterfaceController {
+ public:
+	InterfaceController();
+	virtual ~InterfaceController();
+	int setEnableIPv6(const char *interface, const int on);
+	int setIPv6PrivacyExtensions(const char *interface, const int on);
+	int setIPv6NdOffload(char* interface, const int on);
+	int setMtu(const char *interface, const char *mtu);
+
+ private:
+	void setAcceptRA(const char* value);
+	void setAcceptRARouteTable(int tableOrOffset);
+	void setBaseReachableTimeMs(unsigned int millis);
+	void setIPv6OptimisticMode(const char *value);
+};
+
+#endif
diff --git a/netd/server/List.h b/netd/server/List.h
new file mode 100644
index 0000000..856ce26
--- /dev/null
+++ b/netd/server/List.h
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2005 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.
+ */
+
+//
+// Templated list class.  Normally we'd use STL, but we don't have that.
+// This class mimics STL's interfaces.
+//
+// Objects are copied into the list with the '=' operator or with copy-
+// construction, so if the compiler's auto-generated versions won't work for
+// you, define your own.
+//
+// The only class you want to use from here is "List".
+//
+#ifndef _NETD_LIST_H
+#define _NETD_LIST_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace android {
+namespace netd {
+
+/*
+ * Doubly-linked list.  Instantiate with "List<MyClass> myList".
+ *
+ * Objects added to the list are copied using the assignment operator,
+ * so this must be defined.
+ */
+template<typename T> 
+class List 
+{
+protected:
+    /*
+     * One element in the list.
+     */
+    class _Node {
+    public:
+        explicit _Node(const T& val) : mVal(val) {}
+        ~_Node() {}
+        inline T& getRef() { return mVal; }
+        inline const T& getRef() const { return mVal; }
+        inline _Node* getPrev() const { return mpPrev; }
+        inline _Node* getNext() const { return mpNext; }
+        inline void setVal(const T& val) { mVal = val; }
+        inline void setPrev(_Node* ptr) { mpPrev = ptr; }
+        inline void setNext(_Node* ptr) { mpNext = ptr; }
+    private:
+        friend class List;
+        friend class _ListIterator;
+        T           mVal;
+        _Node*      mpPrev;
+        _Node*      mpNext;
+    };
+
+    /*
+     * Iterator for walking through the list.
+     */
+    
+    template <typename TYPE>
+    struct CONST_ITERATOR {
+        typedef _Node const * NodePtr;
+        typedef const TYPE Type;
+    };
+    
+    template <typename TYPE>
+    struct NON_CONST_ITERATOR {
+        typedef _Node* NodePtr;
+        typedef TYPE Type;
+    };
+    
+    template<
+        typename U,
+        template <class> class Constness
+    > 
+    class _ListIterator {
+        typedef _ListIterator<U, Constness>     _Iter;
+        typedef typename Constness<U>::NodePtr  _NodePtr;
+        typedef typename Constness<U>::Type     _Type;
+
+        explicit _ListIterator(_NodePtr ptr) : mpNode(ptr) {}
+
+    public:
+        _ListIterator() {}
+        _ListIterator(const _Iter& rhs) : mpNode(rhs.mpNode) {}
+        ~_ListIterator() {}
+        
+        // this will handle conversions from iterator to const_iterator
+        // (and also all convertible iterators)
+        // Here, in this implementation, the iterators can be converted
+        // if the nodes can be converted
+        template<typename V> explicit 
+        _ListIterator(const V& rhs) : mpNode(rhs.mpNode) {}
+        
+
+        /*
+         * Dereference operator.  Used to get at the juicy insides.
+         */
+        _Type& operator*() const { return mpNode->getRef(); }
+        _Type* operator->() const { return &(mpNode->getRef()); }
+
+        /*
+         * Iterator comparison.
+         */
+        inline bool operator==(const _Iter& right) const { 
+            return mpNode == right.mpNode; }
+        
+        inline bool operator!=(const _Iter& right) const { 
+            return mpNode != right.mpNode; }
+
+        /*
+         * handle comparisons between iterator and const_iterator
+         */
+        template<typename OTHER>
+        inline bool operator==(const OTHER& right) const { 
+            return mpNode == right.mpNode; }
+        
+        template<typename OTHER>
+        inline bool operator!=(const OTHER& right) const { 
+            return mpNode != right.mpNode; }
+
+        /*
+         * Incr/decr, used to move through the list.
+         */
+        inline _Iter& operator++() {     // pre-increment
+            mpNode = mpNode->getNext();
+            return *this;
+        }
+        const _Iter operator++(int) {    // post-increment
+            _Iter tmp(*this);
+            mpNode = mpNode->getNext();
+            return tmp;
+        }
+        inline _Iter& operator--() {     // pre-increment
+            mpNode = mpNode->getPrev();
+            return *this;
+        }
+        const _Iter operator--(int) {   // post-increment
+            _Iter tmp(*this);
+            mpNode = mpNode->getPrev();
+            return tmp;
+        }
+
+        inline _NodePtr getNode() const { return mpNode; }
+
+        _NodePtr mpNode;    /* should be private, but older gcc fails */
+    private:
+        friend class List;
+    };
+
+public:
+    List() {
+        prep();
+    }
+    List(const List<T>& src) {      // copy-constructor
+        prep();
+        insert(begin(), src.begin(), src.end());
+    }
+    virtual ~List() {
+        clear();
+        delete[] (unsigned char*) mpMiddle;
+    }
+
+    typedef _ListIterator<T, NON_CONST_ITERATOR> iterator;
+    typedef _ListIterator<T, CONST_ITERATOR> const_iterator;
+
+    List<T>& operator=(const List<T>& right);
+
+    /* returns true if the list is empty */
+    inline bool empty() const { return mpMiddle->getNext() == mpMiddle; }
+
+    /* return #of elements in list */
+    size_t size() const {
+        return size_t(distance(begin(), end()));
+    }
+
+    /*
+     * Return the first element or one past the last element.  The
+     * _Node* we're returning is converted to an "iterator" by a
+     * constructor in _ListIterator.
+     */
+    inline iterator begin() { 
+        return iterator(mpMiddle->getNext()); 
+    }
+    inline const_iterator begin() const { 
+        return const_iterator(const_cast<_Node const*>(mpMiddle->getNext())); 
+    }
+    inline iterator end() { 
+        return iterator(mpMiddle); 
+    }
+    inline const_iterator end() const { 
+        return const_iterator(const_cast<_Node const*>(mpMiddle)); 
+    }
+
+    /* add the object to the head or tail of the list */
+    void push_front(const T& val) { insert(begin(), val); }
+    void push_back(const T& val) { insert(end(), val); }
+
+    /* insert before the current node; returns iterator at new node */
+    iterator insert(iterator posn, const T& val) 
+    {
+        _Node* newNode = new _Node(val);        // alloc & copy-construct
+        newNode->setNext(posn.getNode());
+        newNode->setPrev(posn.getNode()->getPrev());
+        posn.getNode()->getPrev()->setNext(newNode);
+        posn.getNode()->setPrev(newNode);
+        return iterator(newNode);
+    }
+
+    /* insert a range of elements before the current node */
+    void insert(iterator posn, const_iterator first, const_iterator last) {
+        for ( ; first != last; ++first)
+            insert(posn, *first);
+    }
+
+    /* remove one entry; returns iterator at next node */
+    iterator erase(iterator posn) {
+        _Node* pNext = posn.getNode()->getNext();
+        _Node* pPrev = posn.getNode()->getPrev();
+        pPrev->setNext(pNext);
+        pNext->setPrev(pPrev);
+        delete posn.getNode();
+        return iterator(pNext);
+    }
+
+    /* remove a range of elements */
+    iterator erase(iterator first, iterator last) {
+        while (first != last)
+            erase(first++);     // don't erase than incr later!
+        return iterator(last);
+    }
+
+    /* remove all contents of the list */
+    void clear() {
+        _Node* pCurrent = mpMiddle->getNext();
+        _Node* pNext;
+
+        while (pCurrent != mpMiddle) {
+            pNext = pCurrent->getNext();
+            delete pCurrent;
+            pCurrent = pNext;
+        }
+        mpMiddle->setPrev(mpMiddle);
+        mpMiddle->setNext(mpMiddle);
+    }
+
+    /*
+     * Measure the distance between two iterators.  On exist, "first"
+     * will be equal to "last".  The iterators must refer to the same
+     * list.
+     *
+     * FIXME: This is actually a generic iterator function. It should be a 
+     * template function at the top-level with specializations for things like
+     * vector<>, which can just do pointer math). Here we limit it to
+     * _ListIterator of the same type but different constness.
+     */
+    template<
+        typename U,
+        template <class> class CL,
+        template <class> class CR
+    > 
+    ptrdiff_t distance(
+            _ListIterator<U, CL> first, _ListIterator<U, CR> last) const 
+    {
+        ptrdiff_t count = 0;
+        while (first != last) {
+            ++first;
+            ++count;
+        }
+        return count;
+    }
+
+private:
+    /*
+     * I want a _Node but don't need it to hold valid data.  More
+     * to the point, I don't want T's constructor to fire, since it
+     * might have side-effects or require arguments.  So, we do this
+     * slightly uncouth storage alloc.
+     */
+    void prep() {
+        mpMiddle = (_Node*) new unsigned char[sizeof(_Node)];
+        mpMiddle->setPrev(mpMiddle);
+        mpMiddle->setNext(mpMiddle);
+    }
+
+    /*
+     * This node plays the role of "pointer to head" and "pointer to tail".
+     * It sits in the middle of a circular list of nodes.  The iterator
+     * runs around the circle until it encounters this one.
+     */
+    _Node*      mpMiddle;
+};
+
+/*
+ * Assignment operator.
+ *
+ * The simplest way to do this would be to clear out the target list and
+ * fill it with the source.  However, we can speed things along by
+ * re-using existing elements.
+ */
+template<class T>
+List<T>& List<T>::operator=(const List<T>& right)
+{
+    if (this == &right)
+        return *this;       // self-assignment
+    iterator firstDst = begin();
+    iterator lastDst = end();
+    const_iterator firstSrc = right.begin();
+    const_iterator lastSrc = right.end();
+    while (firstSrc != lastSrc && firstDst != lastDst)
+        *firstDst++ = *firstSrc++;
+    if (firstSrc == lastSrc)        // ran out of elements in source?
+        erase(firstDst, lastDst);   // yes, erase any extras
+    else
+        insert(lastDst, firstSrc, lastSrc);     // copy remaining over
+    return *this;
+}
+
+}; // namespace netd
+}; // namespace android
+
+#endif // _NETD_LIST_H
diff --git a/netd/server/LocalNetwork.cpp b/netd/server/LocalNetwork.cpp
new file mode 100644
index 0000000..ba0b21e
--- /dev/null
+++ b/netd/server/LocalNetwork.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 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 "LocalNetwork.h"
+
+#include "RouteController.h"
+
+#define LOG_TAG "Netd"
+#include "log/log.h"
+
+LocalNetwork::LocalNetwork(unsigned netId) : Network(netId) {
+}
+
+LocalNetwork::~LocalNetwork() {
+}
+
+Network::Type LocalNetwork::getType() const {
+    return LOCAL;
+}
+
+int LocalNetwork::addInterface(const std::string& interface) {
+    if (hasInterface(interface)) {
+        return 0;
+    }
+    if (int ret = RouteController::addInterfaceToLocalNetwork(mNetId, interface.c_str())) {
+        ALOGE("failed to add interface %s to local netId %u", interface.c_str(), mNetId);
+        return ret;
+    }
+    mInterfaces.insert(interface);
+    return 0;
+}
+
+int LocalNetwork::removeInterface(const std::string& interface) {
+    if (!hasInterface(interface)) {
+        return 0;
+    }
+    if (int ret = RouteController::removeInterfaceFromLocalNetwork(mNetId, interface.c_str())) {
+        ALOGE("failed to remove interface %s from local netId %u", interface.c_str(), mNetId);
+        return ret;
+    }
+    mInterfaces.erase(interface);
+    return 0;
+}
diff --git a/netd/server/LocalNetwork.h b/netd/server/LocalNetwork.h
new file mode 100644
index 0000000..89a67f4
--- /dev/null
+++ b/netd/server/LocalNetwork.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 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_LOCAL_NETWORK_H
+#define NETD_SERVER_LOCAL_NETWORK_H
+
+#include "Network.h"
+
+class LocalNetwork : public Network {
+public:
+    explicit LocalNetwork(unsigned netId);
+    virtual ~LocalNetwork();
+
+private:
+    Type getType() const override;
+    int addInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+    int removeInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+};
+
+#endif  // NETD_SERVER_LOCAL_NETWORK_H
diff --git a/netd/server/MDnsSdListener.cpp b/netd/server/MDnsSdListener.cpp
new file mode 100644
index 0000000..4becbe8
--- /dev/null
+++ b/netd/server/MDnsSdListener.cpp
@@ -0,0 +1,767 @@
+/*
+ * Copyright (C) 2010 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 <dirent.h>
+#include <errno.h>
+#include <linux/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <string.h>
+
+#define LOG_TAG "MDnsDS"
+#define DBG 1
+#define VDBG 1
+
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <sysutils/SocketClient.h>
+
+#include "MDnsSdListener.h"
+#include "ResponseCode.h"
+
+#define MDNS_SERVICE_NAME "mdnsd"
+#define MDNS_SERVICE_STATUS "init.svc.mdnsd"
+
+MDnsSdListener::MDnsSdListener() :
+                 FrameworkListener("mdns", true) {
+    Monitor *m = new Monitor();
+    registerCmd(new Handler(m, this));
+}
+
+MDnsSdListener::Handler::Handler(Monitor *m, MDnsSdListener *listener) :
+   NetdCommand("mdnssd") {
+   if (DBG) ALOGD("MDnsSdListener::Hander starting up");
+   mMonitor = m;
+   mListener = listener;
+}
+
+MDnsSdListener::Handler::~Handler() {}
+
+void MDnsSdListener::Handler::discover(SocketClient *cli,
+        const char *iface,
+        const char *regType,
+        const char *domain,
+        const int requestId,
+        const int requestFlags) {
+    if (VDBG) {
+        ALOGD("discover(%s, %s, %s, %d, %d)", iface, regType, domain, requestId,
+                requestFlags);
+    }
+    Context *context = new Context(requestId, mListener);
+    DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
+    if (ref == NULL) {
+        ALOGE("requestId %d already in use during discover call", requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError,
+                "RequestId already in use during discover call", false);
+        return;
+    }
+    if (VDBG) ALOGD("using ref %p", ref);
+    DNSServiceFlags nativeFlags = iToFlags(requestFlags);
+    int interfaceInt = ifaceNameToI(iface);
+
+    DNSServiceErrorType result = DNSServiceBrowse(ref, nativeFlags, interfaceInt, regType,
+            domain, &MDnsSdListenerDiscoverCallback, context);
+    if (result != kDNSServiceErr_NoError) {
+        ALOGE("Discover request %d got an error from DNSServiceBrowse %d", requestId, result);
+        mMonitor->freeServiceRef(requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError,
+                "Discover request got an error from DNSServiceBrowse", false);
+        return;
+    }
+    mMonitor->startMonitoring(requestId);
+    if (VDBG) ALOGD("discover successful");
+    cli->sendMsg(ResponseCode::CommandOkay, "Discover operation started", false);
+    return;
+}
+
+void MDnsSdListenerDiscoverCallback(DNSServiceRef /* sdRef */, DNSServiceFlags flags,
+        uint32_t /* interfaceIndex */, DNSServiceErrorType errorCode, const char *serviceName,
+        const char *regType, const char *replyDomain, void *inContext) {
+    MDnsSdListener::Context *context = reinterpret_cast<MDnsSdListener::Context *>(inContext);
+    char *msg;
+    int refNumber = context->mRefNumber;
+
+    if (errorCode != kDNSServiceErr_NoError) {
+        asprintf(&msg, "%d %d", refNumber, errorCode);
+        context->mListener->sendBroadcast(ResponseCode::ServiceDiscoveryFailed, msg, false);
+        if (DBG) ALOGE("discover failure for %d, error= %d", refNumber, errorCode);
+    } else {
+        int respCode;
+        char *quotedServiceName = SocketClient::quoteArg(serviceName);
+        if (flags & kDNSServiceFlagsAdd) {
+            if (VDBG) {
+                ALOGD("Discover found new serviceName %s, regType %s and domain %s for %d",
+                        serviceName, regType, replyDomain, refNumber);
+            }
+            respCode = ResponseCode::ServiceDiscoveryServiceAdded;
+        } else {
+            if (VDBG) {
+                ALOGD("Discover lost serviceName %s, regType %s and domain %s for %d",
+                        serviceName, regType, replyDomain, refNumber);
+            }
+            respCode = ResponseCode::ServiceDiscoveryServiceRemoved;
+        }
+        asprintf(&msg, "%d %s %s %s", refNumber, quotedServiceName, regType, replyDomain);
+        free(quotedServiceName);
+        context->mListener->sendBroadcast(respCode, msg, false);
+    }
+    free(msg);
+}
+
+void MDnsSdListener::Handler::stop(SocketClient *cli, int argc, char **argv, const char *str) {
+    if (argc != 3) {
+        char *msg;
+        asprintf(&msg, "Invalid number of arguments to %s", str);
+        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
+        free(msg);
+        return;
+    }
+    int requestId = atoi(argv[2]);
+    DNSServiceRef *ref = mMonitor->lookupServiceRef(requestId);
+    if (ref == NULL) {
+        if (DBG) ALOGE("%s stop used unknown requestId %d", str, requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError, "Unknown requestId", false);
+        return;
+    }
+    if (VDBG) ALOGD("Stopping %s with ref %p", str, ref);
+    DNSServiceRefDeallocate(*ref);
+    mMonitor->freeServiceRef(requestId);
+    char *msg;
+    asprintf(&msg, "%s stopped", str);
+    cli->sendMsg(ResponseCode::CommandOkay, msg, false);
+    free(msg);
+}
+
+void MDnsSdListener::Handler::serviceRegister(SocketClient *cli, int requestId,
+        const char *interfaceName, const char *serviceName, const char *serviceType,
+        const char *domain, const char *host, int port, int txtLen, void *txtRecord) {
+    if (VDBG) {
+        ALOGD("serviceRegister(%d, %s, %s, %s, %s, %s, %d, %d, <binary>)", requestId,
+                interfaceName, serviceName, serviceType, domain, host, port, txtLen);
+    }
+    Context *context = new Context(requestId, mListener);
+    DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
+    port = htons(port);
+    if (ref == NULL) {
+        ALOGE("requestId %d already in use during register call", requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError,
+                "RequestId already in use during register call", false);
+        return;
+    }
+    DNSServiceFlags nativeFlags = 0;
+    int interfaceInt = ifaceNameToI(interfaceName);
+    DNSServiceErrorType result = DNSServiceRegister(ref, interfaceInt, nativeFlags, serviceName,
+            serviceType, domain, host, port, txtLen, txtRecord, &MDnsSdListenerRegisterCallback,
+            context);
+    if (result != kDNSServiceErr_NoError) {
+        ALOGE("service register request %d got an error from DNSServiceRegister %d", requestId,
+                result);
+        mMonitor->freeServiceRef(requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError,
+                "serviceRegister request got an error from DNSServiceRegister", false);
+        return;
+    }
+    mMonitor->startMonitoring(requestId);
+    if (VDBG) ALOGD("serviceRegister successful");
+    cli->sendMsg(ResponseCode::CommandOkay, "serviceRegister started", false);
+    return;
+}
+
+void MDnsSdListenerRegisterCallback(DNSServiceRef /* sdRef */, DNSServiceFlags /* flags */,
+        DNSServiceErrorType errorCode, const char *serviceName, const char * /* regType */,
+        const char * /* domain */, void *inContext) {
+    MDnsSdListener::Context *context = reinterpret_cast<MDnsSdListener::Context *>(inContext);
+    char *msg;
+    int refNumber = context->mRefNumber;
+    if (errorCode != kDNSServiceErr_NoError) {
+        asprintf(&msg, "%d %d", refNumber, errorCode);
+        context->mListener->sendBroadcast(ResponseCode::ServiceRegistrationFailed, msg, false);
+        if (DBG) ALOGE("register failure for %d, error= %d", refNumber, errorCode);
+    } else {
+        char *quotedServiceName = SocketClient::quoteArg(serviceName);
+        asprintf(&msg, "%d %s", refNumber, quotedServiceName);
+        free(quotedServiceName);
+        context->mListener->sendBroadcast(ResponseCode::ServiceRegistrationSucceeded, msg, false);
+        if (VDBG) ALOGD("register succeeded for %d as %s", refNumber, serviceName);
+    }
+    free(msg);
+}
+
+
+void MDnsSdListener::Handler::resolveService(SocketClient *cli, int requestId,
+        const char *interfaceName, const char *serviceName, const char *regType,
+        const char *domain) {
+    if (VDBG) {
+        ALOGD("resolveService(%d, %s, %s, %s, %s)", requestId, interfaceName,
+                serviceName, regType, domain);
+    }
+    Context *context = new Context(requestId, mListener);
+    DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
+    if (ref == NULL) {
+        ALOGE("request Id %d already in use during resolve call", requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError,
+                "RequestId already in use during resolve call", false);
+        return;
+    }
+    DNSServiceFlags nativeFlags = 0;
+    int interfaceInt = ifaceNameToI(interfaceName);
+    DNSServiceErrorType result = DNSServiceResolve(ref, nativeFlags, interfaceInt, serviceName,
+            regType, domain, &MDnsSdListenerResolveCallback, context);
+    if (result != kDNSServiceErr_NoError) {
+        ALOGE("service resolve request %d got an error from DNSServiceResolve %d", requestId,
+                result);
+        mMonitor->freeServiceRef(requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError,
+                "resolveService got an error from DNSServiceResolve", false);
+        return;
+    }
+    mMonitor->startMonitoring(requestId);
+    if (VDBG) ALOGD("resolveService successful");
+    cli->sendMsg(ResponseCode::CommandOkay, "resolveService started", false);
+    return;
+}
+
+void MDnsSdListenerResolveCallback(DNSServiceRef /* sdRef */, DNSServiceFlags /* flags */,
+        uint32_t /* interface */, DNSServiceErrorType errorCode, const char *fullname,
+        const char *hosttarget, uint16_t port, uint16_t txtLen,
+        const unsigned char * /* txtRecord */, void *inContext) {
+    MDnsSdListener::Context *context = reinterpret_cast<MDnsSdListener::Context *>(inContext);
+    char *msg;
+    int refNumber = context->mRefNumber;
+    port = ntohs(port);
+    if (errorCode != kDNSServiceErr_NoError) {
+        asprintf(&msg, "%d %d", refNumber, errorCode);
+        context->mListener->sendBroadcast(ResponseCode::ServiceResolveFailed, msg, false);
+        if (DBG) ALOGE("resolve failure for %d, error= %d", refNumber, errorCode);
+    } else {
+        char *quotedFullName = SocketClient::quoteArg(fullname);
+        char *quotedHostTarget = SocketClient::quoteArg(hosttarget);
+        asprintf(&msg, "%d %s %s %d %d", refNumber, quotedFullName, quotedHostTarget, port, txtLen);
+        free(quotedFullName);
+        free(quotedHostTarget);
+        context->mListener->sendBroadcast(ResponseCode::ServiceResolveSuccess, msg, false);
+        if (VDBG) {
+            ALOGD("resolve succeeded for %d finding %s at %s:%d with txtLen %d",
+                    refNumber, fullname, hosttarget, port, txtLen);
+        }
+    }
+    free(msg);
+}
+
+void MDnsSdListener::Handler::getAddrInfo(SocketClient *cli, int requestId,
+        const char *interfaceName, uint32_t protocol, const char *hostname) {
+    if (VDBG) ALOGD("getAddrInfo(%d, %s %d, %s)", requestId, interfaceName, protocol, hostname);
+    Context *context = new Context(requestId, mListener);
+    DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
+    if (ref == NULL) {
+        ALOGE("request ID %d already in use during getAddrInfo call", requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError,
+                "RequestId already in use during getAddrInfo call", false);
+        return;
+    }
+    DNSServiceFlags nativeFlags = 0;
+    int interfaceInt = ifaceNameToI(interfaceName);
+    DNSServiceErrorType result = DNSServiceGetAddrInfo(ref, nativeFlags, interfaceInt, protocol,
+            hostname, &MDnsSdListenerGetAddrInfoCallback, context);
+    if (result != kDNSServiceErr_NoError) {
+        ALOGE("getAddrInfo request %d got an error from DNSServiceGetAddrInfo %d", requestId,
+                result);
+        mMonitor->freeServiceRef(requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError,
+                "getAddrInfo request got an error from DNSServiceGetAddrInfo", false);
+        return;
+    }
+    mMonitor->startMonitoring(requestId);
+    if (VDBG) ALOGD("getAddrInfo successful");
+    cli->sendMsg(ResponseCode::CommandOkay, "getAddrInfo started", false);
+    return;
+}
+
+void MDnsSdListenerGetAddrInfoCallback(DNSServiceRef /* sdRef */, DNSServiceFlags /* flags */,
+        uint32_t /* interface */, DNSServiceErrorType errorCode, const char *hostname,
+        const struct sockaddr *const sa, uint32_t ttl, void *inContext) {
+    MDnsSdListener::Context *context = reinterpret_cast<MDnsSdListener::Context *>(inContext);
+    int refNumber = context->mRefNumber;
+
+    if (errorCode != kDNSServiceErr_NoError) {
+        char *msg;
+        asprintf(&msg, "%d %d", refNumber, errorCode);
+        context->mListener->sendBroadcast(ResponseCode::ServiceGetAddrInfoFailed, msg, false);
+        if (DBG) ALOGE("getAddrInfo failure for %d, error= %d", refNumber, errorCode);
+        free(msg);
+    } else {
+        char addr[INET6_ADDRSTRLEN];
+        char *msg;
+        char *quotedHostname = SocketClient::quoteArg(hostname);
+        if (sa->sa_family == AF_INET) {
+            inet_ntop(sa->sa_family, &(((struct sockaddr_in *)sa)->sin_addr), addr, sizeof(addr));
+        } else {
+            inet_ntop(sa->sa_family, &(((struct sockaddr_in6 *)sa)->sin6_addr), addr, sizeof(addr));
+        }
+        asprintf(&msg, "%d %s %d %s", refNumber, quotedHostname, ttl, addr);
+        free(quotedHostname);
+        context->mListener->sendBroadcast(ResponseCode::ServiceGetAddrInfoSuccess, msg, false);
+        if (VDBG) {
+            ALOGD("getAddrInfo succeeded for %d: %s", refNumber, msg);
+        }
+        free(msg);
+    }
+}
+
+void MDnsSdListener::Handler::setHostname(SocketClient *cli, int requestId,
+        const char *hostname) {
+    if (VDBG) ALOGD("setHostname(%d, %s)", requestId, hostname);
+    Context *context = new Context(requestId, mListener);
+    DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
+    if (ref == NULL) {
+        ALOGE("request Id %d already in use during setHostname call", requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError,
+                "RequestId already in use during setHostname call", false);
+        return;
+    }
+    DNSServiceFlags nativeFlags = 0;
+    DNSServiceErrorType result = DNSSetHostname(ref, nativeFlags, hostname,
+            &MDnsSdListenerSetHostnameCallback, context);
+    if (result != kDNSServiceErr_NoError) {
+        ALOGE("setHostname request %d got an error from DNSSetHostname %d", requestId, result);
+        mMonitor->freeServiceRef(requestId);
+        cli->sendMsg(ResponseCode::CommandParameterError,
+                "setHostname got an error from DNSSetHostname", false);
+        return;
+    }
+    mMonitor->startMonitoring(requestId);
+    if (VDBG) ALOGD("setHostname successful");
+    cli->sendMsg(ResponseCode::CommandOkay, "setHostname started", false);
+    return;
+}
+
+void MDnsSdListenerSetHostnameCallback(DNSServiceRef /* sdRef */, DNSServiceFlags /* flags */,
+        DNSServiceErrorType errorCode, const char *hostname, void *inContext) {
+    MDnsSdListener::Context *context = reinterpret_cast<MDnsSdListener::Context *>(inContext);
+    char *msg;
+    int refNumber = context->mRefNumber;
+    if (errorCode != kDNSServiceErr_NoError) {
+        asprintf(&msg, "%d %d", refNumber, errorCode);
+        context->mListener->sendBroadcast(ResponseCode::ServiceSetHostnameFailed, msg, false);
+        if (DBG) ALOGE("setHostname failure for %d, error= %d", refNumber, errorCode);
+    } else {
+        char *quotedHostname = SocketClient::quoteArg(hostname);
+        asprintf(&msg, "%d %s", refNumber, quotedHostname);
+        free(quotedHostname);
+        context->mListener->sendBroadcast(ResponseCode::ServiceSetHostnameSuccess, msg, false);
+        if (VDBG) ALOGD("setHostname succeeded for %d.  Set to %s", refNumber, hostname);
+    }
+    free(msg);
+}
+
+
+int MDnsSdListener::Handler::ifaceNameToI(const char * /* iface */) {
+    return 0;
+}
+
+const char *MDnsSdListener::Handler::iToIfaceName(int /* i */) {
+    return NULL;
+}
+
+DNSServiceFlags MDnsSdListener::Handler::iToFlags(int /* i */) {
+    return 0;
+}
+
+int MDnsSdListener::Handler::flagsToI(DNSServiceFlags /* flags */) {
+    return 0;
+}
+
+int MDnsSdListener::Handler::runCommand(SocketClient *cli,
+                                        int argc, char **argv) {
+    if (argc < 2) {
+        char* msg = NULL;
+        asprintf( &msg, "Invalid number of arguments to mdnssd: %i", argc);
+        ALOGW("%s", msg);
+        cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
+        free(msg);
+        return -1;
+    }
+
+    char* cmd = argv[1];
+
+    if (strcmp(cmd, "discover") == 0) {
+        if (argc != 4) {
+            cli->sendMsg(ResponseCode::CommandParameterError,
+                    "Invalid number of arguments to mdnssd discover", false);
+            return 0;
+        }
+        int requestId = atoi(argv[2]);
+        char *serviceType = argv[3];
+
+        discover(cli, NULL, serviceType, NULL, requestId, 0);
+    } else if (strcmp(cmd, "stop-discover") == 0) {
+        stop(cli, argc, argv, "discover");
+    } else if (strcmp(cmd, "register") == 0) {
+        if (argc < 6) {
+            cli->sendMsg(ResponseCode::CommandParameterError,
+                    "Invalid number of arguments to mdnssd register", false);
+            return 0;
+        }
+        int requestId = atoi(argv[2]);
+        char *serviceName = argv[3];
+        char *serviceType = argv[4];
+        int port = atoi(argv[5]);
+        char *interfaceName = NULL; // will use all
+        char *domain = NULL;        // will use default
+        char *host = NULL;          // will use default hostname
+        unsigned char txtRecord[2048] = "";
+        unsigned char *ptr = txtRecord;
+        for (int i = 6; i < argc; ++i) {
+          int dataLength = strlen(argv[i]);
+          if (dataLength < 1) {
+            continue;
+          }
+          if (dataLength > 255) {
+            cli->sendMsg(ResponseCode::CommandParameterError,
+                    "TXT record fields must not be longer than 255 characters", false);
+            return 0;
+          }
+          if (ptr + dataLength + 1 > txtRecord + sizeof(txtRecord)) {
+            cli->sendMsg(ResponseCode::CommandParameterError,
+                    "Total length of TXT record must be smaller than 2048 bytes", false);
+            return 0;
+          }
+          *ptr++ = dataLength;
+          strcpy( (char*) ptr, argv[i]);
+          ptr += dataLength;
+        }
+        serviceRegister(cli, requestId, interfaceName, serviceName,
+                serviceType, domain, host, port, ptr - txtRecord, txtRecord);
+    } else if (strcmp(cmd, "stop-register") == 0) {
+        stop(cli, argc, argv, "register");
+    } else if (strcmp(cmd, "resolve") == 0) {
+        if (argc != 6) {
+            cli->sendMsg(ResponseCode::CommandParameterError,
+                    "Invalid number of arguments to mdnssd resolve", false);
+            return 0;
+        }
+        int requestId = atoi(argv[2]);
+        char *interfaceName = NULL;  // will use all
+        char *serviceName = argv[3];
+        char *regType = argv[4];
+        char *domain = argv[5];
+        resolveService(cli, requestId, interfaceName, serviceName, regType, domain);
+    } else if (strcmp(cmd, "stop-resolve") == 0) {
+        stop(cli, argc, argv, "resolve");
+    } else if (strcmp(cmd, "start-service") == 0) {
+        if (mMonitor->startService()) {
+            cli->sendMsg(ResponseCode::CommandOkay, "Service Started", false);
+        } else {
+            cli->sendMsg(ResponseCode::ServiceStartFailed, "Service already running", false);
+        }
+    } else if (strcmp(cmd, "stop-service") == 0) {
+        if (mMonitor->stopService()) {
+            cli->sendMsg(ResponseCode::CommandOkay, "Service Stopped", false);
+        } else {
+            cli->sendMsg(ResponseCode::ServiceStopFailed, "Service still in use", false);
+        }
+    } else if (strcmp(cmd, "sethostname") == 0) {
+        if (argc != 4) {
+            cli->sendMsg(ResponseCode::CommandParameterError,
+                    "Invalid number of arguments to mdnssd sethostname", false);
+            return 0;
+        }
+        int requestId = atoi(argv[2]);
+        char *hostname = argv[3];
+        setHostname(cli, requestId, hostname);
+    } else if (strcmp(cmd, "stop-sethostname") == 0) {
+        stop(cli, argc, argv, "sethostname");
+    } else if (strcmp(cmd, "getaddrinfo") == 0) {
+        if (argc != 4) {
+            cli->sendMsg(ResponseCode::CommandParameterError,
+                    "Invalid number of arguments to mdnssd getaddrinfo", false);
+            return 0;
+        }
+        int requestId = atoi(argv[2]);
+        char *hostname = argv[3];
+        char *interfaceName = NULL;  // default
+        int protocol = 0;            // intelligient heuristic (both v4 + v6)
+        getAddrInfo(cli, requestId, interfaceName, protocol, hostname);
+    } else if (strcmp(cmd, "stop-getaddrinfo") == 0) {
+        stop(cli, argc, argv, "getaddrinfo");
+    } else {
+        if (VDBG) ALOGE("Unknown cmd %s", cmd);
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown mdnssd cmd", false);
+        return 0;
+    }
+    return 0;
+}
+
+MDnsSdListener::Monitor::Monitor() {
+    mHead = NULL;
+    mLiveCount = 0;
+    mPollFds = NULL;
+    mPollRefs = NULL;
+    mPollSize = 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;
+}
+
+#define NAP_TIME 200  // 200 ms between polls
+static int wait_for_property(const char *name, const char *desired_value, int maxwait)
+{
+    char value[PROPERTY_VALUE_MAX] = {'\0'};
+    int maxnaps = (maxwait * 1000) / NAP_TIME;
+
+    if (maxnaps < 1) {
+        maxnaps = 1;
+    }
+
+    while (maxnaps-- > 0) {
+        usleep(NAP_TIME * 1000);
+        if (property_get(name, value, NULL)) {
+            if (desired_value == NULL || strcmp(value, desired_value) == 0) {
+                return 0;
+            }
+        }
+    }
+    return -1; /* failure */
+}
+
+int MDnsSdListener::Monitor::startService() {
+    int result = 0;
+    char property_value[PROPERTY_VALUE_MAX];
+    pthread_mutex_lock(&mHeadMutex);
+    property_get(MDNS_SERVICE_STATUS, property_value, "");
+    if (strcmp("running", property_value) != 0) {
+        ALOGD("Starting MDNSD");
+        property_set("ctl.start", MDNS_SERVICE_NAME);
+        wait_for_property(MDNS_SERVICE_STATUS, "running", 5);
+        result = -1;
+    } else {
+        result = 0;
+    }
+    pthread_mutex_unlock(&mHeadMutex);
+    return result;
+}
+
+int MDnsSdListener::Monitor::stopService() {
+    int result = 0;
+    pthread_mutex_lock(&mHeadMutex);
+    if (mHead == NULL) {
+        ALOGD("Stopping MDNSD");
+        property_set("ctl.stop", MDNS_SERVICE_NAME);
+        wait_for_property(MDNS_SERVICE_STATUS, "stopped", 5);
+        result = -1;
+    } else {
+        result = 0;
+    }
+    pthread_mutex_unlock(&mHeadMutex);
+    return result;
+}
+
+void MDnsSdListener::Monitor::run() {
+    int pollCount = 1;
+
+    mPollFds = (struct pollfd *)calloc(sizeof(struct pollfd), mPollSize);
+    mPollRefs = (DNSServiceRef **)calloc(sizeof(DNSServiceRef *), mPollSize);
+    LOG_ALWAYS_FATAL_IF((mPollFds == NULL), "initial calloc failed on mPollFds with a size of %d",
+            ((int)sizeof(struct pollfd)) * mPollSize);
+    LOG_ALWAYS_FATAL_IF((mPollRefs == NULL), "initial calloc failed on mPollRefs with a size of %d",
+            ((int)sizeof(DNSServiceRef *)) * mPollSize);
+
+    mPollFds[0].fd = mCtrlSocketPair[0];
+    mPollFds[0].events = POLLIN;
+
+    if (VDBG) ALOGD("MDnsSdListener starting to monitor");
+    while (1) {
+        if (VDBG) ALOGD("Going to poll with pollCount %d", pollCount);
+        int pollResults = poll(mPollFds, pollCount, 10000000);
+        if (pollResults < 0) {
+            ALOGE("Error in poll - got %d", errno);
+        } else if (pollResults > 0) {
+            if (VDBG) ALOGD("Monitor poll got data pollCount = %d, %d", pollCount, pollResults);
+            for(int i = 1; i < pollCount; i++) {
+                if (mPollFds[i].revents != 0) {
+                    if (VDBG) {
+                        ALOGD("Monitor found [%d].revents = %d - calling ProcessResults",
+                                i, mPollFds[i].revents);
+                    }
+                    DNSServiceProcessResult(*(mPollRefs[i]));
+                    mPollFds[i].revents = 0;
+                }
+            }
+            if (VDBG) ALOGD("controlSocket shows revent= %d", mPollFds[0].revents);
+            switch (mPollFds[0].revents) {
+                case POLLIN: {
+                    char readBuf[2];
+                    read(mCtrlSocketPair[0], &readBuf, 1);
+                    if (DBG) ALOGD("MDnsSdListener::Monitor got %c", readBuf[0]);
+                    if (memcmp(RESCAN, readBuf, 1) == 0) {
+                        pollCount = rescan();
+                    }
+                }
+            }
+            mPollFds[0].revents = 0;
+        } else {
+            if (VDBG) ALOGD("MDnsSdListener::Monitor poll timed out");
+        }
+    }
+    free(mPollFds);
+    free(mPollRefs);
+}
+
+#define DBG_RESCAN 0
+
+int MDnsSdListener::Monitor::rescan() {
+// rescan the list from mHead and make new pollfds and serviceRefs
+    if (VDBG) {
+        ALOGD("MDnsSdListener::Monitor poll rescanning - size=%d, live=%d", mPollSize, mLiveCount);
+    }
+    pthread_mutex_lock(&mHeadMutex);
+    Element **prevPtr = &mHead;
+    int i = 1;
+    if (mPollSize <= mLiveCount) {
+        mPollSize = mLiveCount + 5;
+        free(mPollFds);
+        free(mPollRefs);
+        mPollFds = (struct pollfd *)calloc(sizeof(struct pollfd), mPollSize);
+        mPollRefs = (DNSServiceRef **)calloc(sizeof(DNSServiceRef *), mPollSize);
+        LOG_ALWAYS_FATAL_IF((mPollFds == NULL), "calloc failed on mPollFds with a size of %d",
+                ((int)sizeof(struct pollfd)) * mPollSize);
+        LOG_ALWAYS_FATAL_IF((mPollRefs == NULL), "calloc failed on mPollRefs with a size of %d",
+                ((int)sizeof(DNSServiceRef *)) * mPollSize);
+    } else {
+        memset(mPollFds, 0, sizeof(struct pollfd) * mPollSize);
+        memset(mPollRefs, 0, sizeof(DNSServiceRef *) * mPollSize);
+    }
+    mPollFds[0].fd = mCtrlSocketPair[0];
+    mPollFds[0].events = POLLIN;
+    if (DBG_RESCAN) ALOGD("mHead = %p", mHead);
+    while (*prevPtr != NULL) {
+        if (DBG_RESCAN) ALOGD("checking %p, mReady = %d", *prevPtr, (*prevPtr)->mReady);
+        if ((*prevPtr)->mReady == 1) {
+            int fd = DNSServiceRefSockFD((*prevPtr)->mRef);
+            if (fd != -1) {
+                if (DBG_RESCAN) ALOGD("  adding FD %d", fd);
+                mPollFds[i].fd = fd;
+                mPollFds[i].events = POLLIN;
+                mPollRefs[i] = &((*prevPtr)->mRef);
+                i++;
+            } else {
+                ALOGE("Error retreving socket FD for live ServiceRef");
+            }
+            prevPtr = &((*prevPtr)->mNext); // advance to the next element
+        } else if ((*prevPtr)->mReady == -1) {
+            if (DBG_RESCAN) ALOGD("  removing %p from  play", *prevPtr);
+            Element *cur = *prevPtr;
+            *prevPtr = (cur)->mNext; // change our notion of this element and don't advance
+            delete cur;
+        } else if ((*prevPtr)->mReady == 0) {
+            // Not ready so just skip this node and continue on
+            if (DBG_RESCAN) ALOGD("%p not ready.  Continuing.", *prevPtr);
+            prevPtr = &((*prevPtr)->mNext);
+        }
+    }
+    pthread_mutex_unlock(&mHeadMutex);
+    return i;
+}
+
+DNSServiceRef *MDnsSdListener::Monitor::allocateServiceRef(int id, Context *context) {
+    if (lookupServiceRef(id) != NULL) {
+        delete(context);
+        return NULL;
+    }
+    Element *e = new Element(id, context);
+    pthread_mutex_lock(&mHeadMutex);
+    e->mNext = mHead;
+    mHead = e;
+    pthread_mutex_unlock(&mHeadMutex);
+    return &(e->mRef);
+}
+
+DNSServiceRef *MDnsSdListener::Monitor::lookupServiceRef(int id) {
+    pthread_mutex_lock(&mHeadMutex);
+    Element *cur = mHead;
+    while (cur != NULL) {
+        if (cur->mId == id) {
+            DNSServiceRef *result = &(cur->mRef);
+            pthread_mutex_unlock(&mHeadMutex);
+            return result;
+        }
+        cur = cur->mNext;
+    }
+    pthread_mutex_unlock(&mHeadMutex);
+    return NULL;
+}
+
+void MDnsSdListener::Monitor::startMonitoring(int id) {
+    if (VDBG) ALOGD("startMonitoring %d", id);
+    pthread_mutex_lock(&mHeadMutex);
+    Element *cur = mHead;
+    while (cur != NULL) {
+        if (cur->mId == id) {
+            if (DBG_RESCAN) ALOGD("marking %p as ready to be added", cur);
+            mLiveCount++;
+            cur->mReady = 1;
+            pthread_mutex_unlock(&mHeadMutex);
+            write(mCtrlSocketPair[1], RESCAN, 1);  // trigger a rescan for a fresh poll
+            if (VDBG) ALOGD("triggering rescan");
+            return;
+        }
+        cur = cur->mNext;
+    }
+    pthread_mutex_unlock(&mHeadMutex);
+}
+
+void MDnsSdListener::Monitor::freeServiceRef(int id) {
+    if (VDBG) ALOGD("freeServiceRef %d", id);
+    pthread_mutex_lock(&mHeadMutex);
+    Element **prevPtr = &mHead;
+    Element *cur;
+    while (*prevPtr != NULL) {
+        cur = *prevPtr;
+        if (cur->mId == id) {
+            if (DBG_RESCAN) ALOGD("marking %p as ready to be removed", cur);
+            mLiveCount--;
+            if (cur->mReady == 1) {
+                cur->mReady = -1; // tell poll thread to delete
+                write(mCtrlSocketPair[1], RESCAN, 1); // trigger a rescan for a fresh poll
+                if (VDBG) ALOGD("triggering rescan");
+            } else {
+                *prevPtr = cur->mNext;
+                delete cur;
+            }
+            pthread_mutex_unlock(&mHeadMutex);
+            return;
+        }
+        prevPtr = &(cur->mNext);
+    }
+    pthread_mutex_unlock(&mHeadMutex);
+}
diff --git a/netd/server/MDnsSdListener.h b/netd/server/MDnsSdListener.h
new file mode 100644
index 0000000..e9c6066
--- /dev/null
+++ b/netd/server/MDnsSdListener.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2012 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 _MDNSSDLISTENER_H__
+#define _MDNSSDLISTENER_H__
+
+#include <pthread.h>
+#include <sysutils/FrameworkListener.h>
+#include <dns_sd.h>
+
+#include "NetdCommand.h"
+
+// callbacks
+void MDnsSdListenerDiscoverCallback(DNSServiceRef sdRef, DNSServiceFlags flags,
+        uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+        const char *serviceName, const char *regType, const char *replyDomain,
+        void *inContext);
+
+void MDnsSdListenerRegisterCallback(DNSServiceRef sdRef, DNSServiceFlags flags,
+        DNSServiceErrorType errorCode, const char *serviceName, const char *regType,
+        const char *domain, void *inContext);
+
+void MDnsSdListenerResolveCallback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interface,
+        DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port,
+        uint16_t txtLen, const unsigned char *txtRecord, void *inContext);
+
+void MDnsSdListenerSetHostnameCallback(DNSServiceRef, DNSServiceFlags flags,
+        DNSServiceErrorType errorCode, const char *hostname, void *inContext);
+
+void MDnsSdListenerGetAddrInfoCallback(DNSServiceRef sdRef, DNSServiceFlags flags,
+        uint32_t interface, DNSServiceErrorType errorCode, const char *hostname,
+        const struct sockaddr *const sa, uint32_t ttl, void *inContext);
+
+#define RESCAN "1"
+
+class MDnsSdListener : public FrameworkListener {
+public:
+    MDnsSdListener();
+    virtual ~MDnsSdListener() {}
+
+    class Context {
+    public:
+        MDnsSdListener *mListener;
+        int mRefNumber;
+
+        Context(int refNumber, MDnsSdListener *m) {
+            mRefNumber = refNumber;
+            mListener = m;
+        }
+
+        ~Context() {
+        }
+    };
+
+    class Monitor {
+    public:
+        Monitor();
+        virtual ~Monitor() {}
+        DNSServiceRef *allocateServiceRef(int id, Context *c);
+        void startMonitoring(int id);
+        DNSServiceRef *lookupServiceRef(int id);
+        void freeServiceRef(int id);
+        static void *threadStart(void *handler);
+        int startService();
+        int stopService();
+    private:
+        void run();
+        int rescan(); // returns the number of elements in the poll
+        class Element {
+        public:
+            int mId;
+            Element *mNext;
+            DNSServiceRef mRef;
+            Context *mContext;
+            int mReady;
+            Element(int id, Context *context)
+                    : mId(id), mNext(NULL), mContext(context), mReady(0) {}
+            virtual ~Element() { delete(mContext); }
+        };
+        Element *mHead;
+        int mLiveCount;
+        struct pollfd *mPollFds;
+        DNSServiceRef **mPollRefs;
+        int mPollSize;
+        pthread_t mThread;
+        int mCtrlSocketPair[2];
+        pthread_mutex_t mHeadMutex;
+    };
+
+    class Handler : public NetdCommand {
+    public:
+        Handler(Monitor *m, MDnsSdListener *listener);
+        virtual ~Handler();
+        int runCommand(SocketClient *c, int argc, char** argv);
+
+        MDnsSdListener *mListener; // needed for broadcast purposes
+    private:
+        void stop(SocketClient *cli, int argc, char **argv, const char *str);
+
+        void discover(SocketClient *cli, const char *iface, const char *regType,
+                const char *domain, const int requestNumber,
+                const int requestFlags);
+
+        void serviceRegister(SocketClient *cli, int requestId, const char *interfaceName,
+                const char *serviceName, const char *serviceType, const char *domain,
+                const char *host, int port, int textLen, void *txtRecord);
+
+        void resolveService(SocketClient *cli, int requestId,
+                const char *interfaceName, const char *serviceName, const char *regType,
+                const char *domain);
+
+        void setHostname(SocketClient *cli, int requestId, const char *hostname);
+
+        void getAddrInfo(SocketClient *cli, int requestId, const char *interfaceName,
+                uint32_t protocol, const char *hostname);
+
+        int ifaceNameToI(const char *iface);
+        const char *iToIfaceName(int i);
+        DNSServiceFlags iToFlags(int i);
+        int flagsToI(DNSServiceFlags flags);
+        Monitor *mMonitor;
+    };
+};
+
+#endif
diff --git a/netd/server/NatController.cpp b/netd/server/NatController.cpp
new file mode 100644
index 0000000..19d19c7
--- /dev/null
+++ b/netd/server/NatController.cpp
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2008 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_NDEBUG 0
+
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <cutils/properties.h>
+
+#define LOG_TAG "NatController"
+#include <cutils/log.h>
+#include <logwrap/logwrap.h>
+
+#include "NatController.h"
+#include "NetdConstants.h"
+#include "RouteController.h"
+
+const char* NatController::LOCAL_FORWARD = "natctrl_FORWARD";
+const char* NatController::LOCAL_MANGLE_FORWARD = "natctrl_mangle_FORWARD";
+const char* NatController::LOCAL_NAT_POSTROUTING = "natctrl_nat_POSTROUTING";
+const char* NatController::LOCAL_TETHER_COUNTERS_CHAIN = "natctrl_tether_counters";
+
+NatController::NatController() {
+}
+
+NatController::~NatController() {
+}
+
+struct CommandsAndArgs {
+    /* The array size doesn't really matter as the compiler will barf if too many initializers are specified. */
+    const char *cmd[32];
+    bool checkRes;
+};
+
+int NatController::runCmd(int argc, const char **argv) {
+    int res;
+
+    res = android_fork_execvp(argc, (char **)argv, NULL, false, false);
+
+#if !LOG_NDEBUG
+    std::string full_cmd = argv[0];
+    argc--; argv++;
+    /*
+     * HACK: Sometimes runCmd() is called with a ridcously large value (32)
+     * and it works because the argv[] contains a NULL after the last
+     * true argv. So here we use the NULL argv[] to terminate when the argc
+     * is horribly wrong, and argc for the normal cases.
+     */
+    for (; argc && argv[0]; argc--, argv++) {
+        full_cmd += " ";
+        full_cmd += argv[0];
+    }
+    ALOGV("runCmd(%s) res=%d", full_cmd.c_str(), res);
+#endif
+    return res;
+}
+
+int NatController::setupIptablesHooks() {
+    int res;
+    res = setDefaults();
+    if (res < 0) {
+        return res;
+    }
+
+    struct CommandsAndArgs defaultCommands[] = {
+        /*
+         * First chain is for tethering counters.
+         * This chain is reached via --goto, and then RETURNS.
+         *
+         * Second chain is used to limit downstream mss to the upstream pmtu
+         * so we don't end up fragmenting every large packet tethered devices
+         * send.  Note this feature requires kernel support with flag
+         * CONFIG_NETFILTER_XT_TARGET_TCPMSS=y, which not all builds will have,
+         * so the final rule is allowed to fail.
+         * Bug 17629786 asks to make the failure more obvious, or even fatal
+         * so that all builds eventually gain the performance improvement.
+         */
+        {{IPTABLES_PATH, "-w", "-F", LOCAL_TETHER_COUNTERS_CHAIN,}, 0},
+        {{IPTABLES_PATH, "-w", "-X", LOCAL_TETHER_COUNTERS_CHAIN,}, 0},
+        {{IPTABLES_PATH, "-w", "-N", LOCAL_TETHER_COUNTERS_CHAIN,}, 1},
+        {{IPTABLES_PATH, "-w", "-t", "mangle", "-A", LOCAL_MANGLE_FORWARD, "-p", "tcp", "--tcp-flags",
+                "SYN", "SYN", "-j", "TCPMSS", "--clamp-mss-to-pmtu"}, 0},
+    };
+    for (unsigned int cmdNum = 0; cmdNum < ARRAY_SIZE(defaultCommands); cmdNum++) {
+        if (runCmd(ARRAY_SIZE(defaultCommands[cmdNum].cmd), defaultCommands[cmdNum].cmd) &&
+            defaultCommands[cmdNum].checkRes) {
+                return -1;
+        }
+    }
+    ifacePairList.clear();
+
+    return 0;
+}
+
+int NatController::setDefaults() {
+    /*
+     * The following only works because:
+     *  - the defaultsCommands[].cmd array is padded with NULL, and
+     *  - the 1st argc of runCmd() will just be the max for the CommandsAndArgs[].cmd, and
+     *  - internally it will be memcopied to an array and terminated with a NULL.
+     */
+    struct CommandsAndArgs defaultCommands[] = {
+        {{IPTABLES_PATH, "-w", "-F", LOCAL_FORWARD,}, 1},
+        {{IPTABLES_PATH, "-w", "-A", LOCAL_FORWARD, "-j", "DROP"}, 1},
+        {{IPTABLES_PATH, "-w", "-t", "nat", "-F", LOCAL_NAT_POSTROUTING}, 1},
+    };
+    for (unsigned int cmdNum = 0; cmdNum < ARRAY_SIZE(defaultCommands); cmdNum++) {
+        if (runCmd(ARRAY_SIZE(defaultCommands[cmdNum].cmd), defaultCommands[cmdNum].cmd) &&
+            defaultCommands[cmdNum].checkRes) {
+                return -1;
+        }
+    }
+
+    natCount = 0;
+
+    return 0;
+}
+
+int NatController::enableNat(const char* intIface, const char* extIface) {
+    ALOGV("enableNat(intIface=<%s>, extIface=<%s>)",intIface, extIface);
+
+    if (!isIfaceName(intIface) || !isIfaceName(extIface)) {
+        errno = ENODEV;
+        return -1;
+    }
+
+    /* Bug: b/9565268. "enableNat wlan0 wlan0". For now we fail until java-land is fixed */
+    if (!strcmp(intIface, extIface)) {
+        ALOGE("Duplicate interface specified: %s %s", intIface, extIface);
+        errno = EINVAL;
+        return -1;
+    }
+
+    // add this if we are the first added nat
+    if (natCount == 0) {
+        const char *cmd[] = {
+                IPTABLES_PATH,
+                "-w",
+                "-t",
+                "nat",
+                "-A",
+                LOCAL_NAT_POSTROUTING,
+                "-o",
+                extIface,
+                "-j",
+                "MASQUERADE"
+        };
+        if (runCmd(ARRAY_SIZE(cmd), cmd)) {
+            ALOGE("Error setting postroute rule: iface=%s", extIface);
+            // unwind what's been done, but don't care about success - what more could we do?
+            setDefaults();
+            return -1;
+        }
+    }
+
+    if (setForwardRules(true, intIface, extIface) != 0) {
+        ALOGE("Error setting forward rules");
+        if (natCount == 0) {
+            setDefaults();
+        }
+        errno = ENODEV;
+        return -1;
+    }
+
+    /* Always make sure the drop rule is at the end */
+    const char *cmd1[] = {
+            IPTABLES_PATH,
+            "-w",
+            "-D",
+            LOCAL_FORWARD,
+            "-j",
+            "DROP"
+    };
+    runCmd(ARRAY_SIZE(cmd1), cmd1);
+    const char *cmd2[] = {
+            IPTABLES_PATH,
+            "-w",
+            "-A",
+            LOCAL_FORWARD,
+            "-j",
+            "DROP"
+    };
+    runCmd(ARRAY_SIZE(cmd2), cmd2);
+
+    natCount++;
+    return 0;
+}
+
+bool NatController::checkTetherCountingRuleExist(const char *pair_name) {
+    std::list<std::string>::iterator it;
+
+    for (it = ifacePairList.begin(); it != ifacePairList.end(); it++) {
+        if (*it == pair_name) {
+            /* We already have this counter */
+            return true;
+        }
+    }
+    return false;
+}
+
+int NatController::setTetherCountingRules(bool add, const char *intIface, const char *extIface) {
+
+    /* We only ever add tethering quota rules so that they stick. */
+    if (!add) {
+        return 0;
+    }
+    char *pair_name;
+    asprintf(&pair_name, "%s_%s", intIface, extIface);
+
+    if (checkTetherCountingRuleExist(pair_name)) {
+        free(pair_name);
+        return 0;
+    }
+    const char *cmd2b[] = {
+            IPTABLES_PATH,
+            "-w",
+            "-A",
+            LOCAL_TETHER_COUNTERS_CHAIN,
+            "-i",
+            intIface,
+            "-o",
+            extIface,
+            "-j",
+          "RETURN"
+    };
+
+    if (runCmd(ARRAY_SIZE(cmd2b), cmd2b) && add) {
+        free(pair_name);
+        return -1;
+    }
+    ifacePairList.push_front(pair_name);
+    free(pair_name);
+
+    asprintf(&pair_name, "%s_%s", extIface, intIface);
+    if (checkTetherCountingRuleExist(pair_name)) {
+        free(pair_name);
+        return 0;
+    }
+
+    const char *cmd3b[] = {
+            IPTABLES_PATH,
+            "-w",
+            "-A",
+            LOCAL_TETHER_COUNTERS_CHAIN,
+            "-i",
+            extIface,
+            "-o",
+            intIface,
+            "-j",
+            "RETURN"
+    };
+
+    if (runCmd(ARRAY_SIZE(cmd3b), cmd3b) && add) {
+        // unwind what's been done, but don't care about success - what more could we do?
+        free(pair_name);
+        return -1;
+    }
+    ifacePairList.push_front(pair_name);
+    free(pair_name);
+    return 0;
+}
+
+int NatController::setForwardRules(bool add, const char *intIface, const char *extIface) {
+    const char *cmd1[] = {
+            IPTABLES_PATH,
+            "-w",
+            add ? "-A" : "-D",
+            LOCAL_FORWARD,
+            "-i",
+            extIface,
+            "-o",
+            intIface,
+            "-m",
+            "state",
+            "--state",
+            "ESTABLISHED,RELATED",
+            "-g",
+            LOCAL_TETHER_COUNTERS_CHAIN
+    };
+    int rc = 0;
+
+    if (runCmd(ARRAY_SIZE(cmd1), cmd1) && add) {
+        return -1;
+    }
+
+    const char *cmd2[] = {
+            IPTABLES_PATH,
+            "-w",
+            add ? "-A" : "-D",
+            LOCAL_FORWARD,
+            "-i",
+            intIface,
+            "-o",
+            extIface,
+            "-m",
+            "state",
+            "--state",
+            "INVALID",
+            "-j",
+            "DROP"
+    };
+
+    const char *cmd3[] = {
+            IPTABLES_PATH,
+            "-w",
+            add ? "-A" : "-D",
+            LOCAL_FORWARD,
+            "-i",
+            intIface,
+            "-o",
+            extIface,
+            "-g",
+            LOCAL_TETHER_COUNTERS_CHAIN
+    };
+
+    if (runCmd(ARRAY_SIZE(cmd2), cmd2) && add) {
+        // bail on error, but only if adding
+        rc = -1;
+        goto err_invalid_drop;
+    }
+
+    if (runCmd(ARRAY_SIZE(cmd3), cmd3) && add) {
+        // unwind what's been done, but don't care about success - what more could we do?
+        rc = -1;
+        goto err_return;
+    }
+
+    if (setTetherCountingRules(add, intIface, extIface) && add) {
+        rc = -1;
+        goto err_return;
+    }
+
+    return 0;
+
+err_return:
+    cmd2[2] = "-D";
+    runCmd(ARRAY_SIZE(cmd2), cmd2);
+err_invalid_drop:
+    cmd1[2] = "-D";
+    runCmd(ARRAY_SIZE(cmd1), cmd1);
+    return rc;
+}
+
+int NatController::disableNat(const char* intIface, const char* extIface) {
+    if (!isIfaceName(intIface) || !isIfaceName(extIface)) {
+        errno = ENODEV;
+        return -1;
+    }
+
+    setForwardRules(false, intIface, extIface);
+    if (--natCount <= 0) {
+        // handle decrement to 0 case (do reset to defaults) and erroneous dec below 0
+        setDefaults();
+    }
+    return 0;
+}
diff --git a/netd/server/NatController.h b/netd/server/NatController.h
new file mode 100644
index 0000000..f23bf84
--- /dev/null
+++ b/netd/server/NatController.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2008 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 _NAT_CONTROLLER_H
+#define _NAT_CONTROLLER_H
+
+#include <linux/in.h>
+#include <list>
+#include <string>
+
+class NatController {
+public:
+    NatController();
+    virtual ~NatController();
+
+    int enableNat(const char* intIface, const char* extIface);
+    int disableNat(const char* intIface, const char* extIface);
+    int setupIptablesHooks();
+
+    static const char* LOCAL_FORWARD;
+    static const char* LOCAL_MANGLE_FORWARD;
+    static const char* LOCAL_NAT_POSTROUTING;
+    static const char* LOCAL_TETHER_COUNTERS_CHAIN;
+
+    // List of strings of interface pairs.
+    std::list<std::string> ifacePairList;
+
+private:
+    int natCount;
+
+    bool checkTetherCountingRuleExist(const char *pair_name);
+
+    int setDefaults();
+    int runCmd(int argc, const char **argv);
+    int setForwardRules(bool set, const char *intIface, const char *extIface);
+    int setTetherCountingRules(bool add, const char *intIface, const char *extIface);
+};
+
+#endif
diff --git a/netd/server/NetdCommand.cpp b/netd/server/NetdCommand.cpp
new file mode 100644
index 0000000..3bd9322
--- /dev/null
+++ b/netd/server/NetdCommand.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2008 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 "NetdCommand.h"
+
+NetdCommand::NetdCommand(const char *cmd) :
+              FrameworkCommand(cmd)  {
+}
diff --git a/netd/server/NetdCommand.h b/netd/server/NetdCommand.h
new file mode 100644
index 0000000..8e3e54b
--- /dev/null
+++ b/netd/server/NetdCommand.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2008 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_COMMAND_H
+#define _NETD_COMMAND_H
+
+#include <sysutils/FrameworkCommand.h>
+
+class NetdCommand : public FrameworkCommand {
+public:
+    explicit NetdCommand(const char *cmd);
+    virtual ~NetdCommand() {}
+};
+
+#endif
diff --git a/netd/server/NetdConstants.cpp b/netd/server/NetdConstants.cpp
new file mode 100644
index 0000000..c86538b
--- /dev/null
+++ b/netd/server/NetdConstants.cpp
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2012 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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#define LOG_TAG "Netd"
+
+#include <cutils/log.h>
+#include <logwrap/logwrap.h>
+
+#include "NetdConstants.h"
+
+const char * const OEM_SCRIPT_PATH = "/system/bin/oem-iptables-init.sh";
+const char * const IPTABLES_PATH = "/system/bin/iptables";
+const char * const IP6TABLES_PATH = "/system/bin/ip6tables";
+const char * const TC_PATH = "/system/bin/tc";
+const char * const IP_PATH = "/system/bin/ip";
+const char * const ADD = "add";
+const char * const DEL = "del";
+
+static void logExecError(const char* argv[], int res, int status) {
+    const char** argp = argv;
+    std::string args = "";
+    while (*argp) {
+        args += *argp;
+        args += ' ';
+        argp++;
+    }
+    ALOGE("exec() res=%d, status=%d for %s", res, status, args.c_str());
+}
+
+static int execIptablesCommand(int argc, const char *argv[], bool silent) {
+    int res;
+    int status;
+
+    res = android_fork_execvp(argc, (char **)argv, &status, false,
+        !silent);
+    if (res || !WIFEXITED(status) || WEXITSTATUS(status)) {
+        if (!silent) {
+            logExecError(argv, res, status);
+        }
+        if (res)
+            return res;
+        if (!WIFEXITED(status))
+            return ECHILD;
+    }
+    return WEXITSTATUS(status);
+}
+
+static int execIptables(IptablesTarget target, bool silent, va_list args) {
+    /* Read arguments from incoming va_list; we expect the list to be NULL terminated. */
+    std::list<const char*> argsList;
+    argsList.push_back(NULL);
+    const char* arg;
+
+    // Wait to avoid failure due to another process holding the lock
+    argsList.push_back("-w");
+
+    do {
+        arg = va_arg(args, const char *);
+        argsList.push_back(arg);
+    } while (arg);
+
+    int i = 0;
+    const char* argv[argsList.size()];
+    std::list<const char*>::iterator it;
+    for (it = argsList.begin(); it != argsList.end(); it++, i++) {
+        argv[i] = *it;
+    }
+
+    int res = 0;
+    if (target == V4 || target == V4V6) {
+        argv[0] = IPTABLES_PATH;
+        res |= execIptablesCommand(argsList.size(), argv, silent);
+    }
+    if (target == V6 || target == V4V6) {
+        argv[0] = IP6TABLES_PATH;
+        res |= execIptablesCommand(argsList.size(), argv, silent);
+    }
+    return res;
+}
+
+int execIptables(IptablesTarget target, ...) {
+    va_list args;
+    va_start(args, target);
+    int res = execIptables(target, false, args);
+    va_end(args);
+    return res;
+}
+
+int execIptablesSilently(IptablesTarget target, ...) {
+    va_list args;
+    va_start(args, target);
+    int res = execIptables(target, true, args);
+    va_end(args);
+    return res;
+}
+
+/*
+ * Check an interface name for plausibility. This should e.g. help against
+ * directory traversal.
+ */
+bool isIfaceName(const char *name) {
+    size_t i;
+    size_t name_len = strlen(name);
+    if ((name_len == 0) || (name_len > IFNAMSIZ)) {
+        return false;
+    }
+
+    /* First character must be alphanumeric */
+    if (!isalnum(name[0])) {
+        return false;
+    }
+
+    for (i = 1; i < name_len; i++) {
+        if (!isalnum(name[i]) && (name[i] != '_') && (name[i] != '-') && (name[i] != ':')) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+int parsePrefix(const char *prefix, uint8_t *family, void *address, int size, uint8_t *prefixlen) {
+    if (!prefix || !family || !address || !prefixlen) {
+        return -EFAULT;
+    }
+
+    // Find the '/' separating address from prefix length.
+    const char *slash = strchr(prefix, '/');
+    const char *prefixlenString = slash + 1;
+    if (!slash || !*prefixlenString)
+        return -EINVAL;
+
+    // Convert the prefix length to a uint8_t.
+    char *endptr;
+    unsigned templen;
+    templen = strtoul(prefixlenString, &endptr, 10);
+    if (*endptr || templen > 255) {
+        return -EINVAL;
+    }
+    *prefixlen = templen;
+
+    // Copy the address part of the prefix to a local buffer. We have to copy
+    // because inet_pton and getaddrinfo operate on null-terminated address
+    // strings, but prefix is const and has '/' after the address.
+    std::string addressString(prefix, slash - prefix);
+
+    // Parse the address.
+    addrinfo *res;
+    addrinfo hints = {
+        .ai_flags = AI_NUMERICHOST,
+    };
+    int ret = getaddrinfo(addressString.c_str(), NULL, &hints, &res);
+    if (ret || !res) {
+        return -EINVAL;  // getaddrinfo return values are not errno values.
+    }
+
+    // Convert the address string to raw address bytes.
+    void *rawAddress;
+    int rawLength;
+    switch (res[0].ai_family) {
+        case AF_INET: {
+            if (*prefixlen > 32) {
+                return -EINVAL;
+            }
+            sockaddr_in *sin = (sockaddr_in *) res[0].ai_addr;
+            rawAddress = &sin->sin_addr;
+            rawLength = 4;
+            break;
+        }
+        case AF_INET6: {
+            if (*prefixlen > 128) {
+                return -EINVAL;
+            }
+            sockaddr_in6 *sin6 = (sockaddr_in6 *) res[0].ai_addr;
+            rawAddress = &sin6->sin6_addr;
+            rawLength = 16;
+            break;
+        }
+        default: {
+            freeaddrinfo(res);
+            return -EAFNOSUPPORT;
+        }
+    }
+
+    if (rawLength > size) {
+        freeaddrinfo(res);
+        return -ENOSPC;
+    }
+
+    *family = res[0].ai_family;
+    memcpy(address, rawAddress, rawLength);
+    freeaddrinfo(res);
+
+    return rawLength;
+}
diff --git a/netd/server/NetdConstants.h b/netd/server/NetdConstants.h
new file mode 100644
index 0000000..296661d
--- /dev/null
+++ b/netd/server/NetdConstants.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 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_CONSTANTS_H
+#define _NETD_CONSTANTS_H
+
+#include <string>
+#include <list>
+#include <stdarg.h>
+
+const int PROTECT_MARK = 0x1;
+
+extern const char * const IPTABLES_PATH;
+extern const char * const IP6TABLES_PATH;
+extern const char * const IP_PATH;
+extern const char * const TC_PATH;
+extern const char * const OEM_SCRIPT_PATH;
+extern const char * const ADD;
+extern const char * const DEL;
+
+enum IptablesTarget { V4, V6, V4V6 };
+
+int execIptables(IptablesTarget target, ...);
+int execIptablesSilently(IptablesTarget target, ...);
+bool isIfaceName(const char *name);
+int parsePrefix(const char *prefix, uint8_t *family, void *address, int size, uint8_t *prefixlen);
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
+
+#define __INT_STRLEN(i) sizeof(#i)
+#define _INT_STRLEN(i) __INT_STRLEN(i)
+#define UINT32_STRLEN _INT_STRLEN(UINT32_MAX)
+#define UINT32_HEX_STRLEN sizeof("0x12345678")
+
+#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
+
+const uid_t INVALID_UID = static_cast<uid_t>(-1);
+
+#endif
diff --git a/netd/server/NetlinkHandler.cpp b/netd/server/NetlinkHandler.cpp
new file mode 100644
index 0000000..718fbdb
--- /dev/null
+++ b/netd/server/NetlinkHandler.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2008 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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#define LOG_TAG "Netd"
+
+#include <cutils/log.h>
+
+#include <netutils/ifc.h>
+#include <sysutils/NetlinkEvent.h>
+#include "NetlinkHandler.h"
+#include "NetlinkManager.h"
+#include "ResponseCode.h"
+#include "SockDiag.h"
+
+static const char *kUpdated = "updated";
+static const char *kRemoved = "removed";
+
+NetlinkHandler::NetlinkHandler(NetlinkManager *nm, int listenerSocket,
+                               int format) :
+                        NetlinkListener(listenerSocket, format) {
+    mNm = nm;
+}
+
+NetlinkHandler::~NetlinkHandler() {
+}
+
+int NetlinkHandler::start() {
+    return this->startListener();
+}
+
+int NetlinkHandler::stop() {
+    return this->stopListener();
+}
+
+void NetlinkHandler::onEvent(NetlinkEvent *evt) {
+    const char *subsys = evt->getSubsystem();
+    if (!subsys) {
+        ALOGW("No subsystem found in netlink event");
+        return;
+    }
+
+    if (!strcmp(subsys, "net")) {
+        NetlinkEvent::Action action = evt->getAction();
+        const char *iface = evt->findParam("INTERFACE");
+
+        if (action == NetlinkEvent::Action::kAdd) {
+            notifyInterfaceAdded(iface);
+        } else if (action == NetlinkEvent::Action::kRemove) {
+            notifyInterfaceRemoved(iface);
+        } else if (action == NetlinkEvent::Action::kChange) {
+            evt->dump();
+            notifyInterfaceChanged("nana", true);
+        } else if (action == NetlinkEvent::Action::kLinkUp) {
+            notifyInterfaceLinkChanged(iface, true);
+        } else if (action == NetlinkEvent::Action::kLinkDown) {
+            notifyInterfaceLinkChanged(iface, false);
+        } else if (action == NetlinkEvent::Action::kAddressUpdated ||
+                   action == NetlinkEvent::Action::kAddressRemoved) {
+            const char *address = evt->findParam("ADDRESS");
+            const char *flags = evt->findParam("FLAGS");
+            const char *scope = evt->findParam("SCOPE");
+            if (action == NetlinkEvent::Action::kAddressRemoved && iface && address) {
+                // Note: if this interface was deleted, iface is "" and we don't notify.
+                SockDiag sd;
+                if (sd.open()) {
+                    char addrstr[INET6_ADDRSTRLEN];
+                    strncpy(addrstr, address, sizeof(addrstr));
+                    char *slash = strchr(addrstr, '/');
+                    if (slash) {
+                        *slash = '\0';
+                    }
+
+                    int ret = sd.destroySockets(addrstr);
+                    if (ret < 0) {
+                        ALOGE("Error destroying sockets: %s", strerror(ret));
+                    }
+                } else {
+                    ALOGE("Error opening NETLINK_SOCK_DIAG socket: %s", strerror(errno));
+                }
+
+                // TODO: delete this once SOCK_DESTROY works everywhere.
+                if (iface[0]) {
+                    int resetMask = strchr(address, ':') ?
+                            RESET_IPV6_ADDRESSES : RESET_IPV4_ADDRESSES;
+                    resetMask |= RESET_IGNORE_INTERFACE_ADDRESS;
+                    if (int ret = ifc_reset_connections(iface, resetMask)) {
+                        ALOGE("ifc_reset_connections failed on iface %s for address %s (%s)", iface,
+                              address, strerror(ret));
+                    }
+                }
+            }
+            if (iface && iface[0] && address && flags && scope) {
+                notifyAddressChanged(action, address, iface, flags, scope);
+            }
+        } else if (action == NetlinkEvent::Action::kRdnss) {
+            const char *lifetime = evt->findParam("LIFETIME");
+            const char *servers = evt->findParam("SERVERS");
+            if (lifetime && servers) {
+                notifyInterfaceDnsServers(iface, lifetime, servers);
+            }
+        } else if (action == NetlinkEvent::Action::kRouteUpdated ||
+                   action == NetlinkEvent::Action::kRouteRemoved) {
+            const char *route = evt->findParam("ROUTE");
+            const char *gateway = evt->findParam("GATEWAY");
+            const char *iface = evt->findParam("INTERFACE");
+            if (route && (gateway || iface)) {
+                notifyRouteChange(action, route, gateway, iface);
+            }
+        }
+
+    } else if (!strcmp(subsys, "qlog")) {
+        const char *alertName = evt->findParam("ALERT_NAME");
+        const char *iface = evt->findParam("INTERFACE");
+        notifyQuotaLimitReached(alertName, iface);
+
+    } else if (!strcmp(subsys, "strict")) {
+        const char *uid = evt->findParam("UID");
+        const char *hex = evt->findParam("HEX");
+        notifyStrictCleartext(uid, hex);
+
+    } else if (!strcmp(subsys, "xt_idletimer")) {
+        const char *label = evt->findParam("INTERFACE");
+        const char *state = evt->findParam("STATE");
+        const char *timestamp = evt->findParam("TIME_NS");
+        const char *uid = evt->findParam("UID");
+        if (state)
+            notifyInterfaceClassActivity(label, !strcmp("active", state),
+                                         timestamp, uid);
+
+#if !LOG_NDEBUG
+    } else if (strcmp(subsys, "platform") && strcmp(subsys, "backlight")) {
+        /* It is not a VSYNC or a backlight event */
+        ALOGV("unexpected event from subsystem %s", subsys);
+#endif
+    }
+}
+
+void NetlinkHandler::notify(int code, const char *format, ...) {
+    char *msg;
+    va_list args;
+    va_start(args, format);
+    if (vasprintf(&msg, format, args) >= 0) {
+        mNm->getBroadcaster()->sendBroadcast(code, msg, false);
+        free(msg);
+    } else {
+        SLOGE("Failed to send notification: vasprintf: %s", strerror(errno));
+    }
+    va_end(args);
+}
+
+void NetlinkHandler::notifyInterfaceAdded(const char *name) {
+    notify(ResponseCode::InterfaceChange, "Iface added %s", name);
+}
+
+void NetlinkHandler::notifyInterfaceRemoved(const char *name) {
+    notify(ResponseCode::InterfaceChange, "Iface removed %s", name);
+}
+
+void NetlinkHandler::notifyInterfaceChanged(const char *name, bool isUp) {
+    notify(ResponseCode::InterfaceChange,
+           "Iface changed %s %s", name, (isUp ? "up" : "down"));
+}
+
+void NetlinkHandler::notifyInterfaceLinkChanged(const char *name, bool isUp) {
+    notify(ResponseCode::InterfaceChange,
+           "Iface linkstate %s %s", name, (isUp ? "up" : "down"));
+}
+
+void NetlinkHandler::notifyQuotaLimitReached(const char *name, const char *iface) {
+    notify(ResponseCode::BandwidthControl, "limit alert %s %s", name, iface);
+}
+
+void NetlinkHandler::notifyInterfaceClassActivity(const char *name,
+                                                  bool isActive,
+                                                  const char *timestamp,
+                                                  const char *uid) {
+    if (timestamp == NULL)
+        notify(ResponseCode::InterfaceClassActivity,
+           "IfaceClass %s %s", isActive ? "active" : "idle", name);
+    else if (uid != NULL && isActive)
+        notify(ResponseCode::InterfaceClassActivity,
+           "IfaceClass active %s %s %s", name, timestamp, uid);
+    else
+        notify(ResponseCode::InterfaceClassActivity,
+           "IfaceClass %s %s %s", isActive ? "active" : "idle", name, timestamp);
+}
+
+void NetlinkHandler::notifyAddressChanged(NetlinkEvent::Action action, const char *addr,
+                                          const char *iface, const char *flags,
+                                          const char *scope) {
+    notify(ResponseCode::InterfaceAddressChange,
+           "Address %s %s %s %s %s",
+           (action == NetlinkEvent::Action::kAddressUpdated) ? kUpdated : kRemoved,
+           addr, iface, flags, scope);
+}
+
+void NetlinkHandler::notifyInterfaceDnsServers(const char *iface,
+                                               const char *lifetime,
+                                               const char *servers) {
+    notify(ResponseCode::InterfaceDnsInfo, "DnsInfo servers %s %s %s",
+           iface, lifetime, servers);
+}
+
+void NetlinkHandler::notifyRouteChange(NetlinkEvent::Action action, const char *route,
+                                       const char *gateway, const char *iface) {
+    notify(ResponseCode::RouteChange,
+           "Route %s %s%s%s%s%s",
+           (action == NetlinkEvent::Action::kRouteUpdated) ? kUpdated : kRemoved,
+           route,
+           *gateway ? " via " : "",
+           gateway,
+           *iface ? " dev " : "",
+           iface);
+}
+
+void NetlinkHandler::notifyStrictCleartext(const char* uid, const char* hex) {
+    notify(ResponseCode::StrictCleartext, "%s %s", uid, hex);
+}
diff --git a/netd/server/NetlinkHandler.h b/netd/server/NetlinkHandler.h
new file mode 100644
index 0000000..d97c864
--- /dev/null
+++ b/netd/server/NetlinkHandler.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2008 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 _NETLINKHANDLER_H
+#define _NETLINKHANDLER_H
+
+#include <sysutils/NetlinkEvent.h>
+#include <sysutils/NetlinkListener.h>
+#include "NetlinkManager.h"
+
+class NetlinkHandler: public NetlinkListener {
+    NetlinkManager *mNm;
+
+public:
+    NetlinkHandler(NetlinkManager *nm, int listenerSocket, int format);
+    virtual ~NetlinkHandler();
+
+    int start(void);
+    int stop(void);
+
+protected:
+    virtual void onEvent(NetlinkEvent *evt);
+
+    void notify(int code, const char *format, ...);
+    void notifyInterfaceAdded(const char *name);
+    void notifyInterfaceRemoved(const char *name);
+    void notifyInterfaceChanged(const char *name, bool isUp);
+    void notifyInterfaceLinkChanged(const char *name, bool isUp);
+    void notifyQuotaLimitReached(const char *name, const char *iface);
+    void notifyInterfaceClassActivity(const char *name, bool isActive,
+                                      const char *timestamp, const char *uid);
+    void notifyAddressChanged(NetlinkEvent::Action action, const char *addr, const char *iface,
+                              const char *flags, const char *scope);
+    void notifyInterfaceDnsServers(const char *iface, const char *lifetime,
+                                   const char *servers);
+    void notifyRouteChange(NetlinkEvent::Action action, const char *route, const char *gateway, const char *iface);
+    void notifyStrictCleartext(const char* uid, const char* hex);
+};
+#endif
diff --git a/netd/server/NetlinkManager.cpp b/netd/server/NetlinkManager.cpp
new file mode 100644
index 0000000..76af46f
--- /dev/null
+++ b/netd/server/NetlinkManager.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2008 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 <stdio.h>
+#include <errno.h>
+
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#define LOG_TAG "Netd"
+
+#include <cutils/log.h>
+
+#include <netlink/attr.h>
+#include <netlink/genl/genl.h>
+#include <netlink/handlers.h>
+#include <netlink/msg.h>
+
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nfnetlink_log.h>
+#include <linux/netfilter/nfnetlink_compat.h>
+
+#include <arpa/inet.h>
+
+#include "NetlinkManager.h"
+#include "NetlinkHandler.h"
+
+#include "pcap-netfilter-linux-android.h"
+
+const int NetlinkManager::NFLOG_QUOTA_GROUP = 1;
+const int NetlinkManager::NETFILTER_STRICT_GROUP = 2;
+
+NetlinkManager *NetlinkManager::sInstance = NULL;
+
+NetlinkManager *NetlinkManager::Instance() {
+    if (!sInstance)
+        sInstance = new NetlinkManager();
+    return sInstance;
+}
+
+NetlinkManager::NetlinkManager() {
+    mBroadcaster = NULL;
+}
+
+NetlinkManager::~NetlinkManager() {
+}
+
+NetlinkHandler *NetlinkManager::setupSocket(int *sock, int netlinkFamily,
+    int groups, int format, bool configNflog) {
+
+    struct sockaddr_nl nladdr;
+    int sz = 64 * 1024;
+    int on = 1;
+
+    memset(&nladdr, 0, sizeof(nladdr));
+    nladdr.nl_family = AF_NETLINK;
+    nladdr.nl_pid = getpid();
+    nladdr.nl_groups = groups;
+
+    if ((*sock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, netlinkFamily)) < 0) {
+        ALOGE("Unable to create netlink socket: %s", strerror(errno));
+        return NULL;
+    }
+
+    if (setsockopt(*sock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {
+        ALOGE("Unable to set uevent socket SO_RCVBUFFORCE option: %s", strerror(errno));
+        close(*sock);
+        return NULL;
+    }
+
+    if (setsockopt(*sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
+        SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));
+        close(*sock);
+        return NULL;
+    }
+
+    if (bind(*sock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {
+        ALOGE("Unable to bind netlink socket: %s", strerror(errno));
+        close(*sock);
+        return NULL;
+    }
+
+    if (configNflog) {
+        if (android_nflog_send_config_cmd(*sock, 0, NFULNL_CFG_CMD_PF_UNBIND, AF_INET) < 0) {
+            ALOGE("Failed NFULNL_CFG_CMD_PF_UNBIND: %s", strerror(errno));
+            return NULL;
+        }
+        if (android_nflog_send_config_cmd(*sock, 0, NFULNL_CFG_CMD_PF_BIND, AF_INET) < 0) {
+            ALOGE("Failed NFULNL_CFG_CMD_PF_BIND: %s", strerror(errno));
+            return NULL;
+        }
+        if (android_nflog_send_config_cmd(*sock, 0, NFULNL_CFG_CMD_BIND, AF_UNSPEC) < 0) {
+            ALOGE("Failed NFULNL_CFG_CMD_BIND: %s", strerror(errno));
+            return NULL;
+        }
+    }
+
+    NetlinkHandler *handler = new NetlinkHandler(this, *sock, format);
+    if (handler->start()) {
+        ALOGE("Unable to start NetlinkHandler: %s", strerror(errno));
+        close(*sock);
+        return NULL;
+    }
+
+    return handler;
+}
+
+int NetlinkManager::start() {
+    if ((mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT,
+         0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII, false)) == NULL) {
+        return -1;
+    }
+
+    if ((mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE,
+                                     RTMGRP_LINK |
+                                     RTMGRP_IPV4_IFADDR |
+                                     RTMGRP_IPV6_IFADDR |
+                                     RTMGRP_IPV6_ROUTE |
+                                     (1 << (RTNLGRP_ND_USEROPT - 1)),
+         NetlinkListener::NETLINK_FORMAT_BINARY, false)) == NULL) {
+        return -1;
+    }
+
+    if ((mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG,
+            NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY, false)) == NULL) {
+        ALOGE("Unable to open quota socket");
+        // TODO: return -1 once the emulator gets a new kernel.
+    }
+
+    if ((mStrictHandler = setupSocket(&mStrictSock, NETLINK_NETFILTER,
+            0, NetlinkListener::NETLINK_FORMAT_BINARY_UNICAST, true)) == NULL) {
+        ALOGE("Unable to open strict socket");
+        // TODO: return -1 once the emulator gets a new kernel.
+    }
+
+    return 0;
+}
+
+int NetlinkManager::stop() {
+    int status = 0;
+
+    if (mUeventHandler->stop()) {
+        ALOGE("Unable to stop uevent NetlinkHandler: %s", strerror(errno));
+        status = -1;
+    }
+
+    delete mUeventHandler;
+    mUeventHandler = NULL;
+
+    close(mUeventSock);
+    mUeventSock = -1;
+
+    if (mRouteHandler->stop()) {
+        ALOGE("Unable to stop route NetlinkHandler: %s", strerror(errno));
+        status = -1;
+    }
+
+    delete mRouteHandler;
+    mRouteHandler = NULL;
+
+    close(mRouteSock);
+    mRouteSock = -1;
+
+    if (mQuotaHandler) {
+        if (mQuotaHandler->stop()) {
+            ALOGE("Unable to stop quota NetlinkHandler: %s", strerror(errno));
+            status = -1;
+        }
+
+        delete mQuotaHandler;
+        mQuotaHandler = NULL;
+
+        close(mQuotaSock);
+        mQuotaSock = -1;
+    }
+
+    if (mStrictHandler) {
+        if (mStrictHandler->stop()) {
+            ALOGE("Unable to stop strict NetlinkHandler: %s", strerror(errno));
+            status = -1;
+        }
+
+        delete mStrictHandler;
+        mStrictHandler = NULL;
+
+        close(mStrictSock);
+        mStrictSock = -1;
+    }
+
+    return status;
+}
diff --git a/netd/server/NetlinkManager.h b/netd/server/NetlinkManager.h
new file mode 100644
index 0000000..40a5722
--- /dev/null
+++ b/netd/server/NetlinkManager.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2008 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 _NETLINKMANAGER_H
+#define _NETLINKMANAGER_H
+
+#include <sysutils/SocketListener.h>
+#include <sysutils/NetlinkListener.h>
+
+
+class NetlinkHandler;
+
+class NetlinkManager {
+private:
+    static NetlinkManager *sInstance;
+
+private:
+    SocketListener       *mBroadcaster;
+    NetlinkHandler       *mUeventHandler;
+    NetlinkHandler       *mRouteHandler;
+    NetlinkHandler       *mQuotaHandler;
+    NetlinkHandler       *mStrictHandler;
+    int                  mUeventSock;
+    int                  mRouteSock;
+    int                  mQuotaSock;
+    int                  mStrictSock;
+
+public:
+    virtual ~NetlinkManager();
+
+    int start();
+    int stop();
+
+    void setBroadcaster(SocketListener *sl) { mBroadcaster = sl; }
+    SocketListener *getBroadcaster() { return mBroadcaster; }
+
+    static NetlinkManager *Instance();
+
+    /* Group used by xt_quota2 */
+    static const int NFLOG_QUOTA_GROUP;
+    /* Group used by StrictController rules */
+    static const int NETFILTER_STRICT_GROUP;
+
+private:
+    NetlinkManager();
+    NetlinkHandler* setupSocket(int *sock, int netlinkFamily, int groups,
+        int format, bool configNflog);
+};
+#endif
diff --git a/netd/server/Network.cpp b/netd/server/Network.cpp
new file mode 100644
index 0000000..0ca6247
--- /dev/null
+++ b/netd/server/Network.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 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 "Network.h"
+
+#define LOG_TAG "Netd"
+#include "log/log.h"
+
+Network::~Network() {
+    if (!mInterfaces.empty()) {
+        ALOGE("deleting network with netId %u without clearing its interfaces", mNetId);
+    }
+}
+
+unsigned Network::getNetId() const {
+    return mNetId;
+}
+
+bool Network::hasInterface(const std::string& interface) const {
+    return mInterfaces.find(interface) != mInterfaces.end();
+}
+
+const std::set<std::string>& Network::getInterfaces() const {
+    return mInterfaces;
+}
+
+int Network::clearInterfaces() {
+    while (!mInterfaces.empty()) {
+        // Make a copy of the string, so removeInterface() doesn't lose its parameter when it
+        // removes the string from the set.
+        std::string interface = *mInterfaces.begin();
+        if (int ret = removeInterface(interface)) {
+            return ret;
+        }
+    }
+    return 0;
+}
+
+Network::Network(unsigned netId) : mNetId(netId) {
+}
diff --git a/netd/server/Network.h b/netd/server/Network.h
new file mode 100644
index 0000000..3af53d9
--- /dev/null
+++ b/netd/server/Network.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 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_NETWORK_H
+#define NETD_SERVER_NETWORK_H
+
+#include "NetdConstants.h"
+
+#include <set>
+#include <string>
+
+// A Network represents a collection of interfaces participating as a single administrative unit.
+class Network {
+public:
+    enum Type {
+        DUMMY,
+        LOCAL,
+        PHYSICAL,
+        VIRTUAL,
+    };
+
+    // You MUST ensure that no interfaces are still assigned to this network, say by calling
+    // clearInterfaces(), before deleting it. This is because interface removal may fail. If we
+    // automatically removed interfaces in the destructor, you wouldn't know if it failed.
+    virtual ~Network();
+
+    virtual Type getType() const = 0;
+    unsigned getNetId() const;
+
+    bool hasInterface(const std::string& interface) const;
+    const std::set<std::string>& getInterfaces() const;
+
+    // These return 0 on success or negative errno on failure.
+    virtual int addInterface(const std::string& interface) WARN_UNUSED_RESULT = 0;
+    virtual int removeInterface(const std::string& interface) WARN_UNUSED_RESULT = 0;
+    int clearInterfaces() WARN_UNUSED_RESULT;
+
+protected:
+    explicit Network(unsigned netId);
+
+    const unsigned mNetId;
+    std::set<std::string> mInterfaces;
+};
+
+#endif  // NETD_SERVER_NETWORK_H
diff --git a/netd/server/NetworkController.cpp b/netd/server/NetworkController.cpp
new file mode 100644
index 0000000..93a0763
--- /dev/null
+++ b/netd/server/NetworkController.cpp
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+// THREAD-SAFETY
+// -------------
+// The methods in this file are called from multiple threads (from CommandListener, FwmarkServer
+// and DnsProxyListener). So, all accesses to shared state are guarded by a lock.
+//
+// In some cases, a single non-const method acquires and releases the lock several times, like so:
+//     if (isValidNetwork(...)) {  // isValidNetwork() acquires and releases the lock.
+//        setDefaultNetwork(...);  // setDefaultNetwork() also acquires and releases the lock.
+//
+// It might seem that this allows races where the state changes between the two statements, but in
+// fact there are no races because:
+//     1. This pattern only occurs in non-const methods (i.e., those that mutate state).
+//     2. Only CommandListener calls these non-const methods. The others call only const methods.
+//     3. CommandListener only processes one command at a time. I.e., it's serialized.
+// Thus, no other mutation can occur in between the two statements above.
+
+#include "NetworkController.h"
+
+#include "DummyNetwork.h"
+#include "Fwmark.h"
+#include "LocalNetwork.h"
+#include "PhysicalNetwork.h"
+#include "RouteController.h"
+#include "VirtualNetwork.h"
+
+#include "cutils/misc.h"
+#define LOG_TAG "Netd"
+#include "log/log.h"
+#include "resolv_netid.h"
+
+namespace {
+
+// Keep these in sync with ConnectivityService.java.
+const unsigned MIN_NET_ID = 100;
+const unsigned MAX_NET_ID = 65535;
+
+}  // namespace
+
+const unsigned NetworkController::MIN_OEM_ID   =  1;
+const unsigned NetworkController::MAX_OEM_ID   = 50;
+const unsigned NetworkController::DUMMY_NET_ID = 51;
+// NetIds 52..98 are reserved for future use.
+const unsigned NetworkController::LOCAL_NET_ID = 99;
+
+// All calls to methods here are made while holding a write lock on mRWLock.
+class NetworkController::DelegateImpl : public PhysicalNetwork::Delegate {
+public:
+    explicit DelegateImpl(NetworkController* networkController);
+    virtual ~DelegateImpl();
+
+    int modifyFallthrough(unsigned vpnNetId, const std::string& physicalInterface,
+                          Permission permission, bool add) WARN_UNUSED_RESULT;
+
+private:
+    int addFallthrough(const std::string& physicalInterface,
+                       Permission permission) override WARN_UNUSED_RESULT;
+    int removeFallthrough(const std::string& physicalInterface,
+                          Permission permission) override WARN_UNUSED_RESULT;
+
+    int modifyFallthrough(const std::string& physicalInterface, Permission permission,
+                          bool add) WARN_UNUSED_RESULT;
+
+    NetworkController* const mNetworkController;
+};
+
+NetworkController::DelegateImpl::DelegateImpl(NetworkController* networkController) :
+        mNetworkController(networkController) {
+}
+
+NetworkController::DelegateImpl::~DelegateImpl() {
+}
+
+int NetworkController::DelegateImpl::modifyFallthrough(unsigned vpnNetId,
+                                                       const std::string& physicalInterface,
+                                                       Permission permission, bool add) {
+    if (add) {
+        if (int ret = RouteController::addVirtualNetworkFallthrough(vpnNetId,
+                                                                    physicalInterface.c_str(),
+                                                                    permission)) {
+            ALOGE("failed to add fallthrough to %s for VPN netId %u", physicalInterface.c_str(),
+                  vpnNetId);
+            return ret;
+        }
+    } else {
+        if (int ret = RouteController::removeVirtualNetworkFallthrough(vpnNetId,
+                                                                       physicalInterface.c_str(),
+                                                                       permission)) {
+            ALOGE("failed to remove fallthrough to %s for VPN netId %u", physicalInterface.c_str(),
+                  vpnNetId);
+            return ret;
+        }
+    }
+    return 0;
+}
+
+int NetworkController::DelegateImpl::addFallthrough(const std::string& physicalInterface,
+                                                    Permission permission) {
+    return modifyFallthrough(physicalInterface, permission, true);
+}
+
+int NetworkController::DelegateImpl::removeFallthrough(const std::string& physicalInterface,
+                                                       Permission permission) {
+    return modifyFallthrough(physicalInterface, permission, false);
+}
+
+int NetworkController::DelegateImpl::modifyFallthrough(const std::string& physicalInterface,
+                                                       Permission permission, bool add) {
+    for (const auto& entry : mNetworkController->mNetworks) {
+        if (entry.second->getType() == Network::VIRTUAL) {
+            if (int ret = modifyFallthrough(entry.first, physicalInterface, permission, add)) {
+                return ret;
+            }
+        }
+    }
+    return 0;
+}
+
+NetworkController::NetworkController() :
+        mDelegateImpl(new NetworkController::DelegateImpl(this)), mDefaultNetId(NETID_UNSET) {
+    mNetworks[LOCAL_NET_ID] = new LocalNetwork(LOCAL_NET_ID);
+    mNetworks[DUMMY_NET_ID] = new DummyNetwork(DUMMY_NET_ID);
+}
+
+unsigned NetworkController::getDefaultNetwork() const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    return mDefaultNetId;
+}
+
+int NetworkController::setDefaultNetwork(unsigned netId) {
+    android::RWLock::AutoWLock lock(mRWLock);
+
+    if (netId == mDefaultNetId) {
+        return 0;
+    }
+
+    if (netId != NETID_UNSET) {
+        Network* network = getNetworkLocked(netId);
+        if (!network) {
+            ALOGE("no such netId %u", netId);
+            return -ENONET;
+        }
+        if (network->getType() != Network::PHYSICAL) {
+            ALOGE("cannot set default to non-physical network with netId %u", netId);
+            return -EINVAL;
+        }
+        if (int ret = static_cast<PhysicalNetwork*>(network)->addAsDefault()) {
+            return ret;
+        }
+    }
+
+    if (mDefaultNetId != NETID_UNSET) {
+        Network* network = getNetworkLocked(mDefaultNetId);
+        if (!network || network->getType() != Network::PHYSICAL) {
+            ALOGE("cannot find previously set default network with netId %u", mDefaultNetId);
+            return -ESRCH;
+        }
+        if (int ret = static_cast<PhysicalNetwork*>(network)->removeAsDefault()) {
+            return ret;
+        }
+    }
+
+    mDefaultNetId = netId;
+    return 0;
+}
+
+uint32_t NetworkController::getNetworkForDns(unsigned* netId, uid_t uid) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    Fwmark fwmark;
+    fwmark.protectedFromVpn = true;
+    fwmark.permission = PERMISSION_SYSTEM;
+    if (checkUserNetworkAccessLocked(uid, *netId) == 0) {
+        // If a non-zero NetId was explicitly specified, and the user has permission for that
+        // network, use that network's DNS servers. Do not fall through to the default network even
+        // if the explicitly selected network is a split tunnel VPN or a VPN without DNS servers.
+        fwmark.explicitlySelected = true;
+    } else {
+        // If the user is subject to a VPN and the VPN provides DNS servers, use those servers
+        // (possibly falling through to the default network if the VPN doesn't provide a route to
+        // them). Otherwise, use the default network's DNS servers.
+        VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
+        if (virtualNetwork && virtualNetwork->getHasDns()) {
+            *netId = virtualNetwork->getNetId();
+        } else {
+            *netId = mDefaultNetId;
+        }
+    }
+    fwmark.netId = *netId;
+    return fwmark.intValue;
+}
+
+// Returns the NetId that a given UID would use if no network is explicitly selected. Specifically,
+// the VPN that applies to the UID if any; otherwise, the default network.
+unsigned NetworkController::getNetworkForUser(uid_t uid) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    if (VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid)) {
+        return virtualNetwork->getNetId();
+    }
+    return mDefaultNetId;
+}
+
+// Returns the NetId that will be set when a socket connect()s. This is the bypassable VPN that
+// applies to the user if any; otherwise, the default network.
+//
+// In general, we prefer to always set the default network's NetId in connect(), so that if the VPN
+// is a split-tunnel and disappears later, the socket continues working (since the default network's
+// NetId is still valid). Secure VPNs will correctly grab the socket's traffic since they have a
+// high-priority routing rule that doesn't care what NetId the socket has.
+//
+// But bypassable VPNs have a very low priority rule, so we need to mark the socket with the
+// bypassable VPN's NetId if we expect it to get any traffic at all. If the bypassable VPN is a
+// split-tunnel, that's okay, because we have fallthrough rules that will direct the fallthrough
+// traffic to the default network. But it does mean that if the bypassable VPN goes away (and thus
+// the fallthrough rules also go away), the socket that used to fallthrough to the default network
+// will stop working.
+unsigned NetworkController::getNetworkForConnect(uid_t uid) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
+    if (virtualNetwork && !virtualNetwork->isSecure()) {
+        return virtualNetwork->getNetId();
+    }
+    return mDefaultNetId;
+}
+
+void NetworkController::getNetworkContext(
+        unsigned netId, uid_t uid, struct android_net_context* netcontext) const {
+    struct android_net_context nc = {
+            .app_netid = netId,
+            .app_mark = MARK_UNSET,
+            .dns_netid = netId,
+            .dns_mark = MARK_UNSET,
+            .uid = uid,
+    };
+
+    if (nc.app_netid == NETID_UNSET) {
+        nc.app_netid = getNetworkForConnect(uid);
+    }
+    Fwmark fwmark;
+    fwmark.netId = nc.app_netid;
+    nc.app_mark = fwmark.intValue;
+
+    nc.dns_mark = getNetworkForDns(&(nc.dns_netid), uid);
+
+    if (netcontext) {
+        *netcontext = nc;
+    }
+}
+
+unsigned NetworkController::getNetworkForInterface(const char* interface) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    for (const auto& entry : mNetworks) {
+        if (entry.second->hasInterface(interface)) {
+            return entry.first;
+        }
+    }
+    return NETID_UNSET;
+}
+
+bool NetworkController::isVirtualNetwork(unsigned netId) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    Network* network = getNetworkLocked(netId);
+    return network && network->getType() == Network::VIRTUAL;
+}
+
+int NetworkController::createPhysicalNetwork(unsigned netId, Permission permission) {
+    if (!((MIN_NET_ID <= netId && netId <= MAX_NET_ID) ||
+          (MIN_OEM_ID <= netId && netId <= MAX_OEM_ID))) {
+        ALOGE("invalid netId %u", netId);
+        return -EINVAL;
+    }
+
+    if (isValidNetwork(netId)) {
+        ALOGE("duplicate netId %u", netId);
+        return -EEXIST;
+    }
+
+    PhysicalNetwork* physicalNetwork = new PhysicalNetwork(netId, mDelegateImpl);
+    if (int ret = physicalNetwork->setPermission(permission)) {
+        ALOGE("inconceivable! setPermission cannot fail on an empty network");
+        delete physicalNetwork;
+        return ret;
+    }
+
+    android::RWLock::AutoWLock lock(mRWLock);
+    mNetworks[netId] = physicalNetwork;
+    return 0;
+}
+
+int NetworkController::createVirtualNetwork(unsigned netId, bool hasDns, bool secure) {
+    if (!(MIN_NET_ID <= netId && netId <= MAX_NET_ID)) {
+        ALOGE("invalid netId %u", netId);
+        return -EINVAL;
+    }
+
+    if (isValidNetwork(netId)) {
+        ALOGE("duplicate netId %u", netId);
+        return -EEXIST;
+    }
+
+    android::RWLock::AutoWLock lock(mRWLock);
+    if (int ret = modifyFallthroughLocked(netId, true)) {
+        return ret;
+    }
+    mNetworks[netId] = new VirtualNetwork(netId, hasDns, secure);
+    return 0;
+}
+
+int NetworkController::destroyNetwork(unsigned netId) {
+    if (netId == LOCAL_NET_ID) {
+        ALOGE("cannot destroy local network");
+        return -EINVAL;
+    }
+    if (!isValidNetwork(netId)) {
+        ALOGE("no such netId %u", netId);
+        return -ENONET;
+    }
+
+    // TODO: ioctl(SIOCKILLADDR, ...) to kill all sockets on the old network.
+
+    android::RWLock::AutoWLock lock(mRWLock);
+    Network* network = getNetworkLocked(netId);
+
+    // If we fail to destroy a network, things will get stuck badly. Therefore, unlike most of the
+    // other network code, ignore failures and attempt to clear out as much state as possible, even
+    // if we hit an error on the way. Return the first error that we see.
+    int ret = network->clearInterfaces();
+
+    if (mDefaultNetId == netId) {
+        if (int err = static_cast<PhysicalNetwork*>(network)->removeAsDefault()) {
+            ALOGE("inconceivable! removeAsDefault cannot fail on an empty network");
+            if (!ret) {
+                ret = err;
+            }
+        }
+        mDefaultNetId = NETID_UNSET;
+    } else if (network->getType() == Network::VIRTUAL) {
+        if (int err = modifyFallthroughLocked(netId, false)) {
+            if (!ret) {
+                ret = err;
+            }
+        }
+    }
+    mNetworks.erase(netId);
+    delete network;
+    _resolv_delete_cache_for_net(netId);
+    return ret;
+}
+
+int NetworkController::addInterfaceToNetwork(unsigned netId, const char* interface) {
+    if (!isValidNetwork(netId)) {
+        ALOGE("no such netId %u", netId);
+        return -ENONET;
+    }
+
+    unsigned existingNetId = getNetworkForInterface(interface);
+    if (existingNetId != NETID_UNSET && existingNetId != netId) {
+        ALOGE("interface %s already assigned to netId %u", interface, existingNetId);
+        return -EBUSY;
+    }
+
+    android::RWLock::AutoWLock lock(mRWLock);
+    return getNetworkLocked(netId)->addInterface(interface);
+}
+
+int NetworkController::removeInterfaceFromNetwork(unsigned netId, const char* interface) {
+    if (!isValidNetwork(netId)) {
+        ALOGE("no such netId %u", netId);
+        return -ENONET;
+    }
+
+    android::RWLock::AutoWLock lock(mRWLock);
+    return getNetworkLocked(netId)->removeInterface(interface);
+}
+
+Permission NetworkController::getPermissionForUser(uid_t uid) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    return getPermissionForUserLocked(uid);
+}
+
+void NetworkController::setPermissionForUsers(Permission permission,
+                                              const std::vector<uid_t>& uids) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    for (uid_t uid : uids) {
+        mUsers[uid] = permission;
+    }
+}
+
+int NetworkController::checkUserNetworkAccess(uid_t uid, unsigned netId) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    return checkUserNetworkAccessLocked(uid, netId);
+}
+
+int NetworkController::setPermissionForNetworks(Permission permission,
+                                                const std::vector<unsigned>& netIds) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    for (unsigned netId : netIds) {
+        Network* network = getNetworkLocked(netId);
+        if (!network) {
+            ALOGE("no such netId %u", netId);
+            return -ENONET;
+        }
+        if (network->getType() != Network::PHYSICAL) {
+            ALOGE("cannot set permissions on non-physical network with netId %u", netId);
+            return -EINVAL;
+        }
+
+        // TODO: ioctl(SIOCKILLADDR, ...) to kill socets on the network that don't have permission.
+
+        if (int ret = static_cast<PhysicalNetwork*>(network)->setPermission(permission)) {
+            return ret;
+        }
+    }
+    return 0;
+}
+
+int NetworkController::addUsersToNetwork(unsigned netId, const UidRanges& uidRanges) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    Network* network = getNetworkLocked(netId);
+    if (!network) {
+        ALOGE("no such netId %u", netId);
+        return -ENONET;
+    }
+    if (network->getType() != Network::VIRTUAL) {
+        ALOGE("cannot add users to non-virtual network with netId %u", netId);
+        return -EINVAL;
+    }
+    if (int ret = static_cast<VirtualNetwork*>(network)->addUsers(uidRanges)) {
+        return ret;
+    }
+    return 0;
+}
+
+int NetworkController::removeUsersFromNetwork(unsigned netId, const UidRanges& uidRanges) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    Network* network = getNetworkLocked(netId);
+    if (!network) {
+        ALOGE("no such netId %u", netId);
+        return -ENONET;
+    }
+    if (network->getType() != Network::VIRTUAL) {
+        ALOGE("cannot remove users from non-virtual network with netId %u", netId);
+        return -EINVAL;
+    }
+    if (int ret = static_cast<VirtualNetwork*>(network)->removeUsers(uidRanges)) {
+        return ret;
+    }
+    return 0;
+}
+
+int NetworkController::addRoute(unsigned netId, const char* interface, const char* destination,
+                                const char* nexthop, bool legacy, uid_t uid) {
+    return modifyRoute(netId, interface, destination, nexthop, true, legacy, uid);
+}
+
+int NetworkController::removeRoute(unsigned netId, const char* interface, const char* destination,
+                                   const char* nexthop, bool legacy, uid_t uid) {
+    return modifyRoute(netId, interface, destination, nexthop, false, legacy, uid);
+}
+
+bool NetworkController::canProtect(uid_t uid) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    return ((getPermissionForUserLocked(uid) & PERMISSION_SYSTEM) == PERMISSION_SYSTEM) ||
+           mProtectableUsers.find(uid) != mProtectableUsers.end();
+}
+
+void NetworkController::allowProtect(const std::vector<uid_t>& uids) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    mProtectableUsers.insert(uids.begin(), uids.end());
+}
+
+void NetworkController::denyProtect(const std::vector<uid_t>& uids) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    for (uid_t uid : uids) {
+        mProtectableUsers.erase(uid);
+    }
+}
+
+bool NetworkController::isValidNetwork(unsigned netId) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    return getNetworkLocked(netId);
+}
+
+Network* NetworkController::getNetworkLocked(unsigned netId) const {
+    auto iter = mNetworks.find(netId);
+    return iter == mNetworks.end() ? NULL : iter->second;
+}
+
+VirtualNetwork* NetworkController::getVirtualNetworkForUserLocked(uid_t uid) const {
+    for (const auto& entry : mNetworks) {
+        if (entry.second->getType() == Network::VIRTUAL) {
+            VirtualNetwork* virtualNetwork = static_cast<VirtualNetwork*>(entry.second);
+            if (virtualNetwork->appliesToUser(uid)) {
+                return virtualNetwork;
+            }
+        }
+    }
+    return NULL;
+}
+
+Permission NetworkController::getPermissionForUserLocked(uid_t uid) const {
+    auto iter = mUsers.find(uid);
+    if (iter != mUsers.end()) {
+        return iter->second;
+    }
+    return uid < FIRST_APPLICATION_UID ? PERMISSION_SYSTEM : PERMISSION_NONE;
+}
+
+int NetworkController::checkUserNetworkAccessLocked(uid_t uid, unsigned netId) const {
+    Network* network = getNetworkLocked(netId);
+    if (!network) {
+        return -ENONET;
+    }
+
+    // If uid is INVALID_UID, this likely means that we were unable to retrieve the UID of the peer
+    // (using SO_PEERCRED). Be safe and deny access to the network, even if it's valid.
+    if (uid == INVALID_UID) {
+        return -EREMOTEIO;
+    }
+    Permission userPermission = getPermissionForUserLocked(uid);
+    if ((userPermission & PERMISSION_SYSTEM) == PERMISSION_SYSTEM) {
+        return 0;
+    }
+    if (network->getType() == Network::VIRTUAL) {
+        return static_cast<VirtualNetwork*>(network)->appliesToUser(uid) ? 0 : -EPERM;
+    }
+    VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
+    if (virtualNetwork && virtualNetwork->isSecure() &&
+            mProtectableUsers.find(uid) == mProtectableUsers.end()) {
+        return -EPERM;
+    }
+    Permission networkPermission = static_cast<PhysicalNetwork*>(network)->getPermission();
+    return ((userPermission & networkPermission) == networkPermission) ? 0 : -EACCES;
+}
+
+int NetworkController::modifyRoute(unsigned netId, const char* interface, const char* destination,
+                                   const char* nexthop, bool add, bool legacy, uid_t uid) {
+    if (!isValidNetwork(netId)) {
+        ALOGE("no such netId %u", netId);
+        return -ENONET;
+    }
+    unsigned existingNetId = getNetworkForInterface(interface);
+    if (existingNetId == NETID_UNSET) {
+        ALOGE("interface %s not assigned to any netId", interface);
+        return -ENODEV;
+    }
+    if (existingNetId != netId) {
+        ALOGE("interface %s assigned to netId %u, not %u", interface, existingNetId, netId);
+        return -ENOENT;
+    }
+
+    RouteController::TableType tableType;
+    if (netId == LOCAL_NET_ID) {
+        tableType = RouteController::LOCAL_NETWORK;
+    } else if (legacy) {
+        if ((getPermissionForUser(uid) & PERMISSION_SYSTEM) == PERMISSION_SYSTEM) {
+            tableType = RouteController::LEGACY_SYSTEM;
+        } else {
+            tableType = RouteController::LEGACY_NETWORK;
+        }
+    } else {
+        tableType = RouteController::INTERFACE;
+    }
+
+    return add ? RouteController::addRoute(interface, destination, nexthop, tableType) :
+                 RouteController::removeRoute(interface, destination, nexthop, tableType);
+}
+
+int NetworkController::modifyFallthroughLocked(unsigned vpnNetId, bool add) {
+    if (mDefaultNetId == NETID_UNSET) {
+        return 0;
+    }
+    Network* network = getNetworkLocked(mDefaultNetId);
+    if (!network) {
+        ALOGE("cannot find previously set default network with netId %u", mDefaultNetId);
+        return -ESRCH;
+    }
+    if (network->getType() != Network::PHYSICAL) {
+        ALOGE("inconceivable! default network must be a physical network");
+        return -EINVAL;
+    }
+    Permission permission = static_cast<PhysicalNetwork*>(network)->getPermission();
+    for (const auto& physicalInterface : network->getInterfaces()) {
+        if (int ret = mDelegateImpl->modifyFallthrough(vpnNetId, physicalInterface, permission,
+                                                       add)) {
+            return ret;
+        }
+    }
+    return 0;
+}
diff --git a/netd/server/NetworkController.h b/netd/server/NetworkController.h
new file mode 100644
index 0000000..6d72aeb
--- /dev/null
+++ b/netd/server/NetworkController.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 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_NETWORK_CONTROLLER_H
+#define NETD_SERVER_NETWORK_CONTROLLER_H
+
+#include "NetdConstants.h"
+#include "Permission.h"
+
+#include "utils/RWLock.h"
+
+#include <list>
+#include <map>
+#include <set>
+#include <sys/types.h>
+#include <vector>
+
+class Network;
+class UidRanges;
+class VirtualNetwork;
+
+/*
+ * Keeps track of default, per-pid, and per-uid-range network selection, as
+ * well as the mark associated with each network. Networks are identified
+ * by netid. In all set* commands netid == 0 means "unspecified" and is
+ * equivalent to clearing the mapping.
+ */
+class NetworkController {
+public:
+    static const unsigned MIN_OEM_ID;
+    static const unsigned MAX_OEM_ID;
+    static const unsigned LOCAL_NET_ID;
+    static const unsigned DUMMY_NET_ID;
+
+    NetworkController();
+
+    unsigned getDefaultNetwork() const;
+    int setDefaultNetwork(unsigned netId) WARN_UNUSED_RESULT;
+
+    // Sets |*netId| to an appropriate NetId to use for DNS for the given user. Call with |*netId|
+    // set to a non-NETID_UNSET value if the user already has indicated a preference. Returns the
+    // fwmark value to set on the socket when performing the DNS request.
+    uint32_t getNetworkForDns(unsigned* netId, uid_t uid) const;
+    unsigned getNetworkForUser(uid_t uid) const;
+    unsigned getNetworkForConnect(uid_t uid) const;
+    void getNetworkContext(unsigned netId, uid_t uid, struct android_net_context* netcontext) const;
+    unsigned getNetworkForInterface(const char* interface) const;
+    bool isVirtualNetwork(unsigned netId) const;
+
+    int createPhysicalNetwork(unsigned netId, Permission permission) WARN_UNUSED_RESULT;
+    int createVirtualNetwork(unsigned netId, bool hasDns, bool secure) WARN_UNUSED_RESULT;
+    int destroyNetwork(unsigned netId) WARN_UNUSED_RESULT;
+
+    int addInterfaceToNetwork(unsigned netId, const char* interface) WARN_UNUSED_RESULT;
+    int removeInterfaceFromNetwork(unsigned netId, const char* interface) WARN_UNUSED_RESULT;
+
+    Permission getPermissionForUser(uid_t uid) const;
+    void setPermissionForUsers(Permission permission, const std::vector<uid_t>& uids);
+    int checkUserNetworkAccess(uid_t uid, unsigned netId) const;
+    int setPermissionForNetworks(Permission permission,
+                                 const std::vector<unsigned>& netIds) WARN_UNUSED_RESULT;
+
+    int addUsersToNetwork(unsigned netId, const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+    int removeUsersFromNetwork(unsigned netId, const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+
+    // |nexthop| can be NULL (to indicate a directly-connected route), "unreachable" (to indicate a
+    // route that's blocked), "throw" (to indicate the lack of a match), or a regular IP address.
+    //
+    // Routes are added to tables determined by the interface, so only |interface| is actually used.
+    // |netId| is given only to sanity check that the interface has the correct netId.
+    int addRoute(unsigned netId, const char* interface, const char* destination,
+                 const char* nexthop, bool legacy, uid_t uid) WARN_UNUSED_RESULT;
+    int removeRoute(unsigned netId, const char* interface, const char* destination,
+                    const char* nexthop, bool legacy, uid_t uid) WARN_UNUSED_RESULT;
+
+    bool canProtect(uid_t uid) const;
+    void allowProtect(const std::vector<uid_t>& uids);
+    void denyProtect(const std::vector<uid_t>& uids);
+
+private:
+    bool isValidNetwork(unsigned netId) const;
+    Network* getNetworkLocked(unsigned netId) const;
+    VirtualNetwork* getVirtualNetworkForUserLocked(uid_t uid) const;
+    Permission getPermissionForUserLocked(uid_t uid) const;
+    int checkUserNetworkAccessLocked(uid_t uid, unsigned netId) const;
+
+    int modifyRoute(unsigned netId, const char* interface, const char* destination,
+                    const char* nexthop, bool add, bool legacy, uid_t uid) WARN_UNUSED_RESULT;
+    int modifyFallthroughLocked(unsigned vpnNetId, bool add) WARN_UNUSED_RESULT;
+
+    class DelegateImpl;
+    DelegateImpl* const mDelegateImpl;
+
+    // mRWLock guards all accesses to mDefaultNetId, mNetworks, mUsers and mProtectableUsers.
+    mutable android::RWLock mRWLock;
+    unsigned mDefaultNetId;
+    std::map<unsigned, Network*> mNetworks;  // Map keys are NetIds.
+    std::map<uid_t, Permission> mUsers;
+    std::set<uid_t> mProtectableUsers;
+};
+
+#endif  // NETD_SERVER_NETWORK_CONTROLLER_H
diff --git a/netd/server/PhysicalNetwork.cpp b/netd/server/PhysicalNetwork.cpp
new file mode 100644
index 0000000..495a93a
--- /dev/null
+++ b/netd/server/PhysicalNetwork.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2014 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 "PhysicalNetwork.h"
+
+#include "RouteController.h"
+
+#define LOG_TAG "Netd"
+#include "log/log.h"
+
+namespace {
+
+WARN_UNUSED_RESULT int addToDefault(unsigned netId, const std::string& interface,
+                                    Permission permission, PhysicalNetwork::Delegate* delegate) {
+    if (int ret = RouteController::addInterfaceToDefaultNetwork(interface.c_str(), permission)) {
+        ALOGE("failed to add interface %s to default netId %u", interface.c_str(), netId);
+        return ret;
+    }
+    if (int ret = delegate->addFallthrough(interface, permission)) {
+        return ret;
+    }
+    return 0;
+}
+
+WARN_UNUSED_RESULT int removeFromDefault(unsigned netId, const std::string& interface,
+                                         Permission permission,
+                                         PhysicalNetwork::Delegate* delegate) {
+    if (int ret = RouteController::removeInterfaceFromDefaultNetwork(interface.c_str(),
+                                                                     permission)) {
+        ALOGE("failed to remove interface %s from default netId %u", interface.c_str(), netId);
+        return ret;
+    }
+    if (int ret = delegate->removeFallthrough(interface, permission)) {
+        return ret;
+    }
+    return 0;
+}
+
+}  // namespace
+
+PhysicalNetwork::Delegate::~Delegate() {
+}
+
+PhysicalNetwork::PhysicalNetwork(unsigned netId, PhysicalNetwork::Delegate* delegate) :
+        Network(netId), mDelegate(delegate), mPermission(PERMISSION_NONE), mIsDefault(false) {
+}
+
+PhysicalNetwork::~PhysicalNetwork() {
+}
+
+Permission PhysicalNetwork::getPermission() const {
+    return mPermission;
+}
+
+int PhysicalNetwork::setPermission(Permission permission) {
+    if (permission == mPermission) {
+        return 0;
+    }
+    for (const std::string& interface : mInterfaces) {
+        if (int ret = RouteController::modifyPhysicalNetworkPermission(mNetId, interface.c_str(),
+                                                                       mPermission, permission)) {
+            ALOGE("failed to change permission on interface %s of netId %u from %x to %x",
+                  interface.c_str(), mNetId, mPermission, permission);
+            return ret;
+        }
+    }
+    if (mIsDefault) {
+        for (const std::string& interface : mInterfaces) {
+            if (int ret = addToDefault(mNetId, interface, permission, mDelegate)) {
+                return ret;
+            }
+            if (int ret = removeFromDefault(mNetId, interface, mPermission, mDelegate)) {
+                return ret;
+            }
+        }
+    }
+    mPermission = permission;
+    return 0;
+}
+
+int PhysicalNetwork::addAsDefault() {
+    if (mIsDefault) {
+        return 0;
+    }
+    for (const std::string& interface : mInterfaces) {
+        if (int ret = addToDefault(mNetId, interface, mPermission, mDelegate)) {
+            return ret;
+        }
+    }
+    mIsDefault = true;
+    return 0;
+}
+
+int PhysicalNetwork::removeAsDefault() {
+    if (!mIsDefault) {
+        return 0;
+    }
+    for (const std::string& interface : mInterfaces) {
+        if (int ret = removeFromDefault(mNetId, interface, mPermission, mDelegate)) {
+            return ret;
+        }
+    }
+    mIsDefault = false;
+    return 0;
+}
+
+Network::Type PhysicalNetwork::getType() const {
+    return PHYSICAL;
+}
+
+int PhysicalNetwork::addInterface(const std::string& interface) {
+    if (hasInterface(interface)) {
+        return 0;
+    }
+    if (int ret = RouteController::addInterfaceToPhysicalNetwork(mNetId, interface.c_str(),
+                                                                 mPermission)) {
+        ALOGE("failed to add interface %s to netId %u", interface.c_str(), mNetId);
+        return ret;
+    }
+    if (mIsDefault) {
+        if (int ret = addToDefault(mNetId, interface, mPermission, mDelegate)) {
+            return ret;
+        }
+    }
+    mInterfaces.insert(interface);
+    return 0;
+}
+
+int PhysicalNetwork::removeInterface(const std::string& interface) {
+    if (!hasInterface(interface)) {
+        return 0;
+    }
+    if (mIsDefault) {
+        if (int ret = removeFromDefault(mNetId, interface, mPermission, mDelegate)) {
+            return ret;
+        }
+    }
+    // This step will flush the interface index from the cache in RouteController so it must be
+    // done last as further requests to the RouteController regarding this interface will fail
+    // to find the interface index in the cache in cases where the interface is already gone
+    // (e.g. bt-pan).
+    if (int ret = RouteController::removeInterfaceFromPhysicalNetwork(mNetId, interface.c_str(),
+                                                                      mPermission)) {
+        ALOGE("failed to remove interface %s from netId %u", interface.c_str(), mNetId);
+        return ret;
+    }
+    mInterfaces.erase(interface);
+    return 0;
+}
diff --git a/netd/server/PhysicalNetwork.h b/netd/server/PhysicalNetwork.h
new file mode 100644
index 0000000..2ef10df
--- /dev/null
+++ b/netd/server/PhysicalNetwork.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 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_PHYSICAL_NETWORK_H
+#define NETD_SERVER_PHYSICAL_NETWORK_H
+
+#include "Network.h"
+#include "Permission.h"
+
+class PhysicalNetwork : public Network {
+public:
+    class Delegate {
+    public:
+        virtual ~Delegate();
+
+        virtual int addFallthrough(const std::string& physicalInterface,
+                                   Permission permission) WARN_UNUSED_RESULT = 0;
+        virtual int removeFallthrough(const std::string& physicalInterface,
+                                      Permission permission) WARN_UNUSED_RESULT = 0;
+    };
+
+    PhysicalNetwork(unsigned netId, Delegate* delegate);
+    virtual ~PhysicalNetwork();
+
+    // These refer to permissions that apps must have in order to use this network.
+    Permission getPermission() const;
+    int setPermission(Permission permission) WARN_UNUSED_RESULT;
+
+    int addAsDefault() WARN_UNUSED_RESULT;
+    int removeAsDefault() WARN_UNUSED_RESULT;
+
+private:
+    Type getType() const override;
+    int addInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+    int removeInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+
+    Delegate* const mDelegate;
+    Permission mPermission;
+    bool mIsDefault;
+};
+
+#endif  // NETD_SERVER_PHYSICAL_NETWORK_H
diff --git a/netd/server/PppController.cpp b/netd/server/PppController.cpp
new file mode 100644
index 0000000..581b9c6
--- /dev/null
+++ b/netd/server/PppController.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2008 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 <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <dirent.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#define LOG_TAG "PppController"
+#include <cutils/log.h>
+
+#include "PppController.h"
+
+PppController::PppController() {
+    mTtys = new TtyCollection();
+    mPid = 0;
+}
+
+PppController::~PppController() {
+    TtyCollection::iterator it;
+
+    for (it = mTtys->begin(); it != mTtys->end(); ++it) {
+        free(*it);
+    }
+    mTtys->clear();
+}
+
+int PppController::attachPppd(const char *tty, struct in_addr local,
+                              struct in_addr remote, struct in_addr dns1,
+                              struct in_addr dns2) {
+    pid_t pid;
+
+    if (mPid) {
+        ALOGE("Multiple PPPD instances not currently supported");
+        errno = EBUSY;
+        return -1;
+    }
+
+    TtyCollection::iterator it;
+    for (it = mTtys->begin(); it != mTtys->end(); ++it) {
+        if (!strcmp(tty, *it)) {
+            break;
+        }
+    }
+    if (it == mTtys->end()) {
+        ALOGE("Invalid tty '%s' specified", tty);
+        errno = -EINVAL;
+        return -1;
+    }
+
+    if ((pid = fork()) < 0) {
+        ALOGE("fork failed (%s)", strerror(errno));
+        return -1;
+    }
+
+    if (!pid) {
+        char *l = strdup(inet_ntoa(local));
+        char *r = strdup(inet_ntoa(remote));
+        char *d1 = strdup(inet_ntoa(dns1));
+        char *d2 = strdup(inet_ntoa(dns2));
+        char dev[32];
+        char *lr;
+
+        asprintf(&lr, "%s:%s", l, r);
+        free(l);
+        free(r);
+
+        snprintf(dev, sizeof(dev), "/dev/%s", tty);
+
+        // TODO: Deal with pppd bailing out after 99999 seconds of being started
+        // but not getting a connection
+        if (execl("/system/bin/pppd", "/system/bin/pppd", "-detach", dev, "115200",
+                  lr, "ms-dns", d1, "ms-dns", d2, "lcp-max-configure", "99999", (char *) NULL)) {
+            ALOGE("execl failed (%s)", strerror(errno));
+        }
+        free(lr);
+        free(d1);
+        free(d2);
+        ALOGE("Should never get here!");
+        return 0;
+    } else {
+        mPid = pid;
+    }
+    return 0;
+}
+
+int PppController::detachPppd(const char *tty) {
+
+    if (mPid == 0) {
+        ALOGE("PPPD already stopped");
+        return 0;
+    }
+
+    ALOGD("Stopping PPPD services on port %s", tty);
+    kill(mPid, SIGTERM);
+    waitpid(mPid, NULL, 0);
+    mPid = 0;
+    ALOGD("PPPD services on port %s stopped", tty);
+    return 0;
+}
+
+TtyCollection *PppController::getTtyList() {
+    updateTtyList();
+    return mTtys;
+}
+
+int PppController::updateTtyList() {
+    TtyCollection::iterator it;
+
+    for (it = mTtys->begin(); it != mTtys->end(); ++it) {
+        free(*it);
+    }
+    mTtys->clear();
+
+    DIR *d = opendir("/sys/class/tty");
+    if (!d) {
+        ALOGE("Error opening /sys/class/tty (%s)", strerror(errno));
+        return -1;
+    }
+
+    struct dirent *de;
+    while ((de = readdir(d))) {
+        if (de->d_name[0] == '.')
+            continue;
+        if ((!strncmp(de->d_name, "tty", 3)) && (strlen(de->d_name) > 3)) {
+            mTtys->push_back(strdup(de->d_name));
+        }
+    }
+    closedir(d);
+    return 0;
+}
diff --git a/netd/server/PppController.h b/netd/server/PppController.h
new file mode 100644
index 0000000..cc74c8c
--- /dev/null
+++ b/netd/server/PppController.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 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 _PPP_CONTROLLER_H
+#define _PPP_CONTROLLER_H
+
+#include <linux/in.h>
+
+#include "List.h"
+
+typedef android::netd::List<char *> TtyCollection;
+
+class PppController {
+    TtyCollection *mTtys;
+    pid_t          mPid; // TODO: Add support for > 1 pppd instance
+
+public:
+    PppController();
+    virtual ~PppController();
+
+    int attachPppd(const char *tty, struct in_addr local,
+                   struct in_addr remote, struct in_addr dns1,
+                   struct in_addr dns2);
+    int detachPppd(const char *tty);
+    TtyCollection *getTtyList();
+
+private:
+    int updateTtyList();
+};
+
+#endif
diff --git a/netd/server/ResolverController.cpp b/netd/server/ResolverController.cpp
new file mode 100644
index 0000000..16cfd53
--- /dev/null
+++ b/netd/server/ResolverController.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 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 "ResolverController"
+#define DBG 0
+
+#include <cutils/log.h>
+
+#include <net/if.h>
+
+// NOTE: <resolv_netid.h> is a private C library header that provides
+//       declarations for _resolv_set_nameservers_for_net and
+//       _resolv_flush_cache_for_net
+#include <resolv_netid.h>
+#include <resolv_params.h>
+
+#include "ResolverController.h"
+
+int ResolverController::setDnsServers(unsigned netId, const char* searchDomains,
+        const char** servers, int numservers, const __res_params* params) {
+    if (DBG) {
+        ALOGD("setDnsServers netId = %u\n", netId);
+    }
+    _resolv_set_nameservers_for_net(netId, servers, numservers, searchDomains, params);
+    return 0;
+}
+
+int ResolverController::clearDnsServers(unsigned netId) {
+    _resolv_set_nameservers_for_net(netId, NULL, 0, "", NULL);
+    if (DBG) {
+        ALOGD("clearDnsServers netId = %u\n", netId);
+    }
+    return 0;
+}
+
+int ResolverController::flushDnsCache(unsigned netId) {
+    if (DBG) {
+        ALOGD("flushDnsCache netId = %u\n", netId);
+    }
+
+    _resolv_flush_cache_for_net(netId);
+
+    return 0;
+}
diff --git a/netd/server/ResolverController.h b/netd/server/ResolverController.h
new file mode 100644
index 0000000..048ff3f
--- /dev/null
+++ b/netd/server/ResolverController.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 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 _RESOLVER_CONTROLLER_H_
+#define _RESOLVER_CONTROLLER_H_
+
+#include <netinet/in.h>
+#include <linux/in.h>
+
+struct __res_params;
+
+class ResolverController {
+public:
+    ResolverController() {};
+    virtual ~ResolverController() {};
+    int setDnsServers(unsigned netId, const char* searchDomains, const char** servers,
+            int numservers, const __res_params* params);
+    int clearDnsServers(unsigned netid);
+    int flushDnsCache(unsigned netid);
+    // TODO: Add deleteDnsCache(unsigned netId)
+};
+
+#endif /* _RESOLVER_CONTROLLER_H_ */
diff --git a/netd/server/ResponseCode.h b/netd/server/ResponseCode.h
new file mode 100644
index 0000000..19d76c3
--- /dev/null
+++ b/netd/server/ResponseCode.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2008 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 _RESPONSECODE_H
+#define _RESPONSECODE_H
+
+class ResponseCode {
+    // Keep in sync with
+    // frameworks/base/services/java/com/android/server/NetworkManagementService.java
+public:
+    // 100 series - Requestion action was initiated; expect another reply
+    // before proceeding with a new command.
+    static const int ActionInitiated           = 100;
+    static const int InterfaceListResult       = 110;
+    static const int TetherInterfaceListResult = 111;
+    static const int TetherDnsFwdTgtListResult = 112;
+    static const int TtyListResult             = 113;
+    static const int TetheringStatsListResult  = 114;
+    static const int TetherDnsFwdNetIdResult   = 115;
+
+    // 200 series - Requested action has been successfully completed
+    static const int CommandOkay               = 200;
+    static const int TetherStatusResult        = 210;
+    static const int IpFwdStatusResult         = 211;
+    static const int InterfaceGetCfgResult     = 213;
+    static const int SoftapStatusResult        = 214;
+    static const int UsbRNDISStatusResult      = 215;
+    static const int InterfaceRxCounterResult  = 216;
+    static const int InterfaceTxCounterResult  = 217;
+    static const int InterfaceRxThrottleResult = 218;
+    static const int InterfaceTxThrottleResult = 219;
+    static const int QuotaCounterResult        = 220;
+    static const int TetheringStatsResult      = 221;
+    static const int DnsProxyQueryResult       = 222;
+    static const int ClatdStatusResult         = 223;
+
+    // 400 series - The command was accepted but the requested action
+    // did not take place.
+    static const int OperationFailed           = 400;
+    static const int DnsProxyOperationFailed   = 401;
+    static const int ServiceStartFailed        = 402;
+    static const int ServiceStopFailed         = 403;
+
+    // 500 series - The command was not accepted and the requested
+    // action did not take place.
+    static const int CommandSyntaxError = 500;
+    static const int CommandParameterError = 501;
+
+    // 600 series - Unsolicited broadcasts
+    static const int InterfaceChange                = 600;
+    static const int BandwidthControl               = 601;
+    static const int ServiceDiscoveryFailed         = 602;
+    static const int ServiceDiscoveryServiceAdded   = 603;
+    static const int ServiceDiscoveryServiceRemoved = 604;
+    static const int ServiceRegistrationFailed      = 605;
+    static const int ServiceRegistrationSucceeded   = 606;
+    static const int ServiceResolveFailed           = 607;
+    static const int ServiceResolveSuccess          = 608;
+    static const int ServiceSetHostnameFailed       = 609;
+    static const int ServiceSetHostnameSuccess      = 610;
+    static const int ServiceGetAddrInfoFailed       = 611;
+    static const int ServiceGetAddrInfoSuccess      = 612;
+    static const int InterfaceClassActivity         = 613;
+    static const int InterfaceAddressChange         = 614;
+    static const int InterfaceDnsInfo               = 615;
+    static const int RouteChange                    = 616;
+    static const int StrictCleartext                = 617;
+};
+#endif
diff --git a/netd/server/RouteController.cpp b/netd/server/RouteController.cpp
new file mode 100644
index 0000000..e4b7cc1
--- /dev/null
+++ b/netd/server/RouteController.cpp
@@ -0,0 +1,1091 @@
+/*
+ * Copyright (C) 2014 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 "RouteController.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fib_rules.h>
+#include <net/if.h>
+#include <sys/stat.h>
+
+#include <private/android_filesystem_config.h>
+
+#include <map>
+
+#include "Fwmark.h"
+#include "UidRanges.h"
+#include "DummyNetwork.h"
+
+#include "android-base/file.h"
+#define LOG_TAG "Netd"
+#include "log/log.h"
+#include "logwrap/logwrap.h"
+#include "netutils/ifc.h"
+#include "resolv_netid.h"
+
+using android::base::WriteStringToFile;
+
+namespace {
+
+// BEGIN CONSTANTS --------------------------------------------------------------------------------
+
+const uint32_t RULE_PRIORITY_VPN_OVERRIDE_SYSTEM = 10000;
+const uint32_t RULE_PRIORITY_VPN_OVERRIDE_OIF    = 10500;
+const uint32_t RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL = 11000;
+const uint32_t RULE_PRIORITY_SECURE_VPN          = 12000;
+const uint32_t RULE_PRIORITY_EXPLICIT_NETWORK    = 13000;
+const uint32_t RULE_PRIORITY_OUTPUT_INTERFACE    = 14000;
+const uint32_t RULE_PRIORITY_LEGACY_SYSTEM       = 15000;
+const uint32_t RULE_PRIORITY_LEGACY_NETWORK      = 16000;
+const uint32_t RULE_PRIORITY_LOCAL_NETWORK       = 17000;
+const uint32_t RULE_PRIORITY_TETHERING           = 18000;
+const uint32_t RULE_PRIORITY_IMPLICIT_NETWORK    = 19000;
+const uint32_t RULE_PRIORITY_BYPASSABLE_VPN      = 20000;
+const uint32_t RULE_PRIORITY_VPN_FALLTHROUGH     = 21000;
+const uint32_t RULE_PRIORITY_DEFAULT_NETWORK     = 22000;
+const uint32_t RULE_PRIORITY_DIRECTLY_CONNECTED  = 23000;
+const uint32_t RULE_PRIORITY_UNREACHABLE         = 32000;
+
+const uint32_t ROUTE_TABLE_LOCAL_NETWORK  = 97;
+const uint32_t ROUTE_TABLE_LEGACY_NETWORK = 98;
+const uint32_t ROUTE_TABLE_LEGACY_SYSTEM  = 99;
+
+const char* const ROUTE_TABLE_NAME_LOCAL_NETWORK  = "local_network";
+const char* const ROUTE_TABLE_NAME_LEGACY_NETWORK = "legacy_network";
+const char* const ROUTE_TABLE_NAME_LEGACY_SYSTEM  = "legacy_system";
+
+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 our UID routing changes are not
+// upstream (yet?), so we can't just pick them up from kernel headers. When (if?) the changes make
+// it upstream, we'll remove this and rely on the kernel header values. For now, add a static assert
+// that will warn us if upstream has given these values some other meaning.
+const uint16_t FRA_UID_START = 18;
+const uint16_t FRA_UID_END   = 19;
+static_assert(FRA_UID_START > FRA_MAX,
+             "Android-specific FRA_UID_{START,END} values also assigned in Linux uapi. "
+             "Check that these values match what the kernel does and then update this assertion.");
+
+const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
+const uint16_t NETLINK_CREATE_REQUEST_FLAGS = NETLINK_REQUEST_FLAGS | NLM_F_CREATE | NLM_F_EXCL;
+
+const sockaddr_nl NETLINK_ADDRESS = {AF_NETLINK, 0, 0, 0};
+
+const uint8_t AF_FAMILIES[] = {AF_INET, AF_INET6};
+
+const char* const IP_VERSIONS[] = {"-4", "-6"};
+
+const uid_t UID_ROOT = 0;
+const char* const IIF_LOOPBACK = "lo";
+const char* const IIF_NONE = NULL;
+const char* const OIF_NONE = NULL;
+const bool ACTION_ADD = true;
+const bool ACTION_DEL = false;
+const bool MODIFY_NON_UID_BASED_RULES = true;
+
+const char* const RT_TABLES_PATH = "/data/misc/net/rt_tables";
+const mode_t RT_TABLES_MODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;  // mode 0644, rw-r--r--
+
+const unsigned ROUTE_FLUSH_ATTEMPTS = 2;
+
+// Avoids "non-constant-expression cannot be narrowed from type 'unsigned int' to 'unsigned short'"
+// warnings when using RTA_LENGTH(x) inside static initializers (even when x is already uint16_t).
+constexpr uint16_t U16_RTA_LENGTH(uint16_t x) {
+    return RTA_LENGTH(x);
+}
+
+// These are practically const, but can't be declared so, because they are used to initialize
+// non-const pointers ("void* iov_base") in iovec arrays.
+rtattr FRATTR_PRIORITY  = { U16_RTA_LENGTH(sizeof(uint32_t)), FRA_PRIORITY };
+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 RTATTR_TABLE     = { U16_RTA_LENGTH(sizeof(uint32_t)), RTA_TABLE };
+rtattr RTATTR_OIF       = { U16_RTA_LENGTH(sizeof(uint32_t)), RTA_OIF };
+
+uint8_t PADDING_BUFFER[RTA_ALIGNTO] = {0, 0, 0, 0};
+
+// END CONSTANTS ----------------------------------------------------------------------------------
+
+// No locks needed because RouteController is accessed only from one thread (in CommandListener).
+std::map<std::string, uint32_t> interfaceToTable;
+
+uint32_t getRouteTableForInterface(const char* interface) {
+    uint32_t index = if_nametoindex(interface);
+    if (index) {
+        index += RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX;
+        interfaceToTable[interface] = index;
+        return index;
+    }
+    // If the interface goes away if_nametoindex() will return 0 but we still need to know
+    // the index so we can remove the rules and routes.
+    auto iter = interfaceToTable.find(interface);
+    if (iter == interfaceToTable.end()) {
+        ALOGE("cannot find interface %s", interface);
+        return RT_TABLE_UNSPEC;
+    }
+    return iter->second;
+}
+
+void addTableName(uint32_t table, const std::string& name, std::string* contents) {
+    char tableString[UINT32_STRLEN];
+    snprintf(tableString, sizeof(tableString), "%u", table);
+    *contents += tableString;
+    *contents += " ";
+    *contents += name;
+    *contents += "\n";
+}
+
+// Doesn't return success/failure as the file is optional; it's okay if we fail to update it.
+void updateTableNamesFile() {
+    std::string contents;
+
+    addTableName(RT_TABLE_LOCAL, ROUTE_TABLE_NAME_LOCAL, &contents);
+    addTableName(RT_TABLE_MAIN,  ROUTE_TABLE_NAME_MAIN,  &contents);
+
+    addTableName(ROUTE_TABLE_LOCAL_NETWORK,  ROUTE_TABLE_NAME_LOCAL_NETWORK,  &contents);
+    addTableName(ROUTE_TABLE_LEGACY_NETWORK, ROUTE_TABLE_NAME_LEGACY_NETWORK, &contents);
+    addTableName(ROUTE_TABLE_LEGACY_SYSTEM,  ROUTE_TABLE_NAME_LEGACY_SYSTEM,  &contents);
+
+    for (const auto& entry : interfaceToTable) {
+        addTableName(entry.second, entry.first, &contents);
+    }
+
+    if (!WriteStringToFile(contents, RT_TABLES_PATH, RT_TABLES_MODE, AID_SYSTEM, AID_WIFI)) {
+        ALOGE("failed to write to %s (%s)", RT_TABLES_PATH, strerror(errno));
+        return;
+    }
+}
+
+// Sends a netlink request and expects an ack.
+// |iov| is an array of struct iovec that contains the netlink message payload.
+// The netlink header is generated by this function based on |action| and |flags|.
+// Returns -errno if there was an error or if the kernel reported an error.
+
+// Disable optimizations in ASan build.
+// ASan reports an out-of-bounds 32-bit(!) access in the first loop of the
+// function (over iov[]).
+#ifdef __clang__
+#if __has_feature(address_sanitizer)
+__attribute__((optnone))
+#endif
+#endif
+WARN_UNUSED_RESULT int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec* iov, int iovlen) {
+    nlmsghdr nlmsg = {
+        .nlmsg_type = action,
+        .nlmsg_flags = flags,
+    };
+    iov[0].iov_base = &nlmsg;
+    iov[0].iov_len = sizeof(nlmsg);
+    for (int i = 0; i < iovlen; ++i) {
+        nlmsg.nlmsg_len += iov[i].iov_len;
+    }
+
+    int ret;
+    struct {
+        nlmsghdr msg;
+        nlmsgerr err;
+    } response;
+
+    int sock = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE);
+    if (sock != -1 &&
+            connect(sock, reinterpret_cast<const sockaddr*>(&NETLINK_ADDRESS),
+                    sizeof(NETLINK_ADDRESS)) != -1 &&
+            writev(sock, iov, iovlen) != -1 &&
+            (ret = recv(sock, &response, sizeof(response), 0)) != -1) {
+        if (ret == sizeof(response)) {
+            ret = response.err.error;  // Netlink errors are negative errno.
+            if (ret) {
+                ALOGE("netlink response contains error (%s)", strerror(-ret));
+            }
+        } else {
+            ALOGE("bad netlink response message size (%d != %zu)", ret, sizeof(response));
+            ret = -EBADMSG;
+        }
+    } else {
+        ALOGE("netlink socket/connect/writev/recv failed (%s)", strerror(errno));
+        ret = -errno;
+    }
+
+    if (sock != -1) {
+        close(sock);
+    }
+
+    return ret;
+}
+
+// Returns 0 on success or negative errno on failure.
+int padInterfaceName(const char* input, char* name, size_t* length, uint16_t* padding) {
+    if (!input) {
+        *length = 0;
+        *padding = 0;
+        return 0;
+    }
+    *length = strlcpy(name, input, IFNAMSIZ) + 1;
+    if (*length > IFNAMSIZ) {
+        ALOGE("interface name too long (%zu > %u)", *length, IFNAMSIZ);
+        return -ENAMETOOLONG;
+    }
+    *padding = RTA_SPACE(*length) - RTA_LENGTH(*length);
+    return 0;
+}
+
+// Adds or removes a routing rule for IPv4 and IPv6.
+//
+// + If |priority| is RULE_PRIORITY_UNREACHABLE, the rule returns ENETUNREACH (i.e., specifies an
+//   action of FR_ACT_UNREACHABLE). Otherwise, the rule specifies an action of FR_ACT_TO_TBL.
+// + If |table| is non-zero, the rule points at the specified routing table. Otherwise, the table is
+//   unspecified. An unspecified table is only allowed when deleting a rule.
+// + If |mask| is non-zero, the rule matches the specified fwmark and mask. Otherwise, |fwmark| is
+//   ignored.
+// + If |iif| is non-NULL, the rule matches the specified incoming interface.
+// + If |oif| is non-NULL, the rule matches the specified outgoing interface.
+// + If |uidStart| and |uidEnd| are not INVALID_UID, the rule matches packets from UIDs in that
+//   range (inclusive). Otherwise, the rule matches packets from all UIDs.
+//
+// Returns 0 on success or negative errno on failure.
+WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t table,
+                                    uint32_t fwmark, uint32_t mask, const char* iif,
+                                    const char* oif, uid_t uidStart, uid_t uidEnd) {
+    // Ensure that if you set a bit in the fwmark, it's not being ignored by the mask.
+    if (fwmark & ~mask) {
+        ALOGE("mask 0x%x does not select all the bits set in fwmark 0x%x", mask, fwmark);
+        return -ERANGE;
+    }
+
+    // Interface names must include exactly one terminating NULL and be properly padded, or older
+    // kernels will refuse to delete rules.
+    char iifName[IFNAMSIZ], oifName[IFNAMSIZ];
+    size_t iifLength, oifLength;
+    uint16_t iifPadding, oifPadding;
+    if (int ret = padInterfaceName(iif, iifName, &iifLength, &iifPadding)) {
+        return ret;
+    }
+    if (int ret = padInterfaceName(oif, oifName, &oifLength, &oifPadding)) {
+        return ret;
+    }
+
+    // Either both start and end UID must be specified, or neither.
+    if ((uidStart == INVALID_UID) != (uidEnd == INVALID_UID)) {
+        ALOGE("incompatible start and end UIDs (%u vs %u)", uidStart, uidEnd);
+        return -EUSERS;
+    }
+    bool isUidRule = (uidStart != INVALID_UID);
+
+    // Assemble a rule request and put it in an array of iovec structures.
+    fib_rule_hdr rule = {
+        .action = static_cast<uint8_t>(priority != RULE_PRIORITY_UNREACHABLE ? FR_ACT_TO_TBL :
+                                                                               FR_ACT_UNREACHABLE),
+        // Note that here we're implicitly setting rule.table to 0. When we want to specify a
+        // non-zero table, we do this via the FRATTR_TABLE attribute.
+    };
+
+    // Don't ever create a rule that looks up table 0, because table 0 is the local table.
+    // It's OK to specify a table ID of 0 when deleting a rule, because that doesn't actually select
+    // table 0, it's a wildcard that matches anything.
+    if (table == RT_TABLE_UNSPEC && rule.action == FR_ACT_TO_TBL && action != RTM_DELRULE) {
+        ALOGE("RT_TABLE_UNSPEC only allowed when deleting rules");
+        return -ENOTUNIQ;
+    }
+
+    rtattr fraIifName = { U16_RTA_LENGTH(iifLength), FRA_IIFNAME };
+    rtattr fraOifName = { U16_RTA_LENGTH(oifLength), FRA_OIFNAME };
+
+    iovec iov[] = {
+        { NULL,              0 },
+        { &rule,             sizeof(rule) },
+        { &FRATTR_PRIORITY,  sizeof(FRATTR_PRIORITY) },
+        { &priority,         sizeof(priority) },
+        { &FRATTR_TABLE,     table != RT_TABLE_UNSPEC ? sizeof(FRATTR_TABLE) : 0 },
+        { &table,            table != RT_TABLE_UNSPEC ? sizeof(table) : 0 },
+        { &FRATTR_FWMARK,    mask ? sizeof(FRATTR_FWMARK) : 0 },
+        { &fwmark,           mask ? sizeof(fwmark) : 0 },
+        { &FRATTR_FWMASK,    mask ? sizeof(FRATTR_FWMASK) : 0 },
+        { &mask,             mask ? sizeof(mask) : 0 },
+        { &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 },
+        { &fraIifName,       iif != IIF_NONE ? sizeof(fraIifName) : 0 },
+        { iifName,           iifLength },
+        { PADDING_BUFFER,    iifPadding },
+        { &fraOifName,       oif != OIF_NONE ? sizeof(fraOifName) : 0 },
+        { oifName,           oifLength },
+        { PADDING_BUFFER,    oifPadding },
+    };
+
+    uint16_t flags = (action == RTM_NEWRULE) ? NETLINK_CREATE_REQUEST_FLAGS : NETLINK_REQUEST_FLAGS;
+    for (size_t i = 0; i < ARRAY_SIZE(AF_FAMILIES); ++i) {
+        rule.family = AF_FAMILIES[i];
+        if (int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov))) {
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t table,
+                                    uint32_t fwmark, uint32_t mask) {
+    return modifyIpRule(action, priority, table, fwmark, mask, IIF_NONE, OIF_NONE, INVALID_UID,
+                        INVALID_UID);
+}
+
+// Adds or deletes an IPv4 or IPv6 route.
+// Returns 0 on success or negative errno on failure.
+WARN_UNUSED_RESULT int modifyIpRoute(uint16_t action, uint32_t table, const char* interface,
+                                     const char* destination, const char* nexthop) {
+    // At least the destination must be non-null.
+    if (!destination) {
+        ALOGE("null destination");
+        return -EFAULT;
+    }
+
+    // Parse the prefix.
+    uint8_t rawAddress[sizeof(in6_addr)];
+    uint8_t family;
+    uint8_t prefixLength;
+    int rawLength = parsePrefix(destination, &family, rawAddress, sizeof(rawAddress),
+                                &prefixLength);
+    if (rawLength < 0) {
+        ALOGE("parsePrefix failed for destination %s (%s)", destination, strerror(-rawLength));
+        return rawLength;
+    }
+
+    if (static_cast<size_t>(rawLength) > sizeof(rawAddress)) {
+        ALOGE("impossible! address too long (%d vs %zu)", rawLength, sizeof(rawAddress));
+        return -ENOBUFS;  // Cannot happen; parsePrefix only supports IPv4 and IPv6.
+    }
+
+    uint8_t type = RTN_UNICAST;
+    uint32_t ifindex;
+    uint8_t rawNexthop[sizeof(in6_addr)];
+
+    if (nexthop && !strcmp(nexthop, "unreachable")) {
+        type = RTN_UNREACHABLE;
+        // 'interface' is likely non-NULL, as the caller (modifyRoute()) likely used it to lookup
+        // the table number. But it's an error to specify an interface ("dev ...") or a nexthop for
+        // unreachable routes, so nuke them. (IPv6 allows them to be specified; IPv4 doesn't.)
+        interface = OIF_NONE;
+        nexthop = NULL;
+    } else if (nexthop && !strcmp(nexthop, "throw")) {
+        type = RTN_THROW;
+        interface = OIF_NONE;
+        nexthop = NULL;
+    } else {
+        // If an interface was specified, find the ifindex.
+        if (interface != OIF_NONE) {
+            ifindex = if_nametoindex(interface);
+            if (!ifindex) {
+                ALOGE("cannot find interface %s", interface);
+                return -ENODEV;
+            }
+        }
+
+        // If a nexthop was specified, parse it as the same family as the prefix.
+        if (nexthop && inet_pton(family, nexthop, rawNexthop) <= 0) {
+            ALOGE("inet_pton failed for nexthop %s", nexthop);
+            return -EINVAL;
+        }
+    }
+
+    // Assemble a rtmsg and put it in an array of iovec structures.
+    rtmsg route = {
+        .rtm_protocol = RTPROT_STATIC,
+        .rtm_type = type,
+        .rtm_family = family,
+        .rtm_dst_len = prefixLength,
+        .rtm_scope = static_cast<uint8_t>(nexthop ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK),
+    };
+
+    rtattr rtaDst     = { U16_RTA_LENGTH(rawLength), RTA_DST };
+    rtattr rtaGateway = { U16_RTA_LENGTH(rawLength), RTA_GATEWAY };
+
+    iovec iov[] = {
+        { NULL,          0 },
+        { &route,        sizeof(route) },
+        { &RTATTR_TABLE, sizeof(RTATTR_TABLE) },
+        { &table,        sizeof(table) },
+        { &rtaDst,       sizeof(rtaDst) },
+        { rawAddress,    static_cast<size_t>(rawLength) },
+        { &RTATTR_OIF,   interface != OIF_NONE ? sizeof(RTATTR_OIF) : 0 },
+        { &ifindex,      interface != OIF_NONE ? sizeof(ifindex) : 0 },
+        { &rtaGateway,   nexthop ? sizeof(rtaGateway) : 0 },
+        { rawNexthop,    nexthop ? static_cast<size_t>(rawLength) : 0 },
+    };
+
+    uint16_t flags = (action == RTM_NEWROUTE) ? NETLINK_CREATE_REQUEST_FLAGS :
+                                                NETLINK_REQUEST_FLAGS;
+    return sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov));
+}
+
+// An iptables rule to mark incoming packets on a network with the netId of the network.
+//
+// This is so that the kernel can:
+// + Use the right fwmark for (and thus correctly route) replies (e.g.: TCP RST, ICMP errors, ping
+//   replies, SYN-ACKs, etc).
+// + Mark sockets that accept connections from this interface so that the connection stays on the
+//   same interface.
+WARN_UNUSED_RESULT int modifyIncomingPacketMark(unsigned netId, const char* interface,
+                                                Permission permission, bool add) {
+    Fwmark fwmark;
+
+    fwmark.netId = netId;
+    fwmark.explicitlySelected = true;
+    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)) {
+        ALOGE("failed to change iptables rule that sets incoming packet mark");
+        return -EREMOTEIO;
+    }
+
+    return 0;
+}
+
+// A rule to route responses to the local network forwarded via the VPN.
+//
+// When a VPN is in effect, packets from the local network to upstream networks are forwarded into
+// the VPN's tunnel interface. When the VPN forwards the responses, they emerge out of the tunnel.
+WARN_UNUSED_RESULT int modifyVpnOutputToLocalRule(const char* vpnInterface, bool add) {
+    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL,
+                        ROUTE_TABLE_LOCAL_NETWORK, MARK_UNSET, MARK_UNSET, vpnInterface, OIF_NONE,
+                        INVALID_UID, INVALID_UID);
+}
+
+// A rule to route all traffic from a given set of UIDs to go over the VPN.
+//
+// Notice that this rule doesn't use the netId. I.e., no matter what netId the user's socket may
+// have, if they are subject to this VPN, their traffic has to go through it. Allows the traffic to
+// bypass the VPN if the protectedFromVpn bit is set.
+WARN_UNUSED_RESULT int modifyVpnUidRangeRule(uint32_t table, uid_t uidStart, uid_t uidEnd,
+                                             bool secure, bool add) {
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.protectedFromVpn = false;
+    mask.protectedFromVpn = true;
+
+    uint32_t priority;
+
+    if (secure) {
+        priority = RULE_PRIORITY_SECURE_VPN;
+    } else {
+        priority = RULE_PRIORITY_BYPASSABLE_VPN;
+
+        fwmark.explicitlySelected = false;
+        mask.explicitlySelected = true;
+    }
+
+    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, priority, table, fwmark.intValue,
+                        mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd);
+}
+
+// A rule to allow system apps to send traffic over this VPN even if they are not part of the target
+// set of UIDs.
+//
+// This is needed for DnsProxyListener to correctly resolve a request for a user who is in the
+// target set, but where the DnsProxyListener itself is not.
+WARN_UNUSED_RESULT int modifyVpnSystemPermissionRule(unsigned netId, uint32_t table, bool secure,
+                                                     bool add) {
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.netId = netId;
+    mask.netId = FWMARK_NET_ID_MASK;
+
+    fwmark.permission = PERMISSION_SYSTEM;
+    mask.permission = PERMISSION_SYSTEM;
+
+    uint32_t priority = secure ? RULE_PRIORITY_SECURE_VPN : RULE_PRIORITY_BYPASSABLE_VPN;
+
+    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, priority, table, fwmark.intValue,
+                        mask.intValue);
+}
+
+// A rule to route traffic based on an explicitly chosen network.
+//
+// Supports apps that use the multinetwork APIs to restrict their traffic to a network.
+//
+// Even though we check permissions at the time we set a netId into the fwmark of a socket, we need
+// to check it again in the rules here, because a network's permissions may have been updated via
+// modifyNetworkPermission().
+WARN_UNUSED_RESULT int modifyExplicitNetworkRule(unsigned netId, uint32_t table,
+                                                 Permission permission, uid_t uidStart,
+                                                 uid_t uidEnd, bool add) {
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.netId = netId;
+    mask.netId = FWMARK_NET_ID_MASK;
+
+    fwmark.explicitlySelected = true;
+    mask.explicitlySelected = true;
+
+    fwmark.permission = permission;
+    mask.permission = permission;
+
+    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_EXPLICIT_NETWORK, table,
+                        fwmark.intValue, mask.intValue, IIF_NONE, OIF_NONE, uidStart, uidEnd);
+}
+
+// A rule to route traffic based on a chosen outgoing interface.
+//
+// Supports apps that use SO_BINDTODEVICE or IP_PKTINFO options and the kernel that already knows
+// the outgoing interface (typically for link-local communications).
+WARN_UNUSED_RESULT int modifyOutputInterfaceRules(const char* interface, uint32_t table,
+                                                  Permission permission, uid_t uidStart,
+                                                  uid_t uidEnd, bool add) {
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.permission = permission;
+    mask.permission = permission;
+
+    // If this rule does not specify a UID range, then also add a corresponding high-priority rule
+    // for UID. This covers forwarded packets and system daemons such as the tethering DHCP server.
+    if (uidStart == INVALID_UID && uidEnd == INVALID_UID) {
+        if (int ret = modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_VPN_OVERRIDE_OIF,
+                                   table, fwmark.intValue, mask.intValue, IIF_NONE, interface,
+                                   UID_ROOT, UID_ROOT)) {
+            return ret;
+        }
+    }
+
+    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_OUTPUT_INTERFACE, table,
+                        fwmark.intValue, mask.intValue, IIF_NONE, interface, uidStart, uidEnd);
+}
+
+// A rule to route traffic based on the chosen network.
+//
+// This is for sockets that have not explicitly requested a particular network, but have been
+// bound to one when they called connect(). This ensures that sockets connected on a particular
+// network stay on that network even if the default network changes.
+WARN_UNUSED_RESULT int modifyImplicitNetworkRule(unsigned netId, uint32_t table,
+                                                 Permission permission, bool add) {
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.netId = netId;
+    mask.netId = FWMARK_NET_ID_MASK;
+
+    fwmark.explicitlySelected = false;
+    mask.explicitlySelected = true;
+
+    fwmark.permission = permission;
+    mask.permission = permission;
+
+    return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_IMPLICIT_NETWORK, table,
+                        fwmark.intValue, mask.intValue);
+}
+
+// A rule to enable split tunnel VPNs.
+//
+// If a packet with a VPN's netId doesn't find a route in the VPN's routing table, it's allowed to
+// go over the default network, provided it wasn't explicitly restricted to the VPN and has the
+// permissions required by the default network.
+WARN_UNUSED_RESULT int modifyVpnFallthroughRule(uint16_t action, unsigned vpnNetId,
+                                                const char* physicalInterface,
+                                                Permission permission) {
+    uint32_t table = getRouteTableForInterface(physicalInterface);
+    if (table == RT_TABLE_UNSPEC) {
+        return -ESRCH;
+    }
+
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.netId = vpnNetId;
+    mask.netId = FWMARK_NET_ID_MASK;
+
+    fwmark.explicitlySelected = false;
+    mask.explicitlySelected = true;
+
+    fwmark.permission = permission;
+    mask.permission = permission;
+
+    return modifyIpRule(action, RULE_PRIORITY_VPN_FALLTHROUGH, table, fwmark.intValue,
+                        mask.intValue);
+}
+
+// Add rules to allow legacy routes added through the requestRouteToHost() API.
+WARN_UNUSED_RESULT int addLegacyRouteRules() {
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.explicitlySelected = false;
+    mask.explicitlySelected = true;
+
+    // Rules to allow legacy routes to override the default network.
+    if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LEGACY_SYSTEM, ROUTE_TABLE_LEGACY_SYSTEM,
+                               fwmark.intValue, mask.intValue)) {
+        return ret;
+    }
+    if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LEGACY_NETWORK,
+                               ROUTE_TABLE_LEGACY_NETWORK, fwmark.intValue, mask.intValue)) {
+        return ret;
+    }
+
+    fwmark.permission = PERMISSION_SYSTEM;
+    mask.permission = PERMISSION_SYSTEM;
+
+    // A rule to allow legacy routes from system apps to override VPNs.
+    return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_VPN_OVERRIDE_SYSTEM, ROUTE_TABLE_LEGACY_SYSTEM,
+                        fwmark.intValue, mask.intValue);
+}
+
+// Add rules to lookup the local network when specified explicitly or otherwise.
+WARN_UNUSED_RESULT int addLocalNetworkRules(unsigned localNetId) {
+    if (int ret = modifyExplicitNetworkRule(localNetId, ROUTE_TABLE_LOCAL_NETWORK, PERMISSION_NONE,
+                                            INVALID_UID, INVALID_UID, ACTION_ADD)) {
+        return ret;
+    }
+
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.explicitlySelected = false;
+    mask.explicitlySelected = true;
+
+    return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LOCAL_NETWORK, ROUTE_TABLE_LOCAL_NETWORK,
+                        fwmark.intValue, mask.intValue);
+}
+
+int configureDummyNetwork() {
+    const char *interface = DummyNetwork::INTERFACE_NAME;
+    uint32_t table = getRouteTableForInterface(interface);
+    if (table == RT_TABLE_UNSPEC) {
+        // getRouteTableForInterface has already looged an error.
+        return -ESRCH;
+    }
+
+    ifc_init();
+    int ret = ifc_up(interface);
+    ifc_close();
+    if (ret) {
+        ALOGE("Can't bring up %s: %s", interface, strerror(errno));
+        return -errno;
+    }
+
+    if ((ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE,
+                                          INVALID_UID, INVALID_UID, ACTION_ADD))) {
+        ALOGE("Can't create oif rules for %s: %s", interface, strerror(-ret));
+        return ret;
+    }
+
+    if ((ret = modifyIpRoute(RTM_NEWROUTE, table, interface, "0.0.0.0/0", NULL))) {
+        ALOGE("Can't add IPv4 default route to %s: %s", interface, strerror(-ret));
+        return ret;
+    }
+
+    if ((ret = modifyIpRoute(RTM_NEWROUTE, table, interface, "::/0", NULL))) {
+        ALOGE("Can't add IPv6 default route to %s: %s", interface, strerror(-ret));
+        return ret;
+    }
+
+    return 0;
+}
+
+// Add a new rule to look up the 'main' table, with the same selectors as the "default network"
+// rule, but with a lower priority. We will never create routes in the main table; it should only be
+// used for directly-connected routes implicitly created by the kernel when adding IP addresses.
+// This is necessary, for example, when adding a route through a directly-connected gateway: in
+// order to add the route, there must already be a directly-connected route that covers the gateway.
+WARN_UNUSED_RESULT int addDirectlyConnectedRule() {
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.netId = NETID_UNSET;
+    mask.netId = FWMARK_NET_ID_MASK;
+
+    return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_DIRECTLY_CONNECTED, RT_TABLE_MAIN,
+                        fwmark.intValue, mask.intValue, IIF_NONE, OIF_NONE, UID_ROOT, UID_ROOT);
+}
+
+// Add an explicit unreachable rule close to the end of the prioriy list to make it clear that
+// relying on the kernel-default "from all lookup main" rule at priority 32766 is not intended
+// behaviour. We do flush the kernel-default rules at startup, but having an explicit unreachable
+// rule will hopefully make things even clearer.
+WARN_UNUSED_RESULT int addUnreachableRule() {
+    return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_UNREACHABLE, RT_TABLE_UNSPEC, MARK_UNSET,
+                        MARK_UNSET);
+}
+
+WARN_UNUSED_RESULT int modifyLocalNetwork(unsigned netId, const char* interface, bool add) {
+    if (int ret = modifyIncomingPacketMark(netId, interface, PERMISSION_NONE, add)) {
+        return ret;
+    }
+    return modifyOutputInterfaceRules(interface, ROUTE_TABLE_LOCAL_NETWORK, PERMISSION_NONE,
+                                      INVALID_UID, INVALID_UID, add);
+}
+
+WARN_UNUSED_RESULT int modifyPhysicalNetwork(unsigned netId, const char* interface,
+                                             Permission permission, bool add) {
+    uint32_t table = getRouteTableForInterface(interface);
+    if (table == RT_TABLE_UNSPEC) {
+        return -ESRCH;
+    }
+
+    if (int ret = modifyIncomingPacketMark(netId, interface, permission, add)) {
+        return ret;
+    }
+    if (int ret = modifyExplicitNetworkRule(netId, table, permission, INVALID_UID, INVALID_UID,
+                                            add)) {
+        return ret;
+    }
+    if (int ret = modifyOutputInterfaceRules(interface, table, permission, INVALID_UID, INVALID_UID,
+                                            add)) {
+        return ret;
+    }
+    return modifyImplicitNetworkRule(netId, table, permission, add);
+}
+
+WARN_UNUSED_RESULT int modifyVirtualNetwork(unsigned netId, const char* interface,
+                                            const UidRanges& uidRanges, bool secure, bool add,
+                                            bool modifyNonUidBasedRules) {
+    uint32_t table = getRouteTableForInterface(interface);
+    if (table == RT_TABLE_UNSPEC) {
+        return -ESRCH;
+    }
+
+    for (const UidRanges::Range& range : uidRanges.getRanges()) {
+        if (int ret = modifyVpnUidRangeRule(table, range.first, range.second, secure, add)) {
+            return ret;
+        }
+        if (int ret = modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, range.first,
+                                                range.second, add)) {
+            return ret;
+        }
+        if (int ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE, range.first,
+                                                 range.second, add)) {
+            return ret;
+        }
+    }
+
+    if (modifyNonUidBasedRules) {
+        if (int ret = modifyIncomingPacketMark(netId, interface, PERMISSION_NONE, add)) {
+            return ret;
+        }
+        if (int ret = modifyVpnOutputToLocalRule(interface, add)) {
+            return ret;
+        }
+        if (int ret = modifyVpnSystemPermissionRule(netId, table, secure, add)) {
+            return ret;
+        }
+        return modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, UID_ROOT, UID_ROOT, add);
+    }
+
+    return 0;
+}
+
+WARN_UNUSED_RESULT int modifyDefaultNetwork(uint16_t action, const char* interface,
+                                            Permission permission) {
+    uint32_t table = getRouteTableForInterface(interface);
+    if (table == RT_TABLE_UNSPEC) {
+        return -ESRCH;
+    }
+
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.netId = NETID_UNSET;
+    mask.netId = FWMARK_NET_ID_MASK;
+
+    fwmark.permission = permission;
+    mask.permission = permission;
+
+    return modifyIpRule(action, RULE_PRIORITY_DEFAULT_NETWORK, table, fwmark.intValue,
+                        mask.intValue);
+}
+
+WARN_UNUSED_RESULT int modifyTetheredNetwork(uint16_t action, const char* inputInterface,
+                                             const char* outputInterface) {
+    uint32_t table = getRouteTableForInterface(outputInterface);
+    if (table == RT_TABLE_UNSPEC) {
+        return -ESRCH;
+    }
+
+    return modifyIpRule(action, RULE_PRIORITY_TETHERING, table, MARK_UNSET, MARK_UNSET,
+                        inputInterface, OIF_NONE, INVALID_UID, INVALID_UID);
+}
+
+// Returns 0 on success or negative errno on failure.
+WARN_UNUSED_RESULT int flushRules() {
+    for (size_t i = 0; i < ARRAY_SIZE(IP_VERSIONS); ++i) {
+        const char* argv[] = {
+            IP_PATH,
+            IP_VERSIONS[i],
+            "rule",
+            "flush",
+        };
+        if (android_fork_execvp(ARRAY_SIZE(argv), const_cast<char**>(argv), NULL, false, false)) {
+            ALOGE("failed to flush rules");
+            return -EREMOTEIO;
+        }
+    }
+    return 0;
+}
+
+// Adds or removes an IPv4 or IPv6 route to the specified table and, if it's a directly-connected
+// route, to the main table as well.
+// Returns 0 on success or negative errno on failure.
+WARN_UNUSED_RESULT int modifyRoute(uint16_t action, const char* interface, const char* destination,
+                                   const char* nexthop, RouteController::TableType tableType) {
+    uint32_t table;
+    switch (tableType) {
+        case RouteController::INTERFACE: {
+            table = getRouteTableForInterface(interface);
+            if (table == RT_TABLE_UNSPEC) {
+                return -ESRCH;
+            }
+            break;
+        }
+        case RouteController::LOCAL_NETWORK: {
+            table = ROUTE_TABLE_LOCAL_NETWORK;
+            break;
+        }
+        case RouteController::LEGACY_NETWORK: {
+            table = ROUTE_TABLE_LEGACY_NETWORK;
+            break;
+        }
+        case RouteController::LEGACY_SYSTEM: {
+            table = ROUTE_TABLE_LEGACY_SYSTEM;
+            break;
+        }
+    }
+
+    int ret = modifyIpRoute(action, table, interface, destination, nexthop);
+    // Trying to add a route that already exists shouldn't cause an error.
+    if (ret && !(action == RTM_NEWROUTE && ret == -EEXIST)) {
+        return ret;
+    }
+
+    return 0;
+}
+
+// Returns 0 on success or negative errno on failure.
+WARN_UNUSED_RESULT int flushRoutes(const char* interface) {
+    uint32_t table = getRouteTableForInterface(interface);
+    if (table == RT_TABLE_UNSPEC) {
+        return -ESRCH;
+    }
+
+    char tableString[UINT32_STRLEN];
+    snprintf(tableString, sizeof(tableString), "%u", table);
+
+    int ret = 0;
+    for (size_t i = 0; i < ARRAY_SIZE(IP_VERSIONS); ++i) {
+        const char* argv[] = {
+            IP_PATH,
+            IP_VERSIONS[i],
+            "route",
+            "flush",
+            "table",
+            tableString,
+        };
+
+        // A flush works by dumping routes and deleting each route as it's returned, and it can
+        // fail if something else deletes the route between the dump and the delete. This can
+        // happen, for example, if an interface goes down while we're trying to flush its routes.
+        // So try multiple times and only return an error if the last attempt fails.
+        //
+        // TODO: replace this with our own netlink code.
+        unsigned attempts = 0;
+        int err;
+        do {
+            err = android_fork_execvp(ARRAY_SIZE(argv), const_cast<char**>(argv),
+                                      NULL, false, false);
+            ++attempts;
+        } while (err != 0 && attempts < ROUTE_FLUSH_ATTEMPTS);
+        if (err) {
+            ALOGE("failed to flush %s routes in table %s after %d attempts",
+                  IP_VERSIONS[i], tableString, attempts);
+            ret = -EREMOTEIO;
+        }
+    }
+
+    // If we failed to flush routes, the caller may elect to keep this interface around, so keep
+    // track of its name.
+    if (!ret) {
+        interfaceToTable.erase(interface);
+    }
+
+    return ret;
+}
+
+WARN_UNUSED_RESULT int clearTetheringRules(const char* inputInterface) {
+    int ret = 0;
+    while (ret == 0) {
+        ret = modifyIpRule(RTM_DELRULE, RULE_PRIORITY_TETHERING, 0, MARK_UNSET, MARK_UNSET,
+                           inputInterface, OIF_NONE, INVALID_UID, INVALID_UID);
+    }
+
+    if (ret == -ENOENT) {
+        return 0;
+    } else {
+        return ret;
+    }
+}
+
+}  // namespace
+
+int RouteController::Init(unsigned localNetId) {
+    if (int ret = flushRules()) {
+        return ret;
+    }
+    if (int ret = addLegacyRouteRules()) {
+        return ret;
+    }
+    if (int ret = addLocalNetworkRules(localNetId)) {
+        return ret;
+    }
+    if (int ret = addDirectlyConnectedRule()) {
+        return ret;
+    }
+    if (int ret = addUnreachableRule()) {
+        return ret;
+    }
+    // Don't complain if we can't add the dummy network, since not all devices support it.
+    configureDummyNetwork();
+
+    updateTableNamesFile();
+    return 0;
+}
+
+int RouteController::addInterfaceToLocalNetwork(unsigned netId, const char* interface) {
+    return modifyLocalNetwork(netId, interface, ACTION_ADD);
+}
+
+int RouteController::removeInterfaceFromLocalNetwork(unsigned netId, const char* interface) {
+    return modifyLocalNetwork(netId, interface, ACTION_DEL);
+}
+
+int RouteController::addInterfaceToPhysicalNetwork(unsigned netId, const char* interface,
+                                                   Permission permission) {
+    if (int ret = modifyPhysicalNetwork(netId, interface, permission, ACTION_ADD)) {
+        return ret;
+    }
+    updateTableNamesFile();
+    return 0;
+}
+
+int RouteController::removeInterfaceFromPhysicalNetwork(unsigned netId, const char* interface,
+                                                        Permission permission) {
+    if (int ret = modifyPhysicalNetwork(netId, interface, permission, ACTION_DEL)) {
+        return ret;
+    }
+    if (int ret = flushRoutes(interface)) {
+        return ret;
+    }
+    if (int ret = clearTetheringRules(interface)) {
+        return ret;
+    }
+    updateTableNamesFile();
+    return 0;
+}
+
+int RouteController::addInterfaceToVirtualNetwork(unsigned netId, const char* interface,
+                                                  bool secure, const UidRanges& uidRanges) {
+    if (int ret = modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_ADD,
+                                       MODIFY_NON_UID_BASED_RULES)) {
+        return ret;
+    }
+    updateTableNamesFile();
+    return 0;
+}
+
+int RouteController::removeInterfaceFromVirtualNetwork(unsigned netId, const char* interface,
+                                                       bool secure, const UidRanges& uidRanges) {
+    if (int ret = modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_DEL,
+                                       MODIFY_NON_UID_BASED_RULES)) {
+        return ret;
+    }
+    if (int ret = flushRoutes(interface)) {
+        return ret;
+    }
+    updateTableNamesFile();
+    return 0;
+}
+
+int RouteController::modifyPhysicalNetworkPermission(unsigned netId, const char* interface,
+                                                     Permission oldPermission,
+                                                     Permission newPermission) {
+    // Add the new rules before deleting the old ones, to avoid race conditions.
+    if (int ret = modifyPhysicalNetwork(netId, interface, newPermission, ACTION_ADD)) {
+        return ret;
+    }
+    return modifyPhysicalNetwork(netId, interface, oldPermission, ACTION_DEL);
+}
+
+int RouteController::addUsersToVirtualNetwork(unsigned netId, const char* interface, bool secure,
+                                              const UidRanges& uidRanges) {
+    return modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_ADD,
+                                !MODIFY_NON_UID_BASED_RULES);
+}
+
+int RouteController::removeUsersFromVirtualNetwork(unsigned netId, const char* interface,
+                                                   bool secure, const UidRanges& uidRanges) {
+    return modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_DEL,
+                                !MODIFY_NON_UID_BASED_RULES);
+}
+
+int RouteController::addInterfaceToDefaultNetwork(const char* interface, Permission permission) {
+    return modifyDefaultNetwork(RTM_NEWRULE, interface, permission);
+}
+
+int RouteController::removeInterfaceFromDefaultNetwork(const char* interface,
+                                                       Permission permission) {
+    return modifyDefaultNetwork(RTM_DELRULE, interface, permission);
+}
+
+int RouteController::addRoute(const char* interface, const char* destination, const char* nexthop,
+                              TableType tableType) {
+    return modifyRoute(RTM_NEWROUTE, interface, destination, nexthop, tableType);
+}
+
+int RouteController::removeRoute(const char* interface, const char* destination,
+                                 const char* nexthop, TableType tableType) {
+    return modifyRoute(RTM_DELROUTE, interface, destination, nexthop, tableType);
+}
+
+int RouteController::enableTethering(const char* inputInterface, const char* outputInterface) {
+    return modifyTetheredNetwork(RTM_NEWRULE, inputInterface, outputInterface);
+}
+
+int RouteController::disableTethering(const char* inputInterface, const char* outputInterface) {
+    return modifyTetheredNetwork(RTM_DELRULE, inputInterface, outputInterface);
+}
+
+int RouteController::addVirtualNetworkFallthrough(unsigned vpnNetId, const char* physicalInterface,
+                                                  Permission permission) {
+    return modifyVpnFallthroughRule(RTM_NEWRULE, vpnNetId, physicalInterface, permission);
+}
+
+int RouteController::removeVirtualNetworkFallthrough(unsigned vpnNetId,
+                                                     const char* physicalInterface,
+                                                     Permission permission) {
+    return modifyVpnFallthroughRule(RTM_DELRULE, vpnNetId, physicalInterface, permission);
+}
diff --git a/netd/server/RouteController.h b/netd/server/RouteController.h
new file mode 100644
index 0000000..0694ea2
--- /dev/null
+++ b/netd/server/RouteController.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 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_ROUTE_CONTROLLER_H
+#define NETD_SERVER_ROUTE_CONTROLLER_H
+
+#include "NetdConstants.h"
+#include "Permission.h"
+
+#include <sys/types.h>
+
+class UidRanges;
+
+class RouteController {
+public:
+    // How the routing table number is determined for route modification requests.
+    enum TableType {
+        INTERFACE,       // Compute the table number based on the interface index.
+        LOCAL_NETWORK,   // A fixed table used for routes to directly-connected clients/peers.
+        LEGACY_NETWORK,  // Use a fixed table that's used to override the default network.
+        LEGACY_SYSTEM,   // A fixed table, only modifiable by system apps; overrides VPNs too.
+    };
+
+    static const int ROUTE_TABLE_OFFSET_FROM_INDEX = 1000;
+
+    static int Init(unsigned localNetId) WARN_UNUSED_RESULT;
+
+    static int addInterfaceToLocalNetwork(unsigned netId, const char* interface) WARN_UNUSED_RESULT;
+    static int removeInterfaceFromLocalNetwork(unsigned netId,
+                                               const char* interface) WARN_UNUSED_RESULT;
+
+    static int addInterfaceToPhysicalNetwork(unsigned netId, const char* interface,
+                                             Permission permission) WARN_UNUSED_RESULT;
+    static int removeInterfaceFromPhysicalNetwork(unsigned netId, const char* interface,
+                                                  Permission permission) WARN_UNUSED_RESULT;
+
+    static int addInterfaceToVirtualNetwork(unsigned netId, const char* interface, bool secure,
+                                            const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+    static int removeInterfaceFromVirtualNetwork(unsigned netId, const char* interface, bool secure,
+                                                 const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+
+    static int modifyPhysicalNetworkPermission(unsigned netId, const char* interface,
+                                               Permission oldPermission,
+                                               Permission newPermission) WARN_UNUSED_RESULT;
+
+    static int addUsersToVirtualNetwork(unsigned netId, const char* interface, bool secure,
+                                        const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+    static int removeUsersFromVirtualNetwork(unsigned netId, const char* interface, bool secure,
+                                             const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+
+    static int addInterfaceToDefaultNetwork(const char* interface,
+                                            Permission permission) WARN_UNUSED_RESULT;
+    static int removeInterfaceFromDefaultNetwork(const char* interface,
+                                                 Permission permission) WARN_UNUSED_RESULT;
+
+    // |nexthop| can be NULL (to indicate a directly-connected route), "unreachable" (to indicate a
+    // route that's blocked), "throw" (to indicate the lack of a match), or a regular IP address.
+    static int addRoute(const char* interface, const char* destination, const char* nexthop,
+                        TableType tableType) WARN_UNUSED_RESULT;
+    static int removeRoute(const char* interface, const char* destination, const char* nexthop,
+                           TableType tableType) WARN_UNUSED_RESULT;
+
+    static int enableTethering(const char* inputInterface,
+                               const char* outputInterface) WARN_UNUSED_RESULT;
+    static int disableTethering(const char* inputInterface,
+                                const char* outputInterface) WARN_UNUSED_RESULT;
+
+    static int addVirtualNetworkFallthrough(unsigned vpnNetId, const char* physicalInterface,
+                                            Permission permission) WARN_UNUSED_RESULT;
+    static int removeVirtualNetworkFallthrough(unsigned vpnNetId, const char* physicalInterface,
+                                               Permission permission) WARN_UNUSED_RESULT;
+};
+
+#endif  // NETD_SERVER_ROUTE_CONTROLLER_H
diff --git a/netd/server/SockDiag.cpp b/netd/server/SockDiag.cpp
new file mode 100644
index 0000000..cd96a26
--- /dev/null
+++ b/netd/server/SockDiag.cpp
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2016 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 <errno.h>
+#include <netdb.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <linux/netlink.h>
+#include <linux/sock_diag.h>
+#include <linux/inet_diag.h>
+
+#define LOG_TAG "Netd"
+
+#include <cutils/log.h>
+
+#include "NetdConstants.h"
+#include "SockDiag.h"
+
+#include <chrono>
+
+#ifndef SOCK_DESTROY
+#define SOCK_DESTROY 21
+#endif
+
+namespace {
+
+struct AddrinfoDeleter {
+  void operator()(addrinfo *a) { if (a) freeaddrinfo(a); }
+};
+
+typedef std::unique_ptr<addrinfo, AddrinfoDeleter> ScopedAddrinfo;
+
+int checkError(int fd) {
+    struct {
+        nlmsghdr h;
+        nlmsgerr err;
+    } __attribute__((__packed__)) ack;
+    ssize_t bytesread = recv(fd, &ack, sizeof(ack), MSG_DONTWAIT | MSG_PEEK);
+    if (bytesread == -1) {
+       // Read failed (error), or nothing to read (good).
+       return (errno == EAGAIN) ? 0 : -errno;
+    } else if (bytesread == (ssize_t) sizeof(ack) && ack.h.nlmsg_type == NLMSG_ERROR) {
+        // We got an error. Consume it.
+        recv(fd, &ack, sizeof(ack), 0);
+        return ack.err.error;
+    } else {
+        // The kernel replied with something. Leave it to the caller.
+        return 0;
+    }
+}
+
+}  // namespace
+
+bool SockDiag::open() {
+    if (hasSocks()) {
+        return false;
+    }
+
+    mSock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_INET_DIAG);
+    mWriteSock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_INET_DIAG);
+    if (!hasSocks()) {
+        closeSocks();
+        return false;
+    }
+
+    sockaddr_nl nl = { .nl_family = AF_NETLINK };
+    if ((connect(mSock, reinterpret_cast<sockaddr *>(&nl), sizeof(nl)) == -1) ||
+        (connect(mWriteSock, reinterpret_cast<sockaddr *>(&nl), sizeof(nl)) == -1)) {
+        closeSocks();
+        return false;
+    }
+
+    return true;
+}
+
+int SockDiag::sendDumpRequest(uint8_t proto, uint8_t family, const char *addrstr) {
+    addrinfo hints = { .ai_flags = AI_NUMERICHOST };
+    addrinfo *res;
+    in6_addr mapped = { .s6_addr32 = { 0, 0, htonl(0xffff), 0 } };
+    int ret;
+
+    // TODO: refactor the netlink parsing code out of system/core, bring it into netd, and stop
+    // doing string conversions when they're not necessary.
+    if ((ret = getaddrinfo(addrstr, nullptr, &hints, &res)) != 0) {
+        return -EINVAL;
+    }
+
+    // So we don't have to call freeaddrinfo on every failure path.
+    ScopedAddrinfo resP(res);
+
+    void *addr;
+    uint8_t addrlen;
+    if (res->ai_family == AF_INET && family == AF_INET) {
+        in_addr& ina = reinterpret_cast<sockaddr_in*>(res->ai_addr)->sin_addr;
+        addr = &ina;
+        addrlen = sizeof(ina);
+    } else if (res->ai_family == AF_INET && family == AF_INET6) {
+        in_addr& ina = reinterpret_cast<sockaddr_in*>(res->ai_addr)->sin_addr;
+        mapped.s6_addr32[3] = ina.s_addr;
+        addr = &mapped;
+        addrlen = sizeof(mapped);
+    } else if (res->ai_family == AF_INET6 && family == AF_INET6) {
+        in6_addr& in6a = reinterpret_cast<sockaddr_in6*>(res->ai_addr)->sin6_addr;
+        addr = &in6a;
+        addrlen = sizeof(in6a);
+    } else {
+        return -EAFNOSUPPORT;
+    }
+
+    uint8_t prefixlen = addrlen * 8;
+    uint8_t yesjump = sizeof(inet_diag_bc_op) + sizeof(inet_diag_hostcond) + addrlen;
+    uint8_t nojump = yesjump + 4;
+    uint32_t states = ~(1 << TCP_TIME_WAIT);
+
+    struct {
+        nlmsghdr nlh;
+        inet_diag_req_v2 req;
+        nlattr nla;
+        inet_diag_bc_op op;
+        inet_diag_hostcond cond;
+    } __attribute__((__packed__)) request = {
+        .nlh = {
+            .nlmsg_type = SOCK_DIAG_BY_FAMILY,
+            .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
+        },
+        .req = {
+            .sdiag_family = family,
+            .sdiag_protocol = proto,
+            .idiag_states = states,
+        },
+        .nla = {
+            .nla_type = INET_DIAG_REQ_BYTECODE,
+        },
+        .op = {
+            INET_DIAG_BC_S_COND,
+            yesjump,
+            nojump,
+        },
+        .cond = {
+            family,
+            prefixlen,
+            -1,
+            {}
+        },
+    };
+
+    request.nlh.nlmsg_len = sizeof(request) + addrlen;
+    request.nla.nla_len = sizeof(request.nla) + sizeof(request.op) + sizeof(request.cond) + addrlen;
+
+    struct iovec iov[] = {
+        { &request, sizeof(request) },
+        { addr, addrlen },
+    };
+
+    if (writev(mSock, iov, ARRAY_SIZE(iov)) != (int) request.nlh.nlmsg_len) {
+        return -errno;
+    }
+
+    return checkError(mSock);
+}
+
+int SockDiag::readDiagMsg(uint8_t proto, const SockDiag::DumpCallback& callback) {
+    char buf[kBufferSize];
+
+    ssize_t bytesread;
+    do {
+        bytesread = read(mSock, buf, sizeof(buf));
+
+        if (bytesread < 0) {
+            return -errno;
+        }
+
+        uint32_t len = bytesread;
+        for (nlmsghdr *nlh = reinterpret_cast<nlmsghdr *>(buf);
+             NLMSG_OK(nlh, len);
+             nlh = NLMSG_NEXT(nlh, len)) {
+            switch (nlh->nlmsg_type) {
+              case NLMSG_DONE:
+                callback(proto, NULL);
+                return 0;
+              case NLMSG_ERROR: {
+                nlmsgerr *err = reinterpret_cast<nlmsgerr *>(NLMSG_DATA(nlh));
+                return err->error;
+              }
+              default:
+                inet_diag_msg *msg = reinterpret_cast<inet_diag_msg *>(NLMSG_DATA(nlh));
+                callback(proto, msg);
+            }
+        }
+    } while (bytesread > 0);
+
+    return 0;
+}
+
+int SockDiag::sockDestroy(uint8_t proto, const inet_diag_msg *msg) {
+    if (msg == nullptr) {
+       return 0;
+    }
+
+    DestroyRequest request = {
+        .nlh = {
+            .nlmsg_type = SOCK_DESTROY,
+            .nlmsg_flags = NLM_F_REQUEST,
+        },
+        .req = {
+            .sdiag_family = msg->idiag_family,
+            .sdiag_protocol = proto,
+            .idiag_states = (uint32_t) (1 << msg->idiag_state),
+            .id = msg->id,
+        },
+    };
+    request.nlh.nlmsg_len = sizeof(request);
+
+    if (write(mWriteSock, &request, sizeof(request)) < (ssize_t) sizeof(request)) {
+        return -errno;
+    }
+
+    int ret = checkError(mWriteSock);
+    if (!ret) mSocketsDestroyed++;
+    return ret;
+}
+
+int SockDiag::destroySockets(uint8_t proto, int family, const char *addrstr) {
+    if (!hasSocks()) {
+        return -EBADFD;
+    }
+
+    if (int ret = sendDumpRequest(proto, family, addrstr)) {
+        return ret;
+    }
+
+    auto destroy = [this] (uint8_t proto, const inet_diag_msg *msg) {
+        return this->sockDestroy(proto, msg);
+    };
+
+    return readDiagMsg(proto, destroy);
+}
+
+int SockDiag::destroySockets(const char *addrstr) {
+    using ms = std::chrono::duration<float, std::ratio<1, 1000>>;
+
+    mSocketsDestroyed = 0;
+    const auto start = std::chrono::steady_clock::now();
+    if (!strchr(addrstr, ':')) {
+        if (int ret = destroySockets(IPPROTO_TCP, AF_INET, addrstr)) {
+            ALOGE("Failed to destroy IPv4 sockets on %s: %s", addrstr, strerror(-ret));
+            return ret;
+        }
+    }
+    if (int ret = destroySockets(IPPROTO_TCP, AF_INET6, addrstr)) {
+        ALOGE("Failed to destroy IPv6 sockets on %s: %s", addrstr, strerror(-ret));
+        return ret;
+    }
+    auto elapsed = std::chrono::duration_cast<ms>(std::chrono::steady_clock::now() - start);
+
+    if (mSocketsDestroyed > 0) {
+        ALOGI("Destroyed %d sockets on %s in %.1f ms", mSocketsDestroyed, addrstr, elapsed.count());
+    }
+
+    return mSocketsDestroyed;
+}
diff --git a/netd/server/SockDiag.h b/netd/server/SockDiag.h
new file mode 100644
index 0000000..ccd54bf
--- /dev/null
+++ b/netd/server/SockDiag.h
@@ -0,0 +1,37 @@
+#include <functional>
+
+#include <linux/netlink.h>
+#include <linux/sock_diag.h>
+#include <linux/inet_diag.h>
+
+struct inet_diag_msg;
+class SockDiagTest;
+
+class SockDiag {
+
+  public:
+    static const int kBufferSize = 4096;
+    typedef std::function<int(uint8_t proto, const inet_diag_msg *)> DumpCallback;
+
+    struct DestroyRequest {
+        nlmsghdr nlh;
+        inet_diag_req_v2 req;
+    } __attribute__((__packed__));
+
+    SockDiag() : mSock(-1), mWriteSock(-1), mSocketsDestroyed(0) {}
+    bool open();
+    virtual ~SockDiag() { closeSocks(); }
+
+    int sendDumpRequest(uint8_t proto, uint8_t family, const char *addrstr);
+    int readDiagMsg(uint8_t proto, const DumpCallback& callback);
+    int sockDestroy(uint8_t proto, const inet_diag_msg *);
+    int destroySockets(const char *addrstr);
+
+  private:
+    int mSock;
+    int mWriteSock;
+    int mSocketsDestroyed;
+    int destroySockets(uint8_t proto, int family, const char *addrstr);
+    bool hasSocks() { return mSock != -1 && mWriteSock != -1; }
+    void closeSocks() { close(mSock); close(mWriteSock); mSock = mWriteSock = -1; }
+};
diff --git a/netd/server/SoftapController.cpp b/netd/server/SoftapController.cpp
new file mode 100644
index 0000000..ce92820
--- /dev/null
+++ b/netd/server/SoftapController.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2008 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 <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <linux/wireless.h>
+
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+
+#define LOG_TAG "SoftapController"
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <cutils/log.h>
+#include <netutils/ifc.h>
+#include <private/android_filesystem_config.h>
+#include "wifi.h"
+#include "ResponseCode.h"
+
+#include "SoftapController.h"
+
+using android::base::StringPrintf;
+using android::base::WriteStringToFile;
+
+static const char HOSTAPD_CONF_FILE[]    = "/data/misc/wifi/hostapd.conf";
+static const char HOSTAPD_BIN_FILE[]    = "/system/bin/hostapd";
+
+SoftapController::SoftapController()
+    : mPid(0) {}
+
+SoftapController::~SoftapController() {
+}
+
+int SoftapController::startSoftap() {
+    pid_t pid = 1;
+
+    if (mPid) {
+        ALOGE("SoftAP is already running");
+        return ResponseCode::SoftapStatusResult;
+    }
+
+    if (ensure_entropy_file_exists() < 0) {
+        ALOGE("Wi-Fi entropy file was not created");
+    }
+
+    if ((pid = fork()) < 0) {
+        ALOGE("fork failed (%s)", strerror(errno));
+        return ResponseCode::ServiceStartFailed;
+    }
+
+    if (!pid) {
+        ensure_entropy_file_exists();
+        if (execl(HOSTAPD_BIN_FILE, HOSTAPD_BIN_FILE,
+                  "-e", WIFI_ENTROPY_FILE,
+                  HOSTAPD_CONF_FILE, (char *) NULL)) {
+            ALOGE("execl failed (%s)", strerror(errno));
+        }
+        ALOGE("SoftAP failed to start");
+        return ResponseCode::ServiceStartFailed;
+    } else {
+        mPid = pid;
+        ALOGD("SoftAP started successfully");
+        usleep(AP_BSS_START_DELAY);
+    }
+    return ResponseCode::SoftapStatusResult;
+}
+
+int SoftapController::stopSoftap() {
+
+    if (mPid == 0) {
+        ALOGE("SoftAP is not running");
+        return ResponseCode::SoftapStatusResult;
+    }
+
+    ALOGD("Stopping the SoftAP service...");
+    kill(mPid, SIGTERM);
+    waitpid(mPid, NULL, 0);
+
+    mPid = 0;
+    ALOGD("SoftAP stopped successfully");
+    usleep(AP_BSS_STOP_DELAY);
+    return ResponseCode::SoftapStatusResult;
+}
+
+bool SoftapController::isSoftapStarted() {
+    return (mPid != 0);
+}
+
+/*
+ * Arguments:
+ *  argv[2] - wlan interface
+ *  argv[3] - SSID
+ *  argv[4] - Broadcast/Hidden
+ *  argv[5] - Channel
+ *  argv[6] - Security
+ *  argv[7] - Key
+ */
+int SoftapController::setSoftap(int argc, char *argv[]) {
+    int hidden = 0;
+    int channel = AP_CHANNEL_DEFAULT;
+
+    if (argc < 5) {
+        ALOGE("Softap set is missing arguments. Please use:");
+        ALOGE("softap <wlan iface> <SSID> <hidden/broadcast> <channel> <wpa2?-psk|open> <passphrase>");
+        return ResponseCode::CommandSyntaxError;
+    }
+
+    if (!strcasecmp(argv[4], "hidden"))
+        hidden = 1;
+
+    if (argc >= 5) {
+        channel = atoi(argv[5]);
+        if (channel <= 0)
+            channel = AP_CHANNEL_DEFAULT;
+    }
+
+    std::string wbuf(StringPrintf("interface=%s\n"
+            "driver=nl80211\n"
+            "ctrl_interface=/data/misc/wifi/hostapd\n"
+            "ssid=%s\n"
+            "channel=%d\n"
+            "ieee80211n=1\n"
+            "hw_mode=%c\n"
+            "ignore_broadcast_ssid=%d\n"
+            "wowlan_triggers=any\n",
+            argv[2], argv[3], channel, (channel <= 14) ? 'g' : 'a', hidden));
+
+    std::string fbuf;
+    if (argc > 7) {
+        char psk_str[2*SHA256_DIGEST_LENGTH+1];
+        if (!strcmp(argv[6], "wpa-psk")) {
+            if (!generatePsk(argv[3], argv[7], psk_str)) {
+                return ResponseCode::OperationFailed;
+            }
+            fbuf = StringPrintf("%swpa=3\nwpa_pairwise=TKIP CCMP\nwpa_psk=%s\n", wbuf.c_str(), psk_str);
+        } else if (!strcmp(argv[6], "wpa2-psk")) {
+            if (!generatePsk(argv[3], argv[7], psk_str)) {
+                return ResponseCode::OperationFailed;
+            }
+            fbuf = StringPrintf("%swpa=2\nrsn_pairwise=CCMP\nwpa_psk=%s\n", wbuf.c_str(), psk_str);
+        } else if (!strcmp(argv[6], "open")) {
+            fbuf = wbuf;
+        }
+    } else if (argc > 6) {
+        if (!strcmp(argv[6], "open")) {
+            fbuf = wbuf;
+        }
+    } else {
+        fbuf = wbuf;
+    }
+
+    if (!WriteStringToFile(fbuf, HOSTAPD_CONF_FILE, 0660, AID_SYSTEM, AID_WIFI)) {
+        ALOGE("Cannot write to \"%s\": %s", HOSTAPD_CONF_FILE, strerror(errno));
+        return ResponseCode::OperationFailed;
+    }
+    return ResponseCode::SoftapStatusResult;
+}
+
+/*
+ * Arguments:
+ *	argv[2] - interface name
+ *	argv[3] - AP or P2P or STA
+ */
+int SoftapController::fwReloadSoftap(int argc, char *argv[])
+{
+    char *fwpath = NULL;
+
+    if (argc < 4) {
+        ALOGE("SoftAP fwreload is missing arguments. Please use: softap <wlan iface> <AP|P2P|STA>");
+        return ResponseCode::CommandSyntaxError;
+    }
+
+    if (strcmp(argv[3], "AP") == 0) {
+        fwpath = (char *)wifi_get_fw_path(WIFI_GET_FW_PATH_AP);
+    } else if (strcmp(argv[3], "P2P") == 0) {
+        fwpath = (char *)wifi_get_fw_path(WIFI_GET_FW_PATH_P2P);
+    } else if (strcmp(argv[3], "STA") == 0) {
+        fwpath = (char *)wifi_get_fw_path(WIFI_GET_FW_PATH_STA);
+    }
+    if (!fwpath)
+        return ResponseCode::CommandParameterError;
+    if (wifi_change_fw_path((const char *)fwpath)) {
+        ALOGE("Softap fwReload failed");
+        return ResponseCode::OperationFailed;
+    }
+    else {
+        ALOGD("Softap fwReload - Ok");
+    }
+    return ResponseCode::SoftapStatusResult;
+}
+
+bool SoftapController::generatePsk(char *ssid, char *passphrase, char *psk_str) {
+    unsigned char psk[SHA256_DIGEST_LENGTH];
+
+    // Use the PKCS#5 PBKDF2 with 4096 iterations
+    if (PKCS5_PBKDF2_HMAC_SHA1(passphrase, strlen(passphrase),
+                               reinterpret_cast<const unsigned char *>(ssid),
+                               strlen(ssid), 4096, SHA256_DIGEST_LENGTH,
+                               psk) != 1) {
+        ALOGE("Cannot generate PSK using PKCS#5 PBKDF2");
+        return false;
+    }
+
+    for (int j=0; j < SHA256_DIGEST_LENGTH; j++) {
+        sprintf(&psk_str[j*2], "%02x", psk[j]);
+    }
+
+    return true;
+}
diff --git a/netd/server/SoftapController.h b/netd/server/SoftapController.h
new file mode 100644
index 0000000..68025e2
--- /dev/null
+++ b/netd/server/SoftapController.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008 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 _SOFTAP_CONTROLLER_H
+#define _SOFTAP_CONTROLLER_H
+
+#include <linux/in.h>
+#include <net/if.h>
+
+#define SOFTAP_MAX_BUFFER_SIZE	4096
+#define AP_BSS_START_DELAY	200000
+#define AP_BSS_STOP_DELAY	500000
+#define AP_SET_CFG_DELAY	500000
+#define AP_DRIVER_START_DELAY	800000
+#define AP_CHANNEL_DEFAULT	6
+
+class SoftapController {
+public:
+    SoftapController();
+    virtual ~SoftapController();
+
+    int startSoftap();
+    int stopSoftap();
+    bool isSoftapStarted();
+    int setSoftap(int argc, char *argv[]);
+    int fwReloadSoftap(int argc, char *argv[]);
+private:
+    pid_t mPid;
+    bool generatePsk(char *ssid, char *passphrase, char *psk);
+};
+
+#endif
diff --git a/netd/server/StrictController.cpp b/netd/server/StrictController.cpp
new file mode 100644
index 0000000..a04124d
--- /dev/null
+++ b/netd/server/StrictController.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2014 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define LOG_TAG "StrictController"
+#define LOG_NDEBUG 0
+
+#include <cutils/log.h>
+
+#include "ConnmarkFlags.h"
+#include "NetdConstants.h"
+#include "StrictController.h"
+
+const char* StrictController::LOCAL_OUTPUT = "st_OUTPUT";
+const char* StrictController::LOCAL_CLEAR_DETECT = "st_clear_detect";
+const char* StrictController::LOCAL_CLEAR_CAUGHT = "st_clear_caught";
+const char* StrictController::LOCAL_PENALTY_LOG = "st_penalty_log";
+const char* StrictController::LOCAL_PENALTY_REJECT = "st_penalty_reject";
+
+StrictController::StrictController(void) {
+}
+
+int StrictController::enableStrict(void) {
+    char connmarkFlagAccept[16];
+    char connmarkFlagReject[16];
+    char connmarkFlagTestAccept[32];
+    char connmarkFlagTestReject[32];
+    sprintf(connmarkFlagAccept, "0x%x", ConnmarkFlags::STRICT_RESOLVED_ACCEPT);
+    sprintf(connmarkFlagReject, "0x%x", ConnmarkFlags::STRICT_RESOLVED_REJECT);
+    sprintf(connmarkFlagTestAccept, "0x%x/0x%x",
+            ConnmarkFlags::STRICT_RESOLVED_ACCEPT,
+            ConnmarkFlags::STRICT_RESOLVED_ACCEPT);
+    sprintf(connmarkFlagTestReject, "0x%x/0x%x",
+            ConnmarkFlags::STRICT_RESOLVED_REJECT,
+            ConnmarkFlags::STRICT_RESOLVED_REJECT);
+
+    int res = 0;
+
+    disableStrict();
+
+    // Chain triggered when cleartext socket detected and penalty is log
+    res |= execIptables(V4V6, "-N", LOCAL_PENALTY_LOG, NULL);
+    res |= execIptables(V4V6, "-A", LOCAL_PENALTY_LOG,
+            "-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
+    res |= execIptables(V4V6, "-A", LOCAL_PENALTY_LOG,
+            "-j", "NFLOG", "--nflog-group", "0", NULL);
+
+    // Chain triggered when cleartext socket detected and penalty is reject
+    res |= execIptables(V4V6, "-N", LOCAL_PENALTY_REJECT, NULL);
+    res |= execIptables(V4V6, "-A", LOCAL_PENALTY_REJECT,
+            "-j", "CONNMARK", "--or-mark", connmarkFlagReject, NULL);
+    res |= execIptables(V4V6, "-A", LOCAL_PENALTY_REJECT,
+            "-j", "NFLOG", "--nflog-group", "0", NULL);
+    res |= execIptables(V4V6, "-A", LOCAL_PENALTY_REJECT,
+            "-j", "REJECT", NULL);
+
+    // Create chain to detect non-TLS traffic. We use a high-order
+    // mark bit to keep track of connections that we've already resolved.
+    res |= execIptables(V4V6, "-N", LOCAL_CLEAR_DETECT, NULL);
+    res |= execIptables(V4V6, "-N", LOCAL_CLEAR_CAUGHT, NULL);
+
+    // Quickly skip connections that we've already resolved
+    res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT,
+            "-m", "connmark", "--mark", connmarkFlagTestReject,
+            "-j", "REJECT", NULL);
+    res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT,
+            "-m", "connmark", "--mark", connmarkFlagTestAccept,
+            "-j", "RETURN", NULL);
+
+    // Look for IPv4 TCP/UDP connections with TLS/DTLS header
+    res |= execIptables(V4, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp",
+            "-m", "u32", "--u32", "0>>22&0x3C@ 12>>26&0x3C@ 0&0xFFFF0000=0x16030000 &&"
+                                  "0>>22&0x3C@ 12>>26&0x3C@ 4&0x00FF0000=0x00010000",
+            "-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
+    res |= execIptables(V4, "-A", LOCAL_CLEAR_DETECT, "-p", "udp",
+            "-m", "u32", "--u32", "0>>22&0x3C@ 8&0xFFFF0000=0x16FE0000 &&"
+                                  "0>>22&0x3C@ 20&0x00FF0000=0x00010000",
+            "-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
+
+    // Look for IPv6 TCP/UDP connections with TLS/DTLS header.  The IPv6 header
+    // doesn't have an IHL field to shift with, so we have to manually add in
+    // the 40-byte offset at every step.
+    res |= execIptables(V6, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp",
+            "-m", "u32", "--u32", "52>>26&0x3C@ 40&0xFFFF0000=0x16030000 &&"
+                                  "52>>26&0x3C@ 44&0x00FF0000=0x00010000",
+            "-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
+    res |= execIptables(V6, "-A", LOCAL_CLEAR_DETECT, "-p", "udp",
+            "-m", "u32", "--u32", "48&0xFFFF0000=0x16FE0000 &&"
+                                  "60&0x00FF0000=0x00010000",
+            "-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
+
+    // Skip newly classified connections from above
+    res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT,
+            "-m", "connmark", "--mark", connmarkFlagTestAccept,
+            "-j", "RETURN", NULL);
+
+    // Handle TCP/UDP payloads that didn't match TLS/DTLS filters above,
+    // which means we've probably found cleartext data.  The TCP variant
+    // depends on u32 returning false when we try reading into the message
+    // body to ignore empty ACK packets.
+    res |= execIptables(V4, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp",
+            "-m", "state", "--state", "ESTABLISHED",
+            "-m", "u32", "--u32", "0>>22&0x3C@ 12>>26&0x3C@ 0&0x0=0x0",
+            "-j", LOCAL_CLEAR_CAUGHT, NULL);
+    res |= execIptables(V6, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp",
+            "-m", "state", "--state", "ESTABLISHED",
+            "-m", "u32", "--u32", "52>>26&0x3C@ 40&0x0=0x0",
+            "-j", LOCAL_CLEAR_CAUGHT, NULL);
+
+    res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT, "-p", "udp",
+            "-j", LOCAL_CLEAR_CAUGHT, NULL);
+
+    return res;
+}
+
+int StrictController::disableStrict(void) {
+    int res = 0;
+
+    // Flush any existing rules
+    res |= execIptables(V4V6, "-F", LOCAL_OUTPUT, NULL);
+
+    res |= execIptables(V4V6, "-F", LOCAL_PENALTY_LOG, NULL);
+    res |= execIptables(V4V6, "-F", LOCAL_PENALTY_REJECT, NULL);
+    res |= execIptables(V4V6, "-F", LOCAL_CLEAR_CAUGHT, NULL);
+    res |= execIptables(V4V6, "-F", LOCAL_CLEAR_DETECT, NULL);
+
+    res |= execIptables(V4V6, "-X", LOCAL_PENALTY_LOG, NULL);
+    res |= execIptables(V4V6, "-X", LOCAL_PENALTY_REJECT, NULL);
+    res |= execIptables(V4V6, "-X", LOCAL_CLEAR_CAUGHT, NULL);
+    res |= execIptables(V4V6, "-X", LOCAL_CLEAR_DETECT, NULL);
+
+    return res;
+}
+
+int StrictController::setUidCleartextPenalty(uid_t uid, StrictPenalty penalty) {
+    char uidStr[16];
+    sprintf(uidStr, "%d", uid);
+
+    int res = 0;
+    if (penalty == ACCEPT) {
+        // Clean up any old rules
+        execIptables(V4V6, "-D", LOCAL_OUTPUT,
+                "-m", "owner", "--uid-owner", uidStr,
+                "-j", LOCAL_CLEAR_DETECT, NULL);
+        execIptables(V4V6, "-D", LOCAL_CLEAR_CAUGHT,
+                "-m", "owner", "--uid-owner", uidStr,
+                "-j", LOCAL_PENALTY_LOG, NULL);
+        execIptables(V4V6, "-D", LOCAL_CLEAR_CAUGHT,
+                "-m", "owner", "--uid-owner", uidStr,
+                "-j", LOCAL_PENALTY_REJECT, NULL);
+
+    } else {
+        // Always take a detour to investigate this UID
+        res |= execIptables(V4V6, "-I", LOCAL_OUTPUT,
+                "-m", "owner", "--uid-owner", uidStr,
+                "-j", LOCAL_CLEAR_DETECT, NULL);
+
+        if (penalty == LOG) {
+            res |= execIptables(V4V6, "-I", LOCAL_CLEAR_CAUGHT,
+                    "-m", "owner", "--uid-owner", uidStr,
+                    "-j", LOCAL_PENALTY_LOG, NULL);
+        } else if (penalty == REJECT) {
+            res |= execIptables(V4V6, "-I", LOCAL_CLEAR_CAUGHT,
+                    "-m", "owner", "--uid-owner", uidStr,
+                    "-j", LOCAL_PENALTY_REJECT, NULL);
+        }
+    }
+
+    return res;
+}
diff --git a/netd/server/StrictController.h b/netd/server/StrictController.h
new file mode 100644
index 0000000..52a6779
--- /dev/null
+++ b/netd/server/StrictController.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 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 _STRICT_CONTROLLER_H
+#define _STRICT_CONTROLLER_H
+
+#include <string>
+
+enum StrictPenalty { INVALID, ACCEPT, LOG, REJECT };
+
+/*
+ * Help apps catch unwanted low-level networking behavior, like
+ * connections not wrapped in TLS.
+ */
+class StrictController {
+public:
+    StrictController();
+
+    int enableStrict(void);
+    int disableStrict(void);
+
+    int setUidCleartextPenalty(uid_t, StrictPenalty);
+
+    static const char* LOCAL_OUTPUT;
+    static const char* LOCAL_CLEAR_DETECT;
+    static const char* LOCAL_CLEAR_CAUGHT;
+    static const char* LOCAL_PENALTY_LOG;
+    static const char* LOCAL_PENALTY_REJECT;
+};
+
+#endif
diff --git a/netd/server/TetherController.cpp b/netd/server/TetherController.cpp
new file mode 100644
index 0000000..88baa31
--- /dev/null
+++ b/netd/server/TetherController.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2008 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 <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#define LOG_TAG "TetherController"
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include "Fwmark.h"
+#include "NetdConstants.h"
+#include "Permission.h"
+#include "TetherController.h"
+
+namespace {
+
+static const char BP_TOOLS_MODE[] = "bp-tools";
+static const char IPV4_FORWARDING_PROC_FILE[] = "/proc/sys/net/ipv4/ip_forward";
+static const char IPV6_FORWARDING_PROC_FILE[] = "/proc/sys/net/ipv6/conf/all/forwarding";
+
+bool writeToFile(const char* filename, const char* value) {
+    int fd = open(filename, O_WRONLY);
+    if (fd < 0) {
+        ALOGE("Failed to open %s: %s", filename, strerror(errno));
+        return false;
+    }
+
+    const ssize_t len = strlen(value);
+    if (write(fd, value, len) != len) {
+        ALOGE("Failed to write %s to %s: %s", value, filename, strerror(errno));
+        close(fd);
+        return false;
+    }
+    close(fd);
+    return true;
+}
+
+bool inBpToolsMode() {
+    // In BP tools mode, do not disable IP forwarding
+    char bootmode[PROPERTY_VALUE_MAX] = {0};
+    property_get("ro.bootmode", bootmode, "unknown");
+    return !strcmp(BP_TOOLS_MODE, bootmode);
+}
+
+}  // namespace
+
+TetherController::TetherController() {
+    mInterfaces = new InterfaceCollection();
+    mDnsNetId = 0;
+    mDnsForwarders = new NetAddressCollection();
+    mDaemonFd = -1;
+    mDaemonPid = 0;
+    if (inBpToolsMode()) {
+        enableForwarding(BP_TOOLS_MODE);
+    } else {
+        setIpFwdEnabled();
+    }
+}
+
+TetherController::~TetherController() {
+    InterfaceCollection::iterator it;
+
+    for (it = mInterfaces->begin(); it != mInterfaces->end(); ++it) {
+        free(*it);
+    }
+    mInterfaces->clear();
+
+    mDnsForwarders->clear();
+    mForwardingRequests.clear();
+}
+
+bool TetherController::setIpFwdEnabled() {
+    bool success = true;
+    const char* value = mForwardingRequests.empty() ? "0" : "1";
+    ALOGD("Setting IP forward enable = %s", value);
+    success &= writeToFile(IPV4_FORWARDING_PROC_FILE, value);
+    success &= writeToFile(IPV6_FORWARDING_PROC_FILE, value);
+    return success;
+}
+
+bool TetherController::enableForwarding(const char* requester) {
+    // Don't return an error if this requester already requested forwarding. Only return errors for
+    // things that the caller caller needs to care about, such as "couldn't write to the file to
+    // enable forwarding".
+    mForwardingRequests.insert(requester);
+    return setIpFwdEnabled();
+}
+
+bool TetherController::disableForwarding(const char* requester) {
+    mForwardingRequests.erase(requester);
+    return setIpFwdEnabled();
+}
+
+size_t TetherController::forwardingRequestCount() {
+    return mForwardingRequests.size();
+}
+
+#define TETHER_START_CONST_ARG        8
+
+int TetherController::startTethering(int num_addrs, struct in_addr* addrs) {
+    if (mDaemonPid != 0) {
+        ALOGE("Tethering already started");
+        errno = EBUSY;
+        return -1;
+    }
+
+    ALOGD("Starting tethering services");
+
+    pid_t pid;
+    int pipefd[2];
+
+    if (pipe(pipefd) < 0) {
+        ALOGE("pipe failed (%s)", strerror(errno));
+        return -1;
+    }
+
+    /*
+     * TODO: Create a monitoring thread to handle and restart
+     * the daemon if it exits prematurely
+     */
+    if ((pid = fork()) < 0) {
+        ALOGE("fork failed (%s)", strerror(errno));
+        close(pipefd[0]);
+        close(pipefd[1]);
+        return -1;
+    }
+
+    if (!pid) {
+        close(pipefd[1]);
+        if (pipefd[0] != STDIN_FILENO) {
+            if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
+                ALOGE("dup2 failed (%s)", strerror(errno));
+                return -1;
+            }
+            close(pipefd[0]);
+        }
+
+        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;
+        args[0] = (char *)"/system/bin/dnsmasq";
+        args[1] = (char *)"--keep-in-foreground";
+        args[2] = (char *)"--no-resolv";
+        args[3] = (char *)"--no-poll";
+        args[4] = (char *)"--dhcp-authoritative";
+        // TODO: pipe through metered status from ConnService
+        args[5] = (char *)"--dhcp-option-force=43,ANDROID_METERED";
+        args[6] = (char *)"--pid-file";
+        args[7] = (char *)"";
+
+        int nextArg = TETHER_START_CONST_ARG;
+        for (int addrIndex=0; addrIndex < num_addrs;) {
+            char *start = strdup(inet_ntoa(addrs[addrIndex++]));
+            char *end = strdup(inet_ntoa(addrs[addrIndex++]));
+            asprintf(&(args[nextArg++]),"--dhcp-range=%s,%s,1h", start, end);
+            free(start);
+            free(end);
+        }
+
+        if (execv(args[0], args)) {
+            ALOGE("execl failed (%s)", strerror(errno));
+        }
+        ALOGE("Should never get here!");
+        _exit(-1);
+    } else {
+        close(pipefd[0]);
+        mDaemonPid = pid;
+        mDaemonFd = pipefd[1];
+        applyDnsInterfaces();
+        ALOGD("Tethering services running");
+    }
+
+    return 0;
+}
+
+int TetherController::stopTethering() {
+
+    if (mDaemonPid == 0) {
+        ALOGE("Tethering already stopped");
+        return 0;
+    }
+
+    ALOGD("Stopping tethering services");
+
+    kill(mDaemonPid, SIGTERM);
+    waitpid(mDaemonPid, NULL, 0);
+    mDaemonPid = 0;
+    close(mDaemonFd);
+    mDaemonFd = -1;
+    ALOGD("Tethering services stopped");
+    return 0;
+}
+
+bool TetherController::isTetheringStarted() {
+    return (mDaemonPid == 0 ? false : true);
+}
+
+#define MAX_CMD_SIZE 1024
+
+int TetherController::setDnsForwarders(unsigned netId, char **servers, int numServers) {
+    int i;
+    char daemonCmd[MAX_CMD_SIZE];
+
+    Fwmark fwmark;
+    fwmark.netId = netId;
+    fwmark.explicitlySelected = true;
+    fwmark.protectedFromVpn = true;
+    fwmark.permission = PERMISSION_SYSTEM;
+
+    snprintf(daemonCmd, sizeof(daemonCmd), "update_dns:0x%x", fwmark.intValue);
+    int cmdLen = strlen(daemonCmd);
+
+    mDnsForwarders->clear();
+    for (i = 0; i < numServers; i++) {
+        ALOGD("setDnsForwarders(0x%x %d = '%s')", fwmark.intValue, i, servers[i]);
+
+        struct in_addr a;
+
+        if (!inet_aton(servers[i], &a)) {
+            ALOGE("Failed to parse DNS server '%s'", servers[i]);
+            mDnsForwarders->clear();
+            return -1;
+        }
+
+        cmdLen += (strlen(servers[i]) + 1);
+        if (cmdLen + 1 >= MAX_CMD_SIZE) {
+            ALOGD("Too many DNS servers listed");
+            break;
+        }
+
+        strcat(daemonCmd, ":");
+        strcat(daemonCmd, servers[i]);
+        mDnsForwarders->push_back(a);
+    }
+
+    mDnsNetId = netId;
+    if (mDaemonFd != -1) {
+        ALOGD("Sending update msg to dnsmasq [%s]", daemonCmd);
+        if (write(mDaemonFd, daemonCmd, strlen(daemonCmd) +1) < 0) {
+            ALOGE("Failed to send update command to dnsmasq (%s)", strerror(errno));
+            mDnsForwarders->clear();
+            return -1;
+        }
+    }
+    return 0;
+}
+
+unsigned TetherController::getDnsNetId() {
+    return mDnsNetId;
+}
+
+NetAddressCollection *TetherController::getDnsForwarders() {
+    return mDnsForwarders;
+}
+
+int TetherController::applyDnsInterfaces() {
+    char daemonCmd[MAX_CMD_SIZE];
+
+    strcpy(daemonCmd, "update_ifaces");
+    int cmdLen = strlen(daemonCmd);
+    InterfaceCollection::iterator it;
+    bool haveInterfaces = false;
+
+    for (it = mInterfaces->begin(); it != mInterfaces->end(); ++it) {
+        cmdLen += (strlen(*it) + 1);
+        if (cmdLen + 1 >= MAX_CMD_SIZE) {
+            ALOGD("Too many DNS ifaces listed");
+            break;
+        }
+
+        strcat(daemonCmd, ":");
+        strcat(daemonCmd, *it);
+        haveInterfaces = true;
+    }
+
+    if ((mDaemonFd != -1) && haveInterfaces) {
+        ALOGD("Sending update msg to dnsmasq [%s]", daemonCmd);
+        if (write(mDaemonFd, daemonCmd, strlen(daemonCmd) +1) < 0) {
+            ALOGE("Failed to send update command to dnsmasq (%s)", strerror(errno));
+            return -1;
+        }
+    }
+    return 0;
+}
+
+int TetherController::tetherInterface(const char *interface) {
+    ALOGD("tetherInterface(%s)", interface);
+    if (!isIfaceName(interface)) {
+        errno = ENOENT;
+        return -1;
+    }
+    mInterfaces->push_back(strdup(interface));
+
+    if (applyDnsInterfaces()) {
+        InterfaceCollection::iterator it;
+        for (it = mInterfaces->begin(); it != mInterfaces->end(); ++it) {
+            if (!strcmp(interface, *it)) {
+                free(*it);
+                mInterfaces->erase(it);
+                break;
+            }
+        }
+        return -1;
+    } else {
+        return 0;
+    }
+}
+
+int TetherController::untetherInterface(const char *interface) {
+    InterfaceCollection::iterator it;
+
+    ALOGD("untetherInterface(%s)", interface);
+
+    for (it = mInterfaces->begin(); it != mInterfaces->end(); ++it) {
+        if (!strcmp(interface, *it)) {
+            free(*it);
+            mInterfaces->erase(it);
+
+            return applyDnsInterfaces();
+        }
+    }
+    errno = ENOENT;
+    return -1;
+}
+
+InterfaceCollection *TetherController::getTetheredInterfaceList() {
+    return mInterfaces;
+}
diff --git a/netd/server/TetherController.h b/netd/server/TetherController.h
new file mode 100644
index 0000000..91ffb9c
--- /dev/null
+++ b/netd/server/TetherController.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 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 _TETHER_CONTROLLER_H
+#define _TETHER_CONTROLLER_H
+
+#include <netinet/in.h>
+#include <set>
+#include <string>
+
+#include "List.h"
+
+typedef android::netd::List<char *> InterfaceCollection;
+typedef android::netd::List<struct in_addr> NetAddressCollection;
+
+class TetherController {
+    InterfaceCollection  *mInterfaces;
+    // NetId to use for forwarded DNS queries. This may not be the default
+    // network, e.g., in the case where we are tethering to a DUN APN.
+    unsigned              mDnsNetId;
+    NetAddressCollection *mDnsForwarders;
+    pid_t                 mDaemonPid;
+    int                   mDaemonFd;
+    std::set<std::string> mForwardingRequests;
+
+public:
+    TetherController();
+    virtual ~TetherController();
+
+    bool enableForwarding(const char* requester);
+    bool disableForwarding(const char* requester);
+    size_t forwardingRequestCount();
+
+    int startTethering(int num_addrs, struct in_addr* addrs);
+    int stopTethering();
+    bool isTetheringStarted();
+
+    unsigned getDnsNetId();
+    int setDnsForwarders(unsigned netId, char **servers, int numServers);
+    NetAddressCollection *getDnsForwarders();
+
+    int tetherInterface(const char *interface);
+    int untetherInterface(const char *interface);
+    InterfaceCollection *getTetheredInterfaceList();
+
+private:
+    int applyDnsInterfaces();
+    bool setIpFwdEnabled();
+};
+
+#endif
diff --git a/netd/server/UidRanges.cpp b/netd/server/UidRanges.cpp
new file mode 100644
index 0000000..10e445a
--- /dev/null
+++ b/netd/server/UidRanges.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 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 "UidRanges.h"
+
+#include "NetdConstants.h"
+
+#include <stdlib.h>
+
+bool UidRanges::hasUid(uid_t uid) const {
+    auto iter = std::lower_bound(mRanges.begin(), mRanges.end(), Range(uid, uid));
+    return (iter != mRanges.end() && iter->first == uid) ||
+           (iter != mRanges.begin() && (--iter)->second >= uid);
+}
+
+const std::vector<UidRanges::Range>& UidRanges::getRanges() const {
+    return mRanges;
+}
+
+bool UidRanges::parseFrom(int argc, char* argv[]) {
+    mRanges.clear();
+    for (int i = 0; i < argc; ++i) {
+        if (!*argv[i]) {
+            // The UID string is empty.
+            return false;
+        }
+        char* endPtr;
+        uid_t uidStart = strtoul(argv[i], &endPtr, 0);
+        uid_t uidEnd;
+        if (!*endPtr) {
+            // Found a single UID. The range contains just the one UID.
+            uidEnd = uidStart;
+        } else if (*endPtr == '-') {
+            if (!*++endPtr) {
+                // Unexpected end of string.
+                return false;
+            }
+            uidEnd = strtoul(endPtr, &endPtr, 0);
+            if (*endPtr) {
+                // Illegal trailing chars.
+                return false;
+            }
+            if (uidEnd < uidStart) {
+                // Invalid order.
+                return false;
+            }
+        } else {
+            // Not a single uid, not a range. Found some other illegal char.
+            return false;
+        }
+        if (uidStart == INVALID_UID || uidEnd == INVALID_UID) {
+            // Invalid UIDs.
+            return false;
+        }
+        mRanges.push_back(Range(uidStart, uidEnd));
+    }
+    std::sort(mRanges.begin(), mRanges.end());
+    return true;
+}
+
+void UidRanges::add(const UidRanges& other) {
+    auto middle = mRanges.insert(mRanges.end(), other.mRanges.begin(), other.mRanges.end());
+    std::inplace_merge(mRanges.begin(), middle, mRanges.end());
+}
+
+void UidRanges::remove(const UidRanges& other) {
+    auto end = std::set_difference(mRanges.begin(), mRanges.end(), other.mRanges.begin(),
+                                   other.mRanges.end(), mRanges.begin());
+    mRanges.erase(end, mRanges.end());
+}
diff --git a/netd/server/UidRanges.h b/netd/server/UidRanges.h
new file mode 100644
index 0000000..044a8f9
--- /dev/null
+++ b/netd/server/UidRanges.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 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_UID_RANGES_H
+#define NETD_SERVER_UID_RANGES_H
+
+#include <sys/types.h>
+#include <utility>
+#include <vector>
+
+class UidRanges {
+public:
+    typedef std::pair<uid_t, uid_t> Range;
+
+    bool hasUid(uid_t uid) const;
+    const std::vector<Range>& getRanges() const;
+
+    bool parseFrom(int argc, char* argv[]);
+
+    void add(const UidRanges& other);
+    void remove(const UidRanges& other);
+
+private:
+    std::vector<Range> mRanges;
+};
+
+#endif  // NETD_SERVER_UID_RANGES_H
diff --git a/netd/server/VirtualNetwork.cpp b/netd/server/VirtualNetwork.cpp
new file mode 100644
index 0000000..5db3645
--- /dev/null
+++ b/netd/server/VirtualNetwork.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2014 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 "VirtualNetwork.h"
+
+#include "RouteController.h"
+
+#define LOG_TAG "Netd"
+#include "log/log.h"
+
+VirtualNetwork::VirtualNetwork(unsigned netId, bool hasDns, bool secure) :
+        Network(netId), mHasDns(hasDns), mSecure(secure) {
+}
+
+VirtualNetwork::~VirtualNetwork() {
+}
+
+bool VirtualNetwork::getHasDns() const {
+    return mHasDns;
+}
+
+bool VirtualNetwork::isSecure() const {
+    return mSecure;
+}
+
+bool VirtualNetwork::appliesToUser(uid_t uid) const {
+    return mUidRanges.hasUid(uid);
+}
+
+int VirtualNetwork::addUsers(const UidRanges& uidRanges) {
+    for (const std::string& interface : mInterfaces) {
+        if (int ret = RouteController::addUsersToVirtualNetwork(mNetId, interface.c_str(), mSecure,
+                                                                uidRanges)) {
+            ALOGE("failed to add users on interface %s of netId %u", interface.c_str(), mNetId);
+            return ret;
+        }
+    }
+    mUidRanges.add(uidRanges);
+    return 0;
+}
+
+int VirtualNetwork::removeUsers(const UidRanges& uidRanges) {
+    for (const std::string& interface : mInterfaces) {
+        if (int ret = RouteController::removeUsersFromVirtualNetwork(mNetId, interface.c_str(),
+                                                                     mSecure, uidRanges)) {
+            ALOGE("failed to remove users on interface %s of netId %u", interface.c_str(), mNetId);
+            return ret;
+        }
+    }
+    mUidRanges.remove(uidRanges);
+    return 0;
+}
+
+Network::Type VirtualNetwork::getType() const {
+    return VIRTUAL;
+}
+
+int VirtualNetwork::addInterface(const std::string& interface) {
+    if (hasInterface(interface)) {
+        return 0;
+    }
+    if (int ret = RouteController::addInterfaceToVirtualNetwork(mNetId, interface.c_str(), mSecure,
+                                                                mUidRanges)) {
+        ALOGE("failed to add interface %s to VPN netId %u", interface.c_str(), mNetId);
+        return ret;
+    }
+    mInterfaces.insert(interface);
+    return 0;
+}
+
+int VirtualNetwork::removeInterface(const std::string& interface) {
+    if (!hasInterface(interface)) {
+        return 0;
+    }
+    if (int ret = RouteController::removeInterfaceFromVirtualNetwork(mNetId, interface.c_str(),
+                                                                     mSecure, mUidRanges)) {
+        ALOGE("failed to remove interface %s from VPN netId %u", interface.c_str(), mNetId);
+        return ret;
+    }
+    mInterfaces.erase(interface);
+    return 0;
+}
diff --git a/netd/server/VirtualNetwork.h b/netd/server/VirtualNetwork.h
new file mode 100644
index 0000000..d315f97
--- /dev/null
+++ b/netd/server/VirtualNetwork.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 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_VIRTUAL_NETWORK_H
+#define NETD_SERVER_VIRTUAL_NETWORK_H
+
+#include "Network.h"
+#include "UidRanges.h"
+
+// A VirtualNetwork may be "secure" or not.
+//
+// A secure VPN is the usual type of VPN that grabs the default route (and thus all user traffic).
+// Only a few privileged UIDs may skip the VPN and go directly to the underlying physical network.
+//
+// A non-secure VPN ("bypassable" VPN) also grabs all user traffic by default. But all apps are
+// permitted to skip it and pick any other network for their connections.
+class VirtualNetwork : public Network {
+public:
+    VirtualNetwork(unsigned netId, bool hasDns, bool secure);
+    virtual ~VirtualNetwork();
+
+    bool getHasDns() const;
+    bool isSecure() const;
+    bool appliesToUser(uid_t uid) const;
+
+    int addUsers(const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+    int removeUsers(const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+
+private:
+    Type getType() const override;
+    int addInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+    int removeInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+
+    const bool mHasDns;
+    const bool mSecure;
+    UidRanges mUidRanges;
+};
+
+#endif  // NETD_SERVER_VIRTUAL_NETWORK_H
diff --git a/netd/server/main.cpp b/netd/server/main.cpp
new file mode 100644
index 0000000..5e189cc
--- /dev/null
+++ b/netd/server/main.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2008 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 <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <dirent.h>
+
+#define LOG_TAG "Netd"
+
+#include "cutils/log.h"
+
+#include "CommandListener.h"
+#include "NetlinkManager.h"
+#include "DnsProxyListener.h"
+#include "MDnsSdListener.h"
+#include "FwmarkServer.h"
+
+static void blockSigpipe();
+static void remove_pid_file();
+static bool write_pid_file();
+
+const char* const PID_FILE_PATH = "/data/misc/net/netd_pid";
+const int PID_FILE_FLAGS = O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW | O_CLOEXEC;
+const mode_t PID_FILE_MODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;  // mode 0644, rw-r--r--
+
+int main() {
+
+    CommandListener *cl;
+    NetlinkManager *nm;
+    DnsProxyListener *dpl;
+    MDnsSdListener *mdnsl;
+    FwmarkServer* fwmarkServer;
+
+    ALOGI("Netd 1.0 starting");
+    remove_pid_file();
+
+    blockSigpipe();
+
+    if (!(nm = NetlinkManager::Instance())) {
+        ALOGE("Unable to create NetlinkManager");
+        exit(1);
+    };
+
+    cl = new CommandListener();
+    nm->setBroadcaster((SocketListener *) cl);
+
+    if (nm->start()) {
+        ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));
+        exit(1);
+    }
+
+    // Set local DNS mode, to prevent bionic from proxying
+    // back to this service, recursively.
+    setenv("ANDROID_DNS_MODE", "local", 1);
+    dpl = new DnsProxyListener(CommandListener::sNetCtrl);
+    if (dpl->startListener()) {
+        ALOGE("Unable to start DnsProxyListener (%s)", strerror(errno));
+        exit(1);
+    }
+
+    mdnsl = new MDnsSdListener();
+    if (mdnsl->startListener()) {
+        ALOGE("Unable to start MDnsSdListener (%s)", strerror(errno));
+        exit(1);
+    }
+
+    fwmarkServer = new FwmarkServer(CommandListener::sNetCtrl);
+    if (fwmarkServer->startListener()) {
+        ALOGE("Unable to start FwmarkServer (%s)", strerror(errno));
+        exit(1);
+    }
+
+    /*
+     * Now that we're up, we can respond to commands
+     */
+    if (cl->startListener()) {
+        ALOGE("Unable to start CommandListener (%s)", strerror(errno));
+        exit(1);
+    }
+
+    bool wrote_pid = write_pid_file();
+
+    while(1) {
+        sleep(30); // 30 sec
+        if (!wrote_pid) {
+            wrote_pid = write_pid_file();
+        }
+    }
+
+    ALOGI("Netd exiting");
+    remove_pid_file();
+    exit(0);
+}
+
+static bool write_pid_file() {
+    char pid_buf[20];  // current pid_max is 32768, so plenty of room
+    snprintf(pid_buf, sizeof(pid_buf), "%ld\n", (long)getpid());
+
+    int fd = open(PID_FILE_PATH, PID_FILE_FLAGS, PID_FILE_MODE);
+    if (fd == -1) {
+        ALOGE("Unable to create pid file (%s)", strerror(errno));
+        return false;
+    }
+
+    // File creation is affected by umask, so make sure the right mode bits are set.
+    if (fchmod(fd, PID_FILE_MODE) == -1) {
+        ALOGE("failed to set mode 0%o on %s (%s)", PID_FILE_MODE, PID_FILE_PATH, strerror(errno));
+        close(fd);
+        remove_pid_file();
+        return false;
+    }
+
+    if (write(fd, pid_buf, strlen(pid_buf)) != (ssize_t)strlen(pid_buf)) {
+        ALOGE("Unable to write to pid file (%s)", strerror(errno));
+        close(fd);
+        remove_pid_file();
+        return false;
+    }
+    close(fd);
+    return true;
+}
+
+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/netd/server/ndc.cpp b/netd/server/ndc.cpp
new file mode 100644
index 0000000..14f6654
--- /dev/null
+++ b/netd/server/ndc.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2008 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <cutils/sockets.h>
+#include <private/android_filesystem_config.h>
+
+static void usage(char *progname);
+static int do_monitor(int sock, int stop_after_cmd);
+static int do_cmd(int sock, int argc, char **argv);
+
+int main(int argc, char **argv) {
+    int sock;
+    int cmdOffset = 0;
+
+    if (argc < 2)
+        usage(argv[0]);
+
+    // try interpreting the first arg as the socket name - if it fails go back to netd
+
+    if ((sock = socket_local_client(argv[1],
+                                     ANDROID_SOCKET_NAMESPACE_RESERVED,
+                                     SOCK_STREAM)) < 0) {
+        if ((sock = socket_local_client("netd",
+                                         ANDROID_SOCKET_NAMESPACE_RESERVED,
+                                         SOCK_STREAM)) < 0) {
+            fprintf(stderr, "Error connecting (%s)\n", strerror(errno));
+            exit(4);
+        }
+    } else {
+        if (argc < 3) usage(argv[0]);
+        printf("Using alt socket %s\n", argv[1]);
+        cmdOffset = 1;
+    }
+
+    if (!strcmp(argv[1+cmdOffset], "monitor"))
+        exit(do_monitor(sock, 0));
+    exit(do_cmd(sock, argc-cmdOffset, &(argv[cmdOffset])));
+}
+
+static int do_cmd(int sock, int argc, char **argv) {
+    char *final_cmd;
+    char *conv_ptr;
+    int i;
+
+    /* Check if 1st arg is cmd sequence number */ 
+    strtol(argv[1], &conv_ptr, 10);
+    if (conv_ptr == argv[1]) {
+        final_cmd = strdup("0 ");
+    } else {
+        final_cmd = strdup("");
+    }
+    if (final_cmd == NULL) {
+        int res = errno;
+        perror("strdup failed");
+        return res;
+    }
+
+    for (i = 1; i < argc; i++) {
+        if (strchr(argv[i], '"')) {
+            perror("argument with embedded quotes not allowed");
+            free(final_cmd);
+            return 1;
+        }
+        bool needs_quoting = strchr(argv[i], ' ');
+        const char *format = needs_quoting ? "%s\"%s\"%s" : "%s%s%s";
+        char *tmp_final_cmd;
+
+        if (asprintf(&tmp_final_cmd, format, final_cmd, argv[i],
+                     (i == (argc - 1)) ? "" : " ") < 0) {
+            int res = errno;
+            perror("failed asprintf");
+            free(final_cmd);
+            return res;
+        }
+        free(final_cmd);
+        final_cmd = tmp_final_cmd;
+    }
+
+    if (write(sock, final_cmd, strlen(final_cmd) + 1) < 0) {
+        int res = errno;
+        perror("write");
+        free(final_cmd);
+        return res;
+    }
+    free(final_cmd);
+
+    return do_monitor(sock, 1);
+}
+
+static int do_monitor(int sock, int stop_after_cmd) {
+    char *buffer = (char *)malloc(4096);
+
+    if (!stop_after_cmd)
+        printf("[Connected to Netd]\n");
+
+    while(1) {
+        fd_set read_fds;
+        struct timeval to;
+        int rc = 0;
+
+        to.tv_sec = 10;
+        to.tv_usec = 0;
+
+        FD_ZERO(&read_fds);
+        FD_SET(sock, &read_fds);
+
+        rc = TEMP_FAILURE_RETRY(select(sock +1, &read_fds, NULL, NULL, &to));
+        if (rc < 0) {
+            int res = errno;
+            fprintf(stderr, "Error in select (%s)\n", strerror(res));
+            free(buffer);
+            return res;
+        }
+        if (rc == 0) {
+            continue;
+        }
+        if (!FD_ISSET(sock, &read_fds)) {
+            continue;
+        }
+
+        memset(buffer, 0, 4096);
+        if ((rc = read(sock, buffer, 4096)) <= 0) {
+            int res = errno;
+            if (rc == 0)
+                fprintf(stderr, "Lost connection to Netd - did it crash?\n");
+            else
+                fprintf(stderr, "Error reading data (%s)\n", strerror(res));
+            free(buffer);
+            if (rc == 0)
+                return ECONNRESET;
+            return res;
+        }
+
+        int offset = 0;
+        int i = 0;
+
+        for (i = 0; i < rc; i++) {
+            if (buffer[i] == '\0') {
+                int code;
+                char tmp[4];
+
+                strncpy(tmp, buffer + offset, 3);
+                tmp[3] = '\0';
+                code = atoi(tmp);
+
+                printf("%s\n", buffer + offset);
+                if (stop_after_cmd) {
+                    if (code >= 200 && code < 600)
+                        return 0;
+                }
+                offset = i + 1;
+            }
+        }
+    }
+    free(buffer);
+    return 0;
+}
+
+static void usage(char *progname) {
+    fprintf(stderr, "Usage: %s [<sockname>] ([monitor] | ([<cmd_seq_num>] <cmd> [arg ...]))\n", progname);
+    exit(1);
+}
diff --git a/netd/server/netd.rc b/netd/server/netd.rc
new file mode 100644
index 0000000..4fc6fd8
--- /dev/null
+++ b/netd/server/netd.rc
@@ -0,0 +1,6 @@
+service netd /system/bin/netd
+    class main
+    socket netd stream 0660 root system
+    socket dnsproxyd stream 0660 root inet
+    socket mdns stream 0660 root system
+    socket fwmarkd stream 0660 root inet
diff --git a/netd/server/oem_iptables_hook.cpp b/netd/server/oem_iptables_hook.cpp
new file mode 100644
index 0000000..7e4b3cb
--- /dev/null
+++ b/netd/server/oem_iptables_hook.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 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 <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#define LOG_TAG "OemIptablesHook"
+#include <cutils/log.h>
+#include <logwrap/logwrap.h>
+#include "NetdConstants.h"
+
+static int runIptablesCmd(int argc, const char **argv) {
+    int res;
+
+    res = android_fork_execvp(argc, (char **)argv, NULL, false, false);
+    return res;
+}
+
+static bool oemCleanupHooks() {
+    const char *cmd1[] = {
+            IPTABLES_PATH,
+            "-w",
+            "-F",
+            "oem_out"
+    };
+    runIptablesCmd(ARRAY_SIZE(cmd1), cmd1);
+
+    const char *cmd2[] = {
+            IPTABLES_PATH,
+            "-w",
+            "-F",
+            "oem_fwd"
+    };
+    runIptablesCmd(ARRAY_SIZE(cmd2), cmd2);
+
+    const char *cmd3[] = {
+            IPTABLES_PATH,
+            "-w",
+            "-t",
+            "nat",
+            "-F",
+            "oem_nat_pre"
+    };
+    runIptablesCmd(ARRAY_SIZE(cmd3), cmd3);
+    return true;
+}
+
+static bool oemInitChains() {
+    int ret = system(OEM_SCRIPT_PATH);
+    if ((-1 == ret) || (0 != WEXITSTATUS(ret))) {
+        ALOGE("%s failed: %s", OEM_SCRIPT_PATH, strerror(errno));
+        oemCleanupHooks();
+        return false;
+    }
+    return true;
+}
+
+
+void setupOemIptablesHook() {
+    if (0 == access(OEM_SCRIPT_PATH, R_OK | X_OK)) {
+        // The call to oemCleanupHooks() is superfluous when done on bootup,
+        // but is needed for the case where netd has crashed/stopped and is
+        // restarted.
+        if (oemCleanupHooks() && oemInitChains()) {
+            ALOGI("OEM iptable hook installed.");
+        }
+    }
+}
diff --git a/netd/server/oem_iptables_hook.h b/netd/server/oem_iptables_hook.h
new file mode 100644
index 0000000..bc99638
--- /dev/null
+++ b/netd/server/oem_iptables_hook.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 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 _OEM_IPTABLES_HOOK_H
+#define _OEM_IPTABLES_HOOK_H
+
+#define OEM_IPTABLES_FILTER_OUTPUT "oem_out"
+#define OEM_IPTABLES_FILTER_FORWARD "oem_fwd"
+#define OEM_IPTABLES_NAT_PREROUTING "oem_nat_pre"
+
+void setupOemIptablesHook();
+
+#endif
diff --git a/netd/tests/Android.mk b/netd/tests/Android.mk
new file mode 100644
index 0000000..211411b
--- /dev/null
+++ b/netd/tests/Android.mk
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2016 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.
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := sock_diag_test
+LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter
+LOCAL_C_INCLUDES := system/netd/server
+LOCAL_SRC_FILES := sock_diag_test.cpp ../server/SockDiag.cpp
+LOCAL_MODULE_TAGS := tests
+LOCAL_SHARED_LIBRARIES := liblog
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := netd_test
+EXTRA_LDLIBS := -lpthread
+LOCAL_SHARED_LIBRARIES += libbase libcutils libutils liblog libnetd_client
+LOCAL_STATIC_LIBRARIES += libtestUtil
+LOCAL_C_INCLUDES += system/core/base/include system/netd/include \
+                    system/extras/tests/include bionic/libc/dns/include
+LOCAL_SRC_FILES := netd_test.cpp dns_responder.cpp
+LOCAL_MODULE_TAGS := eng tests
+include $(BUILD_NATIVE_TEST)
diff --git a/netd/tests/dns_responder.cpp b/netd/tests/dns_responder.cpp
new file mode 100644
index 0000000..09d6379
--- /dev/null
+++ b/netd/tests/dns_responder.cpp
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2016 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 "dns_responder.h"
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <vector>
+
+#include <log/log.h>
+
+namespace test {
+
+std::string errno2str() {
+    char error_msg[512] = { 0 };
+    if (strerror_r(errno, error_msg, sizeof(error_msg)))
+        return std::string();
+    return std::string(error_msg);
+}
+
+#define APLOGI(fmt, ...) ALOGI(fmt ": [%d] %s", __VA_ARGS__, errno, errno2str().c_str())
+
+std::string str2hex(const char* buffer, size_t len) {
+    std::string str(len*2, '\0');
+    for (size_t i = 0 ; i < len ; ++i) {
+        static const char* hex = "0123456789ABCDEF";
+        uint8_t c = buffer[i];
+        str[i*2] = hex[c >> 4];
+        str[i*2 + 1] = hex[c & 0x0F];
+    }
+    return str;
+}
+
+std::string addr2str(const sockaddr* sa, socklen_t sa_len) {
+    char host_str[NI_MAXHOST] = { 0 };
+    int rv = getnameinfo(sa, sa_len, host_str, sizeof(host_str), nullptr, 0,
+                         NI_NUMERICHOST);
+    if (rv == 0) return std::string(host_str);
+    return std::string();
+}
+
+/* DNS struct helpers */
+
+const char* dnstype2str(unsigned dnstype) {
+    static std::unordered_map<unsigned, const char*> kTypeStrs = {
+        { ns_type::ns_t_a, "A" },
+        { ns_type::ns_t_ns, "NS" },
+        { ns_type::ns_t_md, "MD" },
+        { ns_type::ns_t_mf, "MF" },
+        { ns_type::ns_t_cname, "CNAME" },
+        { ns_type::ns_t_soa, "SOA" },
+        { ns_type::ns_t_mb, "MB" },
+        { ns_type::ns_t_mb, "MG" },
+        { ns_type::ns_t_mr, "MR" },
+        { ns_type::ns_t_null, "NULL" },
+        { ns_type::ns_t_wks, "WKS" },
+        { ns_type::ns_t_ptr, "PTR" },
+        { ns_type::ns_t_hinfo, "HINFO" },
+        { ns_type::ns_t_minfo, "MINFO" },
+        { ns_type::ns_t_mx, "MX" },
+        { ns_type::ns_t_txt, "TXT" },
+        { ns_type::ns_t_rp, "RP" },
+        { ns_type::ns_t_afsdb, "AFSDB" },
+        { ns_type::ns_t_x25, "X25" },
+        { ns_type::ns_t_isdn, "ISDN" },
+        { ns_type::ns_t_rt, "RT" },
+        { ns_type::ns_t_nsap, "NSAP" },
+        { ns_type::ns_t_nsap_ptr, "NSAP-PTR" },
+        { ns_type::ns_t_sig, "SIG" },
+        { ns_type::ns_t_key, "KEY" },
+        { ns_type::ns_t_px, "PX" },
+        { ns_type::ns_t_gpos, "GPOS" },
+        { ns_type::ns_t_aaaa, "AAAA" },
+        { ns_type::ns_t_loc, "LOC" },
+        { ns_type::ns_t_nxt, "NXT" },
+        { ns_type::ns_t_eid, "EID" },
+        { ns_type::ns_t_nimloc, "NIMLOC" },
+        { ns_type::ns_t_srv, "SRV" },
+        { ns_type::ns_t_naptr, "NAPTR" },
+        { ns_type::ns_t_kx, "KX" },
+        { ns_type::ns_t_cert, "CERT" },
+        { ns_type::ns_t_a6, "A6" },
+        { ns_type::ns_t_dname, "DNAME" },
+        { ns_type::ns_t_sink, "SINK" },
+        { ns_type::ns_t_opt, "OPT" },
+        { ns_type::ns_t_apl, "APL" },
+        { ns_type::ns_t_tkey, "TKEY" },
+        { ns_type::ns_t_tsig, "TSIG" },
+        { ns_type::ns_t_ixfr, "IXFR" },
+        { ns_type::ns_t_axfr, "AXFR" },
+        { ns_type::ns_t_mailb, "MAILB" },
+        { ns_type::ns_t_maila, "MAILA" },
+        { ns_type::ns_t_any, "ANY" },
+        { ns_type::ns_t_zxfr, "ZXFR" },
+    };
+    auto it = kTypeStrs.find(dnstype);
+    static const char* kUnknownStr{ "UNKNOWN" };
+    if (it == kTypeStrs.end()) return kUnknownStr;
+    return it->second;
+}
+
+const char* dnsclass2str(unsigned dnsclass) {
+    static std::unordered_map<unsigned, const char*> kClassStrs = {
+        { ns_class::ns_c_in , "Internet" },
+        { 2, "CSNet" },
+        { ns_class::ns_c_chaos, "ChaosNet" },
+        { ns_class::ns_c_hs, "Hesiod" },
+        { ns_class::ns_c_none, "none" },
+        { ns_class::ns_c_any, "any" }
+    };
+    auto it = kClassStrs.find(dnsclass);
+    static const char* kUnknownStr{ "UNKNOWN" };
+    if (it == kClassStrs.end()) return kUnknownStr;
+    return it->second;
+    return "unknown";
+}
+
+struct DNSName {
+    std::string name;
+    const char* read(const char* buffer, const char* buffer_end);
+    char* write(char* buffer, const char* buffer_end) const;
+    const char* toString() const;
+private:
+    const char* parseField(const char* buffer, const char* buffer_end,
+                           bool* last);
+};
+
+const char* DNSName::toString() const {
+    return name.c_str();
+}
+
+const char* DNSName::read(const char* buffer, const char* buffer_end) {
+    const char* cur = buffer;
+    bool last = false;
+    do {
+        cur = parseField(cur, buffer_end, &last);
+        if (cur == nullptr) {
+            ALOGI("parsing failed at line %d", __LINE__);
+            return nullptr;
+        }
+    } while (!last);
+    return cur;
+}
+
+char* DNSName::write(char* buffer, const char* buffer_end) const {
+    char* buffer_cur = buffer;
+    for (size_t pos = 0 ; pos < name.size() ; ) {
+        size_t dot_pos = name.find('.', pos);
+        if (dot_pos == std::string::npos) {
+            // Sanity check, should never happen unless parseField is broken.
+            ALOGI("logic error: all names are expected to end with a '.'");
+            return nullptr;
+        }
+        size_t len = dot_pos - pos;
+        if (len >= 256) {
+            ALOGI("name component '%s' is %zu long, but max is 255",
+                    name.substr(pos, dot_pos - pos).c_str(), len);
+            return nullptr;
+        }
+        if (buffer_cur + sizeof(uint8_t) + len > buffer_end) {
+            ALOGI("buffer overflow at line %d", __LINE__);
+            return nullptr;
+        }
+        *buffer_cur++ = len;
+        buffer_cur = std::copy(std::next(name.begin(), pos),
+                               std::next(name.begin(), dot_pos),
+                               buffer_cur);
+        pos = dot_pos + 1;
+    }
+    // Write final zero.
+    *buffer_cur++ = 0;
+    return buffer_cur;
+}
+
+const char* DNSName::parseField(const char* buffer, const char* buffer_end,
+                                bool* last) {
+    if (buffer + sizeof(uint8_t) > buffer_end) {
+        ALOGI("parsing failed at line %d", __LINE__);
+        return nullptr;
+    }
+    unsigned field_type = *buffer >> 6;
+    unsigned ofs = *buffer & 0x3F;
+    const char* cur = buffer + sizeof(uint8_t);
+    if (field_type == 0) {
+        // length + name component
+        if (ofs == 0) {
+            *last = true;
+            return cur;
+        }
+        if (cur + ofs > buffer_end) {
+            ALOGI("parsing failed at line %d", __LINE__);
+            return nullptr;
+        }
+        name.append(cur, ofs);
+        name.push_back('.');
+        return cur + ofs;
+    } else if (field_type == 3) {
+        ALOGI("name compression not implemented");
+        return nullptr;
+    }
+    ALOGI("invalid name field type");
+    return nullptr;
+}
+
+struct DNSQuestion {
+    DNSName qname;
+    unsigned qtype;
+    unsigned qclass;
+    const char* read(const char* buffer, const char* buffer_end);
+    char* write(char* buffer, const char* buffer_end) const;
+    std::string toString() const;
+};
+
+const char* DNSQuestion::read(const char* buffer, const char* buffer_end) {
+    const char* cur = qname.read(buffer, buffer_end);
+    if (cur == nullptr) {
+        ALOGI("parsing failed at line %d", __LINE__);
+        return nullptr;
+    }
+    if (cur + 2*sizeof(uint16_t) > buffer_end) {
+        ALOGI("parsing failed at line %d", __LINE__);
+        return nullptr;
+    }
+    qtype = ntohs(*reinterpret_cast<const uint16_t*>(cur));
+    qclass = ntohs(*reinterpret_cast<const uint16_t*>(cur + sizeof(uint16_t)));
+    return cur + 2*sizeof(uint16_t);
+}
+
+char* DNSQuestion::write(char* buffer, const char* buffer_end) const {
+    char* buffer_cur = qname.write(buffer, buffer_end);
+    if (buffer_cur == nullptr) return nullptr;
+    if (buffer_cur + 2*sizeof(uint16_t) > buffer_end) {
+        ALOGI("buffer overflow on line %d", __LINE__);
+        return nullptr;
+    }
+    *reinterpret_cast<uint16_t*>(buffer_cur) = htons(qtype);
+    *reinterpret_cast<uint16_t*>(buffer_cur + sizeof(uint16_t)) =
+            htons(qclass);
+    return buffer_cur + 2*sizeof(uint16_t);
+}
+
+std::string DNSQuestion::toString() const {
+    char buffer[4096];
+    int len = snprintf(buffer, sizeof(buffer), "Q<%s,%s,%s>", qname.toString(),
+                       dnstype2str(qtype), dnsclass2str(qclass));
+    return std::string(buffer, len);
+}
+
+struct DNSRecord {
+    DNSName name;
+    unsigned rtype;
+    unsigned rclass;
+    unsigned ttl;
+    std::vector<char> rdata;
+    const char* read(const char* buffer, const char* buffer_end);
+    char* write(char* buffer, const char* buffer_end) const;
+    std::string toString() const;
+private:
+    struct IntFields {
+        uint16_t rtype;
+        uint16_t rclass;
+        uint32_t ttl;
+        uint16_t rdlen;
+    } __attribute__((__packed__));
+
+    const char* readIntFields(const char* buffer, const char* buffer_end,
+            unsigned* rdlen);
+    char* writeIntFields(unsigned rdlen, char* buffer,
+                         const char* buffer_end) const;
+};
+
+const char* DNSRecord::read(const char* buffer, const char* buffer_end) {
+    const char* cur = name.read(buffer, buffer_end);
+    if (cur == nullptr) {
+        ALOGI("parsing failed at line %d", __LINE__);
+        return nullptr;
+    }
+    unsigned rdlen = 0;
+    cur = readIntFields(cur, buffer_end, &rdlen);
+    if (cur == nullptr) {
+        ALOGI("parsing failed at line %d", __LINE__);
+        return nullptr;
+    }
+    if (cur + rdlen > buffer_end) {
+        ALOGI("parsing failed at line %d", __LINE__);
+        return nullptr;
+    }
+    rdata.assign(cur, cur + rdlen);
+    return cur + rdlen;
+}
+
+char* DNSRecord::write(char* buffer, const char* buffer_end) const {
+    char* buffer_cur = name.write(buffer, buffer_end);
+    if (buffer_cur == nullptr) return nullptr;
+    buffer_cur = writeIntFields(rdata.size(), buffer_cur, buffer_end);
+    if (buffer_cur == nullptr) return nullptr;
+    if (buffer_cur + rdata.size() > buffer_end) {
+        ALOGI("buffer overflow on line %d", __LINE__);
+        return nullptr;
+    }
+    return std::copy(rdata.begin(), rdata.end(), buffer_cur);
+}
+
+std::string DNSRecord::toString() const {
+    char buffer[4096];
+    int len = snprintf(buffer, sizeof(buffer), "R<%s,%s,%s>", name.toString(),
+                       dnstype2str(rtype), dnsclass2str(rclass));
+    return std::string(buffer, len);
+}
+
+const char* DNSRecord::readIntFields(const char* buffer, const char* buffer_end,
+                                     unsigned* rdlen) {
+    if (buffer + sizeof(IntFields) > buffer_end ) {
+        ALOGI("parsing failed at line %d", __LINE__);
+        return nullptr;
+    }
+    const auto& intfields = *reinterpret_cast<const IntFields*>(buffer);
+    rtype = ntohs(intfields.rtype);
+    rclass = ntohs(intfields.rclass);
+    ttl = ntohl(intfields.ttl);
+    *rdlen = ntohs(intfields.rdlen);
+    return buffer + sizeof(IntFields);
+}
+
+char* DNSRecord::writeIntFields(unsigned rdlen, char* buffer,
+                                const char* buffer_end) const {
+    if (buffer + sizeof(IntFields) > buffer_end ) {
+        ALOGI("buffer overflow on line %d", __LINE__);
+        return nullptr;
+    }
+    auto& intfields = *reinterpret_cast<IntFields*>(buffer);
+    intfields.rtype = htons(rtype);
+    intfields.rclass = htons(rclass);
+    intfields.ttl = htonl(ttl);
+    intfields.rdlen = htons(rdlen);
+    return buffer + sizeof(IntFields);
+}
+
+struct DNSHeader {
+    unsigned id;
+    bool ra;
+    uint8_t rcode;
+    bool qr;
+    uint8_t opcode;
+    bool aa;
+    bool tr;
+    bool rd;
+    std::vector<DNSQuestion> questions;
+    std::vector<DNSRecord> answers;
+    std::vector<DNSRecord> authorities;
+    std::vector<DNSRecord> additionals;
+    const char* read(const char* buffer, const char* buffer_end);
+    char* write(char* buffer, const char* buffer_end) const;
+    std::string toString() const;
+
+private:
+    struct Header {
+        uint16_t id;
+        uint8_t flags0;
+        uint8_t flags1;
+        uint16_t qdcount;
+        uint16_t ancount;
+        uint16_t nscount;
+        uint16_t arcount;
+    } __attribute__((__packed__));
+
+    const char* readHeader(const char* buffer, const char* buffer_end,
+                           unsigned* qdcount, unsigned* ancount,
+                           unsigned* nscount, unsigned* arcount);
+};
+
+const char* DNSHeader::read(const char* buffer, const char* buffer_end) {
+    unsigned qdcount;
+    unsigned ancount;
+    unsigned nscount;
+    unsigned arcount;
+    const char* cur = readHeader(buffer, buffer_end, &qdcount, &ancount,
+                                 &nscount, &arcount);
+    if (cur == nullptr) {
+        ALOGI("parsing failed at line %d", __LINE__);
+        return nullptr;
+    }
+    if (qdcount) {
+        questions.resize(qdcount);
+        for (unsigned i = 0 ; i < qdcount ; ++i) {
+            cur = questions[i].read(cur, buffer_end);
+            if (cur == nullptr) {
+                ALOGI("parsing failed at line %d", __LINE__);
+                return nullptr;
+            }
+        }
+    }
+    if (ancount) {
+        answers.resize(ancount);
+        for (unsigned i = 0 ; i < ancount ; ++i) {
+            cur = answers[i].read(cur, buffer_end);
+            if (cur == nullptr) {
+                ALOGI("parsing failed at line %d", __LINE__);
+                return nullptr;
+            }
+        }
+    }
+    if (nscount) {
+        authorities.resize(nscount);
+        for (unsigned i = 0 ; i < nscount ; ++i) {
+            cur = authorities[i].read(cur, buffer_end);
+            if (cur == nullptr) {
+                ALOGI("parsing failed at line %d", __LINE__);
+                return nullptr;
+            }
+        }
+    }
+    if (arcount) {
+        additionals.resize(arcount);
+        for (unsigned i = 0 ; i < arcount ; ++i) {
+            cur = additionals[i].read(cur, buffer_end);
+            if (cur == nullptr) {
+                ALOGI("parsing failed at line %d", __LINE__);
+                return nullptr;
+            }
+        }
+    }
+    return cur;
+}
+
+char* DNSHeader::write(char* buffer, const char* buffer_end) const {
+    if (buffer + sizeof(Header) > buffer_end) {
+        ALOGI("buffer overflow on line %d", __LINE__);
+        return nullptr;
+    }
+    Header& header = *reinterpret_cast<Header*>(buffer);
+    // bytes 0-1
+    header.id = htons(id);
+    // byte 2: 7:qr, 3-6:opcode, 2:aa, 1:tr, 0:rd
+    header.flags0 = (qr << 7) | (opcode << 3) | (aa << 2) | (tr << 1) | rd;
+    // byte 3: 7:ra, 6:zero, 5:ad, 4:cd, 0-3:rcode
+    header.flags1 = rcode;
+    // rest of header
+    header.qdcount = htons(questions.size());
+    header.ancount = htons(answers.size());
+    header.nscount = htons(authorities.size());
+    header.arcount = htons(additionals.size());
+    char* buffer_cur = buffer + sizeof(Header);
+    for (const DNSQuestion& question : questions) {
+        buffer_cur = question.write(buffer_cur, buffer_end);
+        if (buffer_cur == nullptr) return nullptr;
+    }
+    for (const DNSRecord& answer : answers) {
+        buffer_cur = answer.write(buffer_cur, buffer_end);
+        if (buffer_cur == nullptr) return nullptr;
+    }
+    for (const DNSRecord& authority : authorities) {
+        buffer_cur = authority.write(buffer_cur, buffer_end);
+        if (buffer_cur == nullptr) return nullptr;
+    }
+    for (const DNSRecord& additional : additionals) {
+        buffer_cur = additional.write(buffer_cur, buffer_end);
+        if (buffer_cur == nullptr) return nullptr;
+    }
+    return buffer_cur;
+}
+
+std::string DNSHeader::toString() const {
+    // TODO
+    return std::string();
+}
+
+const char* DNSHeader::readHeader(const char* buffer, const char* buffer_end,
+                                  unsigned* qdcount, unsigned* ancount,
+                                  unsigned* nscount, unsigned* arcount) {
+    if (buffer + sizeof(Header) > buffer_end)
+        return 0;
+    const auto& header = *reinterpret_cast<const Header*>(buffer);
+    // bytes 0-1
+    id = ntohs(header.id);
+    // byte 2: 7:qr, 3-6:opcode, 2:aa, 1:tr, 0:rd
+    qr = header.flags0 >> 7;
+    opcode = (header.flags0 >> 3) & 0x0F;
+    aa = (header.flags0 >> 2) & 1;
+    tr = (header.flags0 >> 1) & 1;
+    rd = header.flags0 & 1;
+    // byte 3: 7:ra, 6:zero, 5:ad, 4:cd, 0-3:rcode
+    ra = header.flags1 >> 7;
+    rcode = header.flags1 & 0xF;
+    // rest of header
+    *qdcount = ntohs(header.qdcount);
+    *ancount = ntohs(header.ancount);
+    *nscount = ntohs(header.nscount);
+    *arcount = ntohs(header.arcount);
+    return buffer + sizeof(Header);
+}
+
+/* DNS responder */
+
+DNSResponder::DNSResponder(const char* listen_address,
+                           const char* listen_service, int poll_timeout_ms,
+                           uint16_t error_rcode, double response_probability) :
+    listen_address_(listen_address), listen_service_(listen_service),
+    poll_timeout_ms_(poll_timeout_ms), error_rcode_(error_rcode),
+    response_probability_(response_probability),
+    socket_(-1), epoll_fd_(-1), terminate_(false) { }
+
+DNSResponder::~DNSResponder() {
+    stopServer();
+}
+
+void DNSResponder::addMapping(const char* name, ns_type type,
+        const char* addr) {
+    std::lock_guard<std::mutex> lock(mappings_mutex_);
+    auto it = mappings_.find(QueryKey(name, type));
+    if (it != mappings_.end()) {
+        ALOGI("Overwriting mapping for (%s, %s), previous address %s, new "
+            "address %s", name, dnstype2str(type), it->second.c_str(),
+            addr);
+        it->second = addr;
+        return;
+    }
+    mappings_.emplace(std::piecewise_construct,
+                      std::forward_as_tuple(name, type),
+                      std::forward_as_tuple(addr));
+}
+
+void DNSResponder::removeMapping(const char* name, ns_type type) {
+    std::lock_guard<std::mutex> lock(mappings_mutex_);
+    auto it = mappings_.find(QueryKey(name, type));
+    if (it != mappings_.end()) {
+        ALOGI("Cannot remove mapping mapping from (%s, %s), not present", name,
+            dnstype2str(type));
+        return;
+    }
+    mappings_.erase(it);
+}
+
+void DNSResponder::setResponseProbability(double response_probability) {
+    response_probability_ = response_probability;
+}
+
+bool DNSResponder::running() const {
+    return socket_ != -1;
+}
+
+bool DNSResponder::startServer() {
+    if (running()) {
+        ALOGI("server already running");
+        return false;
+    }
+    addrinfo ai_hints{
+        .ai_family = AF_UNSPEC,
+        .ai_socktype = SOCK_DGRAM,
+        .ai_flags = AI_PASSIVE
+    };
+    addrinfo* ai_res;
+    int rv = getaddrinfo(listen_address_.c_str(), listen_service_.c_str(),
+                         &ai_hints, &ai_res);
+    if (rv) {
+        ALOGI("getaddrinfo(%s, %s) failed: %s", listen_address_.c_str(),
+            listen_service_.c_str(), gai_strerror(rv));
+        return false;
+    }
+    int s = -1;
+    for (const addrinfo* ai = ai_res ; ai ; ai = ai->ai_next) {
+        s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+        if (s < 0) continue;
+        const int one = 1;
+        setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
+        if (bind(s, ai->ai_addr, ai->ai_addrlen)) {
+            APLOGI("bind failed for socket %d", s);
+            close(s);
+            s = -1;
+            continue;
+        }
+        std::string host_str = addr2str(ai->ai_addr, ai->ai_addrlen);
+        ALOGI("bound to UDP %s:%s", host_str.c_str(), listen_service_.c_str());
+        break;
+    }
+    freeaddrinfo(ai_res);
+    if (s < 0) {
+        ALOGI("bind() failed");
+        return false;
+    }
+
+    int flags = fcntl(s, F_GETFL, 0);
+    if (flags < 0) flags = 0;
+    if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) {
+        APLOGI("fcntl(F_SETFL) failed for socket %d", s);
+        close(s);
+        return false;
+    }
+
+    int ep_fd = epoll_create(1);
+    if (ep_fd < 0) {
+        char error_msg[512] = { 0 };
+        if (strerror_r(errno, error_msg, sizeof(error_msg)))
+            strncpy(error_msg, "UNKNOWN", sizeof(error_msg));
+        APLOGI("epoll_create() failed: %s", error_msg);
+        close(s);
+        return false;
+    }
+    epoll_event ev;
+    ev.events = EPOLLIN;
+    ev.data.fd = s;
+    if (epoll_ctl(ep_fd, EPOLL_CTL_ADD, s, &ev) < 0) {
+        APLOGI("epoll_ctl() failed for socket %d", s);
+        close(ep_fd);
+        close(s);
+        return false;
+    }
+
+    epoll_fd_ = ep_fd;
+    socket_ = s;
+    {
+        std::lock_guard<std::mutex> lock(update_mutex_);
+        handler_thread_ = std::thread(&DNSResponder::requestHandler, this);
+    }
+    ALOGI("server started successfully");
+    return true;
+}
+
+bool DNSResponder::stopServer() {
+    std::lock_guard<std::mutex> lock(update_mutex_);
+    if (!running()) {
+        ALOGI("server not running");
+        return false;
+    }
+    if (terminate_) {
+        ALOGI("LOGIC ERROR");
+        return false;
+    }
+    ALOGI("stopping server");
+    terminate_ = true;
+    handler_thread_.join();
+    close(epoll_fd_);
+    close(socket_);
+    terminate_ = false;
+    socket_ = -1;
+    ALOGI("server stopped successfully");
+    return true;
+}
+
+std::vector<std::pair<std::string, ns_type >> DNSResponder::queries() const {
+    std::lock_guard<std::mutex> lock(queries_mutex_);
+    return queries_;
+}
+
+void DNSResponder::clearQueries() {
+    std::lock_guard<std::mutex> lock(queries_mutex_);
+    queries_.clear();
+}
+
+void DNSResponder::requestHandler() {
+    epoll_event evs[1];
+    while (!terminate_) {
+        int n = epoll_wait(epoll_fd_, evs, 1, poll_timeout_ms_);
+        if (n == 0) continue;
+        if (n < 0) {
+            ALOGI("epoll_wait() failed");
+            // TODO(imaipi): terminate on error.
+            return;
+        }
+        char buffer[4096];
+        sockaddr_storage sa;
+        socklen_t sa_len = sizeof(sa);
+        ssize_t len;
+        do {
+            len = recvfrom(socket_, buffer, sizeof(buffer), 0,
+                           (sockaddr*) &sa, &sa_len);
+        } while (len < 0 && (errno == EAGAIN || errno == EINTR));
+        if (len <= 0) {
+            ALOGI("recvfrom() failed");
+            continue;
+        }
+        ALOGI("read %zd bytes", len);
+        char response[4096];
+        size_t response_len = sizeof(response);
+        if (handleDNSRequest(buffer, len, response, &response_len) &&
+            response_len > 0) {
+            len = sendto(socket_, response, response_len, 0,
+                         reinterpret_cast<const sockaddr*>(&sa), sa_len);
+            std::string host_str =
+                addr2str(reinterpret_cast<const sockaddr*>(&sa), sa_len);
+            if (len > 0) {
+                ALOGI("sent %zu bytes to %s", len, host_str.c_str());
+            } else {
+                APLOGI("sendto() failed for %s", host_str.c_str());
+            }
+            // Test that the response is actually a correct DNS message.
+            const char* response_end = response + len;
+            DNSHeader header;
+            const char* cur = header.read(response, response_end);
+            if (cur == nullptr) ALOGI("response is flawed");
+
+        } else {
+            ALOGI("not responding");
+        }
+    }
+}
+
+bool DNSResponder::handleDNSRequest(const char* buffer, ssize_t len,
+                                    char* response, size_t* response_len)
+                                    const {
+    ALOGI("request: '%s'", str2hex(buffer, len).c_str());
+    const char* buffer_end = buffer + len;
+    DNSHeader header;
+    const char* cur = header.read(buffer, buffer_end);
+    // TODO(imaipi): for now, unparsable messages are silently dropped, fix.
+    if (cur == nullptr) {
+        ALOGI("failed to parse query");
+        return false;
+    }
+    if (header.qr) {
+        ALOGI("response received instead of a query");
+        return false;
+    }
+    if (header.opcode != ns_opcode::ns_o_query) {
+        ALOGI("unsupported request opcode received");
+        return makeErrorResponse(&header, ns_rcode::ns_r_notimpl, response,
+                                 response_len);
+    }
+    if (header.questions.empty()) {
+        ALOGI("no questions present");
+        return makeErrorResponse(&header, ns_rcode::ns_r_formerr, response,
+                                 response_len);
+    }
+    if (!header.answers.empty()) {
+        ALOGI("already %zu answers present in query", header.answers.size());
+        return makeErrorResponse(&header, ns_rcode::ns_r_formerr, response,
+                                 response_len);
+    }
+    {
+        std::lock_guard<std::mutex> lock(queries_mutex_);
+        for (const DNSQuestion& question : header.questions) {
+            queries_.push_back(make_pair(question.qname.name,
+                                         ns_type(question.qtype)));
+        }
+    }
+
+    // Ignore requests with the preset probability.
+    auto constexpr bound = std::numeric_limits<unsigned>::max();
+    if (arc4random_uniform(bound) > bound*response_probability_) {
+        ALOGI("returning SRVFAIL in accordance with probability distribution");
+        return makeErrorResponse(&header, ns_rcode::ns_r_servfail, response,
+                                 response_len);
+    }
+
+    for (const DNSQuestion& question : header.questions) {
+        if (question.qclass != ns_class::ns_c_in &&
+            question.qclass != ns_class::ns_c_any) {
+            ALOGI("unsupported question class %u", question.qclass);
+            return makeErrorResponse(&header, ns_rcode::ns_r_notimpl, response,
+                                     response_len);
+        }
+        if (!addAnswerRecords(question, &header.answers)) {
+            return makeErrorResponse(&header, ns_rcode::ns_r_servfail, response,
+                                     response_len);
+        }
+    }
+    header.qr = true;
+    char* response_cur = header.write(response, response + *response_len);
+    if (response_cur == nullptr) {
+        return false;
+    }
+    *response_len = response_cur - response;
+    return true;
+}
+
+bool DNSResponder::addAnswerRecords(const DNSQuestion& question,
+                                    std::vector<DNSRecord>* answers) const {
+    auto it = mappings_.find(QueryKey(question.qname.name, question.qtype));
+    if (it == mappings_.end()) {
+        // TODO(imaipi): handle correctly
+        ALOGI("no mapping found for %s %s, lazily refusing to add an answer",
+            question.qname.name.c_str(), dnstype2str(question.qtype));
+        return true;
+    }
+    ALOGI("mapping found for %s %s: %s", question.qname.name.c_str(),
+        dnstype2str(question.qtype), it->second.c_str());
+    DNSRecord record;
+    record.name = question.qname;
+    record.rtype = question.qtype;
+    record.rclass = ns_class::ns_c_in;
+    record.ttl = 1;
+    if (question.qtype == ns_type::ns_t_a) {
+        record.rdata.resize(4);
+        if (inet_pton(AF_INET, it->second.c_str(), record.rdata.data()) != 1) {
+            ALOGI("inet_pton(AF_INET, %s) failed", it->second.c_str());
+            return false;
+        }
+    } else if (question.qtype == ns_type::ns_t_aaaa) {
+        record.rdata.resize(16);
+        if (inet_pton(AF_INET6, it->second.c_str(), record.rdata.data()) != 1) {
+            ALOGI("inet_pton(AF_INET6, %s) failed", it->second.c_str());
+            return false;
+        }
+    } else {
+        ALOGI("unhandled qtype %s", dnstype2str(question.qtype));
+        return false;
+    }
+    answers->push_back(std::move(record));
+    return true;
+}
+
+bool DNSResponder::makeErrorResponse(DNSHeader* header, ns_rcode rcode,
+                                     char* response, size_t* response_len)
+                                     const {
+    header->answers.clear();
+    header->authorities.clear();
+    header->additionals.clear();
+    header->rcode = rcode;
+    header->qr = true;
+    char* response_cur = header->write(response, response + *response_len);
+    if (response_cur == nullptr) return false;
+    *response_len = response_cur - response;
+    return true;
+}
+
+}  // namespace test
+
diff --git a/netd/tests/dns_responder.h b/netd/tests/dns_responder.h
new file mode 100644
index 0000000..4ed4bb2
--- /dev/null
+++ b/netd/tests/dns_responder.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 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 requied 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 DNS_RESPONDER_H
+#define DNS_RESPONDER_H
+
+#include <arpa/nameser.h>
+
+#include <atomic>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+
+namespace test {
+
+struct DNSHeader;
+struct DNSQuestion;
+struct DNSRecord;
+
+/*
+ * Simple DNS responder, which replies to queries with the registered response
+ * for that type. Class is assumed to be IN. If no response is registered, the
+ * default error response code is returned.
+ */
+class DNSResponder {
+public:
+    DNSResponder(const char* listen_address, const char* listen_service,
+                 int poll_timeout_ms, uint16_t error_rcode,
+                 double response_probability);
+    ~DNSResponder();
+    void addMapping(const char* name, ns_type type, const char* addr);
+    void removeMapping(const char* name, ns_type type);
+    void setResponseProbability(double response_probability);
+    bool running() const;
+    bool startServer();
+    bool stopServer();
+    std::vector<std::pair<std::string, ns_type>> queries() const;
+    void clearQueries();
+
+private:
+    // Key used for accessing mappings.
+    struct QueryKey {
+        std::string name;
+        unsigned type;
+        QueryKey(std::string n, unsigned t) : name(n), type(t) {}
+        bool operator == (const QueryKey& o) const {
+            return name == o.name && type == o.type;
+        }
+        bool operator < (const QueryKey& o) const {
+            if (name < o.name) return true;
+            if (name > o.name) return false;
+            return type < o.type;
+        }
+    };
+
+    struct QueryKeyHash {
+        size_t operator() (const QueryKey& key) const {
+            return std::hash<std::string>()(key.name) +
+                   static_cast<size_t>(key.type);
+        }
+    };
+
+    // DNS request handler.
+    void requestHandler();
+
+    // Parses and generates a response message for incoming DNS requests.
+    // Returns false on parsing errors.
+    bool handleDNSRequest(const char* buffer, ssize_t buffer_len,
+                          char* response, size_t* response_len) const;
+
+    bool addAnswerRecords(const DNSQuestion& question,
+                          std::vector<DNSRecord>* answers) const;
+
+    bool generateErrorResponse(DNSHeader* header, ns_rcode rcode,
+                               char* response, size_t* response_len) const;
+    bool makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
+                           size_t* response_len) const;
+
+
+    // Address and service to listen on, currently limited to UDP.
+    const std::string listen_address_;
+    const std::string listen_service_;
+    // epoll_wait() timeout in ms.
+    const int poll_timeout_ms_;
+    // Error code to return for requests for an unknown name.
+    const uint16_t error_rcode_;
+    // Probability that a valid response is being sent instead of being sent
+    // instead of returning error_rcode_.
+    std::atomic<double> response_probability_;
+
+    // Mappings from (name, type) to registered response and the
+    // mutex protecting them.
+    std::unordered_map<QueryKey, std::string, QueryKeyHash> mappings_
+        GUARDED_BY(mappings_mutex_);
+    // TODO(imaipi): enable GUARDED_BY(mappings_mutex_);
+    std::mutex mappings_mutex_;
+    // Query names received so far and the corresponding mutex.
+    mutable std::vector<std::pair<std::string, ns_type>> queries_
+        GUARDED_BY(queries_mutex_);
+    mutable std::mutex queries_mutex_;
+    // Socket on which the server is listening.
+    int socket_;
+    // File descriptor for epoll.
+    int epoll_fd_;
+    // Signal for request handler termination.
+    std::atomic<bool> terminate_ GUARDED_BY(update_mutex_);
+    // Thread for handling incoming threads.
+    std::thread handler_thread_ GUARDED_BY(update_mutex_);
+    std::mutex update_mutex_;
+};
+
+}  // namespace test
+
+#endif  // DNS_RESPONDER_H
diff --git a/netd/tests/netd_test.cpp b/netd/tests/netd_test.cpp
new file mode 100644
index 0000000..97b91bd
--- /dev/null
+++ b/netd/tests/netd_test.cpp
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2016 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 requied 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 <errno.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <cutils/sockets.h>
+#include <android-base/stringprintf.h>
+#include <private/android_filesystem_config.h>
+
+#include <thread>
+
+#include "NetdClient.h"
+
+#include <gtest/gtest.h>
+#define LOG_TAG "resolverTest"
+#include <utils/Log.h>
+#include <testUtil.h>
+
+#include "dns_responder.h"
+#include "resolv_params.h"
+
+using android::base::StringPrintf;
+using android::base::StringAppendF;
+
+// TODO: make this dynamic and stop depending on implementation details.
+#define TEST_OEM_NETWORK "oem29"
+#define TEST_NETID 30
+
+// The only response code used in this test, see
+// frameworks/base/services/java/com/android/server/NetworkManagementService.java
+// for others.
+static constexpr int ResponseCodeOK = 200;
+
+// Returns ResponseCode.
+int netdCommand(const char* sockname, const char* command) {
+    int sock = socket_local_client(sockname,
+                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
+                                   SOCK_STREAM);
+    if (sock < 0) {
+        perror("Error connecting");
+        return -1;
+    }
+
+    // FrameworkListener expects the whole command in one read.
+    char buffer[256];
+    int nwritten = snprintf(buffer, sizeof(buffer), "0 %s", command);
+    if (write(sock, buffer, nwritten + 1) < 0) {
+        perror("Error sending netd command");
+        close(sock);
+        return -1;
+    }
+
+    int nread = read(sock, buffer, sizeof(buffer));
+    if (nread < 0) {
+        perror("Error reading response");
+        close(sock);
+        return -1;
+    }
+    close(sock);
+    return atoi(buffer);
+}
+
+
+bool expectNetdResult(int expected, const char* sockname, const char* format, ...) {
+    char command[256];
+    va_list args;
+    va_start(args, format);
+    vsnprintf(command, sizeof(command), format, args);
+    va_end(args);
+    int result = netdCommand(sockname, command);
+    EXPECT_EQ(expected, result) << command;
+    return (200 <= expected && expected < 300);
+}
+
+
+class ResolverTest : public ::testing::Test {
+protected:
+    virtual void SetUp() {
+        // Ensure resolutions go via proxy.
+        setenv("ANDROID_DNS_MODE", "", 1);
+        uid = getuid();
+        pid = getpid();
+        SetupOemNetwork();
+    }
+
+    virtual void TearDown() {
+        TearDownOemNetwork();
+        netdCommand("netd", "network destroy " TEST_OEM_NETWORK);
+    }
+
+    void SetupOemNetwork() {
+        netdCommand("netd", "network destroy " TEST_OEM_NETWORK);
+        if (expectNetdResult(ResponseCodeOK, "netd",
+                             "network create %s", TEST_OEM_NETWORK)) {
+            oemNetId = TEST_NETID;
+        }
+        setNetworkForProcess(oemNetId);
+        ASSERT_EQ((unsigned) oemNetId, getNetworkForProcess());
+    }
+
+    void TearDownOemNetwork() {
+        if (oemNetId != -1) {
+            expectNetdResult(ResponseCodeOK, "netd",
+                             "network destroy %s", TEST_OEM_NETWORK);
+        }
+    }
+
+    bool SetResolversForNetwork(const std::vector<std::string>& searchDomains,
+            const std::vector<std::string>& servers, const std::string& params) {
+        // No use case for empty domains / servers (yet).
+        if (searchDomains.empty() || servers.empty()) return false;
+
+        std::string cmd = StringPrintf("resolver setnetdns %d \"%s", oemNetId,
+                searchDomains[0].c_str());
+        for (size_t i = 1 ; i < searchDomains.size() ; ++i) {
+            cmd += " ";
+            cmd += searchDomains[i];
+        }
+        cmd += "\" ";
+
+        cmd += servers[0];
+        for (size_t i = 1 ; i < servers.size() ; ++i) {
+            cmd += " ";
+            cmd += servers[i];
+        }
+
+        if (!params.empty()) {
+            cmd += " --params \"";
+            cmd += params;
+            cmd += "\"";
+        }
+
+        int rv = netdCommand("netd", cmd.c_str());
+        std::cout << "command: '" << cmd << "', rv = " << rv << "\n";
+        if (rv != ResponseCodeOK) {
+            return false;
+        }
+        return true;
+    }
+
+    bool FlushCache() const {
+        return expectNetdResult(ResponseCodeOK, "netd", "resolver flushnet %d", oemNetId);
+    }
+
+    std::string ToString(const hostent* he) const {
+        if (he == nullptr) return "<null>";
+        char buffer[INET6_ADDRSTRLEN];
+        if (!inet_ntop(he->h_addrtype, he->h_addr_list[0], buffer, sizeof(buffer))) {
+            return "<invalid>";
+        }
+        return buffer;
+    }
+
+    std::string ToString(const addrinfo* ai) const {
+        if (!ai)
+            return "<null>";
+        for (const auto* aip = ai ; aip != nullptr ; aip = aip->ai_next) {
+            char host[NI_MAXHOST];
+            int rv = getnameinfo(aip->ai_addr, aip->ai_addrlen, host, sizeof(host), nullptr, 0,
+                    NI_NUMERICHOST);
+            if (rv != 0)
+                return gai_strerror(rv);
+            return host;
+        }
+        return "<invalid>";
+    }
+
+    size_t GetNumQueries(const test::DNSResponder& dns, const char* name) const {
+        auto queries = dns.queries();
+        size_t found = 0;
+        for (const auto& p : queries) {
+            std::cout << "query " << p.first << "\n";
+            if (p.first == name) {
+                ++found;
+            }
+        }
+        return found;
+    }
+
+    size_t GetNumQueriesForType(const test::DNSResponder& dns, ns_type type,
+            const char* name) const {
+        auto queries = dns.queries();
+        size_t found = 0;
+        for (const auto& p : queries) {
+            std::cout << "query " << p.first << "\n";
+            if (p.second == type && p.first == name) {
+                ++found;
+            }
+        }
+        return found;
+    }
+
+    int pid;
+    int uid;
+    int oemNetId = -1;
+    const std::vector<std::string> mDefaultSearchDomains = { "example.com" };
+    // <sample validity in s> <success threshold in percent> <min samples> <max samples>
+    const std::string mDefaultParams = "300 25 8 8";
+};
+
+TEST_F(ResolverTest, GetHostByName) {
+    const char* listen_addr = "127.0.0.3";
+    const char* listen_srv = "53";
+    const char* host_name = "hello.example.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail, 1.0);
+    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.3");
+    ASSERT_TRUE(dns.startServer());
+    std::vector<std::string> servers = { listen_addr };
+    ASSERT_TRUE(SetResolversForNetwork(mDefaultSearchDomains, servers, mDefaultParams));
+
+    dns.clearQueries();
+    const hostent* result = gethostbyname("hello");
+    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name));
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(4, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ("1.2.3.3", ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+    dns.stopServer();
+}
+
+TEST_F(ResolverTest, GetAddrInfo) {
+    addrinfo* result = nullptr;
+
+    const char* listen_addr = "127.0.0.4";
+    const char* listen_srv = "53";
+    const char* host_name = "howdie.example.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250,
+                           ns_rcode::ns_r_servfail, 1.0);
+    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
+    dns.addMapping(host_name, ns_type::ns_t_aaaa, "::1.2.3.4");
+    ASSERT_TRUE(dns.startServer());
+    std::vector<std::string> servers = { listen_addr };
+    ASSERT_TRUE(SetResolversForNetwork(mDefaultSearchDomains, servers, mDefaultParams));
+
+    dns.clearQueries();
+    EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result));
+    size_t found = GetNumQueries(dns, host_name);
+    EXPECT_LE(1U, found);
+    // Could be A or AAAA
+    std::string result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4")
+        << ", result_str='" << result_str << "'";
+    if (result) freeaddrinfo(result);
+    result = nullptr;
+
+    // Verify that it's cached.
+    size_t old_found = found;
+    EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result));
+    found = GetNumQueries(dns, host_name);
+    EXPECT_LE(1U, found);
+    EXPECT_EQ(old_found, found);
+    result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4")
+        << result_str;
+    if (result) freeaddrinfo(result);
+    result = nullptr;
+
+    // Verify that cache can be flushed.
+    dns.clearQueries();
+    ASSERT_TRUE(FlushCache());
+    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.44");
+    dns.addMapping(host_name, ns_type::ns_t_aaaa, "::1.2.3.44");
+
+    EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result));
+    EXPECT_LE(1U, GetNumQueries(dns, host_name));
+    // Could be A or AAAA
+    result_str = ToString(result);
+    EXPECT_TRUE(result_str == "1.2.3.44" || result_str == "::1.2.3.44")
+        << ", result_str='" << result_str << "'";
+    if (result) freeaddrinfo(result);
+}
+
+TEST_F(ResolverTest, GetAddrInfoV4) {
+    addrinfo* result = nullptr;
+
+    const char* listen_addr = "127.0.0.5";
+    const char* listen_srv = "53";
+    const char* host_name = "hola.example.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250,
+                           ns_rcode::ns_r_servfail, 1.0);
+    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.5");
+    ASSERT_TRUE(dns.startServer());
+    std::vector<std::string> servers = { listen_addr };
+    ASSERT_TRUE(SetResolversForNetwork(mDefaultSearchDomains, servers, mDefaultParams));
+
+    addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_INET;
+    EXPECT_EQ(0, getaddrinfo("hola", nullptr, &hints, &result));
+    EXPECT_EQ(1U, GetNumQueries(dns, host_name));
+    EXPECT_EQ("1.2.3.5", ToString(result));
+    if (result) freeaddrinfo(result);
+}
+
+TEST_F(ResolverTest, MultidomainResolution) {
+    std::vector<std::string> searchDomains = { "example1.com", "example2.com", "example3.com" };
+    const char* listen_addr = "127.0.0.6";
+    const char* listen_srv = "53";
+    const char* host_name = "nihao.example2.com.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250,
+                           ns_rcode::ns_r_servfail, 1.0);
+    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.3");
+    ASSERT_TRUE(dns.startServer());
+    std::vector<std::string> servers = { listen_addr };
+    ASSERT_TRUE(SetResolversForNetwork(searchDomains, servers, mDefaultParams));
+
+    dns.clearQueries();
+    const hostent* result = gethostbyname("nihao");
+    EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name));
+    ASSERT_FALSE(result == nullptr);
+    ASSERT_EQ(4, result->h_length);
+    ASSERT_FALSE(result->h_addr_list[0] == nullptr);
+    EXPECT_EQ("1.2.3.3", ToString(result));
+    EXPECT_TRUE(result->h_addr_list[1] == nullptr);
+    dns.stopServer();
+}
+
+TEST_F(ResolverTest, GetAddrInfoV6_failing) {
+    addrinfo* result = nullptr;
+
+    const char* listen_addr0 = "127.0.0.7";
+    const char* listen_addr1 = "127.0.0.8";
+    const char* listen_srv = "53";
+    const char* host_name = "ohayou.example.com.";
+    test::DNSResponder dns0(listen_addr0, listen_srv, 250,
+                            ns_rcode::ns_r_servfail, 0.0);
+    test::DNSResponder dns1(listen_addr1, listen_srv, 250,
+                            ns_rcode::ns_r_servfail, 1.0);
+    dns0.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::5");
+    dns1.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::6");
+    ASSERT_TRUE(dns0.startServer());
+    ASSERT_TRUE(dns1.startServer());
+    std::vector<std::string> servers = { listen_addr0, listen_addr1 };
+    // <sample validity in s> <success threshold in percent> <min samples> <max samples>
+    unsigned sample_validity = 300;
+    int success_threshold = 25;
+    int sample_count = 8;
+    std::string params = StringPrintf("%u %d %d %d", sample_validity, success_threshold,
+            sample_count, sample_count);
+    ASSERT_TRUE(SetResolversForNetwork(mDefaultSearchDomains, servers, params));
+
+    // Repeatedly perform resolutions for non-existing domains until MAXNSSAMPLES resolutions have
+    // reached the dns0, which is set to fail. No more requests should then arrive at that server
+    // for the next sample_lifetime seconds.
+    // TODO: This approach is implementation-dependent, change once metrics reporting is available.
+    addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_INET6;
+    for (int i = 0 ; i < sample_count ; ++i) {
+        std::string domain = StringPrintf("nonexistent%d", i);
+        getaddrinfo(domain.c_str(), nullptr, &hints, &result);
+    }
+    // Due to 100% errors for all possible samples, the server should be ignored from now on and
+    // only the second one used for all following queries, until NSSAMPLE_VALIDITY is reached.
+    dns0.clearQueries();
+    dns1.clearQueries();
+    EXPECT_EQ(0, getaddrinfo("ohayou", nullptr, &hints, &result));
+    EXPECT_EQ(0U, GetNumQueries(dns0, host_name));
+    EXPECT_EQ(1U, GetNumQueries(dns1, host_name));
+    if (result) freeaddrinfo(result);
+}
+
+TEST_F(ResolverTest, GetAddrInfoV6_concurrent) {
+    const char* listen_addr0 = "127.0.0.9";
+    const char* listen_addr1 = "127.0.0.10";
+    const char* listen_addr2 = "127.0.0.11";
+    const char* listen_srv = "53";
+    const char* host_name = "konbanha.example.com.";
+    test::DNSResponder dns0(listen_addr0, listen_srv, 250,
+                            ns_rcode::ns_r_servfail, 1.0);
+    test::DNSResponder dns1(listen_addr1, listen_srv, 250,
+                            ns_rcode::ns_r_servfail, 1.0);
+    test::DNSResponder dns2(listen_addr2, listen_srv, 250,
+                            ns_rcode::ns_r_servfail, 1.0);
+    dns0.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::5");
+    dns1.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::6");
+    dns2.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::7");
+    ASSERT_TRUE(dns0.startServer());
+    ASSERT_TRUE(dns1.startServer());
+    ASSERT_TRUE(dns2.startServer());
+    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]() {
+            unsigned delay = arc4random_uniform(1*1000*1000); // <= 1s
+            usleep(delay);
+            std::vector<std::string> serverSubset;
+            for (const auto& server : servers) {
+                if (arc4random_uniform(2)) {
+                    serverSubset.push_back(server);
+                }
+            }
+            if (serverSubset.empty()) serverSubset = servers;
+            ASSERT_TRUE(SetResolversForNetwork(mDefaultSearchDomains, serverSubset,
+                    mDefaultParams));
+            addrinfo hints;
+            memset(&hints, 0, sizeof(hints));
+            hints.ai_family = AF_INET6;
+            addrinfo* result = nullptr;
+            int rv = getaddrinfo("konbanha", nullptr, &hints, &result);
+            EXPECT_EQ(0, rv) << "error [" << rv << "] " << gai_strerror(rv);
+        });
+    }
+    for (std::thread& thread : threads) {
+        thread.join();
+    }
+}
+
+TEST_F(ResolverTest, SearchPathChange) {
+    addrinfo* result = nullptr;
+
+    const char* listen_addr = "127.0.0.13";
+    const char* listen_srv = "53";
+    const char* host_name1 = "test13.domain1.org.";
+    const char* host_name2 = "test13.domain2.org.";
+    test::DNSResponder dns(listen_addr, listen_srv, 250,
+                           ns_rcode::ns_r_servfail, 1.0);
+    dns.addMapping(host_name1, ns_type::ns_t_aaaa, "2001:db8::13");
+    dns.addMapping(host_name2, ns_type::ns_t_aaaa, "2001:db8::1:13");
+    ASSERT_TRUE(dns.startServer());
+    std::vector<std::string> servers = { listen_addr };
+    std::vector<std::string> domains = { "domain1.org" };
+    ASSERT_TRUE(SetResolversForNetwork(domains, servers, mDefaultParams));
+
+    addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_INET6;
+    EXPECT_EQ(0, getaddrinfo("test13", nullptr, &hints, &result));
+    EXPECT_EQ(1U, dns.queries().size());
+    EXPECT_EQ(1U, GetNumQueries(dns, host_name1));
+    EXPECT_EQ("2001:db8::13", ToString(result));
+    if (result) freeaddrinfo(result);
+
+    // Test that changing the domain search path on its own works.
+    domains = { "domain2.org" };
+    ASSERT_TRUE(SetResolversForNetwork(domains, servers, mDefaultParams));
+    dns.clearQueries();
+
+    EXPECT_EQ(0, getaddrinfo("test13", nullptr, &hints, &result));
+    EXPECT_EQ(1U, dns.queries().size());
+    EXPECT_EQ(1U, GetNumQueries(dns, host_name2));
+    EXPECT_EQ("2001:db8::1:13", ToString(result));
+    if (result) freeaddrinfo(result);
+}
diff --git a/netd/tests/sock_diag_test.cpp b/netd/tests/sock_diag_test.cpp
new file mode 100644
index 0000000..8ee9908
--- /dev/null
+++ b/netd/tests/sock_diag_test.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2016 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.
+ *
+ * sock_diag_test.cpp - unit tests for SockDiag.cpp
+ */
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <linux/inet_diag.h>
+
+#include <gtest/gtest.h>
+
+#include "NetdConstants.h"
+#include "SockDiag.h"
+
+
+#define NUM_SOCKETS 500
+
+
+class SockDiagTest : public ::testing::Test {
+};
+
+uint16_t bindAndListen(int s) {
+    for (int i = 0; i < 10; i++) {
+        uint16_t port = 1024 + arc4random_uniform(0xffff - 1024);
+        sockaddr_in6 sin6 = { .sin6_family = AF_INET6, .sin6_port = htons(port) };
+        if (bind(s, (sockaddr *) &sin6, sizeof(sin6)) == 0) {
+            listen(s, 1);
+            return port;
+        }
+    }
+    close(s);
+    return 0;
+}
+
+const char *tcpStateName(uint8_t state) {
+    static const char *states[] = {
+        "???",
+        "TCP_ESTABLISHED",
+        "TCP_SYN_SENT",
+        "TCP_SYN_RECV",
+        "TCP_FIN_WAIT1",
+        "TCP_FIN_WAIT2",
+        "TCP_TIME_WAIT",
+        "TCP_CLOSE",
+        "TCP_CLOSE_WAIT",
+        "TCP_LAST_ACK",
+        "TCP_LISTEN",
+        "TCP_CLOSING",
+        "TCP_NEW_SYN_RECV",
+    };
+    return states[(state < ARRAY_SIZE(states)) ? state : 0];
+}
+
+TEST_F(SockDiagTest, TestDump) {
+    int v4socket = socket(AF_INET, SOCK_STREAM, 0);
+    int v6socket = socket(AF_INET6, SOCK_STREAM, 0);
+    int listensocket = socket(AF_INET6, SOCK_STREAM, 0);
+    ASSERT_NE(-1, v4socket) << "Failed to open IPv4 socket";
+    ASSERT_NE(-1, v6socket) << "Failed to open IPv6 socket";
+    ASSERT_NE(-1, listensocket) << "Failed to open listen socket";
+
+    uint16_t port = bindAndListen(listensocket);
+    ASSERT_NE(0, port) << "Can't bind to server port";
+
+    // Connect to loopback.
+    sockaddr_in server4 = { .sin_family = AF_INET, .sin_port = htons(port) };
+    sockaddr_in6 server6 = { .sin6_family = AF_INET6, .sin6_port = htons(port) };
+    ASSERT_EQ(0, connect(v4socket, (sockaddr *) &server4, sizeof(server4)))
+        << "IPv4 connect failed: " << strerror(errno);
+    ASSERT_EQ(0, connect(v6socket, (sockaddr *) &server6, sizeof(server6)))
+        << "IPv6 connect failed: " << strerror(errno);
+
+    sockaddr_in6 client46, client6;
+    socklen_t clientlen = std::max(sizeof(client46), sizeof(client6));
+    int accepted4 = accept(listensocket, (sockaddr *) &client46, &clientlen);
+    int accepted6 = accept(listensocket, (sockaddr *) &client6, &clientlen);
+    ASSERT_NE(-1, accepted4);
+    ASSERT_NE(-1, accepted6);
+
+    int v4SocketsSeen = 0;
+    bool seenclient46 = false;
+    bool seenNull = false;
+    char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN];
+
+    fprintf(stderr, "Ports:\n  server=%d. client46=%d, client6=%d\n",
+            port, ntohs(client46.sin6_port), ntohs(client6.sin6_port));
+
+    auto checkIPv4Dump = [&] (uint8_t /* proto */, const inet_diag_msg *msg) {
+        if (msg == nullptr) {
+            EXPECT_FALSE(seenNull);
+            seenNull = true;
+            return 0;
+        }
+        EXPECT_EQ(htonl(INADDR_LOOPBACK), msg->id.idiag_src[0]);
+        v4SocketsSeen++;
+        seenclient46 |= (msg->id.idiag_sport == client46.sin6_port);
+        inet_ntop(AF_INET, msg->id.idiag_src, src, sizeof(src));
+        inet_ntop(AF_INET, msg->id.idiag_src, dst, sizeof(dst));
+        fprintf(stderr, "  v4 %s:%d -> %s:%d %s\n",
+                src, htons(msg->id.idiag_sport),
+                dst, htons(msg->id.idiag_dport),
+                tcpStateName(msg->idiag_state));
+        return 0;
+    };
+
+    int v6SocketsSeen = 0;
+    bool seenClient6 = false, seenServer46 = false, seenServer6 = false;
+
+    auto checkIPv6Dump = [&] (uint8_t /* proto */, const inet_diag_msg *msg) {
+        if (msg == nullptr) {
+            EXPECT_FALSE(seenNull);
+            seenNull = true;
+            return 0;
+        }
+        struct in6_addr *saddr = (struct in6_addr *) msg->id.idiag_src;
+        EXPECT_TRUE(
+            IN6_IS_ADDR_LOOPBACK(saddr) ||
+            (IN6_IS_ADDR_V4MAPPED(saddr) && saddr->s6_addr32[3] == htonl(INADDR_LOOPBACK)));
+        v6SocketsSeen++;
+        seenClient6 |= (msg->id.idiag_sport == client6.sin6_port);
+        seenServer46 |= (msg->id.idiag_sport == htons(port));
+        seenServer6 |= (msg->id.idiag_sport == htons(port));
+        inet_ntop(AF_INET6, msg->id.idiag_src, src, sizeof(src));
+        inet_ntop(AF_INET6, msg->id.idiag_src, dst, sizeof(dst));
+        fprintf(stderr, "  v6 [%s]:%d -> [%s]:%d %s\n",
+                src, htons(msg->id.idiag_sport),
+                dst, htons(msg->id.idiag_dport),
+                tcpStateName(msg->idiag_state));
+        return 0;
+    };
+
+    SockDiag sd;
+    ASSERT_TRUE(sd.open()) << "Failed to open SOCK_DIAG socket";
+
+    seenNull = false;
+    int ret = sd.sendDumpRequest(IPPROTO_TCP, AF_INET, "127.0.0.1");
+    ASSERT_EQ(0, ret) << "Failed to send IPv4 dump request: " << strerror(-ret);
+    fprintf(stderr, "Sent IPv4 dump\n");
+    sd.readDiagMsg(IPPROTO_TCP, checkIPv4Dump);
+    EXPECT_GE(v4SocketsSeen, 1);
+    EXPECT_TRUE(seenclient46);
+    EXPECT_FALSE(seenServer46);
+
+    seenNull = false;
+    ret = sd.sendDumpRequest(IPPROTO_TCP, AF_INET6, "127.0.0.1");
+    ASSERT_EQ(0, ret) << "Failed to send mapped dump request: " << strerror(-ret);
+    fprintf(stderr, "Sent mapped dump\n");
+    sd.readDiagMsg(IPPROTO_TCP, checkIPv6Dump);
+    EXPECT_TRUE(seenServer46);
+
+    seenNull = false;
+    ret = sd.sendDumpRequest(IPPROTO_TCP, AF_INET6, "::1");
+    ASSERT_EQ(0, ret) << "Failed to send IPv6 dump request: " << strerror(-ret);
+    fprintf(stderr, "Sent IPv6 dump\n");
+
+    sd.readDiagMsg(IPPROTO_TCP, checkIPv6Dump);
+    EXPECT_GE(v6SocketsSeen, 1);
+    EXPECT_TRUE(seenClient6);
+    EXPECT_TRUE(seenServer6);
+
+    close(v4socket);
+    close(v6socket);
+    close(listensocket);
+    close(accepted4);
+    close(accepted6);
+}
+
+TEST_F(SockDiagTest, TestMicroBenchmark) {
+    fprintf(stderr, "Benchmarking closing %d sockets\n", NUM_SOCKETS);
+
+    int listensocket = socket(AF_INET6, SOCK_STREAM, 0);
+    ASSERT_NE(-1, listensocket) << "Failed to open listen socket";
+
+    uint16_t port = bindAndListen(listensocket);
+    ASSERT_NE(0, port) << "Can't bind to server port";
+    sockaddr_in6 server = { .sin6_family = AF_INET6, .sin6_port = htons(port) };
+
+    using ms = std::chrono::duration<float, std::ratio<1, 1000>>;
+
+    int clientsockets[NUM_SOCKETS], serversockets[NUM_SOCKETS];
+    uint16_t clientports[NUM_SOCKETS];
+    sockaddr_in6 client;
+    socklen_t clientlen;
+
+    auto start = std::chrono::steady_clock::now();
+    for (int i = 0; i < NUM_SOCKETS; i++) {
+        int s = socket(AF_INET6, SOCK_STREAM, 0);
+        clientlen = sizeof(client);
+        ASSERT_EQ(0, connect(s, (sockaddr *) &server, sizeof(server)))
+            << "Connecting socket " << i << " failed " << strerror(errno);
+        serversockets[i] = accept(listensocket, (sockaddr *) &client, &clientlen);
+        ASSERT_NE(-1, serversockets[i])
+            << "Accepting socket " << i << " failed " << strerror(errno);
+        clientports[i] = client.sin6_port;
+        clientsockets[i] = s;
+    }
+    fprintf(stderr, "  Connecting: %6.1f ms\n",
+            std::chrono::duration_cast<ms>(std::chrono::steady_clock::now() - start).count());
+
+    SockDiag sd;
+    ASSERT_TRUE(sd.open()) << "Failed to open SOCK_DIAG socket";
+
+    start = std::chrono::steady_clock::now();
+    int ret = sd.destroySockets("::1");
+    EXPECT_LE(0, ret) << ": Failed to destroy sockets on ::1: " << strerror(-ret);
+    fprintf(stderr, "  Destroying: %6.1f ms\n",
+            std::chrono::duration_cast<ms>(std::chrono::steady_clock::now() - start).count());
+
+    int err;
+    start = std::chrono::steady_clock::now();
+    for (int i = 0; i < NUM_SOCKETS; i++) {
+        ret = send(clientsockets[i], "foo", sizeof("foo"), 0);
+        err = errno;
+        EXPECT_EQ(-1, ret) << "Client socket " << i << " not closed";
+        if (ret == -1) {
+            // Since we're connected to ourselves, the error might be ECONNABORTED (if we destroyed
+            // the socket) or ECONNRESET (if the other end was destroyed and sent a RST).
+            EXPECT_TRUE(errno == ECONNABORTED || errno == ECONNRESET)
+                << "Client socket: unexpected error: " << strerror(errno);
+        }
+
+        ret = send(serversockets[i], "foo", sizeof("foo"), 0);
+        err = errno;
+        EXPECT_EQ(-1, ret) << "Server socket " << i << " not closed";
+        if (ret == -1) {
+            EXPECT_TRUE(errno == ECONNABORTED || errno == ECONNRESET)
+                << "Server socket: unexpected error: " << strerror(errno);
+        }
+    }
+    fprintf(stderr, "   Verifying: %6.1f ms\n",
+            std::chrono::duration_cast<ms>(std::chrono::steady_clock::now() - start).count());
+
+
+
+    start = std::chrono::steady_clock::now();
+    for (int i = 0; i < NUM_SOCKETS; i++) {
+        close(clientsockets[i]);
+        close(serversockets[i]);
+    }
+    fprintf(stderr, "     Closing: %6.1f ms\n",
+            std::chrono::duration_cast<ms>(std::chrono::steady_clock::now() - start).count());
+
+    close(listensocket);
+}