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", ¶ms.sample_validity,
+ ¶ms.success_threshold, ¶ms.min_samples, ¶ms.max_samples) != 4) {
+ return false;
+ }
+ paramsPtr = ¶ms;
+ }
+ 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");
+