cregit-Linux how code gets into the kernel

Release 4.11 net/netfilter/nfnetlink_log.c

Directory: net/netfilter
/*
 * This is a module which is used for logging packets to userspace via
 * nfetlink.
 *
 * (C) 2005 by Harald Welte <laforge@netfilter.org>
 * (C) 2006-2012 Patrick McHardy <kaber@trash.net>
 *
 * Based on the old ipv4-only ipt_ULOG.c:
 * (C) 2000-2004 by Harald Welte <laforge@netfilter.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/init.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_bridge.h>
#include <net/netlink.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nfnetlink_log.h>
#include <linux/netfilter/nf_conntrack_common.h>
#include <linux/spinlock.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/security.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <net/sock.h>
#include <net/netfilter/nf_log.h>
#include <net/netns/generic.h>
#include <net/netfilter/nfnetlink_log.h>

#include <linux/atomic.h>

#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
#include "../bridge/br_private.h"
#endif


#define NFULNL_NLBUFSIZ_DEFAULT	NLMSG_GOODSIZE

#define NFULNL_TIMEOUT_DEFAULT 	100	
/* every second */

#define NFULNL_QTHRESH_DEFAULT 	100	
/* 100 packets */
/* max packet size is limited by 16-bit struct nfattr nfa_len field */

#define NFULNL_COPY_RANGE_MAX	(0xFFFF - NLA_HDRLEN)


#define PRINTR(x, args...)	do { if (net_ratelimit()) \
                                     printk(x, ## args); } while (0);


struct nfulnl_instance {
	
struct hlist_node hlist;	/* global list of instances */
	
spinlock_t lock;
	
atomic_t use;			/* use count */

	
unsigned int qlen;		/* number of nlmsgs in skb */
	
struct sk_buff *skb;		/* pre-allocatd skb */
	
struct timer_list timer;
	
struct net *net;
	
struct user_namespace *peer_user_ns;	/* User namespace of the peer process */
	
u32 peer_portid;		/* PORTID of the peer process */

	/* configurable parameters */
	
unsigned int flushtimeout;	/* timeout until queue flush */
	
unsigned int nlbufsiz;		/* netlink buffer allocation size */
	
unsigned int qthreshold;	/* threshold of the queue */
	
u_int32_t copy_range;
	
u_int32_t seq;			/* instance-local sequential counter */
	
u_int16_t group_num;		/* number of this queue */
	
u_int16_t flags;
	
u_int8_t copy_mode;
	
struct rcu_head rcu;
};


#define INSTANCE_BUCKETS	16


static unsigned int nfnl_log_net_id __read_mostly;


struct nfnl_log_net {
	
spinlock_t instances_lock;
	
struct hlist_head instance_table[INSTANCE_BUCKETS];
	
atomic_t global_seq;
};


static struct nfnl_log_net *nfnl_log_pernet(struct net *net) { return net_generic(net, nfnl_log_net_id); }

Contributors

PersonTokensPropCommitsCommitProp
Gao Feng21100.00%1100.00%
Total21100.00%1100.00%


static inline u_int8_t instance_hashfn(u_int16_t group_num) { return ((group_num & 0xff) % INSTANCE_BUCKETS); }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte21100.00%1100.00%
Total21100.00%1100.00%


static struct nfulnl_instance * __instance_lookup(struct nfnl_log_net *log, u_int16_t group_num) { struct hlist_head *head; struct nfulnl_instance *inst; head = &log->instance_table[instance_hashfn(group_num)]; hlist_for_each_entry_rcu(inst, head, hlist) { if (inst->group_num == group_num) return inst; } return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte5587.30%133.33%
Gao Feng711.11%133.33%
Eric Dumazet11.59%133.33%
Total63100.00%3100.00%


static inline void instance_get(struct nfulnl_instance *inst) { atomic_inc(&inst->use); }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte20100.00%1100.00%
Total20100.00%1100.00%


static struct nfulnl_instance * instance_lookup_get(struct nfnl_log_net *log, u_int16_t group_num) { struct nfulnl_instance *inst; rcu_read_lock_bh(); inst = __instance_lookup(log, group_num); if (inst && !atomic_inc_not_zero(&inst->use)) inst = NULL; rcu_read_unlock_bh(); return inst; }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte3562.50%125.00%
Eric Dumazet1425.00%250.00%
Gao Feng712.50%125.00%
Total56100.00%4100.00%


static void nfulnl_instance_free_rcu(struct rcu_head *head) { struct nfulnl_instance *inst = container_of(head, struct nfulnl_instance, rcu); put_net(inst->net); kfree(inst); module_put(THIS_MODULE); }

Contributors

PersonTokensPropCommitsCommitProp
Eric Dumazet2762.79%150.00%
Gao Feng1637.21%150.00%
Total43100.00%2100.00%


static void instance_put(struct nfulnl_instance *inst) { if (inst && atomic_dec_and_test(&inst->use)) call_rcu_bh(&inst->rcu, nfulnl_instance_free_rcu); }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte2781.82%150.00%
Eric Dumazet618.18%150.00%
Total33100.00%2100.00%

static void nfulnl_timer(unsigned long data);
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); int err; spin_lock_bh(&log->instances_lock); if (__instance_lookup(log, group_num)) { err = -EEXIST; goto out_unlock; } inst = kzalloc(sizeof(*inst), GFP_ATOMIC); if (!inst) { err = -ENOMEM; goto out_unlock; } if (!try_module_get(THIS_MODULE)) { kfree(inst); err = -EAGAIN; goto out_unlock; } INIT_HLIST_NODE(&inst->hlist); spin_lock_init(&inst->lock); /* needs to be two, since we _put() after creation */ atomic_set(&inst->use, 2); setup_timer(&inst->timer, nfulnl_timer, (unsigned long)inst); 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)]); spin_unlock_bh(&log->instances_lock); return inst; out_unlock: spin_unlock_bh(&log->instances_lock); return ERR_PTR(err); }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte16461.65%215.38%
Gao Feng3412.78%17.69%
Patrick McHardy269.77%215.38%
Michał Mirosław197.14%215.38%
Eric W. Biedermann145.26%215.38%
Eric Dumazet41.50%215.38%
Hideaki Yoshifuji / 吉藤英明41.50%17.69%
Richard Weinberger10.38%17.69%
Total266100.00%13100.00%

static void __nfulnl_flush(struct nfulnl_instance *inst); /* called with BH disabled */
static void __instance_destroy(struct nfulnl_instance *inst) { /* first pull it out of the global list */ hlist_del_rcu(&inst->hlist); /* then flush all pending packets from skb */ spin_lock(&inst->lock); /* lockless readers wont be able to use us */ inst->copy_mode = NFULNL_COPY_DISABLED; if (inst->skb) __nfulnl_flush(inst); spin_unlock(&inst->lock); /* and finally put the refcount */ instance_put(inst); }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte4980.33%125.00%
Eric Dumazet1016.39%125.00%
Michał Mirosław11.64%125.00%
Patrick McHardy11.64%125.00%
Total61100.00%4100.00%


static inline void instance_destroy(struct nfnl_log_net *log, struct nfulnl_instance *inst) { spin_lock_bh(&log->instances_lock); __instance_destroy(inst); spin_unlock_bh(&log->instances_lock); }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte1847.37%125.00%
Gao Feng923.68%125.00%
Patrick McHardy923.68%125.00%
Eric Dumazet25.26%125.00%
Total38100.00%4100.00%


static int nfulnl_set_mode(struct nfulnl_instance *inst, u_int8_t mode, unsigned int range) { int status = 0; spin_lock_bh(&inst->lock); 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); break; default: status = -EINVAL; break; } spin_unlock_bh(&inst->lock); return status; }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte8983.18%133.33%
Florian Westphal109.35%133.33%
Michał Mirosław87.48%133.33%
Total107100.00%3100.00%


static int nfulnl_set_nlbufsiz(struct nfulnl_instance *inst, u_int32_t nlbufsiz) { int status; spin_lock_bh(&inst->lock); if (nlbufsiz < NFULNL_NLBUFSIZ_DEFAULT) status = -ERANGE; else if (nlbufsiz > 131072) status = -ERANGE; else { inst->nlbufsiz = nlbufsiz; status = 0; } spin_unlock_bh(&inst->lock); return status; }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte72100.00%1100.00%
Total72100.00%1100.00%


static void nfulnl_set_timeout(struct nfulnl_instance *inst, u_int32_t timeout) { spin_lock_bh(&inst->lock); inst->flushtimeout = timeout; spin_unlock_bh(&inst->lock); }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte3597.22%150.00%
Rami Rosen12.78%150.00%
Total36100.00%2100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte3597.22%150.00%
Rami Rosen12.78%150.00%
Total36100.00%2100.00%


static int nfulnl_set_flags(struct nfulnl_instance *inst, u_int16_t flags) { spin_lock_bh(&inst->lock); inst->flags = flags; spin_unlock_bh(&inst->lock); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte39100.00%1100.00%
Total39100.00%1100.00%


static struct sk_buff * nfulnl_alloc_skb(struct net *net, u32 peer_portid, unsigned int inst_size, unsigned int pkt_size) { struct sk_buff *skb; unsigned int n; /* alloc skb which should be big enough for a whole multipart * message. WARNING: has to be <= 128k due to slab restrictions */ n = max(inst_size, pkt_size); skb = alloc_skb(n, GFP_ATOMIC | __GFP_NOWARN); if (!skb) { if (n > pkt_size) { /* try to allocate only as much as we need for current * packet */ skb = alloc_skb(pkt_size, GFP_ATOMIC); } } return skb; }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte4757.32%116.67%
Patrick McHardy2631.71%233.33%
Gao Feng56.10%116.67%
Florian Westphal22.44%116.67%
Calvin Owens22.44%116.67%
Total82100.00%6100.00%


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, MSG_DONTWAIT); out: inst->qlen = 0; inst->skb = NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte4539.82%114.29%
Houcheng Lin3026.55%114.29%
David S. Miller1916.81%114.29%
Eric Leblond1412.39%114.29%
Gao Feng32.65%114.29%
Eric W. Biedermann10.88%114.29%
Alexey Dobriyan10.88%114.29%
Total113100.00%7100.00%


static void __nfulnl_flush(struct nfulnl_instance *inst) { /* timer holds a reference */ if (del_timer(&inst->timer)) instance_put(inst); if (inst->skb) __nfulnl_send(inst); }

Contributors

PersonTokensPropCommitsCommitProp
Michał Mirosław38100.00%1100.00%
Total38100.00%1100.00%


static void nfulnl_timer(unsigned long data) { struct nfulnl_instance *inst = (struct nfulnl_instance *)data; spin_lock_bh(&inst->lock); if (inst->skb) __nfulnl_send(inst); spin_unlock_bh(&inst->lock); instance_put(inst); }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte4379.63%133.33%
Michał Mirosław1120.37%266.67%
Total54100.00%3100.00%

/* This is an inline function, we don't really care about a long * list of arguments */
static inline int __build_packet_message(struct nfnl_log_net *log, struct nfulnl_instance *inst, const struct sk_buff *skb, unsigned int data_len, u_int8_t pf, unsigned int hooknum, const struct net_device *indev, const struct net_device *outdev, const char *prefix, unsigned int plen, const struct nfnl_ct_hook *nfnl_ct, struct nf_conn *ct, enum ip_conntrack_info ctinfo) { struct nfulnl_msg_packet_hdr pmsg; struct nlmsghdr *nlh; struct nfgenmsg *nfmsg; sk_buff_data_t old_tail = inst->skb->tail; struct sock *sk; const unsigned char *hwhdrp; nlh = nlmsg_put(inst->skb, 0, 0, NFNL_SUBSYS_ULOG << 8 | NFULNL_MSG_PACKET, sizeof(struct nfgenmsg), 0); if (!nlh) return -1; nfmsg = nlmsg_data(nlh); nfmsg->nfgen_family = pf; nfmsg->version = NFNETLINK_V0; nfmsg->res_id = htons(inst->group_num); memset(&pmsg, 0, sizeof(pmsg)); pmsg.hw_protocol = skb->protocol; pmsg.hook = hooknum; if (nla_put(inst->skb, NFULA_PACKET_HDR, sizeof(pmsg), &pmsg)) goto nla_put_failure; if (prefix && nla_put(inst->skb, NFULA_PREFIX, plen, prefix)) goto nla_put_failure; if (indev) { #if !IS_ENABLED(CONFIG_BRIDGE_NETFILTER) if (nla_put_be32(inst->skb, NFULA_IFINDEX_INDEV, htonl(indev->ifindex))) goto nla_put_failure; #else if (pf == PF_BRIDGE) { /* Case 1: outdev is physical input device, we need to * look for bridge group (when called from * netfilter_bridge) */ if (nla_put_be32(inst->skb, NFULA_IFINDEX_PHYSINDEV, htonl(indev->ifindex)) || /* this is the bridge group "brX" */ /* rcu_read_lock()ed by nf_hook_thresh or * nf_log_packet. */ nla_put_be32(inst->skb, NFULA_IFINDEX_INDEV, htonl(br_port_get_rcu(indev)->br->dev->ifindex))) goto nla_put_failure; } else { struct net_device *physindev; /* Case 2: indev is bridge group, we need to look for * physical device (when called from ipv4) */ if (nla_put_be32(inst->skb, NFULA_IFINDEX_INDEV, htonl(indev->ifindex))) goto nla_put_failure; physindev = nf_bridge_get_physindev(skb); if (physindev && nla_put_be32(inst->skb, NFULA_IFINDEX_PHYSINDEV, htonl(physindev->ifindex))) goto nla_put_failure; } #endif } if (outdev) { #if !IS_ENABLED(CONFIG_BRIDGE_NETFILTER) if (nla_put_be32(inst->skb, NFULA_IFINDEX_OUTDEV, htonl(outdev->ifindex))) goto nla_put_failure; #else if (pf == PF_BRIDGE) { /* Case 1: outdev is physical output device, we need to * look for bridge group (when called from * netfilter_bridge) */ if (nla_put_be32(inst->skb, NFULA_IFINDEX_PHYSOUTDEV, htonl(outdev->ifindex)) || /* this is the bridge group "brX" */ /* rcu_read_lock()ed by nf_hook_thresh or * nf_log_packet. */ nla_put_be32(inst->skb, NFULA_IFINDEX_OUTDEV, htonl(br_port_get_rcu(outdev)->br->dev->ifindex))) goto nla_put_failure; } else { struct net_device *physoutdev; /* Case 2: indev is a bridge group, we need to look * for physical device (when called from ipv4) */ if (nla_put_be32(inst->skb, NFULA_IFINDEX_OUTDEV, htonl(outdev->ifindex))) goto nla_put_failure; physoutdev = nf_bridge_get_physoutdev(skb); if (physoutdev && nla_put_be32(inst->skb, NFULA_IFINDEX_PHYSOUTDEV, htonl(physoutdev->ifindex))) goto nla_put_failure; } #endif } if (skb->mark && nla_put_be32(inst->skb, NFULA_MARK, htonl(skb->mark))) goto nla_put_failure; if (indev && skb->dev && skb->mac_header != skb->network_header) { struct nfulnl_msg_packet_hw phw; int len; memset(&phw, 0, sizeof(phw)); len = dev_parse_header(skb, phw.hw_addr); if (len > 0) { phw.hw_addrlen = htons(len); if (nla_put(inst->skb, NFULA_HWADDR, sizeof(phw), &phw)) goto nla_put_failure; } } if (indev && skb_mac_header_was_set(skb)) { if (nla_put_be16(inst->skb, NFULA_HWTYPE, htons(skb->dev->type)) || nla_put_be16(inst->skb, NFULA_HWLEN, htons(skb->dev->hard_header_len))) goto nla_put_failure; hwhdrp = skb_mac_header(skb); if (skb->dev->type == ARPHRD_SIT) hwhdrp -= ETH_HLEN; if (hwhdrp >= skb->head && nla_put(inst->skb, NFULA_HWHEADER, skb->dev->hard_header_len, hwhdrp)) goto nla_put_failure; } if (skb->tstamp) { struct nfulnl_msg_packet_timestamp ts; struct timespec64 kts = ktime_to_timespec64(skb->tstamp); ts.sec = cpu_to_be64(kts.tv_sec); ts.usec = cpu_to_be64(kts.tv_nsec / NSEC_PER_USEC); if (nla_put(inst->skb, NFULA_TIMESTAMP, sizeof(ts), &ts)) goto nla_put_failure; } /* UID */ sk = skb->sk; if (sk && sk_fullsock(sk)) { read_lock_bh(&sk->sk_callback_lock); if (sk->sk_socket && sk->sk_socket->file) { struct file *file = sk->sk_socket->file; const struct cred *cred = file->f_cred; struct user_namespace *user_ns = inst->peer_user_ns; __be32 uid = htonl(from_kuid_munged(user_ns, cred->fsuid)); __be32 gid = htonl(from_kgid_munged(user_ns, cred->fsgid)); read_unlock_bh(&sk->sk_callback_lock); if (nla_put_be32(inst->skb, NFULA_UID, uid) || nla_put_be32(inst->skb, NFULA_GID, gid)) goto nla_put_failure; } else read_unlock_bh(&sk->sk_callback_lock); } /* local sequence number */ if ((inst->flags & NFULNL_CFG_F_SEQ) && nla_put_be32(inst->skb, NFULA_SEQ, htonl(inst->seq++))) goto nla_put_failure; /* global sequence number */ if ((inst->flags & NFULNL_CFG_F_SEQ_GLOBAL) && nla_put_be32(inst->skb, NFULA_SEQ_GLOBAL, htonl(atomic_inc_return(&log->global_seq)))) goto nla_put_failure; if (ct && nfnl_ct->build(inst->skb, ct, ctinfo, NFULA_CT, NFULA_CT_INFO) < 0) goto nla_put_failure; if (data_len) { struct nlattr *nla; int size = nla_attr_size(data_len); if (skb_tailroom(inst->skb) < nla_total_size(data_len)) goto nla_put_failure; nla = (struct nlattr *)skb_put(inst->skb, nla_total_size(data_len)); nla->nla_type = NFULA_PAYLOAD; nla->nla_len = size; if (skb_copy_bits(skb, 0, nla_data(nla), data_len)) BUG(); } nlh->nlmsg_len = inst->skb->tail - old_tail; return 0; nla_put_failure: PRINTR(KERN_ERR "nfnetlink_log: error creating log nlmsg\n"); return -1; }

Contributors

PersonTokensPropCommitsCommitProp
Harald Welte60751.62%38.33%
David S. Miller13811.73%25.56%
Patrick McHardy1069.01%822.22%
Eric Leblond574.85%12.78%
Ken-ichirou MATSUZAWA413.49%12.78%
Bob Hockney403.40%12.78%
Dan Carpenter302.55%12.78%
Eric Dumazet252.13%38.33%
Florian Westphal242.04%12.78%
Linus Torvalds231.96%12.78%
Pablo Neira Ayuso151.28%25.56%
David Howells141.19%12.78%
Stephen Hemminger90.77%12.78%
Arnd Bergmann80.68%12.78%
Eric W. Biedermann80.68%12.78%
Nicolas Cavallari80.68%12.78%
Gao Feng70.60%12.78%
Jiri Pirko60.51%12.78%
Al Viro30.26%12.78%
Arnaldo Carvalho de Melo30.26%12.78%
Aaron Conole20.17%12.78%
Jan Engelhardt10.09%12.78%
Thomas Graf10.09%12.78%
Total1176100.00%36100.00%

static struct nf_loginfo default_loginfo = { .type = NF_LOG_TYPE_ULOG, .u = { .ulog = { .copy_len = 0xffff, .group = 0, .qthreshold = 1, }, }, }; /* log handler for internal netfilter logging api */
void nfulnl_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 *li_user, const char *prefix) { size_t size; unsigned int data_len; struct nfulnl_instance *inst; const struct nf_loginfo *li; unsigned int qthreshold; unsigned int plen; struct nfnl_log_net *log = nfnl_log_pernet(net); const struct nfnl_ct_hook *nfnl_ct = NULL; struct nf_conn *ct = NULL; enum ip_conntrack_info uninitialized_var(ctinfo); if (li_user && li_user->type == NF_LOG_TYPE_ULOG) li = li_user