/* * 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 <arpa/inet.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 kDefaultPacketRange = 0; 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 = htons(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 = htonl(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 = htons(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 = htons(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, ntohs(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) { return subscribe(nfLogGroup, kDefaultPacketRange, fn); } Status NFLogListener::subscribe( uint16_t nfLogGroup, uint32_t copyRange, 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 const uint8_t copyMode = copyRange > 0 ? NFULNL_COPY_PACKET : NFULNL_COPY_NONE; return cfgMode(sendFn, nfLogGroup, copyRange, copyMode); } 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