patch-2.4.4 linux/net/ipv6/reassembly.c

Next file: linux/net/ipv6/route.c
Previous file: linux/net/ipv6/raw.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.3/linux/net/ipv6/reassembly.c linux/net/ipv6/reassembly.c
@@ -5,7 +5,7 @@
  *	Authors:
  *	Pedro Roque		<roque@di.fc.ul.pt>	
  *
- *	$Id: reassembly.c,v 1.22 2000/12/08 17:41:54 davem Exp $
+ *	$Id: reassembly.c,v 1.26 2001/03/07 22:00:57 davem Exp $
  *
  *	Based on: net/ipv4/ip_fragment.c
  *
@@ -79,11 +79,12 @@
 	int			len;
 	int			meat;
 	int			iif;
+	struct timeval		stamp;
+	unsigned int		csum;
 	__u8			last_in;	/* has first/last segment arrived? */
 #define COMPLETE		4
 #define FIRST_IN		2
 #define LAST_IN			1
-	__u8			nexthdr;
 	__u16			nhoffset;
 	struct frag_queue	**pprev;
 };
@@ -349,7 +350,7 @@
 
 
 static void ip6_frag_queue(struct frag_queue *fq, struct sk_buff *skb, 
-			   struct frag_hdr *fhdr, u8 *nhptr)
+			   struct frag_hdr *fhdr, int nhoff)
 {
 	struct sk_buff *prev, *next;
 	int offset, end;
@@ -362,10 +363,14 @@
 			((u8 *) (fhdr + 1) - (u8 *) (skb->nh.ipv6h + 1)));
 
 	if ((unsigned int)end >= 65536) {
-		icmpv6_param_prob(skb,ICMPV6_HDR_FIELD, (u8*)&fhdr->frag_off); 
-		goto err;
+ 		icmpv6_param_prob(skb,ICMPV6_HDR_FIELD, (u8*)&fhdr->frag_off - skb->nh.raw);
+ 		return;
 	}
 
+ 	if (skb->ip_summed == CHECKSUM_HW)
+ 		skb->csum = csum_sub(skb->csum,
+ 				     csum_partial(skb->nh.raw, (u8*)(fhdr+1)-skb->nh.raw, 0));
+
 	/* Is this the final fragment? */
 	if (!(fhdr->frag_off & __constant_htons(0x0001))) {
 		/* If we already have some bits beyond end
@@ -381,16 +386,12 @@
 		 * Required by the RFC.
 		 */
 		if (end & 0x7) {
-			printk(KERN_DEBUG "fragment not rounded to 8bytes\n");
-
-			/*
-			   It is not in specs, but I see no reasons
-			   to send an error in this case. --ANK
+			/* RFC2460 says always send parameter problem in
+			 * this case. -DaveM
 			 */
-			if (offset == 0)
-				icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, 
-						  &skb->nh.ipv6h->payload_len);
-			goto err;
+			icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, 
+					  offsetof(struct ipv6hdr, payload_len));
+			return;
 		}
 		if (end > fq->len) {
 			/* Some bits beyond end -> corruption. */
@@ -404,8 +405,14 @@
 		goto err;
 
 	/* Point into the IP datagram 'data' part. */
-	skb_pull(skb, (u8 *) (fhdr + 1) - skb->data);
-	skb_trim(skb, end - offset);
+	if (!pskb_pull(skb, (u8 *) (fhdr + 1) - skb->data))
+		goto err;
+	if (end-offset < skb->len) {
+		if (pskb_trim(skb, end - offset))
+			goto err;
+		if (skb->ip_summed != CHECKSUM_UNNECESSARY)
+			skb->ip_summed = CHECKSUM_NONE;
+	}
 
 	/* Find out which fragments are in front and at the back of us
 	 * in the chain of fragments so far.  We must know where to put
@@ -429,7 +436,10 @@
 			offset += i;
 			if (end <= offset)
 				goto err;
-			skb_pull(skb, i);
+			if (!pskb_pull(skb, i))
+				goto err;
+			if (skb->ip_summed != CHECKSUM_UNNECESSARY)
+				skb->ip_summed = CHECKSUM_NONE;
 		}
 	}
 
@@ -443,9 +453,12 @@
 			/* Eat head of the next overlapped fragment
 			 * and leave the loop. The next ones cannot overlap.
 			 */
+			if (!pskb_pull(next, i))
+				goto err;
 			FRAG6_CB(next)->offset += i;	/* next fragment */
-			skb_pull(next, i);
 			fq->meat -= i;
+			if (next->ip_summed != CHECKSUM_UNNECESSARY)
+				next->ip_summed = CHECKSUM_NONE;
 			break;
 		} else {
 			struct sk_buff *free_it = next;
@@ -474,20 +487,18 @@
 	else
 		fq->fragments = skb;
 
-	fq->iif = skb->dev->ifindex;
+	if (skb->dev)
+		fq->iif = skb->dev->ifindex;
 	skb->dev = NULL;
+	fq->stamp = skb->stamp;
 	fq->meat += skb->len;
 	atomic_add(skb->truesize, &ip6_frag_mem);
 
-	/* First fragment.
-	   nexthdr and nhptr are get from the first fragment.
-	   Moreover, nexthdr is UNDEFINED for all the fragments but the
-	   first one.
-	   (fixed --ANK (980728))
+	/* The first fragment.
+	 * nhoffset is obtained from the first fragment, of course.
 	 */
 	if (offset == 0) {
-		fq->nexthdr = fhdr->nexthdr;
-		fq->nhoffset = nhptr - skb->nh.raw;
+		fq->nhoffset = nhoff;
 		fq->last_in |= FIRST_IN;
 	}
 	return;
@@ -505,21 +516,13 @@
  *	queue is eligible for reassembly i.e. it is not COMPLETE,
  *	the last and the first frames arrived and all the bits are here.
  */
-static u8 *ip6_frag_reasm(struct frag_queue *fq, struct sk_buff **skb_in,
+static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff **skb_in,
 			  struct net_device *dev)
 {
 	struct sk_buff *fp, *head = fq->fragments;
-	struct sk_buff *skb;
+	int    remove_fraghdr = 0;
 	int    payload_len;
-	int    unfrag_len;
-	int    copy;
-	u8     *nhptr;
-
-	/* 
-	 * we know the m_flag arrived and we have a queue,
-	 * starting from 0, without gaps.
-	 * this means we have all fragments.
-	 */
+	int    nhoff;
 
 	fq_kill(fq);
 
@@ -527,40 +530,86 @@
 	BUG_TRAP(FRAG6_CB(head)->offset == 0);
 
 	/* Unfragmented part is taken from the first segment. */
-	unfrag_len = head->h.raw - (u8 *) (head->nh.ipv6h + 1);
-	payload_len = unfrag_len + fq->len;
+	payload_len = (head->data - head->nh.raw) - sizeof(struct ipv6hdr) + fq->len;
+	nhoff = head->h.raw - head->nh.raw;
 
-	if (payload_len > 65535)
-		goto out_oversize;
+	if (payload_len > 65535) {
+		payload_len -= 8;
+		if (payload_len > 65535)
+			goto out_oversize;
+		remove_fraghdr = 1;
+	}
 
-	if ((skb = dev_alloc_skb(sizeof(struct ipv6hdr) + payload_len))==NULL)
+	/* Head of list must not be cloned. */
+	if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
 		goto out_oom;
 
-	copy = unfrag_len + sizeof(struct ipv6hdr);
-
-	skb->mac.raw = skb->data;
-	skb->nh.ipv6h = (struct ipv6hdr *) skb->data;
-	skb->dev = dev;
-	skb->protocol = __constant_htons(ETH_P_IPV6);
-	skb->pkt_type = head->pkt_type;
-	FRAG6_CB(skb)->h = FRAG6_CB(head)->h;
-	skb->dst = dst_clone(head->dst);
-
-	memcpy(skb_put(skb, copy), head->nh.ipv6h, copy);
-	nhptr = skb->nh.raw + fq->nhoffset;
-	*nhptr = fq->nexthdr;
-
-	skb->h.raw = skb->tail;
-
-	skb->nh.ipv6h->payload_len = ntohs(payload_len);
-
-	*skb_in = skb;
+	/* If the first fragment is fragmented itself, we split
+	 * it to two chunks: the first with data and paged part
+	 * and the second, holding only fragments. */
+	if (skb_shinfo(head)->frag_list) {
+		struct sk_buff *clone;
+		int i, plen = 0;
+
+		if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
+			goto out_oom;
+		clone->next = head->next;
+		head->next = clone;
+		skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
+		skb_shinfo(head)->frag_list = NULL;
+		for (i=0; i<skb_shinfo(head)->nr_frags; i++)
+			plen += skb_shinfo(head)->frags[i].size;
+		clone->len = clone->data_len = head->data_len - plen;
+		head->data_len -= clone->len;
+		head->len -= clone->len;
+		clone->csum = 0;
+		clone->ip_summed = head->ip_summed;
+		atomic_add(clone->truesize, &ip6_frag_mem);
+	}
+
+	/* Normally we do not remove frag header from datagram, but
+	 * we have to do this and to relocate header, when payload
+	 * is > 65535-8. */
+	if (remove_fraghdr) {
+		nhoff = fq->nhoffset;
+		head->nh.raw[nhoff] = head->h.raw[0];
+		memmove(head->head+8, head->head, (head->data-head->head)-8);
+		head->mac.raw += 8;
+		head->nh.raw += 8;
+	} else {
+		((struct frag_hdr*)head->h.raw)->frag_off = 0;
+	}
 
-	for (fp = fq->fragments; fp; fp=fp->next)
-		memcpy(skb_put(skb, fp->len), fp->data, fp->len);
+	skb_shinfo(head)->frag_list = head->next;
+	head->h.raw = head->data;
+	skb_push(head, head->data - head->nh.raw);
+	atomic_sub(head->truesize, &ip6_frag_mem);
+
+	for (fp=head->next; fp; fp = fp->next) {
+		head->data_len += fp->len;
+		head->len += fp->len;
+		if (head->ip_summed != fp->ip_summed)
+			head->ip_summed = CHECKSUM_NONE;
+		else if (head->ip_summed == CHECKSUM_HW)
+			head->csum = csum_add(head->csum, fp->csum);
+		head->truesize += fp->truesize;
+		atomic_sub(fp->truesize, &ip6_frag_mem);
+	}
+
+	head->next = NULL;
+	head->dev = dev;
+	head->stamp = fq->stamp;
+	head->nh.ipv6h->payload_len = ntohs(payload_len);
+
+	*skb_in = head;
+
+	/* Yes, and fold redundant checksum back. 8) */
+	if (head->ip_summed == CHECKSUM_HW)
+		head->csum = csum_partial(head->nh.raw, head->h.raw-head->nh.raw, head->csum);
 
 	IP6_INC_STATS_BH(Ip6ReasmOKs);
-	return nhptr;
+	fq->fragments = NULL;
+	return nhoff;
 
 out_oversize:
 	if (net_ratelimit())
@@ -571,14 +620,14 @@
 		printk(KERN_DEBUG "ip6_frag_reasm: no memory for reassembly\n");
 out_fail:
 	IP6_INC_STATS_BH(Ip6ReasmFails);
-	return NULL;
+	return -1;
 }
 
-u8* ipv6_reassembly(struct sk_buff **skbp, __u8 *nhptr)
+int ipv6_reassembly(struct sk_buff **skbp, int nhoff)
 {
 	struct sk_buff *skb = *skbp; 
-	struct frag_hdr *fhdr = (struct frag_hdr *) (skb->h.raw);
 	struct net_device *dev = skb->dev;
+	struct frag_hdr *fhdr;
 	struct frag_queue *fq;
 	struct ipv6hdr *hdr;
 
@@ -588,31 +637,34 @@
 
 	/* Jumbo payload inhibits frag. header */
 	if (hdr->payload_len==0) {
-		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, skb->h.raw);
-		return NULL;
+		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, skb->h.raw-skb->nh.raw);
+		return -1;
 	}
-	if ((u8 *)(fhdr+1) > skb->tail) {
-		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, skb->h.raw);
-		return NULL;
+	if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+sizeof(struct frag_hdr))) {
+		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, skb->h.raw-skb->nh.raw);
+		return -1;
 	}
 
+	hdr = skb->nh.ipv6h;
+	fhdr = (struct frag_hdr *)skb->h.raw;
+
 	if (!(fhdr->frag_off & __constant_htons(0xFFF9))) {
 		/* It is not a fragmented frame */
 		skb->h.raw += sizeof(struct frag_hdr);
 		IP6_INC_STATS_BH(Ip6ReasmOKs);
 
-		return &fhdr->nexthdr;
+		return (u8*)fhdr - skb->nh.raw;
 	}
 
 	if (atomic_read(&ip6_frag_mem) > sysctl_ip6frag_high_thresh)
 		ip6_evictor();
 
 	if ((fq = fq_find(fhdr->identification, &hdr->saddr, &hdr->daddr)) != NULL) {
-		u8 *ret = NULL;
+		int ret = -1;
 
 		spin_lock(&fq->lock);
 
-		ip6_frag_queue(fq, skb, fhdr, nhptr);
+		ip6_frag_queue(fq, skb, fhdr, nhoff);
 
 		if (fq->last_in == (FIRST_IN|LAST_IN) &&
 		    fq->meat == fq->len)
@@ -625,5 +677,5 @@
 
 	IP6_INC_STATS_BH(Ip6ReasmFails);
 	kfree_skb(skb);
-	return NULL;
+	return -1;
 }

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)