[NETFILTER]: Remove IPv4 only connection tracking/NAT
[safe/jmp/linux-2.6] / net / ipv4 / netfilter / ip_queue.c
index 08f80e2..5842f1a 100644 (file)
  *
  * 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 
+ * 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. 
+ *             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)
  *
  */
@@ -35,6 +35,7 @@
 #include <linux/sysctl.h>
 #include <linux/proc_fs.h>
 #include <linux/security.h>
+#include <linux/mutex.h>
 #include <net/sock.h>
 #include <net/route.h>
 
@@ -51,17 +52,17 @@ struct ipq_queue_entry {
 
 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 unsigned char copy_mode __read_mostly = IPQ_COPY_NONE;
+static unsigned int queue_maxlen __read_mostly = IPQ_QMAX_DEFAULT;
 static DEFINE_RWLOCK(queue_lock);
-static int peer_pid;
-static unsigned int copy_range;
+static int peer_pid __read_mostly;
+static unsigned int copy_range __read_mostly;
 static unsigned int queue_total;
 static unsigned int queue_dropped = 0;
 static unsigned int queue_user_dropped = 0;
-static struct sock *ipqnl;
+static struct sock *ipqnl __read_mostly;
 static LIST_HEAD(queue_list);
-static DECLARE_MUTEX(ipqnl_sem);
+static DEFINE_MUTEX(ipqnl_mutex);
 
 static void
 ipq_issue_verdict(struct ipq_queue_entry *entry, int verdict)
@@ -96,7 +97,7 @@ __ipq_find_entry(ipq_cmpfn cmpfn, unsigned long data)
 
        list_for_each_prev(p, &queue_list) {
                struct ipq_queue_entry *entry = (struct ipq_queue_entry *)p;
-               
+
                if (!cmpfn || cmpfn(entry, data))
                        return entry;
        }
@@ -128,7 +129,7 @@ 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);
 }
@@ -137,21 +138,21 @@ 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;
 
@@ -172,7 +173,7 @@ 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);
@@ -196,20 +197,21 @@ ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp)
        struct sk_buff *skb;
        struct ipq_packet_msg *pmsg;
        struct nlmsghdr *nlh;
+       struct timeval tv;
 
        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))) {
+               if ((entry->skb->ip_summed == CHECKSUM_PARTIAL ||
+                    entry->skb->ip_summed == CHECKSUM_COMPLETE) &&
+                   (*errp = skb_checksum_help(entry->skb))) {
                        read_unlock_bh(&queue_lock);
                        return NULL;
                }
@@ -217,10 +219,10 @@ ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp)
                        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);
@@ -232,7 +234,7 @@ ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp)
        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);
@@ -240,34 +242,35 @@ ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp)
 
        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;
+       tv = ktime_to_timeval(entry->skb->tstamp);
+       pmsg->timestamp_sec   = tv.tv_sec;
+       pmsg->timestamp_usec  = tv.tv_usec;
+       pmsg->mark            = entry->skb->mark;
        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);
+                                                                  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;
 
@@ -302,26 +305,26 @@ ipq_enqueue_packet(struct sk_buff *skb, struct nf_info *info,
        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; 
+               goto err_out_free_nskb;
 
        if (queue_total >= queue_maxlen) {
-                queue_dropped++;
+               queue_dropped++;
                status = -ENOSPC;
                if (net_ratelimit())
-                         printk (KERN_WARNING "ip_queue: full at %d entries, "
+                         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 */ 
+       /* 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++;
+               queue_user_dropped++;
                goto err_out_unlock;
        }
 
@@ -331,8 +334,8 @@ ipq_enqueue_packet(struct sk_buff *skb, struct nf_info *info,
        return status;
 
 err_out_free_nskb:
-       kfree_skb(nskb); 
-       
+       kfree_skb(nskb);
+
 err_out_unlock:
        write_unlock_bh(&queue_lock);
 
@@ -350,18 +353,19 @@ ipq_mangle_ipv4(ipq_verdict_msg_t *v, struct ipq_queue_entry *e)
        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 (diff < 0) {
+               if (pskb_trim(e->skb, v->data_len))
+                       return -ENOMEM;
+       } 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);
+                                                skb_headroom(e->skb),
+                                                diff,
+                                                GFP_ATOMIC);
                        if (newskb == NULL) {
                                printk(KERN_WARNING "ip_queue: OOM "
                                      "in mangle, dropping packet\n");
@@ -401,11 +405,11 @@ ipq_set_verdict(struct ipq_verdict_msg *vmsg, unsigned int len)
                return -ENOENT;
        else {
                int verdict = vmsg->value;
-               
+
                if (vmsg->data_len && vmsg->data_len == len)
                        if (ipq_mangle_ipv4(vmsg, entry) < 0)
                                verdict = NF_DROP;
-               
+
                ipq_issue_verdict(entry, verdict);
                return 0;
        }
@@ -424,7 +428,7 @@ ipq_set_mode(unsigned char mode, unsigned int range)
 
 static int
 ipq_receive_peer(struct ipq_peer_msg *pmsg,
-                 unsigned char type, unsigned int len)
+                unsigned char type, unsigned int len)
 {
        int status = 0;
 
@@ -434,15 +438,15 @@ ipq_receive_peer(struct ipq_peer_msg *pmsg,
        switch (type) {
        case IPQM_MODE:
                status = ipq_set_mode(pmsg->msg.mode.value,
-                                     pmsg->msg.mode.range);
+                                     pmsg->msg.mode.range);
                break;
-               
+
        case IPQM_VERDICT:
                if (pmsg->msg.verdict.value > NF_MAX_VERDICT)
                        status = -EINVAL;
                else
                        status = ipq_set_verdict(&pmsg->msg.verdict,
-                                                len - sizeof(*pmsg));
+                                                len - sizeof(*pmsg));
                        break;
        default:
                status = -EINVAL;
@@ -456,11 +460,19 @@ dev_cmp(struct ipq_queue_entry *entry, unsigned long ifindex)
        if (entry->info->indev)
                if (entry->info->indev->ifindex == ifindex)
                        return 1;
-                       
        if (entry->info->outdev)
                if (entry->info->outdev->ifindex == ifindex)
                        return 1;
-
+#ifdef CONFIG_BRIDGE_NETFILTER
+       if (entry->skb->nf_bridge) {
+               if (entry->skb->nf_bridge->physindev &&
+                   entry->skb->nf_bridge->physindev->ifindex == ifindex)
+                       return 1;
+               if (entry->skb->nf_bridge->physoutdev &&
+                   entry->skb->nf_bridge->physoutdev->ifindex == ifindex)
+                       return 1;
+       }
+#endif
        return 0;
 }
 
@@ -468,7 +480,7 @@ static void
 ipq_dev_drop(int ifindex)
 {
        struct ipq_queue_entry *entry;
-       
+
        while ((entry = ipq_find_dequeue_entry(dev_cmp, ifindex)) != NULL)
                ipq_issue_verdict(entry, NF_DROP);
 }
@@ -492,25 +504,25 @@ ipq_rcv_skb(struct sk_buff *skb)
 
        pid = nlh->nlmsg_pid;
        flags = nlh->nlmsg_flags;
-       
+
        if(pid <= 0 || !(flags & NLM_F_REQUEST) || flags & NLM_F_MULTI)
                RCV_SKB_FAIL(-EINVAL);
-               
+
        if (flags & MSG_TRUNC)
                RCV_SKB_FAIL(-ECOMM);
-               
+
        type = nlh->nlmsg_type;
        if (type < NLMSG_NOOP || type >= IPQM_MAX)
                RCV_SKB_FAIL(-EINVAL);
-               
+
        if (type <= IPQM_BASE)
                return;
-               
-       if (security_netlink_recv(skb))
+
+       if (security_netlink_recv(skb, CAP_NET_ADMIN))
                RCV_SKB_FAIL(-EPERM);
-       
+
        write_lock_bh(&queue_lock);
-       
+
        if (peer_pid) {
                if (peer_pid != pid) {
                        write_unlock_bh(&queue_lock);
@@ -520,17 +532,17 @@ ipq_rcv_skb(struct sk_buff *skb)
                net_enable_timestamp();
                peer_pid = pid;
        }
-               
+
        write_unlock_bh(&queue_lock);
-       
+
        status = ipq_receive_peer(NLMSG_DATA(nlh), type,
-                                 nlmsglen - NLMSG_LENGTH(0));
+                                 nlmsglen - NLMSG_LENGTH(0));
        if (status < 0)
                RCV_SKB_FAIL(status);
-               
+
        if (flags & NLM_F_ACK)
                netlink_ack(skb, nlh, 0);
-        return;
+       return;
 }
 
 static void
@@ -539,20 +551,20 @@ ipq_rcv_sk(struct sock *sk, int len)
        struct sk_buff *skb;
        unsigned int qlen;
 
-       down(&ipqnl_sem);
-                       
+       mutex_lock(&ipqnl_mutex);
+
        for (qlen = skb_queue_len(&sk->sk_receive_queue); qlen; qlen--) {
                skb = skb_dequeue(&sk->sk_receive_queue);
                ipq_rcv_skb(skb);
                kfree_skb(skb);
        }
-               
-       up(&ipqnl_sem);
+
+       mutex_unlock(&ipqnl_mutex);
 }
 
 static int
 ipq_rcv_dev_event(struct notifier_block *this,
-                  unsigned long event, void *ptr)
+                 unsigned long event, void *ptr)
 {
        struct net_device *dev = ptr;
 
@@ -568,7 +580,7 @@ static struct notifier_block ipq_dev_notifier = {
 
 static int
 ipq_rcv_nl_event(struct notifier_block *this,
-                 unsigned long event, void *ptr)
+                unsigned long event, void *ptr)
 {
        struct netlink_notify *n = ptr;
 
@@ -597,7 +609,7 @@ static ctl_table ipq_table[] = {
                .mode           = 0644,
                .proc_handler   = proc_dointvec
        },
-       { .ctl_name = 0 }
+       { .ctl_name = 0 }
 };
 
 static ctl_table ipq_dir_table[] = {
@@ -627,25 +639,25 @@ ipq_get_info(char *buffer, char **start, off_t offset, int length)
        int len;
 
        read_lock_bh(&queue_lock);
-       
+
        len = sprintf(buffer,
-                     "Peer PID          : %d\n"
-                     "Copy mode         : %hu\n"
-                     "Copy range        : %u\n"
-                     "Queue length      : %u\n"
-                     "Queue max. length : %u\n"
+                     "Peer PID          : %d\n"
+                     "Copy mode         : %hu\n"
+                     "Copy range        : %u\n"
+                     "Queue length      : %u\n"
+                     "Queue max. length : %u\n"
                      "Queue dropped     : %u\n"
                      "Netlink dropped   : %u\n",
-                     peer_pid,
-                     copy_mode,
-                     copy_range,
-                     queue_total,
-                     queue_maxlen,
+                     peer_pid,
+                     copy_mode,
+                     copy_range,
+                     queue_total,
+                     queue_maxlen,
                      queue_dropped,
                      queue_user_dropped);
 
        read_unlock_bh(&queue_lock);
-       
+
        *start = buffer + offset;
        len -= offset;
        if (len > length)
@@ -661,14 +673,10 @@ static struct nf_queue_handler nfqh = {
        .outfn  = &ipq_enqueue_packet,
 };
 
-static int
-init_or_cleanup(int init)
+static int __init ip_queue_init(void)
 {
        int status = -ENOMEM;
        struct proc_dir_entry *proc;
-       
-       if (!init)
-               goto cleanup;
 
        netlink_register_notifier(&ipq_nl_notifier);
        ipqnl = netlink_kernel_create(NETLINK_FIREWALL, 0, ipq_rcv_sk,
@@ -685,10 +693,10 @@ init_or_cleanup(int init)
                printk(KERN_ERR "ip_queue: failed to create proc entry\n");
                goto cleanup_ipqnl;
        }
-       
+
        register_netdevice_notifier(&ipq_dev_notifier);
-       ipq_sysctl_header = register_sysctl_table(ipq_root_table, 0);
-       
+       ipq_sysctl_header = register_sysctl_table(ipq_root_table);
+
        status = nf_register_queue_handler(PF_INET, &nfqh);
        if (status < 0) {
                printk(KERN_ERR "ip_queue: failed to register queue handler\n");
@@ -696,40 +704,41 @@ init_or_cleanup(int init)
        }
        return status;
 
-cleanup:
-       nf_unregister_queue_handlers(&nfqh);
-       synchronize_net();
-       ipq_flush(NF_DROP);
-       
 cleanup_sysctl:
        unregister_sysctl_table(ipq_sysctl_header);
        unregister_netdevice_notifier(&ipq_dev_notifier);
        proc_net_remove(IPQ_PROC_FS_NAME);
-       
+
 cleanup_ipqnl:
        sock_release(ipqnl->sk_socket);
-       down(&ipqnl_sem);
-       up(&ipqnl_sem);
-       
+       mutex_lock(&ipqnl_mutex);
+       mutex_unlock(&ipqnl_mutex);
+
 cleanup_netlink_notifier:
        netlink_unregister_notifier(&ipq_nl_notifier);
        return status;
 }
 
-static int __init init(void)
+static void __exit ip_queue_fini(void)
 {
-       
-       return init_or_cleanup(1);
-}
+       nf_unregister_queue_handlers(&nfqh);
+       synchronize_net();
+       ipq_flush(NF_DROP);
 
-static void __exit fini(void)
-{
-       init_or_cleanup(0);
+       unregister_sysctl_table(ipq_sysctl_header);
+       unregister_netdevice_notifier(&ipq_dev_notifier);
+       proc_net_remove(IPQ_PROC_FS_NAME);
+
+       sock_release(ipqnl->sk_socket);
+       mutex_lock(&ipqnl_mutex);
+       mutex_unlock(&ipqnl_mutex);
+
+       netlink_unregister_notifier(&ipq_nl_notifier);
 }
 
 MODULE_DESCRIPTION("IPv4 packet queue handler");
 MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");
 MODULE_LICENSE("GPL");
 
-module_init(init);
-module_exit(fini);
+module_init(ip_queue_init);
+module_exit(ip_queue_fini);