diff options
| author | James Morris <jmorris@redhat.com> | 2004-06-03 08:03:01 -0700 |
|---|---|---|
| committer | James Morris <jmorris@redhat.com> | 2004-06-03 08:03:01 -0700 |
| commit | b0f171707fba2fc73bc104a6a79964c4f0cfc46f (patch) | |
| tree | a01d8171b88c33e5306b357c6e0b37707ebb3c76 /net/core/dev.c | |
| parent | 5a1c7700d664d88062c69842efd693f5a1aa03af (diff) | |
[NETFILTER]: Fix checksum bug for multicast/broadcast packets on postrouting hook.
In a nutshell, skb checksum mangling has been removed from
nf_hook_slow() and pushed up to whatever really needs to do it.
Namely: NAT, ip_fw_compat, ipt_TCPMSS, IPSec transforms.
skb_checksum_help() has been changed to perform an skb_copy() if needed
(e.g. the original problem case where bcast/mcast was cloning packets for
transmission over loopback, changing ip_summed).
Because of the above, the output path has been modified to take into
account the fact that an skb may need to be changed in some places. There
are some minor changes in the routing code to take care of the now
different input and output function prototypes. The ipv6 fragmentation
code has been modified to detect a changed skb.
The rest of the patch (probably the bulk of it) is simply the result of
changing to double skb pointers.
I've tested this with ipv4, ipv6, ipsec (including xfrm bundles), NAT and
the original DHCP test case. Everything seems to be working ok.
Signed-off-by: James Morris <jmorris@redhat.com>
Signed-off-by: David S. Miller <davem@redhat.com>
Diffstat (limited to 'net/core/dev.c')
| -rw-r--r-- | net/core/dev.c | 49 |
1 files changed, 33 insertions, 16 deletions
diff --git a/net/core/dev.c b/net/core/dev.c index dfd505c1c511..fb20e1047f27 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1180,28 +1180,46 @@ void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev) rcu_read_unlock(); } -/* Calculate csum in the case, when packet is misrouted. - * If it failed by some reason, ignore and send skb with wrong - * checksum. +/* + * Invalidate hardware checksum when packet is to be mangled, and + * complete checksum manually on outgoing path. */ -struct sk_buff *skb_checksum_help(struct sk_buff *skb) +int skb_checksum_help(struct sk_buff **pskb, int inward) { unsigned int csum; - int offset = skb->h.raw - skb->data; + int ret = 0, offset = (*pskb)->h.raw - (*pskb)->data; + + if (inward) { + (*pskb)->ip_summed = CHECKSUM_NONE; + goto out; + } - if (offset > (int)skb->len) + if (skb_shared(*pskb) || skb_cloned(*pskb)) { + struct sk_buff *newskb = skb_copy(*pskb, GFP_ATOMIC); + if (!newskb) { + ret = -ENOMEM; + goto out; + } + if ((*pskb)->sk) + skb_set_owner_w(newskb, (*pskb)->sk); + kfree_skb(*pskb); + *pskb = newskb; + } + + if (offset > (int)(*pskb)->len) BUG(); - csum = skb_checksum(skb, offset, skb->len-offset, 0); + csum = skb_checksum(*pskb, offset, (*pskb)->len-offset, 0); - offset = skb->tail - skb->h.raw; + offset = (*pskb)->tail - (*pskb)->h.raw; if (offset <= 0) BUG(); - if (skb->csum + 2 > offset) + if ((*pskb)->csum + 2 > offset) BUG(); - *(u16*)(skb->h.raw + skb->csum) = csum_fold(csum); - skb->ip_summed = CHECKSUM_NONE; - return skb; + *(u16*)((*pskb)->h.raw + (*pskb)->csum) = csum_fold(csum); + (*pskb)->ip_summed = CHECKSUM_NONE; +out: + return ret; } #ifdef CONFIG_HIGHMEM @@ -1326,10 +1344,9 @@ int dev_queue_xmit(struct sk_buff *skb) if (skb->ip_summed == CHECKSUM_HW && (!(dev->features & (NETIF_F_HW_CSUM | NETIF_F_NO_CSUM)) && (!(dev->features & NETIF_F_IP_CSUM) || - skb->protocol != htons(ETH_P_IP)))) { - if ((skb = skb_checksum_help(skb)) == NULL) - goto out; - } + skb->protocol != htons(ETH_P_IP)))) + if (skb_checksum_help(&skb, 0)) + goto out_kfree_skb; /* Grab device queue */ spin_lock_bh(&dev->queue_lock); |
