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"); + // TODO(imaipi): terminate on error. + return; + } + char buffer[4096]; + sockaddr_storage sa; + socklen_t sa_len = sizeof(sa); + ssize_t len; + do { + len = recvfrom(socket_, buffer, sizeof(buffer), 0, + (sockaddr*) &sa, &sa_len); + } while (len < 0 && (errno == EAGAIN || errno == EINTR)); + if (len <= 0) { + ALOGI("recvfrom() failed"); + continue; + } + ALOGI("read %zd bytes", len); + char response[4096]; + size_t response_len = sizeof(response); + if (handleDNSRequest(buffer, len, response, &response_len) && + response_len > 0) { + len = sendto(socket_, response, response_len, 0, + reinterpret_cast<const sockaddr*>(&sa), sa_len); + std::string host_str = + addr2str(reinterpret_cast<const sockaddr*>(&sa), sa_len); + if (len > 0) { + ALOGI("sent %zu bytes to %s", len, host_str.c_str()); + } else { + APLOGI("sendto() failed for %s", host_str.c_str()); + } + // Test that the response is actually a correct DNS message. + const char* response_end = response + len; + DNSHeader header; + const char* cur = header.read(response, response_end); + if (cur == nullptr) ALOGI("response is flawed"); + + } else { + ALOGI("not responding"); + } + } +} + +bool DNSResponder::handleDNSRequest(const char* buffer, ssize_t len, + char* response, size_t* response_len) + const { + ALOGI("request: '%s'", str2hex(buffer, len).c_str()); + const char* buffer_end = buffer + len; + DNSHeader header; + const char* cur = header.read(buffer, buffer_end); + // TODO(imaipi): for now, unparsable messages are silently dropped, fix. + if (cur == nullptr) { + ALOGI("failed to parse query"); + return false; + } + if (header.qr) { + ALOGI("response received instead of a query"); + return false; + } + if (header.opcode != ns_opcode::ns_o_query) { + ALOGI("unsupported request opcode received"); + return makeErrorResponse(&header, ns_rcode::ns_r_notimpl, response, + response_len); + } + if (header.questions.empty()) { + ALOGI("no questions present"); + return makeErrorResponse(&header, ns_rcode::ns_r_formerr, response, + response_len); + } + if (!header.answers.empty()) { + ALOGI("already %zu answers present in query", header.answers.size()); + return makeErrorResponse(&header, ns_rcode::ns_r_formerr, response, + response_len); + } + { + std::lock_guard<std::mutex> lock(queries_mutex_); + for (const DNSQuestion& question : header.questions) { + queries_.push_back(make_pair(question.qname.name, + ns_type(question.qtype))); + } + } + + // Ignore requests with the preset probability. + auto constexpr bound = std::numeric_limits<unsigned>::max(); + if (arc4random_uniform(bound) > bound*response_probability_) { + ALOGI("returning SRVFAIL in accordance with probability distribution"); + return makeErrorResponse(&header, ns_rcode::ns_r_servfail, response, + response_len); + } + + for (const DNSQuestion& question : header.questions) { + if (question.qclass != ns_class::ns_c_in && + question.qclass != ns_class::ns_c_any) { + ALOGI("unsupported question class %u", question.qclass); + return makeErrorResponse(&header, ns_rcode::ns_r_notimpl, response, + response_len); + } + if (!addAnswerRecords(question, &header.answers)) { + return makeErrorResponse(&header, ns_rcode::ns_r_servfail, response, + response_len); + } + } + header.qr = true; + char* response_cur = header.write(response, response + *response_len); + if (response_cur == nullptr) { + return false; + } + *response_len = response_cur - response; + return true; +} + +bool DNSResponder::addAnswerRecords(const DNSQuestion& question, + std::vector<DNSRecord>* answers) const { + auto it = mappings_.find(QueryKey(question.qname.name, question.qtype)); + if (it == mappings_.end()) { + // TODO(imaipi): handle correctly + ALOGI("no mapping found for %s %s, lazily refusing to add an answer", + question.qname.name.c_str(), dnstype2str(question.qtype)); + return true; + } + ALOGI("mapping found for %s %s: %s", question.qname.name.c_str(), + dnstype2str(question.qtype), it->second.c_str()); + DNSRecord record; + record.name = question.qname; + record.rtype = question.qtype; + record.rclass = ns_class::ns_c_in; + record.ttl = 1; + if (question.qtype == ns_type::ns_t_a) { + record.rdata.resize(4); + if (inet_pton(AF_INET, it->second.c_str(), record.rdata.data()) != 1) { + ALOGI("inet_pton(AF_INET, %s) failed", it->second.c_str()); + return false; + } + } else if (question.qtype == ns_type::ns_t_aaaa) { + record.rdata.resize(16); + if (inet_pton(AF_INET6, it->second.c_str(), record.rdata.data()) != 1) { + ALOGI("inet_pton(AF_INET6, %s) failed", it->second.c_str()); + return false; + } + } else { + ALOGI("unhandled qtype %s", dnstype2str(question.qtype)); + return false; + } + answers->push_back(std::move(record)); + return true; +} + +bool DNSResponder::makeErrorResponse(DNSHeader* header, ns_rcode rcode, + char* response, size_t* response_len) + const { + header->answers.clear(); + header->authorities.clear(); + header->additionals.clear(); + header->rcode = rcode; + header->qr = true; + char* response_cur = header->write(response, response + *response_len); + if (response_cur == nullptr) return false; + *response_len = response_cur - response; + return true; +} + +} // namespace test +
diff --git a/netd/tests/dns_responder.h b/netd/tests/dns_responder.h new file mode 100644 index 0000000..4ed4bb2 --- /dev/null +++ b/netd/tests/dns_responder.h
@@ -0,0 +1,132 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless requied by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef DNS_RESPONDER_H +#define DNS_RESPONDER_H + +#include <arpa/nameser.h> + +#include <atomic> +#include <mutex> +#include <string> +#include <thread> +#include <unordered_map> +#include <vector> + +#include <android-base/thread_annotations.h> + +namespace test { + +struct DNSHeader; +struct DNSQuestion; +struct DNSRecord; + +/* + * Simple DNS responder, which replies to queries with the registered response + * for that type. Class is assumed to be IN. If no response is registered, the + * default error response code is returned. + */ +class DNSResponder { +public: + DNSResponder(const char* listen_address, const char* listen_service, + int poll_timeout_ms, uint16_t error_rcode, + double response_probability); + ~DNSResponder(); + void addMapping(const char* name, ns_type type, const char* addr); + void removeMapping(const char* name, ns_type type); + void setResponseProbability(double response_probability); + bool running() const; + bool startServer(); + bool stopServer(); + std::vector<std::pair<std::string, ns_type>> queries() const; + void clearQueries(); + +private: + // Key used for accessing mappings. + struct QueryKey { + std::string name; + unsigned type; + QueryKey(std::string n, unsigned t) : name(n), type(t) {} + bool operator == (const QueryKey& o) const { + return name == o.name && type == o.type; + } + bool operator < (const QueryKey& o) const { + if (name < o.name) return true; + if (name > o.name) return false; + return type < o.type; + } + }; + + struct QueryKeyHash { + size_t operator() (const QueryKey& key) const { + return std::hash<std::string>()(key.name) + + static_cast<size_t>(key.type); + } + }; + + // DNS request handler. + void requestHandler(); + + // Parses and generates a response message for incoming DNS requests. + // Returns false on parsing errors. + bool handleDNSRequest(const char* buffer, ssize_t buffer_len, + char* response, size_t* response_len) const; + + bool addAnswerRecords(const DNSQuestion& question, + std::vector<DNSRecord>* answers) const; + + bool generateErrorResponse(DNSHeader* header, ns_rcode rcode, + char* response, size_t* response_len) const; + bool makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* response, + size_t* response_len) const; + + + // Address and service to listen on, currently limited to UDP. + const std::string listen_address_; + const std::string listen_service_; + // epoll_wait() timeout in ms. + const int poll_timeout_ms_; + // Error code to return for requests for an unknown name. + const uint16_t error_rcode_; + // Probability that a valid response is being sent instead of being sent + // instead of returning error_rcode_. + std::atomic<double> response_probability_; + + // Mappings from (name, type) to registered response and the + // mutex protecting them. + std::unordered_map<QueryKey, std::string, QueryKeyHash> mappings_ + GUARDED_BY(mappings_mutex_); + // TODO(imaipi): enable GUARDED_BY(mappings_mutex_); + std::mutex mappings_mutex_; + // Query names received so far and the corresponding mutex. + mutable std::vector<std::pair<std::string, ns_type>> queries_ + GUARDED_BY(queries_mutex_); + mutable std::mutex queries_mutex_; + // Socket on which the server is listening. + int socket_; + // File descriptor for epoll. + int epoll_fd_; + // Signal for request handler termination. + std::atomic<bool> terminate_ GUARDED_BY(update_mutex_); + // Thread for handling incoming threads. + std::thread handler_thread_ GUARDED_BY(update_mutex_); + std::mutex update_mutex_; +}; + +} // namespace test + +#endif // DNS_RESPONDER_H
diff --git a/netd/tests/netd_test.cpp b/netd/tests/netd_test.cpp new file mode 100644 index 0000000..97b91bd --- /dev/null +++ b/netd/tests/netd_test.cpp
@@ -0,0 +1,465 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless requied by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include <arpa/inet.h> +#include <errno.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <cutils/sockets.h> +#include <android-base/stringprintf.h> +#include <private/android_filesystem_config.h> + +#include <thread> + +#include "NetdClient.h" + +#include <gtest/gtest.h> +#define LOG_TAG "resolverTest" +#include <utils/Log.h> +#include <testUtil.h> + +#include "dns_responder.h" +#include "resolv_params.h" + +using android::base::StringPrintf; +using android::base::StringAppendF; + +// TODO: make this dynamic and stop depending on implementation details. +#define TEST_OEM_NETWORK "oem29" +#define TEST_NETID 30 + +// The only response code used in this test, see +// frameworks/base/services/java/com/android/server/NetworkManagementService.java +// for others. +static constexpr int ResponseCodeOK = 200; + +// Returns ResponseCode. +int netdCommand(const char* sockname, const char* command) { + int sock = socket_local_client(sockname, + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM); + if (sock < 0) { + perror("Error connecting"); + return -1; + } + + // FrameworkListener expects the whole command in one read. + char buffer[256]; + int nwritten = snprintf(buffer, sizeof(buffer), "0 %s", command); + if (write(sock, buffer, nwritten + 1) < 0) { + perror("Error sending netd command"); + close(sock); + return -1; + } + + int nread = read(sock, buffer, sizeof(buffer)); + if (nread < 0) { + perror("Error reading response"); + close(sock); + return -1; + } + close(sock); + return atoi(buffer); +} + + +bool expectNetdResult(int expected, const char* sockname, const char* format, ...) { + char command[256]; + va_list args; + va_start(args, format); + vsnprintf(command, sizeof(command), format, args); + va_end(args); + int result = netdCommand(sockname, command); + EXPECT_EQ(expected, result) << command; + return (200 <= expected && expected < 300); +} + + +class ResolverTest : public ::testing::Test { +protected: + virtual void SetUp() { + // Ensure resolutions go via proxy. + setenv("ANDROID_DNS_MODE", "", 1); + uid = getuid(); + pid = getpid(); + SetupOemNetwork(); + } + + virtual void TearDown() { + TearDownOemNetwork(); + netdCommand("netd", "network destroy " TEST_OEM_NETWORK); + } + + void SetupOemNetwork() { + netdCommand("netd", "network destroy " TEST_OEM_NETWORK); + if (expectNetdResult(ResponseCodeOK, "netd", + "network create %s", TEST_OEM_NETWORK)) { + oemNetId = TEST_NETID; + } + setNetworkForProcess(oemNetId); + ASSERT_EQ((unsigned) oemNetId, getNetworkForProcess()); + } + + void TearDownOemNetwork() { + if (oemNetId != -1) { + expectNetdResult(ResponseCodeOK, "netd", + "network destroy %s", TEST_OEM_NETWORK); + } + } + + bool SetResolversForNetwork(const std::vector<std::string>& searchDomains, + const std::vector<std::string>& servers, const std::string& params) { + // No use case for empty domains / servers (yet). + if (searchDomains.empty() || servers.empty()) return false; + + std::string cmd = StringPrintf("resolver setnetdns %d \"%s", oemNetId, + searchDomains[0].c_str()); + for (size_t i = 1 ; i < searchDomains.size() ; ++i) { + cmd += " "; + cmd += searchDomains[i]; + } + cmd += "\" "; + + cmd += servers[0]; + for (size_t i = 1 ; i < servers.size() ; ++i) { + cmd += " "; + cmd += servers[i]; + } + + if (!params.empty()) { + cmd += " --params \""; + cmd += params; + cmd += "\""; + } + + int rv = netdCommand("netd", cmd.c_str()); + std::cout << "command: '" << cmd << "', rv = " << rv << "\n"; + if (rv != ResponseCodeOK) { + return false; + } + return true; + } + + bool FlushCache() const { + return expectNetdResult(ResponseCodeOK, "netd", "resolver flushnet %d", oemNetId); + } + + std::string ToString(const hostent* he) const { + if (he == nullptr) return "<null>"; + char buffer[INET6_ADDRSTRLEN]; + if (!inet_ntop(he->h_addrtype, he->h_addr_list[0], buffer, sizeof(buffer))) { + return "<invalid>"; + } + return buffer; + } + + std::string ToString(const addrinfo* ai) const { + if (!ai) + return "<null>"; + for (const auto* aip = ai ; aip != nullptr ; aip = aip->ai_next) { + char host[NI_MAXHOST]; + int rv = getnameinfo(aip->ai_addr, aip->ai_addrlen, host, sizeof(host), nullptr, 0, + NI_NUMERICHOST); + if (rv != 0) + return gai_strerror(rv); + return host; + } + return "<invalid>"; + } + + size_t GetNumQueries(const test::DNSResponder& dns, const char* name) const { + auto queries = dns.queries(); + size_t found = 0; + for (const auto& p : queries) { + std::cout << "query " << p.first << "\n"; + if (p.first == name) { + ++found; + } + } + return found; + } + + size_t GetNumQueriesForType(const test::DNSResponder& dns, ns_type type, + const char* name) const { + auto queries = dns.queries(); + size_t found = 0; + for (const auto& p : queries) { + std::cout << "query " << p.first << "\n"; + if (p.second == type && p.first == name) { + ++found; + } + } + return found; + } + + int pid; + int uid; + int oemNetId = -1; + const std::vector<std::string> mDefaultSearchDomains = { "example.com" }; + // <sample validity in s> <success threshold in percent> <min samples> <max samples> + const std::string mDefaultParams = "300 25 8 8"; +}; + +TEST_F(ResolverTest, GetHostByName) { + const char* listen_addr = "127.0.0.3"; + const char* listen_srv = "53"; + const char* host_name = "hello.example.com."; + test::DNSResponder dns(listen_addr, listen_srv, 250, ns_rcode::ns_r_servfail, 1.0); + dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.3"); + ASSERT_TRUE(dns.startServer()); + std::vector<std::string> servers = { listen_addr }; + ASSERT_TRUE(SetResolversForNetwork(mDefaultSearchDomains, servers, mDefaultParams)); + + dns.clearQueries(); + const hostent* result = gethostbyname("hello"); + EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name)); + ASSERT_FALSE(result == nullptr); + ASSERT_EQ(4, result->h_length); + ASSERT_FALSE(result->h_addr_list[0] == nullptr); + EXPECT_EQ("1.2.3.3", ToString(result)); + EXPECT_TRUE(result->h_addr_list[1] == nullptr); + dns.stopServer(); +} + +TEST_F(ResolverTest, GetAddrInfo) { + addrinfo* result = nullptr; + + const char* listen_addr = "127.0.0.4"; + const char* listen_srv = "53"; + const char* host_name = "howdie.example.com."; + test::DNSResponder dns(listen_addr, listen_srv, 250, + ns_rcode::ns_r_servfail, 1.0); + dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4"); + dns.addMapping(host_name, ns_type::ns_t_aaaa, "::1.2.3.4"); + ASSERT_TRUE(dns.startServer()); + std::vector<std::string> servers = { listen_addr }; + ASSERT_TRUE(SetResolversForNetwork(mDefaultSearchDomains, servers, mDefaultParams)); + + dns.clearQueries(); + EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result)); + size_t found = GetNumQueries(dns, host_name); + EXPECT_LE(1U, found); + // Could be A or AAAA + std::string result_str = ToString(result); + EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4") + << ", result_str='" << result_str << "'"; + if (result) freeaddrinfo(result); + result = nullptr; + + // Verify that it's cached. + size_t old_found = found; + EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result)); + found = GetNumQueries(dns, host_name); + EXPECT_LE(1U, found); + EXPECT_EQ(old_found, found); + result_str = ToString(result); + EXPECT_TRUE(result_str == "1.2.3.4" || result_str == "::1.2.3.4") + << result_str; + if (result) freeaddrinfo(result); + result = nullptr; + + // Verify that cache can be flushed. + dns.clearQueries(); + ASSERT_TRUE(FlushCache()); + dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.44"); + dns.addMapping(host_name, ns_type::ns_t_aaaa, "::1.2.3.44"); + + EXPECT_EQ(0, getaddrinfo("howdie", nullptr, nullptr, &result)); + EXPECT_LE(1U, GetNumQueries(dns, host_name)); + // Could be A or AAAA + result_str = ToString(result); + EXPECT_TRUE(result_str == "1.2.3.44" || result_str == "::1.2.3.44") + << ", result_str='" << result_str << "'"; + if (result) freeaddrinfo(result); +} + +TEST_F(ResolverTest, GetAddrInfoV4) { + addrinfo* result = nullptr; + + const char* listen_addr = "127.0.0.5"; + const char* listen_srv = "53"; + const char* host_name = "hola.example.com."; + test::DNSResponder dns(listen_addr, listen_srv, 250, + ns_rcode::ns_r_servfail, 1.0); + dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.5"); + ASSERT_TRUE(dns.startServer()); + std::vector<std::string> servers = { listen_addr }; + ASSERT_TRUE(SetResolversForNetwork(mDefaultSearchDomains, servers, mDefaultParams)); + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + EXPECT_EQ(0, getaddrinfo("hola", nullptr, &hints, &result)); + EXPECT_EQ(1U, GetNumQueries(dns, host_name)); + EXPECT_EQ("1.2.3.5", ToString(result)); + if (result) freeaddrinfo(result); +} + +TEST_F(ResolverTest, MultidomainResolution) { + std::vector<std::string> searchDomains = { "example1.com", "example2.com", "example3.com" }; + const char* listen_addr = "127.0.0.6"; + const char* listen_srv = "53"; + const char* host_name = "nihao.example2.com."; + test::DNSResponder dns(listen_addr, listen_srv, 250, + ns_rcode::ns_r_servfail, 1.0); + dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.3"); + ASSERT_TRUE(dns.startServer()); + std::vector<std::string> servers = { listen_addr }; + ASSERT_TRUE(SetResolversForNetwork(searchDomains, servers, mDefaultParams)); + + dns.clearQueries(); + const hostent* result = gethostbyname("nihao"); + EXPECT_EQ(1U, GetNumQueriesForType(dns, ns_type::ns_t_a, host_name)); + ASSERT_FALSE(result == nullptr); + ASSERT_EQ(4, result->h_length); + ASSERT_FALSE(result->h_addr_list[0] == nullptr); + EXPECT_EQ("1.2.3.3", ToString(result)); + EXPECT_TRUE(result->h_addr_list[1] == nullptr); + dns.stopServer(); +} + +TEST_F(ResolverTest, GetAddrInfoV6_failing) { + addrinfo* result = nullptr; + + const char* listen_addr0 = "127.0.0.7"; + const char* listen_addr1 = "127.0.0.8"; + const char* listen_srv = "53"; + const char* host_name = "ohayou.example.com."; + test::DNSResponder dns0(listen_addr0, listen_srv, 250, + ns_rcode::ns_r_servfail, 0.0); + test::DNSResponder dns1(listen_addr1, listen_srv, 250, + ns_rcode::ns_r_servfail, 1.0); + dns0.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::5"); + dns1.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::6"); + ASSERT_TRUE(dns0.startServer()); + ASSERT_TRUE(dns1.startServer()); + std::vector<std::string> servers = { listen_addr0, listen_addr1 }; + // <sample validity in s> <success threshold in percent> <min samples> <max samples> + unsigned sample_validity = 300; + int success_threshold = 25; + int sample_count = 8; + std::string params = StringPrintf("%u %d %d %d", sample_validity, success_threshold, + sample_count, sample_count); + ASSERT_TRUE(SetResolversForNetwork(mDefaultSearchDomains, servers, params)); + + // Repeatedly perform resolutions for non-existing domains until MAXNSSAMPLES resolutions have + // reached the dns0, which is set to fail. No more requests should then arrive at that server + // for the next sample_lifetime seconds. + // TODO: This approach is implementation-dependent, change once metrics reporting is available. + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + for (int i = 0 ; i < sample_count ; ++i) { + std::string domain = StringPrintf("nonexistent%d", i); + getaddrinfo(domain.c_str(), nullptr, &hints, &result); + } + // Due to 100% errors for all possible samples, the server should be ignored from now on and + // only the second one used for all following queries, until NSSAMPLE_VALIDITY is reached. + dns0.clearQueries(); + dns1.clearQueries(); + EXPECT_EQ(0, getaddrinfo("ohayou", nullptr, &hints, &result)); + EXPECT_EQ(0U, GetNumQueries(dns0, host_name)); + EXPECT_EQ(1U, GetNumQueries(dns1, host_name)); + if (result) freeaddrinfo(result); +} + +TEST_F(ResolverTest, GetAddrInfoV6_concurrent) { + const char* listen_addr0 = "127.0.0.9"; + const char* listen_addr1 = "127.0.0.10"; + const char* listen_addr2 = "127.0.0.11"; + const char* listen_srv = "53"; + const char* host_name = "konbanha.example.com."; + test::DNSResponder dns0(listen_addr0, listen_srv, 250, + ns_rcode::ns_r_servfail, 1.0); + test::DNSResponder dns1(listen_addr1, listen_srv, 250, + ns_rcode::ns_r_servfail, 1.0); + test::DNSResponder dns2(listen_addr2, listen_srv, 250, + ns_rcode::ns_r_servfail, 1.0); + dns0.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::5"); + dns1.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::6"); + dns2.addMapping(host_name, ns_type::ns_t_aaaa, "2001:db8::7"); + ASSERT_TRUE(dns0.startServer()); + ASSERT_TRUE(dns1.startServer()); + ASSERT_TRUE(dns2.startServer()); + const std::vector<std::string> servers = { listen_addr0, listen_addr1, listen_addr2 }; + std::vector<std::thread> threads(10); + for (std::thread& thread : threads) { + thread = std::thread([this, &servers, &dns0, &dns1, &dns2]() { + unsigned delay = arc4random_uniform(1*1000*1000); // <= 1s + usleep(delay); + std::vector<std::string> serverSubset; + for (const auto& server : servers) { + if (arc4random_uniform(2)) { + serverSubset.push_back(server); + } + } + if (serverSubset.empty()) serverSubset = servers; + ASSERT_TRUE(SetResolversForNetwork(mDefaultSearchDomains, serverSubset, + mDefaultParams)); + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + addrinfo* result = nullptr; + int rv = getaddrinfo("konbanha", nullptr, &hints, &result); + EXPECT_EQ(0, rv) << "error [" << rv << "] " << gai_strerror(rv); + }); + } + for (std::thread& thread : threads) { + thread.join(); + } +} + +TEST_F(ResolverTest, SearchPathChange) { + addrinfo* result = nullptr; + + const char* listen_addr = "127.0.0.13"; + const char* listen_srv = "53"; + const char* host_name1 = "test13.domain1.org."; + const char* host_name2 = "test13.domain2.org."; + test::DNSResponder dns(listen_addr, listen_srv, 250, + ns_rcode::ns_r_servfail, 1.0); + dns.addMapping(host_name1, ns_type::ns_t_aaaa, "2001:db8::13"); + dns.addMapping(host_name2, ns_type::ns_t_aaaa, "2001:db8::1:13"); + ASSERT_TRUE(dns.startServer()); + std::vector<std::string> servers = { listen_addr }; + std::vector<std::string> domains = { "domain1.org" }; + ASSERT_TRUE(SetResolversForNetwork(domains, servers, mDefaultParams)); + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + EXPECT_EQ(0, getaddrinfo("test13", nullptr, &hints, &result)); + EXPECT_EQ(1U, dns.queries().size()); + EXPECT_EQ(1U, GetNumQueries(dns, host_name1)); + EXPECT_EQ("2001:db8::13", ToString(result)); + if (result) freeaddrinfo(result); + + // Test that changing the domain search path on its own works. + domains = { "domain2.org" }; + ASSERT_TRUE(SetResolversForNetwork(domains, servers, mDefaultParams)); + dns.clearQueries(); + + EXPECT_EQ(0, getaddrinfo("test13", nullptr, &hints, &result)); + EXPECT_EQ(1U, dns.queries().size()); + EXPECT_EQ(1U, GetNumQueries(dns, host_name2)); + EXPECT_EQ("2001:db8::1:13", ToString(result)); + if (result) freeaddrinfo(result); +}
diff --git a/netd/tests/sock_diag_test.cpp b/netd/tests/sock_diag_test.cpp new file mode 100644 index 0000000..8ee9908 --- /dev/null +++ b/netd/tests/sock_diag_test.cpp
@@ -0,0 +1,257 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * sock_diag_test.cpp - unit tests for SockDiag.cpp + */ + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <linux/inet_diag.h> + +#include <gtest/gtest.h> + +#include "NetdConstants.h" +#include "SockDiag.h" + + +#define NUM_SOCKETS 500 + + +class SockDiagTest : public ::testing::Test { +}; + +uint16_t bindAndListen(int s) { + for (int i = 0; i < 10; i++) { + uint16_t port = 1024 + arc4random_uniform(0xffff - 1024); + sockaddr_in6 sin6 = { .sin6_family = AF_INET6, .sin6_port = htons(port) }; + if (bind(s, (sockaddr *) &sin6, sizeof(sin6)) == 0) { + listen(s, 1); + return port; + } + } + close(s); + return 0; +} + +const char *tcpStateName(uint8_t state) { + static const char *states[] = { + "???", + "TCP_ESTABLISHED", + "TCP_SYN_SENT", + "TCP_SYN_RECV", + "TCP_FIN_WAIT1", + "TCP_FIN_WAIT2", + "TCP_TIME_WAIT", + "TCP_CLOSE", + "TCP_CLOSE_WAIT", + "TCP_LAST_ACK", + "TCP_LISTEN", + "TCP_CLOSING", + "TCP_NEW_SYN_RECV", + }; + return states[(state < ARRAY_SIZE(states)) ? state : 0]; +} + +TEST_F(SockDiagTest, TestDump) { + int v4socket = socket(AF_INET, SOCK_STREAM, 0); + int v6socket = socket(AF_INET6, SOCK_STREAM, 0); + int listensocket = socket(AF_INET6, SOCK_STREAM, 0); + ASSERT_NE(-1, v4socket) << "Failed to open IPv4 socket"; + ASSERT_NE(-1, v6socket) << "Failed to open IPv6 socket"; + ASSERT_NE(-1, listensocket) << "Failed to open listen socket"; + + uint16_t port = bindAndListen(listensocket); + ASSERT_NE(0, port) << "Can't bind to server port"; + + // Connect to loopback. + sockaddr_in server4 = { .sin_family = AF_INET, .sin_port = htons(port) }; + sockaddr_in6 server6 = { .sin6_family = AF_INET6, .sin6_port = htons(port) }; + ASSERT_EQ(0, connect(v4socket, (sockaddr *) &server4, sizeof(server4))) + << "IPv4 connect failed: " << strerror(errno); + ASSERT_EQ(0, connect(v6socket, (sockaddr *) &server6, sizeof(server6))) + << "IPv6 connect failed: " << strerror(errno); + + sockaddr_in6 client46, client6; + socklen_t clientlen = std::max(sizeof(client46), sizeof(client6)); + int accepted4 = accept(listensocket, (sockaddr *) &client46, &clientlen); + int accepted6 = accept(listensocket, (sockaddr *) &client6, &clientlen); + ASSERT_NE(-1, accepted4); + ASSERT_NE(-1, accepted6); + + int v4SocketsSeen = 0; + bool seenclient46 = false; + bool seenNull = false; + char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN]; + + fprintf(stderr, "Ports:\n server=%d. client46=%d, client6=%d\n", + port, ntohs(client46.sin6_port), ntohs(client6.sin6_port)); + + auto checkIPv4Dump = [&] (uint8_t /* proto */, const inet_diag_msg *msg) { + if (msg == nullptr) { + EXPECT_FALSE(seenNull); + seenNull = true; + return 0; + } + EXPECT_EQ(htonl(INADDR_LOOPBACK), msg->id.idiag_src[0]); + v4SocketsSeen++; + seenclient46 |= (msg->id.idiag_sport == client46.sin6_port); + inet_ntop(AF_INET, msg->id.idiag_src, src, sizeof(src)); + inet_ntop(AF_INET, msg->id.idiag_src, dst, sizeof(dst)); + fprintf(stderr, " v4 %s:%d -> %s:%d %s\n", + src, htons(msg->id.idiag_sport), + dst, htons(msg->id.idiag_dport), + tcpStateName(msg->idiag_state)); + return 0; + }; + + int v6SocketsSeen = 0; + bool seenClient6 = false, seenServer46 = false, seenServer6 = false; + + auto checkIPv6Dump = [&] (uint8_t /* proto */, const inet_diag_msg *msg) { + if (msg == nullptr) { + EXPECT_FALSE(seenNull); + seenNull = true; + return 0; + } + struct in6_addr *saddr = (struct in6_addr *) msg->id.idiag_src; + EXPECT_TRUE( + IN6_IS_ADDR_LOOPBACK(saddr) || + (IN6_IS_ADDR_V4MAPPED(saddr) && saddr->s6_addr32[3] == htonl(INADDR_LOOPBACK))); + v6SocketsSeen++; + seenClient6 |= (msg->id.idiag_sport == client6.sin6_port); + seenServer46 |= (msg->id.idiag_sport == htons(port)); + seenServer6 |= (msg->id.idiag_sport == htons(port)); + inet_ntop(AF_INET6, msg->id.idiag_src, src, sizeof(src)); + inet_ntop(AF_INET6, msg->id.idiag_src, dst, sizeof(dst)); + fprintf(stderr, " v6 [%s]:%d -> [%s]:%d %s\n", + src, htons(msg->id.idiag_sport), + dst, htons(msg->id.idiag_dport), + tcpStateName(msg->idiag_state)); + return 0; + }; + + SockDiag sd; + ASSERT_TRUE(sd.open()) << "Failed to open SOCK_DIAG socket"; + + seenNull = false; + int ret = sd.sendDumpRequest(IPPROTO_TCP, AF_INET, "127.0.0.1"); + ASSERT_EQ(0, ret) << "Failed to send IPv4 dump request: " << strerror(-ret); + fprintf(stderr, "Sent IPv4 dump\n"); + sd.readDiagMsg(IPPROTO_TCP, checkIPv4Dump); + EXPECT_GE(v4SocketsSeen, 1); + EXPECT_TRUE(seenclient46); + EXPECT_FALSE(seenServer46); + + seenNull = false; + ret = sd.sendDumpRequest(IPPROTO_TCP, AF_INET6, "127.0.0.1"); + ASSERT_EQ(0, ret) << "Failed to send mapped dump request: " << strerror(-ret); + fprintf(stderr, "Sent mapped dump\n"); + sd.readDiagMsg(IPPROTO_TCP, checkIPv6Dump); + EXPECT_TRUE(seenServer46); + + seenNull = false; + ret = sd.sendDumpRequest(IPPROTO_TCP, AF_INET6, "::1"); + ASSERT_EQ(0, ret) << "Failed to send IPv6 dump request: " << strerror(-ret); + fprintf(stderr, "Sent IPv6 dump\n"); + + sd.readDiagMsg(IPPROTO_TCP, checkIPv6Dump); + EXPECT_GE(v6SocketsSeen, 1); + EXPECT_TRUE(seenClient6); + EXPECT_TRUE(seenServer6); + + close(v4socket); + close(v6socket); + close(listensocket); + close(accepted4); + close(accepted6); +} + +TEST_F(SockDiagTest, TestMicroBenchmark) { + fprintf(stderr, "Benchmarking closing %d sockets\n", NUM_SOCKETS); + + int listensocket = socket(AF_INET6, SOCK_STREAM, 0); + ASSERT_NE(-1, listensocket) << "Failed to open listen socket"; + + uint16_t port = bindAndListen(listensocket); + ASSERT_NE(0, port) << "Can't bind to server port"; + sockaddr_in6 server = { .sin6_family = AF_INET6, .sin6_port = htons(port) }; + + using ms = std::chrono::duration<float, std::ratio<1, 1000>>; + + int clientsockets[NUM_SOCKETS], serversockets[NUM_SOCKETS]; + uint16_t clientports[NUM_SOCKETS]; + sockaddr_in6 client; + socklen_t clientlen; + + auto start = std::chrono::steady_clock::now(); + for (int i = 0; i < NUM_SOCKETS; i++) { + int s = socket(AF_INET6, SOCK_STREAM, 0); + clientlen = sizeof(client); + ASSERT_EQ(0, connect(s, (sockaddr *) &server, sizeof(server))) + << "Connecting socket " << i << " failed " << strerror(errno); + serversockets[i] = accept(listensocket, (sockaddr *) &client, &clientlen); + ASSERT_NE(-1, serversockets[i]) + << "Accepting socket " << i << " failed " << strerror(errno); + clientports[i] = client.sin6_port; + clientsockets[i] = s; + } + fprintf(stderr, " Connecting: %6.1f ms\n", + std::chrono::duration_cast<ms>(std::chrono::steady_clock::now() - start).count()); + + SockDiag sd; + ASSERT_TRUE(sd.open()) << "Failed to open SOCK_DIAG socket"; + + start = std::chrono::steady_clock::now(); + int ret = sd.destroySockets("::1"); + EXPECT_LE(0, ret) << ": Failed to destroy sockets on ::1: " << strerror(-ret); + fprintf(stderr, " Destroying: %6.1f ms\n", + std::chrono::duration_cast<ms>(std::chrono::steady_clock::now() - start).count()); + + int err; + start = std::chrono::steady_clock::now(); + for (int i = 0; i < NUM_SOCKETS; i++) { + ret = send(clientsockets[i], "foo", sizeof("foo"), 0); + err = errno; + EXPECT_EQ(-1, ret) << "Client socket " << i << " not closed"; + if (ret == -1) { + // Since we're connected to ourselves, the error might be ECONNABORTED (if we destroyed + // the socket) or ECONNRESET (if the other end was destroyed and sent a RST). + EXPECT_TRUE(errno == ECONNABORTED || errno == ECONNRESET) + << "Client socket: unexpected error: " << strerror(errno); + } + + ret = send(serversockets[i], "foo", sizeof("foo"), 0); + err = errno; + EXPECT_EQ(-1, ret) << "Server socket " << i << " not closed"; + if (ret == -1) { + EXPECT_TRUE(errno == ECONNABORTED || errno == ECONNRESET) + << "Server socket: unexpected error: " << strerror(errno); + } + } + fprintf(stderr, " Verifying: %6.1f ms\n", + std::chrono::duration_cast<ms>(std::chrono::steady_clock::now() - start).count()); + + + + start = std::chrono::steady_clock::now(); + for (int i = 0; i < NUM_SOCKETS; i++) { + close(clientsockets[i]); + close(serversockets[i]); + } + fprintf(stderr, " Closing: %6.1f ms\n", + std::chrono::duration_cast<ms>(std::chrono::steady_clock::now() - start).count()); + + close(listensocket); +}