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