Commit 7c5f2825 authored by Herbert Xu's avatar Herbert Xu Committed by David S. Miller

[NET]: Fix CHECKSUM_HW wrt. packet trimming.

When something, on receive, between a CHECKSUM_HW using
driver and the code validating skb->csum trims the head
of the packet we have to reset skb->ip_summed back to
CHECKSUM_NONE because this kind of change invalidates
the skb->csum value calculated by the driver.

Various spots handle this properly, but not all.  So
abstract these actions into helper routines in order
to avoid code duplication.
Signed-off-by: default avatarHerbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 1ba955f6
......@@ -338,7 +338,9 @@ static int pppoe_rcv_core(struct sock *sk, struct sk_buff *skb)
struct pppoe_hdr *ph = (struct pppoe_hdr *) skb->nh.raw;
int len = ntohs(ph->length);
skb_pull(skb, sizeof(struct pppoe_hdr));
skb_trim(skb, len);
skb_postpull_rcsum(skb, ph, sizeof(*ph));
if (pskb_trim_rcsum(skb, len))
goto abort_kfree;
ppp_input(&po->chan, skb);
} else if (sk->sk_state & PPPOX_RELAY) {
......
......@@ -1105,6 +1105,42 @@ static inline int skb_linearize(struct sk_buff *skb, int gfp)
return __skb_linearize(skb, gfp);
}
/**
* skb_postpull_rcsum - update checksum for received skb after pull
* @skb: buffer to update
* @start: start of data before pull
* @len: length of data pulled
*
* After doing a pull on a received packet, you need to call this to
* update the CHECKSUM_HW checksum, or set ip_summed to CHECKSUM_NONE
* so that it can be recomputed from scratch.
*/
static inline void skb_postpull_rcsum(struct sk_buff *skb,
const void *start, int len)
{
if (skb->ip_summed == CHECKSUM_HW)
skb->csum = csum_sub(skb->csum, csum_partial(start, len, 0));
}
/**
* pskb_trim_rcsum - trim received skb and update checksum
* @skb: buffer to trim
* @len: new length
*
* This is exactly the same as pskb_trim except that it ensures the
* checksum of received packets are still valid after the operation.
*/
static inline int pskb_trim_rcsum(struct sk_buff *skb, unsigned int len)
{
if (len >= skb->len)
return 0;
if (skb->ip_summed == CHECKSUM_HW)
skb->ip_summed = CHECKSUM_NONE;
return __pskb_trim(skb, len);
}
static inline void *kmap_skb_frag(const skb_frag_t *frag)
{
#ifdef CONFIG_HIGHMEM
......
......@@ -618,10 +618,8 @@ static int ipgre_rcv(struct sk_buff *skb)
skb->mac.raw = skb->nh.raw;
skb->nh.raw = __pskb_pull(skb, offset);
skb_postpull_rcsum(skb, skb->mac.raw, offset);
memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
if (skb->ip_summed == CHECKSUM_HW)
skb->csum = csum_sub(skb->csum,
csum_partial(skb->mac.raw, skb->nh.raw-skb->mac.raw, 0));
skb->pkt_type = PACKET_HOST;
#ifdef CONFIG_NET_IPGRE_BROADCAST
if (MULTICAST(iph->daddr)) {
......
......@@ -410,10 +410,9 @@ int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
* is IP we can trim to the true length of the frame.
* Note this now means skb->len holds ntohs(iph->tot_len).
*/
if (skb->len > len) {
__pskb_trim(skb, len);
if (skb->ip_summed == CHECKSUM_HW)
skb->ip_summed = CHECKSUM_NONE;
if (pskb_trim_rcsum(skb, len)) {
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto drop;
}
}
......
......@@ -95,15 +95,11 @@ int ipv6_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt
if (pkt_len || hdr->nexthdr != NEXTHDR_HOP) {
if (pkt_len + sizeof(struct ipv6hdr) > skb->len)
goto truncated;
if (pkt_len + sizeof(struct ipv6hdr) < skb->len) {
if (__pskb_trim(skb, pkt_len + sizeof(struct ipv6hdr))){
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
goto drop;
}
hdr = skb->nh.ipv6h;
if (skb->ip_summed == CHECKSUM_HW)
skb->ip_summed = CHECKSUM_NONE;
if (pskb_trim_rcsum(skb, pkt_len + sizeof(struct ipv6hdr))) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
goto drop;
}
hdr = skb->nh.ipv6h;
}
if (hdr->nexthdr == NEXTHDR_HOP) {
......@@ -138,7 +134,6 @@ static inline int ip6_input_finish(struct sk_buff *skb)
unsigned int nhoff;
int nexthdr;
u8 hash;
int cksum_sub = 0;
skb->h.raw = skb->nh.raw + sizeof(struct ipv6hdr);
......@@ -173,11 +168,8 @@ static inline int ip6_input_finish(struct sk_buff *skb)
if (ipprot->flags & INET6_PROTO_FINAL) {
struct ipv6hdr *hdr;
if (!cksum_sub && skb->ip_summed == CHECKSUM_HW) {
skb->csum = csum_sub(skb->csum,
csum_partial(skb->nh.raw, skb->h.raw-skb->nh.raw, 0));
cksum_sub++;
}
skb_postpull_rcsum(skb, skb->nh.raw,
skb->h.raw - skb->nh.raw);
hdr = skb->nh.ipv6h;
if (ipv6_addr_is_multicast(&hdr->daddr) &&
!ipv6_chk_mcast_addr(skb->dev, &hdr->daddr,
......
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