diff options
Diffstat (limited to 'net/netfilter/nf_queue.c')
| -rw-r--r-- | net/netfilter/nf_queue.c | 42 | 
1 files changed, 33 insertions, 9 deletions
| diff --git a/net/netfilter/nf_queue.c b/net/netfilter/nf_queue.c index 99ffd288508..ce60cf0f6c1 100644 --- a/net/netfilter/nf_queue.c +++ b/net/netfilter/nf_queue.c @@ -40,7 +40,7 @@ int nf_register_queue_handler(u_int8_t pf, const struct nf_queue_handler *qh)  	else if (old)  		ret = -EBUSY;  	else { -		RCU_INIT_POINTER(queue_handler[pf], qh); +		rcu_assign_pointer(queue_handler[pf], qh);  		ret = 0;  	}  	mutex_unlock(&queue_handler_mutex); @@ -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;  } | 
