]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - net/bridge/br_netfilter.c
Merge branch 'upstream-fixes'
[linux-2.6-omap-h63xx.git] / net / bridge / br_netfilter.c
index 2d52fee63a8cf1653e90281be1ca154c61f24d70..6bb0c7eb1ef0300d827d755be0d4eab7f73d600a 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/ip.h>
 #include <linux/netdevice.h>
 #include <linux/skbuff.h>
+#include <linux/if_arp.h>
 #include <linux/if_ether.h>
 #include <linux/if_vlan.h>
 #include <linux/netfilter_bridge.h>
 #include <linux/netfilter_ipv6.h>
 #include <linux/netfilter_arp.h>
 #include <linux/in_route.h>
+
 #include <net/ip.h>
 #include <net/ipv6.h>
+#include <net/route.h>
+
 #include <asm/uaccess.h>
 #include <asm/checksum.h>
 #include "br_private.h"
@@ -47,9 +51,6 @@
 #define store_orig_dstaddr(skb)         (skb_origaddr(skb) = (skb)->nh.iph->daddr)
 #define dnat_took_place(skb)    (skb_origaddr(skb) != (skb)->nh.iph->daddr)
 
-#define has_bridge_parent(device)      ((device)->br_port != NULL)
-#define bridge_parent(device)          ((device)->br_port->br->dev)
-
 #ifdef CONFIG_SYSCTL
 static struct ctl_table_header *brnf_sysctl_header;
 static int brnf_call_iptables = 1;
@@ -94,6 +95,12 @@ static struct rtable __fake_rtable = {
        .rt_flags       = 0,
 };
 
+static inline struct net_device *bridge_parent(const struct net_device *dev)
+{
+       struct net_bridge_port *port = rcu_dereference(dev->br_port);
+
+       return port ? port->br->dev : NULL;
+}
 
 /* PF_BRIDGE/PRE_ROUTING *********************************************/
 /* Undo the changes made for ip6tables PREROUTING and continue the
@@ -185,11 +192,15 @@ static int br_nf_pre_routing_finish_bridge(struct sk_buff *skb)
        skb->nf_bridge->mask ^= BRNF_NF_BRIDGE_PREROUTING;
 
        skb->dev = bridge_parent(skb->dev);
-       if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
-               skb_pull(skb, VLAN_HLEN);
-               skb->nh.raw += VLAN_HLEN;
+       if (!skb->dev)
+               kfree_skb(skb);
+       else {
+               if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
+                       skb_pull(skb, VLAN_HLEN);
+                       skb->nh.raw += VLAN_HLEN;
+               }
+               skb->dst->output(skb);
        }
-       skb->dst->output(skb);
        return 0;
 }
 
@@ -214,9 +225,11 @@ static int br_nf_pre_routing_finish(struct sk_buff *skb)
                                     .tos = RT_TOS(iph->tos)} }, .proto = 0};
 
                        if (!ip_route_output_key(&rt, &fl)) {
-                               /* Bridged-and-DNAT'ed traffic doesn't
-                                * require ip_forwarding. */
-                               if (((struct dst_entry *)rt)->dev == dev) {
+                               /* - Bridged-and-DNAT'ed traffic doesn't
+                                *   require ip_forwarding.
+                                * - Deal with redirected traffic. */
+                               if (((struct dst_entry *)rt)->dev == dev ||
+                                   rt->rt_type == RTN_LOCAL) {
                                        skb->dst = (struct dst_entry *)rt;
                                        goto bridged_dnat;
                                }
@@ -264,7 +277,7 @@ bridged_dnat:
 }
 
 /* Some common code for IPv4/IPv6 */
-static void setup_pre_routing(struct sk_buff *skb)
+static struct net_device *setup_pre_routing(struct sk_buff *skb)
 {
        struct nf_bridge_info *nf_bridge = skb->nf_bridge;
 
@@ -276,6 +289,8 @@ static void setup_pre_routing(struct sk_buff *skb)
        nf_bridge->mask |= BRNF_NF_BRIDGE_PREROUTING;
        nf_bridge->physindev = skb->dev;
        skb->dev = bridge_parent(skb->dev);
+
+       return skb->dev;
 }
 
 /* We only check the length. A bridge shouldn't do any hop-by-hop stuff anyway */
@@ -293,7 +308,7 @@ static int check_hbh_len(struct sk_buff *skb)
        len -= 2;
 
        while (len > 0) {
-               int optlen = raw[off+1]+2;
+               int optlen = skb->nh.raw[off+1]+2;
 
                switch (skb->nh.raw[off]) {
                case IPV6_TLV_PAD0:
@@ -306,18 +321,15 @@ static int check_hbh_len(struct sk_buff *skb)
                case IPV6_TLV_JUMBO:
                        if (skb->nh.raw[off+1] != 4 || (off&3) != 2)
                                goto bad;
-
                        pkt_len = ntohl(*(u32*)(skb->nh.raw+off+2));
-
+                       if (pkt_len <= IPV6_MAXPLEN ||
+                           skb->nh.ipv6h->payload_len)
+                               goto bad;
                        if (pkt_len > skb->len - sizeof(struct ipv6hdr))
                                goto bad;
-                       if (pkt_len + sizeof(struct ipv6hdr) < skb->len) {
-                               if (__pskb_trim(skb,
-                                   pkt_len + sizeof(struct ipv6hdr)))
-                                       goto bad;
-                               if (skb->ip_summed == CHECKSUM_HW)
-                                       skb->ip_summed = CHECKSUM_NONE;
-                       }
+                       if (pskb_trim_rcsum(skb,
+                           pkt_len+sizeof(struct ipv6hdr)))
+                               goto bad;
                        break;
                default:
                        if (optlen > len)
@@ -370,9 +382,11 @@ static unsigned int br_nf_pre_routing_ipv6(unsigned int hook,
        if (hdr->nexthdr == NEXTHDR_HOP && check_hbh_len(skb))
                        goto inhdr_error;
 
+       nf_bridge_put(skb->nf_bridge);
        if ((nf_bridge = nf_bridge_alloc(skb)) == NULL)
                return NF_DROP;
-       setup_pre_routing(skb);
+       if (!setup_pre_routing(skb))
+               return NF_DROP;
 
        NF_HOOK(PF_INET6, NF_IP6_PRE_ROUTING, skb, skb->dev, NULL,
                br_nf_pre_routing_finish_ipv6);
@@ -390,8 +404,9 @@ inhdr_error:
  * target in particular.  Save the original destination IP
  * address to be able to detect DNAT afterwards. */
 static unsigned int br_nf_pre_routing(unsigned int hook, struct sk_buff **pskb,
-   const struct net_device *in, const struct net_device *out,
-   int (*okfn)(struct sk_buff *))
+                                     const struct net_device *in,
+                                     const struct net_device *out,
+                                     int (*okfn)(struct sk_buff *))
 {
        struct iphdr *iph;
        __u32 len;
@@ -408,8 +423,10 @@ static unsigned int br_nf_pre_routing(unsigned int hook, struct sk_buff **pskb,
                        goto out;
 
                if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
+                       u8 *vhdr = skb->data;
                        skb_pull(skb, VLAN_HLEN);
-                       (skb)->nh.raw += VLAN_HLEN;
+                       skb_postpull_rcsum(skb, vhdr, VLAN_HLEN);
+                       skb->nh.raw += VLAN_HLEN;
                }
                return br_nf_pre_routing_ipv6(hook, skb, in, out, okfn);
        }
@@ -425,8 +442,10 @@ static unsigned int br_nf_pre_routing(unsigned int hook, struct sk_buff **pskb,
                goto out;
 
        if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
+               u8 *vhdr = skb->data;
                skb_pull(skb, VLAN_HLEN);
-               (skb)->nh.raw += VLAN_HLEN;
+               skb_postpull_rcsum(skb, vhdr, VLAN_HLEN);
+               skb->nh.raw += VLAN_HLEN;
        }
 
        if (!pskb_may_pull(skb, sizeof(struct iphdr)))
@@ -453,9 +472,11 @@ static unsigned int br_nf_pre_routing(unsigned int hook, struct sk_buff **pskb,
                        skb->ip_summed = CHECKSUM_NONE;
        }
 
+       nf_bridge_put(skb->nf_bridge);
        if ((nf_bridge = nf_bridge_alloc(skb)) == NULL)
                return NF_DROP;
-       setup_pre_routing(skb);
+       if (!setup_pre_routing(skb))
+               return NF_DROP;
        store_orig_dstaddr(skb);
 
        NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, skb->dev, NULL,
@@ -529,11 +550,16 @@ static unsigned int br_nf_forward_ip(unsigned int hook, struct sk_buff **pskb,
        struct sk_buff *skb = *pskb;
        struct nf_bridge_info *nf_bridge;
        struct vlan_ethhdr *hdr = vlan_eth_hdr(skb);
+       struct net_device *parent;
        int pf;
 
        if (!skb->nf_bridge)
                return NF_ACCEPT;
 
+       parent = bridge_parent(out);
+       if (!parent)
+               return NF_DROP;
+
        if (skb->protocol == __constant_htons(ETH_P_IP) || IS_VLAN_IP)
                pf = PF_INET;
        else
@@ -554,8 +580,8 @@ static unsigned int br_nf_forward_ip(unsigned int hook, struct sk_buff **pskb,
        nf_bridge->mask |= BRNF_BRIDGED;
        nf_bridge->physoutdev = skb->dev;
 
-       NF_HOOK(pf, NF_IP_FORWARD, skb, bridge_parent(in),
-               bridge_parent(out), br_nf_forward_finish);
+       NF_HOOK(pf, NF_IP_FORWARD, skb, bridge_parent(in), parent,
+               br_nf_forward_finish);
 
        return NF_STOLEN;
 }
@@ -678,6 +704,8 @@ static unsigned int br_nf_local_out(unsigned int hook, struct sk_buff **pskb,
                goto out;
        }
        realoutdev = bridge_parent(skb->dev);
+       if (!realoutdev)
+               return NF_DROP;
 
 #if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
        /* iptables should match -o br0.x */
@@ -691,9 +719,11 @@ static unsigned int br_nf_local_out(unsigned int hook, struct sk_buff **pskb,
        /* IP forwarded traffic has a physindev, locally
         * generated traffic hasn't. */
        if (realindev != NULL) {
-               if (!(nf_bridge->mask & BRNF_DONT_TAKE_PARENT) &&
-                   has_bridge_parent(realindev))
-                       realindev = bridge_parent(realindev);
+               if (!(nf_bridge->mask & BRNF_DONT_TAKE_PARENT) ) {
+                       struct net_device *parent = bridge_parent(realindev);
+                       if (parent)
+                               realindev = parent;
+               }
 
                NF_HOOK_THRESH(pf, NF_IP_FORWARD, skb, realindev,
                               realoutdev, br_nf_local_out_finish,
@@ -733,6 +763,9 @@ static unsigned int br_nf_post_routing(unsigned int hook, struct sk_buff **pskb,
        if (!nf_bridge)
                return NF_ACCEPT;
 
+       if (!realoutdev)
+               return NF_DROP;
+
        if (skb->protocol == __constant_htons(ETH_P_IP) || IS_VLAN_IP)
                pf = PF_INET;
        else
@@ -772,8 +805,8 @@ static unsigned int br_nf_post_routing(unsigned int hook, struct sk_buff **pskb,
 print_error:
        if (skb->dev != NULL) {
                printk("[%s]", skb->dev->name);
-               if (has_bridge_parent(skb->dev))
-                       printk("[%s]", bridge_parent(skb->dev)->name);
+               if (realoutdev)
+                       printk("[%s]", realoutdev->name);
        }
        printk(" head:%p, raw:%p, data:%p\n", skb->head, skb->mac.raw,
                                              skb->data);