📄 ip_queue.c
字号:
/* * This is a module which is used for queueing IPv4 packets and * communicating with userspace via netlink. * * (C) 2000-2002 James Morris <jmorris@intercode.com.au> * (C) 2003-2005 Netfilter Core Team <coreteam@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. * * 2000-03-27: Simplified code (thanks to Andi Kleen for clues). * 2000-05-20: Fixed notifier problems (following Miguel Freitas' report). * 2000-06-19: Fixed so nfmark is copied to metadata (reported by Sebastian * Zander). * 2000-08-01: Added Nick Williams' MAC support. * 2002-06-25: Code cleanup. * 2005-01-10: Added /proc counter for dropped packets; fixed so * packets aren't delivered to user space if they're going * to be dropped. * 2005-05-26: local_bh_{disable,enable} around nf_reinject (Harald Welte) * */#include <linux/module.h>#include <linux/skbuff.h>#include <linux/init.h>#include <linux/ip.h>#include <linux/notifier.h>#include <linux/netdevice.h>#include <linux/netfilter.h>#include <linux/netfilter_ipv4/ip_queue.h>#include <linux/netfilter_ipv4/ip_tables.h>#include <linux/netlink.h>#include <linux/spinlock.h>#include <linux/sysctl.h>#include <linux/proc_fs.h>#include <linux/security.h>#include <net/sock.h>#include <net/route.h>#define IPQ_QMAX_DEFAULT 1024#define IPQ_PROC_FS_NAME "ip_queue"#define NET_IPQ_QMAX 2088#define NET_IPQ_QMAX_NAME "ip_queue_maxlen"struct ipq_queue_entry { struct list_head list; struct nf_info *info; struct sk_buff *skb;};typedef int (*ipq_cmpfn)(struct ipq_queue_entry *, unsigned long);static unsigned char copy_mode = IPQ_COPY_NONE;static unsigned int queue_maxlen = IPQ_QMAX_DEFAULT;static DEFINE_RWLOCK(queue_lock);static int peer_pid;static unsigned int copy_range;static unsigned int queue_total;static unsigned int queue_dropped = 0;static unsigned int queue_user_dropped = 0;static struct sock *ipqnl;static LIST_HEAD(queue_list);static DECLARE_MUTEX(ipqnl_sem);static voidipq_issue_verdict(struct ipq_queue_entry *entry, int verdict){ /* TCP input path (and probably other bits) assume to be called * from softirq context, not from syscall, like ipq_issue_verdict is * called. TCP input path deadlocks with locks taken from timer * softirq, e.g. We therefore emulate this by local_bh_disable() */ local_bh_disable(); nf_reinject(entry->skb, entry->info, verdict); local_bh_enable(); kfree(entry);}static inline void__ipq_enqueue_entry(struct ipq_queue_entry *entry){ list_add(&entry->list, &queue_list); queue_total++;}/* * Find and return a queued entry matched by cmpfn, or return the last * entry if cmpfn is NULL. */static inline struct ipq_queue_entry *__ipq_find_entry(ipq_cmpfn cmpfn, unsigned long data){ struct list_head *p; list_for_each_prev(p, &queue_list) { struct ipq_queue_entry *entry = (struct ipq_queue_entry *)p; if (!cmpfn || cmpfn(entry, data)) return entry; } return NULL;}static inline void__ipq_dequeue_entry(struct ipq_queue_entry *entry){ list_del(&entry->list); queue_total--;}static inline struct ipq_queue_entry *__ipq_find_dequeue_entry(ipq_cmpfn cmpfn, unsigned long data){ struct ipq_queue_entry *entry; entry = __ipq_find_entry(cmpfn, data); if (entry == NULL) return NULL; __ipq_dequeue_entry(entry); return entry;}static inline void__ipq_flush(int verdict){ struct ipq_queue_entry *entry; while ((entry = __ipq_find_dequeue_entry(NULL, 0))) ipq_issue_verdict(entry, verdict);}static inline int__ipq_set_mode(unsigned char mode, unsigned int range){ int status = 0; switch(mode) { case IPQ_COPY_NONE: case IPQ_COPY_META: copy_mode = mode; copy_range = 0; break; case IPQ_COPY_PACKET: copy_mode = mode; copy_range = range; if (copy_range > 0xFFFF) copy_range = 0xFFFF; break; default: status = -EINVAL; } return status;}static inline void__ipq_reset(void){ peer_pid = 0; net_disable_timestamp(); __ipq_set_mode(IPQ_COPY_NONE, 0); __ipq_flush(NF_DROP);}static struct ipq_queue_entry *ipq_find_dequeue_entry(ipq_cmpfn cmpfn, unsigned long data){ struct ipq_queue_entry *entry; write_lock_bh(&queue_lock); entry = __ipq_find_dequeue_entry(cmpfn, data); write_unlock_bh(&queue_lock); return entry;}static voidipq_flush(int verdict){ write_lock_bh(&queue_lock); __ipq_flush(verdict); write_unlock_bh(&queue_lock);}static struct sk_buff *ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp){ unsigned char *old_tail; size_t size = 0; size_t data_len = 0; struct sk_buff *skb; struct ipq_packet_msg *pmsg; struct nlmsghdr *nlh; read_lock_bh(&queue_lock); switch (copy_mode) { case IPQ_COPY_META: case IPQ_COPY_NONE: size = NLMSG_SPACE(sizeof(*pmsg)); data_len = 0; break; case IPQ_COPY_PACKET: if (entry->skb->ip_summed == CHECKSUM_HW && (*errp = skb_checksum_help(entry->skb, entry->info->outdev == NULL))) { read_unlock_bh(&queue_lock); return NULL; } if (copy_range == 0 || copy_range > entry->skb->len) data_len = entry->skb->len; else data_len = copy_range; size = NLMSG_SPACE(sizeof(*pmsg) + data_len); break; default: *errp = -EINVAL; read_unlock_bh(&queue_lock); return NULL; } read_unlock_bh(&queue_lock); skb = alloc_skb(size, GFP_ATOMIC); if (!skb) goto nlmsg_failure; old_tail= skb->tail; nlh = NLMSG_PUT(skb, 0, 0, IPQM_PACKET, size - sizeof(*nlh)); pmsg = NLMSG_DATA(nlh); memset(pmsg, 0, sizeof(*pmsg)); pmsg->packet_id = (unsigned long )entry; pmsg->data_len = data_len; pmsg->timestamp_sec = entry->skb->tstamp.off_sec; pmsg->timestamp_usec = entry->skb->tstamp.off_usec; pmsg->mark = entry->skb->nfmark; pmsg->hook = entry->info->hook; pmsg->hw_protocol = entry->skb->protocol; if (entry->info->indev) strcpy(pmsg->indev_name, entry->info->indev->name); else pmsg->indev_name[0] = '\0'; if (entry->info->outdev) strcpy(pmsg->outdev_name, entry->info->outdev->name); else pmsg->outdev_name[0] = '\0'; if (entry->info->indev && entry->skb->dev) { pmsg->hw_type = entry->skb->dev->type; if (entry->skb->dev->hard_header_parse) pmsg->hw_addrlen = entry->skb->dev->hard_header_parse(entry->skb, pmsg->hw_addr); } if (data_len) if (skb_copy_bits(entry->skb, 0, pmsg->payload, data_len)) BUG(); nlh->nlmsg_len = skb->tail - old_tail; return skb;nlmsg_failure: if (skb) kfree_skb(skb); *errp = -EINVAL; printk(KERN_ERR "ip_queue: error creating packet message\n"); return NULL;}static intipq_enqueue_packet(struct sk_buff *skb, struct nf_info *info, unsigned int queuenum, void *data){ int status = -EINVAL; struct sk_buff *nskb; struct ipq_queue_entry *entry; if (copy_mode == IPQ_COPY_NONE) return -EAGAIN; entry = kmalloc(sizeof(*entry), GFP_ATOMIC); if (entry == NULL) { printk(KERN_ERR "ip_queue: OOM in ipq_enqueue_packet()\n"); return -ENOMEM; } entry->info = info; entry->skb = skb; nskb = ipq_build_packet_message(entry, &status); if (nskb == NULL) goto err_out_free; write_lock_bh(&queue_lock); if (!peer_pid) goto err_out_free_nskb; if (queue_total >= queue_maxlen) { queue_dropped++; status = -ENOSPC; if (net_ratelimit()) printk (KERN_WARNING "ip_queue: full at %d entries, " "dropping packets(s). Dropped: %d\n", queue_total, queue_dropped); goto err_out_free_nskb; } /* netlink_unicast will either free the nskb or attach it to a socket */ status = netlink_unicast(ipqnl, nskb, peer_pid, MSG_DONTWAIT); if (status < 0) { queue_user_dropped++; goto err_out_unlock; } __ipq_enqueue_entry(entry); write_unlock_bh(&queue_lock); return status;err_out_free_nskb: kfree_skb(nskb); err_out_unlock: write_unlock_bh(&queue_lock);err_out_free: kfree(entry); return status;}static intipq_mangle_ipv4(ipq_verdict_msg_t *v, struct ipq_queue_entry *e){ int diff; struct iphdr *user_iph = (struct iphdr *)v->payload; if (v->data_len < sizeof(*user_iph)) return 0; diff = v->data_len - e->skb->len; if (diff < 0) skb_trim(e->skb, v->data_len); else if (diff > 0) { if (v->data_len > 0xFFFF) return -EINVAL; if (diff > skb_tailroom(e->skb)) { struct sk_buff *newskb; newskb = skb_copy_expand(e->skb, skb_headroom(e->skb), diff, GFP_ATOMIC); if (newskb == NULL) { printk(KERN_WARNING "ip_queue: OOM " "in mangle, dropping packet\n"); return -ENOMEM;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -