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, &timestampNs](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);