可通过ulogd的配置文件/etc/ulogd.conf配置NFLOG相关设置。参见ebtables日志nflog。
ulogd-2.0.7中的文件input/packet/ulogd_inppkt_NFLOG.c,用于设置nfnetlink日志配置。首先创建通信套接口nflog_open,子系统ID指定为NFNL_SUBSYS_ULOG。
static int start(struct ulogd_pluginstance *upi)
{
struct nflog_input *ui = (struct nflog_input *) upi->private;
ulogd_log(ULOGD_DEBUG, "opening nfnetlink socket\n");
ui->nful_h = nflog_open();
设置监听的协议类型,如下AF_INET,AF_INET6,和AF_BRIDGE。对应于NFULNL_CFG_CMD_PF_BIND命令。
/* This is the system logging (conntrack, ...) facility */
if ((group_ce(upi->config_kset).u.value == 0) ||
(bind_ce(upi->config_kset).u.value > 0)) {
if (become_system_logging(upi, AF_INET) == -1)
goto out_handle;
if (become_system_logging(upi, AF_INET6) == -1)
goto out_handle;
if (become_system_logging(upi, AF_BRIDGE) == -1)
goto out_handle;
}
绑定配置的netlink组,对应于命令NFULNL_CFG_CMD_BIND。
ulogd_log(ULOGD_DEBUG, "binding to log group %d\n",
group_ce(upi->config_kset).u.value);
ui->nful_gh = nflog_bind_group(ui->nful_h, group_ce(upi->config_kset).u.value);
配置NFULNL_COPY_PACKET模式,对应于命令NFULNL_MSG_CONFIG。
nflog_set_mode(ui->nful_gh, NFULNL_COPY_PACKET, 0xffff);
ulogd设置接收缓存的大小,使用了SO_RCVBUFFORCE/SO_RCVBUF,并没有使用libnetfilter_log库中提供的NFULNL_MSG_CONFIG命令种的netlink属性NFULA_CFG_NLBUFSIZ。
if (nlsockbufsize_ce(upi->config_kset).u.value) {
setnlbufsiz(upi, nlsockbufsize_ce(upi->config_kset).u.value);
通过命令NFULNL_MSG_CONFIG中的属性NFULA_CFG_QTHRESH设置队列阈值。
if (nlthreshold_ce(upi->config_kset).u.value) {
if (nflog_set_qthresh(ui->nful_gh, nlthreshold_ce(upi->config_kset).u.value) >= 0)
ulogd_log(ULOGD_NOTICE,
"NFLOG netlink queue threshold has been set to %d\n",
nlthreshold_ce(upi->config_kset).u.value);
}
通过命令NFULNL_MSG_CONFIG中的属性NFULA_CFG_TIMEOUT设置内核日志发送定时器。
if (nltimeout_ce(upi->config_kset).u.value) {
if (nflog_set_timeout(ui->nful_gh, nltimeout_ce(upi->config_kset).u.value) >= 0)
ulogd_log(ULOGD_NOTICE,
"NFLOG netlink queue timeout has "
"been set to %d\n",
nltimeout_ce(upi->config_kset).u.value);
}
通过命令NFULNL_MSG_CONFIG中的属性NFULA_CFG_FLAGS设置标志位NFULNL_CFG_F_SEQ/NFULNL_CFG_F_SEQ_GLOBAL。
/* set log flags based on configuration */
flags = 0;
if (seq_ce(upi->config_kset).u.value != 0)
flags = NFULNL_CFG_F_SEQ;
if (seq_ce(upi->config_kset).u.value != 0)
flags |= NFULNL_CFG_F_SEQ_GLOBAL;
if (flags) {
if (nflog_set_flags(ui->nful_gh, flags) < 0)
ulogd_log(ULOGD_ERROR, "unable to set flags 0x%x\n", flags);
}
除去接收缓存的配置之外,其它配置都是通过调用libnetfilter_log库中的函数实现的。
注册如下nfnetlink子系统nfulnl_subsys,子系统ID为NFNL_SUBSYS_ULOG。上节ulogd在创建套接口时指定了此ID值。
static const struct nfnetlink_subsystem nfulnl_subsys = {
.name = "log",
.subsys_id = NFNL_SUBSYS_ULOG,
.cb_count = NFULNL_MSG_MAX,
.cb = nfulnl_cb,
};
static int __init nfnetlink_log_init(void)
{
status = nfnetlink_subsys_register(&nfulnl_subsys);
注册两个(NFULNL_MSG_MAX)回调函数,如下,其中NFULNL_MSG_PACKET处理函数为空。
static const struct nfnl_callback nfulnl_cb[NFULNL_MSG_MAX] = {
[NFULNL_MSG_PACKET] = { .call = nfulnl_recv_unsupp,
.attr_count = NFULA_MAX, },
[NFULNL_MSG_CONFIG] = { .call = nfulnl_recv_config,
.attr_count = NFULA_CFG_MAX,
.policy = nfula_cfg_policy },
};
如下内核的配置处理函数,首先,解析命令字,对于NFULNL_CFG_CMD_PF_BIND协议绑定命令,有函数nf_log_bind_pf处理,将命名空间中的logger的协议记录设置为nfulnl_logger(名称nfnetlink_log),通过sysctl命令可观察到绑定关系,数组2,7,10分别对应NFPROTO_IPV4,NFPROTO_BRIDGE和NFPROTO_IPV6。
# sysctl -a | grep nf_log
net.netfilter.nf_log.0 = NONE
net.netfilter.nf_log.1 = NONE
net.netfilter.nf_log.10 = nfnetlink_log
net.netfilter.nf_log.11 = NONE
net.netfilter.nf_log.12 = NONE
net.netfilter.nf_log.2 = nfnetlink_log
net.netfilter.nf_log.3 = NONE
net.netfilter.nf_log.4 = NONE
net.netfilter.nf_log.5 = NONE
net.netfilter.nf_log.6 = NONE
net.netfilter.nf_log.7 = nfnetlink_log
net.netfilter.nf_log.8 = NONE
net.netfilter.nf_log.9 = NONE
另外,netlink组配置并不是通过属性下发,而是位于nfgenmsg结构的成员res_id中,先将其取出。
static int nfulnl_recv_config(struct net *net, struct sock *ctnl,
struct sk_buff *skb, const struct nlmsghdr *nlh,
const struct nlattr * const nfula[],
struct netlink_ext_ack *extack)
{
struct nfgenmsg *nfmsg = nlmsg_data(nlh);
u_int16_t group_num = ntohs(nfmsg->res_id);
struct nfulnl_instance *inst;
struct nfulnl_msg_config_cmd *cmd = NULL;
struct nfnl_log_net *log = nfnl_log_pernet(net);
if (nfula[NFULA_CFG_CMD]) {
u_int8_t pf = nfmsg->nfgen_family;
cmd = nla_data(nfula[NFULA_CFG_CMD]);
/* Commands without queue context */
switch (cmd->command) {
case NFULNL_CFG_CMD_PF_BIND:
return nf_log_bind_pf(net, pf, &nfulnl_logger);
case NFULNL_CFG_CMD_PF_UNBIND:
nf_log_unbind_pf(net, pf);
return 0;
}
}
如果group对应的实例已经存在,但是portid不相同,表明为不同的套接口,返回错误。
inst = instance_lookup_get(log, group_num);
if (inst && inst->peer_portid != NETLINK_CB(skb).portid) {
ret = -EPERM;
goto out_put;
}
如果命名不是协议绑定或者解绑,继续处理其它命令。对于NFULNL_CFG_CMD_BIND,如果实例不存在,由函数instance_create进行创建。
if (cmd != NULL) {
switch (cmd->command) {
case NFULNL_CFG_CMD_BIND:
if (inst) {
ret = -EBUSY;
goto out_put;
}
inst = instance_create(net, group_num,
NETLINK_CB(skb).portid,
sk_user_ns(NETLINK_CB(skb).sk));
break;
case NFULNL_CFG_CMD_UNBIND:
if (!inst) {
ret = -ENODEV;
goto out;
}
instance_destroy(log, inst);
goto out_put;
default:
ret = -ENOTSUPP;
goto out_put;
}
如下根据属性字段,分别设置模式,超时,缓存/队列大小和标志位。
if (nfula[NFULA_CFG_MODE]) {
struct nfulnl_msg_config_mode *params = nla_data(nfula[NFULA_CFG_MODE]);
nfulnl_set_mode(inst, params->copy_mode, ntohl(params->copy_range));
}
if (nfula[NFULA_CFG_TIMEOUT]) {
__be32 timeout = nla_get_be32(nfula[NFULA_CFG_TIMEOUT]);
nfulnl_set_timeout(inst, ntohl(timeout));
}
if (nfula[NFULA_CFG_NLBUFSIZ]) {
__be32 nlbufsiz = nla_get_be32(nfula[NFULA_CFG_NLBUFSIZ]);
nfulnl_set_nlbufsiz(inst, ntohl(nlbufsiz));
}
if (nfula[NFULA_CFG_QTHRESH]) {
__be32 qthresh = nla_get_be32(nfula[NFULA_CFG_QTHRESH]);
nfulnl_set_qthresh(inst, ntohl(qthresh));
}
if (nfula[NFULA_CFG_FLAGS]) nfulnl_set_flags(inst, flags);
如果组对应的实例不存在,分配新的实例。
static struct nfulnl_instance *
instance_create(struct net *net, u_int16_t group_num,
u32 portid, struct user_namespace *user_ns)
{
struct nfulnl_instance *inst;
struct nfnl_log_net *log = nfnl_log_pernet(net);
spin_lock_bh(&log->instances_lock);
if (__instance_lookup(log, group_num)) {
err = -EEXIST;
goto out_unlock;
}
inst = kzalloc(sizeof(*inst), GFP_ATOMIC);
INIT_HLIST_NODE(&inst->hlist);
spin_lock_init(&inst->lock);
/* needs to be two, since we _put() after creation */
refcount_set(&inst->use, 2);
为实例设置初始值,将实例链接在命名空间的实例链表上。
timer_setup(&inst->timer, nfulnl_timer, 0);
inst->net = get_net(net);
inst->peer_user_ns = user_ns;
inst->peer_portid = portid;
inst->group_num = group_num;
inst->qthreshold = NFULNL_QTHRESH_DEFAULT;
inst->flushtimeout = NFULNL_TIMEOUT_DEFAULT;
inst->nlbufsiz = NFULNL_NLBUFSIZ_DEFAULT;
inst->copy_mode = NFULNL_COPY_PACKET;
inst->copy_range = NFULNL_COPY_RANGE_MAX;
hlist_add_head_rcu(&inst->hlist, &log->instance_table[instance_hashfn(group_num)]);
以上初始化了实例的定时器,当队列长度超过定义的长度(NFULNL_QTHRESH_DEFAULT),或者定时器到期时,发送实例的缓存报文。
static void
nfulnl_timer(struct timer_list *t)
{
struct nfulnl_instance *inst = from_timer(inst, t, timer);
spin_lock_bh(&inst->lock);
if (inst->skb)
__nfulnl_send(inst);
如果实例中缓存的skb,由多个报文数据组成,增加NLMSG_DONE表示结束。
static void
__nfulnl_send(struct nfulnl_instance *inst)
{
if (inst->qlen > 1) {
struct nlmsghdr *nlh = nlmsg_put(inst->skb, 0, 0,
NLMSG_DONE, sizeof(struct nfgenmsg), 0);
if (WARN_ONCE(!nlh, "bad nlskb size: %u, tailroom %d\n",
inst->skb->len, skb_tailroom(inst->skb))) {
kfree_skb(inst->skb);
goto out;
}
}
nfnetlink_unicast(inst->skb, inst->net, inst->peer_portid);
设置拷贝模式,对于NFULNL_COPY_PACKET,设置拷贝报文数据的长度。
static int
nfulnl_set_mode(struct nfulnl_instance *inst, u_int8_t mode,
unsigned int range)
{
switch (mode) {
case NFULNL_COPY_NONE:
case NFULNL_COPY_META:
inst->copy_mode = mode;
inst->copy_range = 0;
break;
case NFULNL_COPY_PACKET:
inst->copy_mode = mode;
if (range == 0)
range = NFULNL_COPY_RANGE_MAX;
inst->copy_range = min_t(unsigned int,
range, NFULNL_COPY_RANGE_MAX);
设置实例的flush超时。如上默认为100s(NFULNL_TIMEOUT_DEFAULT)。
static void
nfulnl_set_timeout(struct nfulnl_instance *inst, u_int32_t timeout)
{
spin_lock_bh(&inst->lock);
inst->flushtimeout = timeout;
在函数nfulnl_log_packet中,启动定时器时,定时时长设置为了默认1秒钟。
if (inst->qlen >= qthreshold)
__nfulnl_flush(inst);
/* timer_pending always called within inst->lock, so there
* is no chance of a race here */
else if (!timer_pending(&inst->timer)) {
instance_get(inst);
inst->timer.expires = jiffies + (inst->flushtimeout*HZ/100);
add_timer(&inst->timer);
}
设置实例的队列长度和标志位。
static void
nfulnl_set_qthresh(struct nfulnl_instance *inst, u_int32_t qthresh)
{
spin_lock_bh(&inst->lock);
inst->qthreshold = qthresh;
spin_unlock_bh(&inst->lock);
}
static int
nfulnl_set_flags(struct nfulnl_instance *inst, u_int16_t flags)
{
spin_lock_bh(&inst->lock);
inst->flags = flags;
内核版本 5.10