diff options
Diffstat (limited to 'net/netfilter/xt_qtaguid.c')
-rw-r--r-- | net/netfilter/xt_qtaguid.c | 1520 |
1 files changed, 1520 insertions, 0 deletions
diff --git a/net/netfilter/xt_qtaguid.c b/net/netfilter/xt_qtaguid.c new file mode 100644 index 00000000000..22552c9b81c --- /dev/null +++ b/net/netfilter/xt_qtaguid.c @@ -0,0 +1,1520 @@ +/* + * Kernel iptables module to track stats for packets based on user tags. + * + * (C) 2011 Google, Inc + * + * 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. + */ + +/* TODO: support ipv6 for iface_stat. + * Currently if an iface is only v6 it will not have stats collected. */ + +#include <linux/file.h> +#include <linux/inetdevice.h> +#include <linux/module.h> +#include <linux/netfilter/x_tables.h> +#include <linux/netfilter/xt_qtaguid.h> +#include <linux/skbuff.h> +#include <linux/workqueue.h> +#include <net/sock.h> +#include <net/tcp.h> +#include <net/udp.h> + +#include <linux/netfilter/xt_socket.h> +/* We only use the xt_socket funcs within a similar context to avoid unexpected + * return values. */ +#define XT_SOCKET_SUPPORTED_HOOKS \ + ((1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_LOCAL_IN)) + + +static const char *module_procdirname = "xt_qtaguid"; +static struct proc_dir_entry *xt_qtaguid_procdir; + +static unsigned int proc_iface_perms = S_IRUGO; +module_param_named(iface_perms, proc_iface_perms, uint, S_IRUGO | S_IWUSR); + +static struct proc_dir_entry *xt_qtaguid_stats_file; +static unsigned int proc_stats_perms = S_IRUGO; +module_param_named(stats_perms, proc_stats_perms, uint, S_IRUGO | S_IWUSR); + +static struct proc_dir_entry *xt_qtaguid_ctrl_file; +#ifdef CONFIG_ANDROID_PARANOID_NETWORK +static unsigned int proc_ctrl_perms = S_IRUGO | S_IWUGO; +#else +static unsigned int proc_ctrl_perms = S_IRUGO | S_IWUSR; +#endif +module_param_named(ctrl_perms, proc_ctrl_perms, uint, S_IRUGO | S_IWUSR); + +#ifdef CONFIG_ANDROID_PARANOID_NETWORK +#include <linux/android_aid.h> +static gid_t proc_stats_readall_gid = AID_NET_BW_STATS; +static gid_t proc_ctrl_write_gid = AID_NET_BW_ACCT; +#else +/* 0 means, don't limit anybody */ +static gid_t proc_stats_readall_gid; +static gid_t proc_ctrl_write_gid; +#endif +module_param_named(stats_readall_gid, proc_stats_readall_gid, uint, + S_IRUGO | S_IWUSR); +module_param_named(ctrl_write_gid, proc_ctrl_write_gid, uint, + S_IRUGO | S_IWUSR); + +/* After the kernel has initiallized this module, it is still possible + * to make it passive: + * - do not register it via iptables. + * the matching code will not be invoked. + * - set passive to 0 + * the iface stats handling will not be act on notifications. + * This is mostly usefull when a bug is suspected. + */ +static bool module_passive; +module_param_named(passive, module_passive, bool, S_IRUGO | S_IWUSR); + +/*---------------------------------------------------------------------------*/ +/* + * Tags: + * + * They represent what the data usage counters will be tracked against. + * By default a tag is just based on the UID. + * The UID is used as the base for policying, and can not be ignored. + * So a tag will always at least represent a UID (uid_tag). + * + * A tag can be augmented with an "accounting tag" which is associated + * with a UID. + * User space can set the acct_tag portion of the tag which is then used + * with sockets: all data belong to that socket will be counted against the + * tag. The policing is then based on the tag's uid_tag portion, + * and stats are collected for the acct_tag portion seperately. + * + * There could be + * a: {acct_tag=1, uid_tag=10003} + * b: {acct_tag=2, uid_tag=10003} + * c: {acct_tag=3, uid_tag=10003} + * d: {acct_tag=0, uid_tag=10003} + * (a, b, and c represent tags associated with specific sockets. + * d is for the totals for that uid, including all untagged traffic. + * Typically d is used with policing/quota rules. + * + * We want tag_t big enough to distinguish uid_t and acct_tag. + * It might become a struct if needed. + * Nothing should be using it as an int. + */ +typedef uint64_t tag_t; /* Only used via accessors */ + +static const char *iface_stat_procdirname = "iface_stat"; +static struct proc_dir_entry *iface_stat_procdir; + +enum ifs_tx_rx { + IFS_TX, + IFS_RX, + IFS_MAX_DIRECTIONS +}; + +/* For now, TCP, UDP, the rest */ +enum ifs_proto { + IFS_TCP, + IFS_UDP, + IFS_PROTO_OTHER, + IFS_MAX_PROTOS +}; + +struct byte_packet_counters { + uint64_t bytes; + uint64_t packets; +}; + +struct data_counters { + struct byte_packet_counters bpc[IFS_MAX_DIRECTIONS][IFS_MAX_PROTOS]; +}; + +struct tag_stat { + struct rb_node node; + tag_t tag; + + struct data_counters counters; + /* If this tag is acct_tag based, we need to count against the + * matching parent uid_tag. */ + struct data_counters *parent_counters; + struct proc_dir_entry *proc_ptr; +}; + +struct iface_stat { + struct list_head list; + char *ifname; + uint64_t rx_bytes; + uint64_t rx_packets; + uint64_t tx_bytes; + uint64_t tx_packets; + bool active; + struct proc_dir_entry *proc_ptr; + + struct rb_root tag_stat_tree; + spinlock_t tag_stat_list_lock; +}; + +static LIST_HEAD(iface_stat_list); +static DEFINE_SPINLOCK(iface_stat_list_lock); + +/* + * Track tag that this socket is transferring data for, and not necesseraly + * the uid that owns the socket. + * This is the tag against which tag_stat.counters will be billed. + */ +struct sock_tag { + struct rb_node node; + struct sock *sk; + tag_t tag; +}; + +static struct rb_root sock_tag_tree = RB_ROOT; +static DEFINE_SPINLOCK(sock_tag_list_lock); + +static bool qtaguid_mt(const struct sk_buff *skb, struct xt_action_param *par); + +/*----------------------------------------------*/ +static inline int tag_compare(tag_t t1, tag_t t2) +{ + return t1 < t2 ? -1 : t1 == t2 ? 0 : 1; +} + + +static inline tag_t combine_atag_with_uid(tag_t acct_tag, uid_t uid) +{ + return acct_tag | uid; +} +static inline tag_t make_tag_from_uid(uid_t uid) +{ + return uid; +} +static inline uid_t get_uid_from_tag(tag_t tag) +{ + return tag & 0xFFFFFFFFULL; +} +static inline tag_t get_utag_from_tag(tag_t tag) +{ + return tag & 0xFFFFFFFFULL; +} +static inline tag_t get_atag_from_tag(tag_t tag) +{ + return tag & ~0xFFFFFFFFULL; +} + +static inline bool valid_atag(tag_t tag) +{ + return !(tag & 0xFFFFFFFFULL); +} + +static inline void dc_add_byte_packets(struct data_counters *counters, + enum ifs_tx_rx direction, + enum ifs_proto ifs_proto, + int bytes, + int packets) +{ + counters->bpc[direction][ifs_proto].bytes += bytes; + counters->bpc[direction][ifs_proto].packets += packets; +} + +static inline uint64_t dc_sum_bytes(struct data_counters *counters, + enum ifs_tx_rx direction) +{ + return counters->bpc[direction][IFS_TCP].bytes + + counters->bpc[direction][IFS_UDP].bytes + + counters->bpc[direction][IFS_PROTO_OTHER].bytes; +} + +static inline uint64_t dc_sum_packets(struct data_counters *counters, + enum ifs_tx_rx direction) +{ + return counters->bpc[direction][IFS_TCP].packets + + counters->bpc[direction][IFS_UDP].packets + + counters->bpc[direction][IFS_PROTO_OTHER].packets; +} + +static struct tag_stat *tag_stat_tree_search(struct rb_root *root, tag_t tag) +{ + struct rb_node *node = root->rb_node; + + while (node) { + struct tag_stat *data = rb_entry(node, struct tag_stat, node); + int result = tag_compare(tag, data->tag); + pr_debug("qtaguid: tag_stat_tree_search(): tag=0x%llx" + " (uid=%d)\n", + data->tag, + get_uid_from_tag(data->tag)); + + if (result < 0) + node = node->rb_left; + else if (result > 0) + node = node->rb_right; + else + return data; + } + return NULL; +} + +static void tag_stat_tree_insert(struct tag_stat *data, struct rb_root *root) +{ + struct rb_node **new = &(root->rb_node), *parent = NULL; + + /* Figure out where to put new node */ + while (*new) { + struct tag_stat *this = rb_entry(*new, struct tag_stat, + node); + int result = tag_compare(data->tag, this->tag); + pr_debug("qtaguid: tag_stat_tree_insert(): tag=0x%llx" + " (uid=%d)\n", + this->tag, + get_uid_from_tag(this->tag)); + parent = *new; + if (result < 0) + new = &((*new)->rb_left); + else if (result > 0) + new = &((*new)->rb_right); + else + BUG(); + } + + /* Add new node and rebalance tree. */ + rb_link_node(&data->node, parent, new); + rb_insert_color(&data->node, root); +} + +static struct sock_tag *sock_tag_tree_search(struct rb_root *root, + const struct sock *sk) +{ + struct rb_node *node = root->rb_node; + + while (node) { + struct sock_tag *data = rb_entry(node, struct sock_tag, node); + ptrdiff_t result = sk - data->sk; + if (result < 0) + node = node->rb_left; + else if (result > 0) + node = node->rb_right; + else + return data; + } + return NULL; +} + +static void sock_tag_tree_insert(struct sock_tag *data, struct rb_root *root) +{ + struct rb_node **new = &(root->rb_node), *parent = NULL; + + /* Figure out where to put new node */ + while (*new) { + struct sock_tag *this = rb_entry(*new, struct sock_tag, node); + ptrdiff_t result = data->sk - this->sk; + parent = *new; + if (result < 0) + new = &((*new)->rb_left); + else if (result > 0) + new = &((*new)->rb_right); + else + BUG(); + } + + /* Add new node and rebalance tree. */ + rb_link_node(&data->node, parent, new); + rb_insert_color(&data->node, root); +} + +static int read_proc_u64(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len; + uint64_t value; + char *p = page; + uint64_t *iface_entry = data; + if (!data) + return 0; + + value = *iface_entry; + p += sprintf(p, "%llu\n", value); + len = (p - page) - off; + *eof = (len <= count) ? 1 : 0; + *start = page + off; + return len; +} + +static int read_proc_bool(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len; + bool value; + char *p = page; + bool *bool_entry = data; + if (!data) + return 0; + + value = *bool_entry; + p += sprintf(p, "%u\n", value); + len = (p - page) - off; + *eof = (len <= count) ? 1 : 0; + *start = page + off; + return len; +} + +/* Find the entry for tracking the specified interface. */ +static struct iface_stat *get_iface_stat(const char *ifname) +{ + unsigned long flags; + struct iface_stat *iface_entry; + if (!ifname) + return NULL; + + spin_lock_irqsave(&iface_stat_list_lock, flags); + list_for_each_entry(iface_entry, &iface_stat_list, list) { + if (!strcmp(iface_entry->ifname, ifname)) + goto done; + } + iface_entry = NULL; +done: + spin_unlock_irqrestore(&iface_stat_list_lock, flags); + return iface_entry; +} + +/* + * Create a new entry for tracking the specified interface. + * Do nothing if the entry already exists. + * Called when an interface is configured with a valid IP address. + */ +void iface_stat_create(const struct net_device *net_dev) +{ + struct in_device *in_dev; + unsigned long flags; + struct iface_stat *new_iface; + struct proc_dir_entry *proc_entry; + const char *ifname; + struct iface_stat *entry; + __be32 ipaddr = 0; + struct in_ifaddr *ifa = NULL; + + ASSERT_RTNL(); /* No need for separate locking */ + + pr_debug("iface_stat: create(): netdev=%p->name=%s\n", + net_dev, net_dev ? net_dev->name : ""); + if (!net_dev) { + pr_err("iface_stat: create(): no net dev!\n"); + return; + } + + in_dev = __in_dev_get_rtnl(net_dev); + if (!in_dev) { + pr_err("iface_stat: create(): no inet dev!\n"); + return; + } + + pr_debug("iface_stat: create(): in_dev=%p\n", in_dev); + ifname = net_dev->name; + pr_debug("iface_stat: create(): ifname=%p\n", ifname); + for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) { + pr_debug("iface_stat: create(): for(): ifa=%p ifname=%p\n", + ifa, ifname); + pr_debug("iface_stat: create(): ifname=%s ifa_label=%s\n", + ifname, ifa->ifa_label ? ifa->ifa_label : "(null)"); + if (ifa->ifa_label && !strcmp(ifname, ifa->ifa_label)) + break; + } + + if (ifa) { + ipaddr = ifa->ifa_local; + } else { + pr_err("iface_stat: create(): dev %s has no matching IP\n", + ifname); + return; + } + + entry = get_iface_stat(net_dev->name); + if (entry != NULL) { + pr_debug("iface_stat: create(): dev %s entry=%p\n", ifname, + entry); + if (ipv4_is_loopback(ipaddr)) { + entry->active = false; + pr_debug("iface_stat: create(): disable tracking of " + "loopback dev %s\n", ifname); + } else { + entry->active = true; + pr_debug("iface_stat: create(): enable tracking of " + "dev %s with ip=%pI4\n", + ifname, &ipaddr); + } + return; + } else if (ipv4_is_loopback(ipaddr)) { + pr_debug("iface_stat: create(): ignore loopback dev %s" + " ip=%pI4\n", ifname, &ipaddr); + return; + } + + new_iface = kzalloc(sizeof(*new_iface), GFP_KERNEL); + if (new_iface == NULL) { + pr_err("iface_stat: create(): failed to alloc iface_stat\n"); + return; + } + new_iface->ifname = kstrdup(ifname, GFP_KERNEL); + if (new_iface->ifname == NULL) { + pr_err("iface_stat: create(): failed to alloc ifname\n"); + kfree(new_iface); + return; + } + spin_lock_init(&new_iface->tag_stat_list_lock); + + new_iface->active = true; + + new_iface->tag_stat_tree = RB_ROOT; + spin_lock_irqsave(&iface_stat_list_lock, flags); + list_add(&new_iface->list, &iface_stat_list); + spin_unlock_irqrestore(&iface_stat_list_lock, flags); + + proc_entry = proc_mkdir(ifname, iface_stat_procdir); + new_iface->proc_ptr = proc_entry; + + /* TODO: make root access only */ + create_proc_read_entry("tx_bytes", proc_iface_perms, proc_entry, + read_proc_u64, &new_iface->tx_bytes); + create_proc_read_entry("rx_bytes", proc_iface_perms, proc_entry, + read_proc_u64, &new_iface->rx_bytes); + create_proc_read_entry("tx_packets", proc_iface_perms, proc_entry, + read_proc_u64, &new_iface->tx_packets); + create_proc_read_entry("rx_packets", proc_iface_perms, proc_entry, + read_proc_u64, &new_iface->rx_packets); + create_proc_read_entry("active", proc_iface_perms, proc_entry, + read_proc_bool, &new_iface->active); + + pr_debug("iface_stat: create(): done entry=%p dev=%s ip=%pI4\n", + new_iface, ifname, &ipaddr); +} + +static struct sock_tag *get_sock_stat_nl(const struct sock *sk) +{ + pr_debug("xt_qtaguid: get_sock_stat_nl(sk=%p)\n", sk); + return sock_tag_tree_search(&sock_tag_tree, sk); +} + +static struct sock_tag *get_sock_stat(const struct sock *sk) +{ + unsigned long flags; + struct sock_tag *sock_tag_entry; + pr_debug("xt_qtaguid: get_sock_stat(sk=%p)\n", sk); + if (!sk) + return NULL; + spin_lock_irqsave(&sock_tag_list_lock, flags); + sock_tag_entry = get_sock_stat_nl(sk); + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + return sock_tag_entry; +} + +static void +data_counters_update(struct data_counters *dc, enum ifs_tx_rx direction, + int proto, int bytes) +{ + switch (proto) { + case IPPROTO_TCP: + dc_add_byte_packets(dc, direction, IFS_TCP, bytes, 1); + break; + case IPPROTO_UDP: + dc_add_byte_packets(dc, direction, IFS_UDP, bytes, 1); + break; + case IPPROTO_IP: + default: + dc_add_byte_packets(dc, direction, IFS_PROTO_OTHER, bytes, 1); + break; + } +} + + +/* + * Update stats for the specified interface. Do nothing if the entry + * does not exist (when a device was never configured with an IP address). + * Called when an device is being unregistered. + */ +void iface_stat_update(struct net_device *dev) +{ + struct rtnl_link_stats64 dev_stats, *stats; + struct iface_stat *entry; + stats = dev_get_stats(dev, &dev_stats); + ASSERT_RTNL(); + + entry = get_iface_stat(dev->name); + if (entry == NULL) { + pr_debug("iface_stat: dev %s monitor not found\n", dev->name); + return; + } + if (entry->active) { + entry->tx_bytes += stats->tx_bytes; + entry->tx_packets += stats->tx_packets; + entry->rx_bytes += stats->rx_bytes; + entry->rx_packets += stats->rx_packets; + entry->active = false; + pr_debug("iface_stat: Updating stats for " + "dev %s which went down\n", dev->name); + } else { + pr_debug("iface_stat: Did not update stats for " + "dev %s which went down\n", dev->name); + } +} + + +static void tag_stat_update(struct tag_stat *tag_entry, + enum ifs_tx_rx direction, int proto, int bytes) +{ + pr_debug("xt_qtaguid: tag_stat_update(tag=0x%llx (uid=%d) dir=%d " + "proto=%d bytes=%d)\n", + tag_entry->tag, get_uid_from_tag(tag_entry->tag), direction, + proto, bytes); + data_counters_update(&tag_entry->counters, direction, proto, bytes); + if (tag_entry->parent_counters) + data_counters_update(tag_entry->parent_counters, direction, + proto, bytes); +} + + +/* Create a new entry for tracking the specified {acct_tag,uid_tag} within + * the interface. + * iface_entry->tag_stat_list_lock should be held. */ +static struct tag_stat *create_if_tag_stat(struct iface_stat *iface_entry, + tag_t tag) +{ + struct tag_stat *new_tag_stat_entry = NULL; + pr_debug("iface_stat: create_if_tag_stat(): ife=%p tag=0x%llx" + " (uid=%d)\n", + iface_entry, tag, get_uid_from_tag(tag)); + new_tag_stat_entry = kzalloc(sizeof(*new_tag_stat_entry), GFP_ATOMIC); + if (!new_tag_stat_entry) { + pr_err("iface_stat: failed to alloc new tag entry\n"); + goto done; + } + new_tag_stat_entry->tag = tag; + tag_stat_tree_insert(new_tag_stat_entry, &iface_entry->tag_stat_tree); +done: + return new_tag_stat_entry; +} + +static struct iface_stat *get_iface_entry(const char *ifname) +{ + struct iface_stat *iface_entry; + unsigned long flags; + + /* Find the entry for tracking the specified tag within the interface */ + if (ifname == NULL) { + pr_info("iface_stat: NULL device name\n"); + return NULL; + } + + + /* Iterate over interfaces */ + spin_lock_irqsave(&iface_stat_list_lock, flags); + list_for_each_entry(iface_entry, &iface_stat_list, list) { + if (!strcmp(ifname, iface_entry->ifname)) + goto done; + } + iface_entry = NULL; +done: + spin_unlock_irqrestore(&iface_stat_list_lock, flags); + return iface_entry; +} + +static void if_tag_stat_update(const char *ifname, uid_t uid, + const struct sock *sk, enum ifs_tx_rx direction, + int proto, int bytes) +{ + struct tag_stat *tag_stat_entry; + tag_t tag, acct_tag; + tag_t uid_tag; + struct data_counters *uid_tag_counters; + struct sock_tag *sock_tag_entry; + struct iface_stat *iface_entry; + unsigned long flags; + struct tag_stat *new_tag_stat; + pr_debug("xt_qtaguid: if_tag_stat_update(ifname=%s " + "uid=%d sk=%p dir=%d proto=%d bytes=%d)\n", + ifname, uid, sk, direction, proto, bytes); + + + iface_entry = get_iface_entry(ifname); + if (!iface_entry) { + pr_err("iface_stat: interface %s not found\n", ifname); + return; + } + /* else { If the iface_entry becomes inactive, it is still ok + * to process the data. } */ + + pr_debug("iface_stat: stat_update() got entry=%p\n", iface_entry); + + /* Look for a tagged sock. + * It will have an acct_uid. */ + sock_tag_entry = get_sock_stat(sk); + if (sock_tag_entry) { + tag = sock_tag_entry->tag; + acct_tag = get_atag_from_tag(tag); + uid_tag = get_utag_from_tag(tag); + } else { + uid_tag = make_tag_from_uid(uid); + acct_tag = 0; + tag = combine_atag_with_uid(acct_tag, uid); + } + pr_debug("iface_stat: stat_update(): looking for tag=0x%llx (uid=%d)" + " in ife=%p\n", + tag, get_uid_from_tag(tag), iface_entry); + /* Loop over tag list under this interface for {acct_tag,uid_tag} */ + spin_lock_irqsave(&iface_entry->tag_stat_list_lock, flags); + + tag_stat_entry = tag_stat_tree_search(&iface_entry->tag_stat_tree, + tag); + if (tag_stat_entry) { + /* Updating the {acct_tag, uid_tag} entry handles both stats: + * {0, uid_tag} will also get updated. */ + tag_stat_update(tag_stat_entry, direction, proto, bytes); + spin_unlock_irqrestore(&iface_entry->tag_stat_list_lock, flags); + return; + } + + /* Loop over tag list under this interface for {0,uid_tag} */ + tag_stat_entry = tag_stat_tree_search(&iface_entry->tag_stat_tree, + uid_tag); + if (!tag_stat_entry) { + /* Here: the base uid_tag did not exist */ + /* + * No parent counters. So + * - No {0, uid_tag} stats and no {acc_tag, uid_tag} stats. + */ + new_tag_stat = create_if_tag_stat(iface_entry, uid_tag); + uid_tag_counters = &new_tag_stat->counters; + } else { + uid_tag_counters = &tag_stat_entry->counters; + } + + if (acct_tag) { + new_tag_stat = create_if_tag_stat(iface_entry, tag); + new_tag_stat->parent_counters = uid_tag_counters; + } + spin_unlock_irqrestore(&iface_entry->tag_stat_list_lock, flags); + tag_stat_update(new_tag_stat, direction, proto, bytes); +} + +static int iface_netdev_event_handler(struct notifier_block *nb, + unsigned long event, void *ptr) { + struct net_device *dev = ptr; + + if (unlikely(module_passive)) + return NOTIFY_DONE; + + pr_debug("iface_stat: netdev_event(): ev=0x%lx netdev=%p->name=%s\n", + event, dev, dev ? dev->name : ""); + + switch (event) { + case NETDEV_UP: + case NETDEV_REBOOT: + case NETDEV_CHANGE: + case NETDEV_REGISTER: /* Most likely no IP */ + case NETDEV_CHANGEADDR: /* MAC addr change */ + case NETDEV_CHANGENAME: + case NETDEV_FEAT_CHANGE: /* Might be usefull when cell type changes */ + iface_stat_create(dev); + break; + case NETDEV_UNREGISTER: + iface_stat_update(dev); + break; + } + return NOTIFY_DONE; +} + +static int iface_inetaddr_event_handler(struct notifier_block *nb, + unsigned long event, void *ptr) { + + struct in_ifaddr *ifa = ptr; + struct in_device *in_dev = ifa->ifa_dev; + struct net_device *dev = in_dev->dev; + + if (unlikely(module_passive)) + return NOTIFY_DONE; + + pr_debug("iface_stat: inetaddr_event(): ev=0x%lx netdev=%p->name=%s\n", + event, dev, dev ? dev->name : ""); + + switch (event) { + case NETDEV_UP: + iface_stat_create(dev); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block iface_netdev_notifier_blk = { + .notifier_call = iface_netdev_event_handler, +}; + +static struct notifier_block iface_inetaddr_notifier_blk = { + .notifier_call = iface_inetaddr_event_handler, +}; + +static int __init iface_stat_init(struct proc_dir_entry *parent_procdir) +{ + int err; + + iface_stat_procdir = proc_mkdir(iface_stat_procdirname, parent_procdir); + if (!iface_stat_procdir) { + pr_err("iface_stat: failed to create proc entry\n"); + err = -1; + goto err; + } + err = register_netdevice_notifier(&iface_netdev_notifier_blk); + if (err) { + pr_err("iface_stat: failed to register dev event handler\n"); + goto err_unreg_nd; + } + err = register_inetaddr_notifier(&iface_inetaddr_notifier_blk); + if (err) { + pr_err("iface_stat: failed to register dev event handler\n"); + goto err_zap_entry; + } + return 0; + +err_unreg_nd: + unregister_netdevice_notifier(&iface_netdev_notifier_blk); +err_zap_entry: + remove_proc_entry(iface_stat_procdirname, parent_procdir); +err: + return err; +} + +static struct sock *qtaguid_find_sk(const struct sk_buff *skb, + struct xt_action_param *par) +{ + struct sock *sk; + unsigned int hook_mask = (1 << par->hooknum); + + pr_debug("xt_qtaguid: find_sk(skb=%p) hooknum=%d family=%d\n", skb, + par->hooknum, par->family); + + /* Let's not abuse the the xt_socket_get*_sk(), or else it will + * return garbage SKs. */ + if (!(hook_mask & XT_SOCKET_SUPPORTED_HOOKS)) + return NULL; + + switch (par->family) { + case NFPROTO_IPV6: + sk = xt_socket_get6_sk(skb, par); + break; + case NFPROTO_IPV4: + sk = xt_socket_get4_sk(skb, par); + break; + default: + return NULL; + } + + /* Seems to be issues on the file ptr for TCP_TIME_WAIT SKs. + * http://kerneltrap.org/mailarchive/linux-netdev/2010/10/21/6287959 + * Not fixed in 3.0-r3 :( + */ + if (sk) { + pr_debug("xt_qtaguid: %p->sk_proto=%u " + "->sk_state=%d\n", sk, sk->sk_protocol, sk->sk_state); + if (sk->sk_state == TCP_TIME_WAIT) { + xt_socket_put_sk(sk); + sk = NULL; + } + } + return sk; +} + +static void account_for_uid(const struct sk_buff *skb, + const struct sock *alternate_sk, uid_t uid, + struct xt_action_param *par) +{ + const struct net_device *el_dev; + + if (!skb->dev) { + pr_debug("xt_qtaguid[%d]: no skb->dev\n", par->hooknum); + el_dev = par->in ? : par->out; + } else { + const struct net_device *other_dev; + el_dev = skb->dev; + other_dev = par->in ? : par->out; + if (el_dev != other_dev) { + pr_debug("xt_qtaguid[%d]: skb->dev=%p %s vs " + "par->(in/out)=%p %s\n", + par->hooknum, el_dev, el_dev->name, other_dev, + other_dev->name); + } + } + + if (unlikely(!el_dev)) { + pr_info("xt_qtaguid[%d]: no par->in/out?!!\n", par->hooknum); + } else if (unlikely(!el_dev->name)) { + pr_info("xt_qtaguid[%d]: no dev->name?!!\n", par->hooknum); + } else { + pr_debug("xt_qtaguid[%d]: dev name=%s type=%d\n", + par->hooknum, + el_dev->name, + el_dev->type); + + if_tag_stat_update(el_dev->name, uid, + skb->sk ? skb->sk : alternate_sk, + par->in ? IFS_RX : IFS_TX, + ip_hdr(skb)->protocol, skb->len); + } +} + +static bool qtaguid_mt(const struct sk_buff *skb, struct xt_action_param *par) +{ + const struct xt_qtaguid_match_info *info = par->matchinfo; + const struct file *filp; + bool got_sock = false; + struct sock *sk; + uid_t sock_uid; + bool res; + + pr_debug("xt_qtaguid[%d]: entered skb=%p par->in=%p/out=%p fam=%d\n", + par->hooknum, skb, par->in, par->out, par->family); + + if (skb == NULL) { + res = (info->match ^ info->invert) == 0; + goto ret_res; + } + + sk = skb->sk; + + if (sk == NULL) { + /* A missing sk->sk_socket happens when packets are in-flight + * and the matching socket is already closed and gone. + */ + sk = qtaguid_find_sk(skb, par); + /* If we got the socket from the find_sk(), we will need to put + * it back, as nf_tproxy_get_sock_v4() got it. */ + got_sock = sk; + } + pr_debug("xt_qtaguid[%d]: sk=%p got_sock=%d proto=%d\n", + par->hooknum, sk, got_sock, ip_hdr(skb)->protocol); + if (sk != NULL) { + pr_debug("xt_qtaguid[%d]: sk=%p->sk_socket=%p->file=%p\n", + par->hooknum, sk, sk->sk_socket, + sk->sk_socket ? sk->sk_socket->file : (void *)-1LL); + filp = sk->sk_socket ? sk->sk_socket->file : NULL; + pr_debug("xt_qtaguid[%d]: filp...uid=%d\n", + par->hooknum, filp ? filp->f_cred->fsuid : -1); + } + + if (sk == NULL || sk->sk_socket == NULL) { + /* Here, the qtaguid_find_sk() using connection tracking + * couldn't find the owner, so for now we just count them + * against the system. */ + /* TODO: unhack how to force just accounting. + * For now we only do iface stats when the uid-owner is not + * requested */ + if (!(info->match & XT_QTAGUID_UID)) + account_for_uid(skb, sk, 0, par); + pr_debug("xt_qtaguid[%d]: leaving (sk?sk->sk_socket)=%p\n", + par->hooknum, + sk ? sk->sk_socket : NULL); + res = (info->match ^ info->invert) == 0; + goto put_sock_ret_res; + } else if (info->match & info->invert & XT_QTAGUID_SOCKET) { + res = false; + goto put_sock_ret_res; + } + filp = sk->sk_socket->file; + if (filp == NULL) { + pr_debug("xt_qtaguid[%d]: leaving filp=NULL\n", par->hooknum); + res = ((info->match ^ info->invert) & + (XT_QTAGUID_UID | XT_QTAGUID_GID)) == 0; + goto put_sock_ret_res; + } + sock_uid = filp->f_cred->fsuid; + /* TODO: unhack how to force just accounting. + * For now we only do iface stats when the uid-owner is not requested */ + if (!(info->match & XT_QTAGUID_UID)) + account_for_uid(skb, sk, sock_uid, par); + + /* The following two tests fail the match when: + * id not in range AND no inverted condition requested + * or id in range AND inverted condition requested + * Thus (!a && b) || (a && !b) == a ^ b + */ + if (info->match & XT_QTAGUID_UID) + if ((filp->f_cred->fsuid >= info->uid_min && + filp->f_cred->fsuid <= info->uid_max) ^ + !(info->invert & XT_QTAGUID_UID)) { + pr_debug("xt_qtaguid[%d]: leaving uid not matching\n", + par->hooknum); + res = false; + goto put_sock_ret_res; + } + if (info->match & XT_QTAGUID_GID) + if ((filp->f_cred->fsgid >= info->gid_min && + filp->f_cred->fsgid <= info->gid_max) ^ + !(info->invert & XT_QTAGUID_GID)) { + pr_debug("xt_qtaguid[%d]: leaving gid not matching\n", + par->hooknum); + res = false; + goto put_sock_ret_res; + } + + pr_debug("xt_qtaguid[%d]: leaving matched\n", par->hooknum); + res = true; + +put_sock_ret_res: + if (got_sock) + xt_socket_put_sk(sk); +ret_res: + pr_debug("xt_qtaguid[%d]: left %d\n", par->hooknum, res); + return res; +} + +/* + * Procfs reader to get all active socket tags using style "1)" as described in + * fs/proc/generic.c + */ +static int qtaguid_ctrl_proc_read(char *page, char **num_items_returned, + off_t items_to_skip, int char_count, int *eof, + void *data) +{ + char *outp = page; + int len; + unsigned long flags; + uid_t uid; + struct sock_tag *sock_tag_entry; + struct rb_node *node; + int item_index = 0; + + if (unlikely(module_passive)) { + *eof = 1; + return 0; + } + + pr_debug("xt_qtaguid: proc ctrl page=%p off=%ld char_count=%d *eof=%d\n", + page, items_to_skip, char_count, *eof); + + if (*eof) + return 0; + + spin_lock_irqsave(&sock_tag_list_lock, flags); + for (node = rb_first(&sock_tag_tree); + node; + node = rb_next(node)) { + if (item_index++ < items_to_skip) + continue; + sock_tag_entry = rb_entry(node, struct sock_tag, node); + uid = get_uid_from_tag(sock_tag_entry->tag); + pr_debug("xt_qtaguid: proc_read(): sk=%p tag=0x%llx (uid=%d)\n", + sock_tag_entry->sk, + sock_tag_entry->tag, + uid); + len = snprintf(outp, char_count, + "sock=%p tag=0x%llx (uid=%u)\n", + sock_tag_entry->sk, sock_tag_entry->tag, uid); + if (len >= char_count) { + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + *outp = '\0'; + return outp - page; + } + outp += len; + char_count -= len; + (*num_items_returned)++; + } + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + *eof = 1; + return outp - page; +} + +int can_impersonate_uid(uid_t uid) +{ + return uid == current_fsuid() + || !proc_ctrl_write_gid + || in_egroup_p(proc_ctrl_write_gid); +} + +int can_read_other_uid_stats(uid_t uid) +{ + return uid == current_fsuid() + || !proc_ctrl_write_gid + || in_egroup_p(proc_stats_readall_gid); +} + +/* Delete socket tags, and stat tags associated with a given + * accouting tag and uid. */ +static int ctrl_cmd_delete(const char *input) +{ + char cmd; + uid_t uid = 0; + uid_t entry_uid; + tag_t acct_tag = 0; + tag_t tag; + int res, argc; + unsigned long flags, flags2; + struct iface_stat *iface_entry; + struct rb_node *node; + struct sock_tag *st_entry; + struct tag_stat *ts_entry; + + pr_debug("xt_qtaguid: ctrl_delete(%s): entered\n", input); + argc = sscanf(input, "%c %llu %u", &cmd, &acct_tag, &uid); + pr_debug("xt_qtaguid: ctrl_delete(%s): argc=%d cmd=%c " + "acct_tag=0x%llx uid=%u\n", input, argc, cmd, + acct_tag, uid); + if (argc < 2) { + res = -EINVAL; + goto err; + } + if (!valid_atag(acct_tag)) { + pr_info("xt_qtaguid: ctrl_delete(%s): invalid tag\n", input); + res = -EINVAL; + goto err; + } + if (argc < 3) { + uid = current_fsuid(); + } else if (!can_impersonate_uid(uid)) { + pr_info("xt_qtaguid: ctrl_delete(%s): insuficient priv\n", + input); + res = -EPERM; + goto err; + } + + spin_lock_irqsave(&sock_tag_list_lock, flags); + node = rb_first(&sock_tag_tree); + while (node) { + st_entry = rb_entry(node, struct sock_tag, node); + entry_uid = get_uid_from_tag(st_entry->tag); + node = rb_next(node); + if (entry_uid != uid) + continue; + + if (!acct_tag || st_entry->tag == tag) { + pr_debug("xt_qtaguid: ctrl_delete(): " + "erase sk=%p tag=0x%llx (uid=%d)\n", + st_entry->sk, + st_entry->tag, + entry_uid); + rb_erase(&ts_entry->node, &sock_tag_tree); + kfree(st_entry); + } + } + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + + /* If acct_tag is 0, then all entries belonging to uid are + * erased. */ + tag = combine_atag_with_uid(acct_tag, uid); + spin_lock_irqsave(&iface_stat_list_lock, flags); + list_for_each_entry(iface_entry, &iface_stat_list, list) { + + spin_lock_irqsave(&iface_entry->tag_stat_list_lock, flags2); + node = rb_first(&iface_entry->tag_stat_tree); + while (node) { + ts_entry = rb_entry(node, struct tag_stat, node); + entry_uid = get_uid_from_tag(ts_entry->tag); + node = rb_next(node); + if (entry_uid != uid) + continue; + if (!acct_tag || ts_entry->tag == tag) { + pr_debug("xt_qtaguid: ctrl_delete(): erase " + "%s 0x%llx %u\n", + iface_entry->ifname, + get_atag_from_tag(ts_entry->tag), + entry_uid); + rb_erase(&ts_entry->node, + &iface_entry->tag_stat_tree); + kfree(ts_entry); + } + } + spin_unlock_irqrestore(&iface_entry->tag_stat_list_lock, + flags2); + + } + spin_unlock_irqrestore(&iface_stat_list_lock, flags); + + res = 0; + +err: + pr_debug("xt_qtaguid: ctrl_delete(%s) res=%d\n", input, res); + return res; +} + + +static int ctrl_cmd_tag(const char *input) +{ + char cmd; + int sock_fd = 0; + uid_t uid = 0; + tag_t acct_tag = 0; + struct socket *el_socket; + int res, argc; + struct sock_tag *sock_tag_entry; + unsigned long flags; + + /* Unassigned args will get defaulted later. */ + argc = sscanf(input, "%c %d %llu %u", &cmd, &sock_fd, &acct_tag, &uid); + pr_debug("xt_qtaguid: ctrl_tag(%s): argc=%d cmd=%c sock_fd=%d " + "acct_tag=0x%llx uid=%u\n", input, argc, cmd, sock_fd, + acct_tag, uid); + if (argc < 2) { + res = -EINVAL; + goto err; + } + el_socket = sockfd_lookup(sock_fd, &res); + if (!el_socket) { + pr_info("xt_qtaguid: ctrl_tag(%s): failed to lookup" + " sock_fd=%d err=%d\n", input, sock_fd, res); + goto err; + } + if (argc < 3) { + acct_tag = 0; + } else if (!valid_atag(acct_tag)) { + pr_info("xt_qtaguid: ctrl_tag(%s): invalid tag\n", input); + res = -EINVAL; + goto err; + } + if (argc < 4) { + uid = current_fsuid(); + } else if (!can_impersonate_uid(uid)) { + pr_info("xt_qtaguid: ctrl_tag(%s): insuficient priv\n", + input); + res = -EPERM; + goto err; + } + + spin_lock_irqsave(&sock_tag_list_lock, flags); + sock_tag_entry = get_sock_stat_nl(el_socket->sk); + if (sock_tag_entry) { + sock_tag_entry->tag = combine_atag_with_uid(acct_tag, + uid); + } else { + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + sock_tag_entry = kzalloc(sizeof(*sock_tag_entry), + GFP_KERNEL); + if (!sock_tag_entry) { + res = -ENOMEM; + goto err; + } + sock_tag_entry->sk = el_socket->sk; + sock_tag_entry->tag = combine_atag_with_uid(acct_tag, + uid); + spin_lock_irqsave(&sock_tag_list_lock, flags); + sock_tag_tree_insert(sock_tag_entry, &sock_tag_tree); + } + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + + pr_debug("xt_qtaguid: tag: sock_tag_entry->sk=%p " + "...->tag=0x%llx (uid=%u)\n", + sock_tag_entry->sk, sock_tag_entry->tag, + get_uid_from_tag(sock_tag_entry->tag)); + res = 0; + +err: + pr_debug("xt_qtaguid: ctrl_tag(%s) res=%d\n", input, res); + return res; +} + + +static int ctrl_cmd_untag(const char *input) +{ + char cmd; + int sock_fd = 0; + struct socket *el_socket; + int res, argc; + struct sock_tag *sock_tag_entry; + unsigned long flags; + + pr_debug("xt_qtaguid: ctrl_untag(%s): entered\n", input); + argc = sscanf(input, "%c %d", &cmd, &sock_fd); + pr_debug("xt_qtaguid: ctrl_untag(%s): argc=%d cmd=%c sock_fd=%d\n", + input, argc, cmd, sock_fd); + if (argc < 2) { + res = -EINVAL; + goto err; + } + el_socket = sockfd_lookup(sock_fd, &res); + if (!el_socket) { + pr_info("xt_qtaguid: ctrl_untag(%s): failed to lookup" + " sock_fd=%d err=%d\n", input, sock_fd, res); + goto err; + } + spin_lock_irqsave(&sock_tag_list_lock, flags); + sock_tag_entry = get_sock_stat_nl(el_socket->sk); + if (!sock_tag_entry) { + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + res = -EINVAL; + goto err; + } + /* The socket already belongs to the current process + * so it can do whatever it wants to it. */ + rb_erase(&sock_tag_entry->node, &sock_tag_tree); + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + kfree(sock_tag_entry); + + res = 0; +err: + pr_debug("xt_qtaguid: ctrl_untag(%s): res=%d\n", input, res); + return res; +} + +static int qtaguid_ctrl_parse(const char *input, int count) +{ + char cmd; + int res; + + pr_debug("xt_qtaguid: ctrl(%s): entered\n", input); + cmd = input[0]; + /* Collect params for commands */ + switch (cmd) { + case 'd': + res = ctrl_cmd_delete(input); + break; + + case 't': + res = ctrl_cmd_tag(input); + break; + + case 'u': + res = ctrl_cmd_untag(input); + break; + + default: + res = -EINVAL; + goto err; + } + if (!res) + res = count; +err: + pr_debug("xt_qtaguid: ctrl(%s): res=%d\n", input, res); + return res; +} + +#define MAX_QTAGUID_CTRL_INPUT_LEN 255 +static int qtaguid_ctrl_proc_write(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char input_buf[MAX_QTAGUID_CTRL_INPUT_LEN]; + + if (unlikely(module_passive)) + return count; + + if (count >= MAX_QTAGUID_CTRL_INPUT_LEN) + return -EINVAL; + + if (copy_from_user(input_buf, buffer, count)) + return -EFAULT; + + input_buf[count] = '\0'; + return qtaguid_ctrl_parse(input_buf, count); +} + +static int print_stats_line(char *outp, int char_count, int item_index, + char *ifname, tag_t tag, + struct data_counters *counters) +{ + int len; + if (!item_index) { + len = snprintf(outp, char_count, + "idx iface acct_tag_hex uid_tag_int " + "rx_bytes rx_packets " + "tx_bytes tx_packets " + "rx_tcp_packets rx_tcp_bytes " + "rx_udp_packets rx_udp_bytes " + "rx_other_packets rx_other_bytes " + "tx_tcp_packets tx_tcp_bytes " + "tx_udp_packets tx_udp_bytes " + "tx_other_packets tx_other_bytes\n"); + } else { + uid_t stat_uid = get_uid_from_tag(tag); + if (!can_read_other_uid_stats(stat_uid)) { + pr_debug("xt_qtaguid: insufficient priv for stat line:" + "%s 0x%llx %u\n", + ifname, get_atag_from_tag(tag), stat_uid); + return 0; + } + len = snprintf(outp, char_count, + "%d %s 0x%llx %u " + "%llu %llu " + "%llu %llu " + "%llu %llu " + "%llu %llu " + "%llu %llu " + "%llu %llu " + "%llu %llu " + "%llu %llu\n", + item_index, + ifname, + get_atag_from_tag(tag), + stat_uid, + dc_sum_bytes(counters, IFS_RX), + dc_sum_packets(counters, IFS_RX), + dc_sum_bytes(counters, IFS_TX), + dc_sum_packets(counters, IFS_TX), + counters->bpc[IFS_RX][IFS_TCP].bytes, + counters->bpc[IFS_RX][IFS_TCP].packets, + counters->bpc[IFS_RX][IFS_UDP].bytes, + counters->bpc[IFS_RX][IFS_UDP].packets, + counters->bpc[IFS_RX][IFS_PROTO_OTHER].bytes, + counters->bpc[IFS_RX][IFS_PROTO_OTHER].packets, + counters->bpc[IFS_TX][IFS_TCP].bytes, + counters->bpc[IFS_TX][IFS_TCP].packets, + counters->bpc[IFS_TX][IFS_UDP].bytes, + counters->bpc[IFS_TX][IFS_UDP].packets, + counters->bpc[IFS_TX][IFS_PROTO_OTHER].bytes, + counters->bpc[IFS_TX][IFS_PROTO_OTHER].packets); + } + return len; +} + + +/* + * Procfs reader to get all tag stats using style "1)" as described in + * fs/proc/generic.c + * Groups all protocols tx/rx bytes. + */ +static int qtaguid_stats_proc_read(char *page, char **num_items_returned, + off_t items_to_skip, int char_count, int *eof, + void *data) +{ + char *outp = page; + int len; + unsigned long flags, flags2; + struct iface_stat *iface_entry; + struct tag_stat *ts_entry; + int item_index = 0; + + if (unlikely(module_passive)) { + *eof = 1; + return 0; + } + + pr_debug("xt_qtaguid:proc stats page=%p *num_items_returned=%p off=%ld " + "char_count=%d *eof=%d\n", page, *num_items_returned, + items_to_skip, char_count, *eof); + + if (*eof) + return 0; + + if (!items_to_skip) { + /* The idx is there to help debug when things go belly up. */ + len = print_stats_line(outp, char_count, /*index*/0, NULL, + make_tag_from_uid(0), NULL); + /* Don't advance the outp unless the whole line was printed */ + if (len >= char_count) { + *outp = '\0'; + return outp - page; + } + outp += len; + char_count -= len; + } + spin_lock_irqsave(&iface_stat_list_lock, flags); + list_for_each_entry(iface_entry, &iface_stat_list, list) { + struct rb_node *node; + spin_lock_irqsave(&iface_entry->tag_stat_list_lock, flags2); + for (node = rb_first(&iface_entry->tag_stat_tree); + node; + node = rb_next(node)) { + ts_entry = rb_entry(node, struct tag_stat, node); + if (item_index++ < items_to_skip) + continue; + len = print_stats_line(outp, char_count, + item_index, + iface_entry->ifname, + ts_entry->tag, + &ts_entry->counters); + if (len >= char_count) { + *outp = '\0'; + spin_unlock_irqrestore( + &iface_entry->tag_stat_list_lock, + flags2); + spin_unlock_irqrestore( + &iface_stat_list_lock, flags); + return outp - page; + } + if (len) { + outp += len; + char_count -= len; + (*num_items_returned)++; + } + } + spin_unlock_irqrestore(&iface_entry->tag_stat_list_lock, + flags2); + } + spin_unlock_irqrestore(&iface_stat_list_lock, flags); + + *eof = 1; + return outp - page; +} + +/*------------------------------------------*/ +static int __init qtaguid_proc_register(struct proc_dir_entry **res_procdir) +{ + int ret; + *res_procdir = proc_mkdir(module_procdirname, init_net.proc_net); + if (!*res_procdir) { + pr_err("xt_qtaguid: failed to create proc/.../xt_qtaguid\n"); + ret = -ENOMEM; + goto no_dir; + } + + xt_qtaguid_ctrl_file = create_proc_entry("ctrl", proc_ctrl_perms, + *res_procdir); + if (!xt_qtaguid_ctrl_file) { + pr_err("xt_qtaguid: failed to create xt_qtaguid/ctrl " + " file\n"); + ret = -ENOMEM; + goto no_ctrl_entry; + } + xt_qtaguid_ctrl_file->read_proc = qtaguid_ctrl_proc_read; + xt_qtaguid_ctrl_file->write_proc = qtaguid_ctrl_proc_write; + + xt_qtaguid_stats_file = create_proc_entry("stats", proc_stats_perms, + *res_procdir); + if (!xt_qtaguid_stats_file) { + pr_err("xt_qtaguid: failed to create xt_qtaguid/stats " + "file\n"); + ret = -ENOMEM; + goto no_stats_entry; + } + xt_qtaguid_stats_file->read_proc = qtaguid_stats_proc_read; + /* + * TODO: add support counter hacking + * xt_qtaguid_stats_file->write_proc = qtaguid_stats_proc_write; + */ + return 0; + +no_stats_entry: + remove_proc_entry("ctrl", *res_procdir); +no_ctrl_entry: + remove_proc_entry("xt_qtaguid", NULL); +no_dir: + return ret; +} + +static struct xt_match qtaguid_mt_reg __read_mostly = { + /* + * This module masquerades as the "owner" module so that iptables + * tools can deal with it. + */ + .name = "owner", + .revision = 1, + .family = NFPROTO_UNSPEC, + .match = qtaguid_mt, + .matchsize = sizeof(struct xt_qtaguid_match_info), + .me = THIS_MODULE, +}; + +static int __init qtaguid_mt_init(void) +{ + if (qtaguid_proc_register(&xt_qtaguid_procdir) + || iface_stat_init(xt_qtaguid_procdir) + || xt_register_match(&qtaguid_mt_reg)) + return -1; + return 0; +} + +/* TODO: allow unloading of the module. + * For now stats are permanent. + * Kconfig forces'y/n' and never an 'm'. + */ + +module_init(qtaguid_mt_init); +MODULE_AUTHOR("jpa <jpa@google.com>"); +MODULE_DESCRIPTION("Xtables: socket owner+tag matching and associated stats"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ipt_owner"); +MODULE_ALIAS("ip6t_owner"); +MODULE_ALIAS("ipt_qtaguid"); +MODULE_ALIAS("ip6t_qtaguid"); |