Add WakeupController and NFLogListener
These classes work together to parse and dispatch NFLOG messages in
response to inbound packets annotated by the WiFi driver.
Test: as follows
- built
- flashed
- booted
- netd_unit_test passes
Change-Id: Id26d62858bf4bc4186ae66850f08077adf6fc2ac
diff --git a/server/Android.mk b/server/Android.mk
index 9a3fa99..9973ef5 100644
--- a/server/Android.mk
+++ b/server/Android.mk
@@ -71,6 +71,7 @@
libmdnssd \
libnetdaidl \
libnetutils \
+ libnetdutils \
libnl \
libsysutils \
libbase \
@@ -100,8 +101,10 @@
NetlinkHandler.cpp \
NetlinkManager.cpp \
NetlinkCommands.cpp \
+ NetlinkListener.cpp \
Network.cpp \
NetworkController.cpp \
+ NFLogListener.cpp \
PhysicalNetwork.cpp \
PppController.cpp \
ResolverController.cpp \
@@ -111,6 +114,7 @@
TetherController.cpp \
UidRanges.cpp \
VirtualNetwork.cpp \
+ WakeupController.cpp \
XfrmController.cpp \
main.cpp \
oem_iptables_hook.cpp \
@@ -163,16 +167,20 @@
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
LOCAL_SHARED_LIBRARIES := \
libbase \
libbinder \
@@ -180,6 +188,9 @@
liblog \
liblogwrap \
libnetutils \
+ libnetdutils \
+ libnl \
+ libpcap \
libsysutils \
libutils \
diff --git a/server/Controllers.cpp b/server/Controllers.cpp
index 84f719b..ad77ee1 100644
--- a/server/Controllers.cpp
+++ b/server/Controllers.cpp
@@ -73,6 +73,7 @@
};
static const char* MANGLE_INPUT[] = {
+ WakeupController::LOCAL_MANGLE_INPUT,
RouteController::LOCAL_MANGLE_INPUT,
NULL,
};
@@ -121,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();
}
diff --git a/server/Controllers.h b/server/Controllers.h
index bd372d8..0bfa0e7 100644
--- a/server/Controllers.h
+++ b/server/Controllers.h
@@ -32,6 +32,7 @@
#include "ResolverController.h"
#include "StrictController.h"
#include "TetherController.h"
+#include "WakeupController.h"
#include "XfrmController.h"
namespace android {
@@ -53,6 +54,7 @@
StrictController strictCtrl;
EventReporter eventReporter;
IptablesRestoreController iptablesRestoreCtrl;
+ WakeupController wakeupCtrl;
XfrmController xfrmCtrl;
void init();
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/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/NetdNativeService.cpp b/server/NetdNativeService.cpp
index f0729b1..7e555f5 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::fromExceptionCode(s.code(), s.msg().c_str());
+}
+
binder::Status checkPermission(const char *permission) {
pid_t pid;
uid_t uid;
@@ -410,5 +418,19 @@
socket));
}
+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 fbe860c..a95b483 100644
--- a/server/NetdNativeService.h
+++ b/server/NetdNativeService.h
@@ -48,6 +48,13 @@
std::vector<std::string>* domains, std::vector<int32_t>* params,
std::vector<int32_t>* stats) 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;
@@ -108,7 +115,6 @@
binder::Status ipSecRemoveTransportModeTransform(
const android::base::unique_fd& socket);
-
};
} // namespace net
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..7a7627a 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;
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/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/binder/android/net/INetd.aidl b/server/binder/android/net/INetd.aidl
index 54ba684..f1552b4 100644
--- a/server/binder/android/net/INetd.aidl
+++ b/server/binder/android/net/INetd.aidl
@@ -295,4 +295,21 @@
*/
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);
}
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 9215f21..50570fd 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,6 +56,8 @@
using android::net::FwmarkServer;
using android::net::NetdNativeService;
using android::net::NetlinkManager;
+using android::net::NFLogListener;
+using android::net::makeNFLogListener;
static void remove_pid_file();
static bool write_pid_file();
@@ -80,8 +83,19 @@
exit(1);
};
+ std::unique_ptr<NFLogListener> logListener;
+ {
+ auto result = makeNFLogListener();
+ if (!isOk(result)) {
+ ALOGE("Unable to create NFLogListener: %s", result.status().msg().c_str());
+ exit(1);
+ }
+ logListener = std::move(result.value());
+ }
+
gCtls = new android::net::Controllers();
gCtls->init();
+ gCtls->wakeupCtrl.init(logListener.get());
CommandListener cl;
nm->setBroadcaster((SocketListener *) &cl);