227 lines
8.0 KiB
C++
227 lines
8.0 KiB
C++
/*
|
|
* 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 <log/log.h>
|
|
#include <netdutils/Misc.h>
|
|
#include <netdutils/Netfilter.h>
|
|
#include <netdutils/Syscalls.h>
|
|
|
|
#include "NFLogListener.h"
|
|
|
|
namespace android {
|
|
namespace net {
|
|
|
|
using netdutils::extract;
|
|
using netdutils::findWithDefault;
|
|
using netdutils::makeSlice;
|
|
using netdutils::NetlinkListener;
|
|
using netdutils::NetlinkListenerInterface;
|
|
using netdutils::Slice;
|
|
using netdutils::sSyscalls;
|
|
using netdutils::Status;
|
|
using netdutils::StatusOr;
|
|
using netdutils::status::ok;
|
|
|
|
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 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 (const auto& [key, value] : mDispatchMap) {
|
|
expectOk(cfgCmdUnbind(sendFn, key));
|
|
}
|
|
}
|
|
|
|
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 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 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), "NFLogListener");
|
|
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
|