Netfilter日志记录

发布时间 2023-09-20 20:42:50作者: codestacklinuxer

iptables -t raw -I PREROUTING -p tcp --dport 80 -j LOG

# iptables -t raw -I PREROUTING -p tcp --dport 80 -j LOG --log-level 3 --log-prefix "ipt-err:"  可以指定log级别

日志级别可通过syslog定义进行查看。另外LOG目标还可指定参数:–log-tcp-sequence,–log-tcp-options,–log-ip-options,–log-uid和–log-macdecode。

# man syslog
#define KERN_EMERG    "<0>"  /* system is unusable               */
#define KERN_ALERT    "<1>"  /* action must be taken immediately */
#define KERN_CRIT     "<2>"  /* critical conditions              */
#define KERN_ERR      "<3>"  /* error conditions                 */
#define KERN_WARNING  "<4>"  /* warning conditions               */
#define KERN_NOTICE   "<5>"  /* normal but significant condition */
#define KERN_INFO     "<6>"  /* informational                    */
#define KERN_DEBUG    "<7>"  /* debug-level messages             */

 

如果将IPv4协议(NFPROTO_IPV4=2)的logger设置为nf_log_ipv4。

sysctl net.netfilter.nf_log.2=nf_log_ipv4
#
# sysctl -a | grep nf_log
net.netfilter.nf_log.0 = NONE
net.netfilter.nf_log.1 = NONE
net.netfilter.nf_log.2 = nf_log_ipv4

内核由函数log_tg_init注册LOG目标,即结构log_tg_regs。

static struct xt_target log_tg_regs[] __read_mostly = {
	{
		.name		= "LOG",
		.family		= NFPROTO_IPV4,
		.target		= log_tg,
		.targetsize	= sizeof(struct xt_log_info),
		.checkentry	= log_tg_check,
		.destroy	= log_tg_destroy,
		.me		= THIS_MODULE,
	},
};

函数log_tg_check进行必要的参数合法性判断,之后查找系统中注册的logger,以上示例为nf_log_ipv4

static int log_tg_check(const struct xt_tgchk_param *par)
{
	const struct xt_log_info *loginfo = par->targinfo;
	int ret;

	if (par->family != NFPROTO_IPV4 && par->family != NFPROTO_IPV6)
		return -EINVAL;

	if (loginfo->level >= 8) {
		pr_debug("level %u >= 8\n", loginfo->level);
		return -EINVAL;
	}

	if (loginfo->prefix[sizeof(loginfo->prefix)-1] != '\0') {
		pr_debug("prefix is not null-terminated\n");
		return -EINVAL;
	}

	ret = nf_logger_find_get(par->family, NF_LOG_TYPE_LOG);
	if (ret != 0 && !par->nft_compat) {
		request_module("%s", "nf_log_syslog");

		ret = nf_logger_find_get(par->family, NF_LOG_TYPE_LOG);
	}

	return ret;
}

以下将匹配LOG规则的报文通过函数nf_log_packet进行日志输出,在使用日志nf_log_ipv4时,其日志输出函数为nf_log_ip_packet。函数返回值为XT_CONTINUE,继续下一跳规则匹配。

static unsigned int
log_tg(struct sk_buff *skb, const struct xt_action_param *par)
{
	const struct xt_log_info *loginfo = par->targinfo;
	struct net *net = xt_net(par);
	struct nf_loginfo li;

	li.type = NF_LOG_TYPE_LOG;
	li.u.log.level = loginfo->level;
	li.u.log.logflags = loginfo->logflags;

	nf_log_packet(net, xt_family(par), xt_hooknum(par), skb, xt_in(par),
		      xt_out(par), &li, "%s", loginfo->prefix);
	return XT_CONTINUE;
}

 

系统内的logger按照协议和类型保存在全局二维数组loggers中。

int sysctl_nf_log_all_netns __read_mostly;
EXPORT_SYMBOL(sysctl_nf_log_all_netns);
    
static struct nf_logger __rcu *loggers[NFPROTO_NUMPROTO][NF_LOG_TYPE_MAX] __read_mostly;

参数nf_log_all_netns默认为零,仅记录init_net命名空间中的报文日志,参考函数nf_log_ip_packet

 

日志记录器注册

如果协议类型等于NFPROTO_UNSPEC,将日志logger赋值给所有的协议类型。否则,将logger赋值给对应的协议。

/* return EEXIST if the same logger is registered, 0 on success. */
int nf_log_register(u_int8_t pf, struct nf_logger *logger)
{
	int i;
	int ret = 0;

	if (pf >= ARRAY_SIZE(init_net.nf.nf_loggers))
		return -EINVAL;

	mutex_lock(&nf_log_mutex);

	if (pf == NFPROTO_UNSPEC) {
		for (i = NFPROTO_UNSPEC; i < NFPROTO_NUMPROTO; i++) {
			if (rcu_access_pointer(loggers[i][logger->type])) {
				ret = -EEXIST;
				goto unlock;
			}
		}
		for (i = NFPROTO_UNSPEC; i < NFPROTO_NUMPROTO; i++)
			rcu_assign_pointer(loggers[i][logger->type], logger);
	} else {
		if (rcu_access_pointer(loggers[pf][logger->type])) {
			ret = -EEXIST;
			goto unlock;
		}
		rcu_assign_pointer(loggers[pf][logger->type], logger);
	}

unlock:
	mutex_unlock(&nf_log_mutex);
	return ret;
}

ret = nf_log_register(NFPROTO_IPV4, &nf_ip_logger);

函数nf_log_set设置每个命名空间的日志记录logger。协议类型不能等于NFPROTO_UNSPEC。


int nf_log_set(struct net *net, u_int8_t pf, const struct nf_logger *logger)
{
	const struct nf_logger *log;

	if (pf == NFPROTO_UNSPEC || pf >= ARRAY_SIZE(net->nf.nf_loggers))
		return -EOPNOTSUPP;

	mutex_lock(&nf_log_mutex);
	log = nft_log_dereference(net->nf.nf_loggers[pf]);
	if (log == NULL)
		rcu_assign_pointer(net->nf.nf_loggers[pf], logger);

	mutex_unlock(&nf_log_mutex);

	return 0;
}


int ret = nf_log_set(net, NFPROTO_IPV4, &nf_ip_logger);
 

日志打印

如果参数loginfo不等于空,使用全局loggers中注册的日志记录器,否则,使用命名空间中设置日志记录器

void nf_log_packet(struct net *net,
		   u_int8_t pf,
		   unsigned int hooknum,
		   const struct sk_buff *skb,
		   const struct net_device *in,
		   const struct net_device *out,
		   const struct nf_loginfo *loginfo,
		   const char *fmt, ...)
{
	va_list args;
	char prefix[NF_LOG_PREFIXLEN];
	const struct nf_logger *logger;

	rcu_read_lock();
	if (loginfo != NULL)
		logger = rcu_dereference(loggers[pf][loginfo->type]);
	else
		logger = rcu_dereference(net->nf.nf_loggers[pf]);

	if (logger) {
		va_start(args, fmt);
		vsnprintf(prefix, sizeof(prefix), fmt, args);
		va_end(args);
		logger->logfn(net, pf, hooknum, skb, in, out, loginfo, prefix);
	}
	rcu_read_unlock();
}

IPv4日志记录器

协议类型NFPROTO_IPV4注册了全局和命名空间日志记录器nf_log_ipv4,其输出函数为nf_log_ip_packet。

 

static struct nf_logger nf_ip_logger __read_mostly = {
    .name       = "nf_log_ipv4",
    .type       = NF_LOG_TYPE_LOG,
    .logfn      = nf_log_ip_packet,
    .me     = THIS_MODULE,
};
static int __net_init nf_log_ipv4_net_init(struct net *net)
{   
    return nf_log_set(net, NFPROTO_IPV4, &nf_ip_logger);
}
static struct pernet_operations nf_log_ipv4_net_ops = {
    .init = nf_log_ipv4_net_init,
    .exit = nf_log_ipv4_net_exit,
};
static int __init nf_log_ipv4_init(void)
{
    ret = nf_log_register(NFPROTO_IPV4, &nf_ip_logger);

 只有在sysctl_nf_log_all_netns为真的情况下,才会生成其它命名空间的报文日志。函数nf_log_buf_close输出日志缓存中的内容。 

 


/* Guard against containers flooding syslog. */
static bool nf_log_allowed(const struct net *net)
{
	return net_eq(net, &init_net) || sysctl_nf_log_all_netns;
}
static void nf_log_ip_packet(struct net *net, u_int8_t pf,
			     unsigned int hooknum, const struct sk_buff *skb,
			     const struct net_device *in,
			     const struct net_device *out,
			     const struct nf_loginfo *loginfo,
			     const char *prefix)
{
	struct nf_log_buf *m;

	if (!nf_log_allowed(net))
		return;

	m = nf_log_buf_open();

	if (!loginfo)
		loginfo = &default_loginfo;

	nf_log_dump_packet_common(m, pf, hooknum, skb, in,
				  out, loginfo, prefix);

	if (in)
		dump_mac_header(m, loginfo, skb);

	dump_ipv4_packet(net, m, loginfo, skb, skb_network_offset(skb));

	nf_log_buf_close(m);
}