netfilter: nf_queue: fix queueing of bridged gro skbs
authorFlorian Westphal <fw@strlen.de>
Mon, 6 Feb 2012 12:23:10 +0000 (13:23 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Thu, 9 Feb 2012 19:47:53 +0000 (20:47 +0100)
When trying to nf_queue GRO/GSO skbs, nf_queue uses skb_gso_segment
to split the skb.

However, if nf_queue is called via bridge netfilter, the mac header
won't be preserved -- packets will thus contain a bogus mac header.

Fix this by setting skb->data to the mac header when skb->nf_bridge
is set and restoring skb->data afterwards for all segments.

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
net/netfilter/nf_queue.c

index b3a7db678b8dddf12e9495630e0e051a0b78cfe9..ce60cf0f6c11a49d9d8bc8bf5c8918d10dbfb316 100644 (file)
@@ -203,6 +203,27 @@ err:
        return status;
 }
 
+#ifdef CONFIG_BRIDGE_NETFILTER
+/* When called from bridge netfilter, skb->data must point to MAC header
+ * before calling skb_gso_segment(). Else, original MAC header is lost
+ * and segmented skbs will be sent to wrong destination.
+ */
+static void nf_bridge_adjust_skb_data(struct sk_buff *skb)
+{
+       if (skb->nf_bridge)
+               __skb_push(skb, skb->network_header - skb->mac_header);
+}
+
+static void nf_bridge_adjust_segmented_data(struct sk_buff *skb)
+{
+       if (skb->nf_bridge)
+               __skb_pull(skb, skb->network_header - skb->mac_header);
+}
+#else
+#define nf_bridge_adjust_skb_data(s) do {} while (0)
+#define nf_bridge_adjust_segmented_data(s) do {} while (0)
+#endif
+
 int nf_queue(struct sk_buff *skb,
             struct list_head *elem,
             u_int8_t pf, unsigned int hook,
@@ -212,7 +233,7 @@ int nf_queue(struct sk_buff *skb,
             unsigned int queuenum)
 {
        struct sk_buff *segs;
-       int err;
+       int err = -EINVAL;
        unsigned int queued;
 
        if (!skb_is_gso(skb))
@@ -228,23 +249,25 @@ int nf_queue(struct sk_buff *skb,
                break;
        }
 
+       nf_bridge_adjust_skb_data(skb);
        segs = skb_gso_segment(skb, 0);
        /* Does not use PTR_ERR to limit the number of error codes that can be
         * returned by nf_queue.  For instance, callers rely on -ECANCELED to mean
         * 'ignore this hook'.
         */
        if (IS_ERR(segs))
-               return -EINVAL;
-
+               goto out_err;
        queued = 0;
        err = 0;
        do {
                struct sk_buff *nskb = segs->next;
 
                segs->next = NULL;
-               if (err == 0)
+               if (err == 0) {
+                       nf_bridge_adjust_segmented_data(segs);
                        err = __nf_queue(segs, elem, pf, hook, indev,
                                           outdev, okfn, queuenum);
+               }
                if (err == 0)
                        queued++;
                else
@@ -252,11 +275,12 @@ int nf_queue(struct sk_buff *skb,
                segs = nskb;
        } while (segs);
 
-       /* also free orig skb if only some segments were queued */
-       if (unlikely(err && queued))
-               err = 0;
-       if (err == 0)
+       if (queued) {
                kfree_skb(skb);
+               return 0;
+       }
+  out_err:
+       nf_bridge_adjust_segmented_data(skb);
        return err;
 }