Commit a8db7b2d authored by Florian Westphal's avatar Florian Westphal Committed by Pablo Neira Ayuso

netfilter: nf_queue: fix queueing of bridged gro skbs

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: default avatarFlorian Westphal <fw@strlen.de>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent e0aac52e
...@@ -203,6 +203,27 @@ static int __nf_queue(struct sk_buff *skb, ...@@ -203,6 +203,27 @@ static int __nf_queue(struct sk_buff *skb,
return status; 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, int nf_queue(struct sk_buff *skb,
struct list_head *elem, struct list_head *elem,
u_int8_t pf, unsigned int hook, u_int8_t pf, unsigned int hook,
...@@ -212,7 +233,7 @@ int nf_queue(struct sk_buff *skb, ...@@ -212,7 +233,7 @@ int nf_queue(struct sk_buff *skb,
unsigned int queuenum) unsigned int queuenum)
{ {
struct sk_buff *segs; struct sk_buff *segs;
int err; int err = -EINVAL;
unsigned int queued; unsigned int queued;
if (!skb_is_gso(skb)) if (!skb_is_gso(skb))
...@@ -228,23 +249,25 @@ int nf_queue(struct sk_buff *skb, ...@@ -228,23 +249,25 @@ int nf_queue(struct sk_buff *skb,
break; break;
} }
nf_bridge_adjust_skb_data(skb);
segs = skb_gso_segment(skb, 0); segs = skb_gso_segment(skb, 0);
/* Does not use PTR_ERR to limit the number of error codes that can be /* 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 * returned by nf_queue. For instance, callers rely on -ECANCELED to mean
* 'ignore this hook'. * 'ignore this hook'.
*/ */
if (IS_ERR(segs)) if (IS_ERR(segs))
return -EINVAL; goto out_err;
queued = 0; queued = 0;
err = 0; err = 0;
do { do {
struct sk_buff *nskb = segs->next; struct sk_buff *nskb = segs->next;
segs->next = NULL; segs->next = NULL;
if (err == 0) if (err == 0) {
nf_bridge_adjust_segmented_data(segs);
err = __nf_queue(segs, elem, pf, hook, indev, err = __nf_queue(segs, elem, pf, hook, indev,
outdev, okfn, queuenum); outdev, okfn, queuenum);
}
if (err == 0) if (err == 0)
queued++; queued++;
else else
...@@ -252,11 +275,12 @@ int nf_queue(struct sk_buff *skb, ...@@ -252,11 +275,12 @@ int nf_queue(struct sk_buff *skb,
segs = nskb; segs = nskb;
} while (segs); } while (segs);
/* also free orig skb if only some segments were queued */ if (queued) {
if (unlikely(err && queued))
err = 0;
if (err == 0)
kfree_skb(skb); kfree_skb(skb);
return 0;
}
out_err:
nf_bridge_adjust_segmented_data(skb);
return err; return err;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment