datapath/linux/compat/ip_fragment.c
/* Add new segment to existing queue. */
Add new segment to existing queue.
Add new segment to existing queue .
static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
	struct sk_buff *prev, *next;
	struct net_device *dev;
	unsigned int fragsize;
	int flags, offset;
	int ihl, end;
	int err = -ENOENT;
	u8 ecn;
	if (qp_flags(qp) & INET_FRAG_COMPLETE)
		goto err;
	if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
	    unlikely(ip_frag_too_far(qp)) &&
	    unlikely(err = ip_frag_reinit(qp))) {
		ipq_kill(qp);
		goto err;
	}
	ecn = ip4_frag_ecn(ip_hdr(skb)->tos);
	offset = ntohs(ip_hdr(skb)->frag_off);
	flags = offset & ~IP_OFFSET;
	offset &= IP_OFFSET;
	offset <<= 3;		
	ihl = ip_hdrlen(skb);
	end = offset + skb->len - skb_network_offset(skb) - ihl;
	err = -EINVAL;
	if ((flags & IP_MF) == 0) {
		if (end < qp->q.len ||
		    ((qp_flags(qp) & INET_FRAG_LAST_IN) && end != qp->q.len))
			goto err;
		qp_flags(qp) |= INET_FRAG_LAST_IN;
		qp->q.len = end;
	} else {
		if (end&7) {
			end &= ~7;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)
				skb->ip_summed = CHECKSUM_NONE;
		}
		if (end > qp->q.len) {
			if (qp_flags(qp) & INET_FRAG_LAST_IN)
				goto err;
			qp->q.len = end;
		}
	}
	if (end == offset)
		goto err;
	err = -ENOMEM;
	if (!pskb_pull(skb, skb_network_offset(skb) + ihl))
		goto err;
	err = pskb_trim_rcsum(skb, end - offset);
	if (err)
		goto err;
	prev = qp->q.fragments_tail;
	if (!prev || FRAG_CB(prev)->offset < offset) {
		next = NULL;
		goto found;
	}
	prev = NULL;
	for (next = qp->q.fragments; next != NULL; next = next->next) {
		if (FRAG_CB(next)->offset >= offset)
			break;	
		prev = next;
	}
found:
	if (prev) {
		int i = (FRAG_CB(prev)->offset + prev->len) - offset;
		if (i > 0) {
			offset += i;
			err = -EINVAL;
			if (end <= offset)
				goto err;
			err = -ENOMEM;
			if (!pskb_pull(skb, i))
				goto err;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)
				skb->ip_summed = CHECKSUM_NONE;
		}
	}
	err = -ENOMEM;
	while (next && FRAG_CB(next)->offset < end) {
		int i = end - FRAG_CB(next)->offset; 
		if (i < next->len) {
			if (!pskb_pull(next, i))
				goto err;
			FRAG_CB(next)->offset += i;
			qp->q.meat -= i;
			if (next->ip_summed != CHECKSUM_UNNECESSARY)
				next->ip_summed = CHECKSUM_NONE;
			break;
		} else {
			struct sk_buff *free_it = next;
			next = next->next;
			if (prev)
				prev->next = next;
			else
				qp->q.fragments = next;
			qp->q.meat -= free_it->len;
			sub_frag_mem_limit(qp->q.net, free_it->truesize);
			kfree_skb(free_it);
		}
	}
	FRAG_CB(skb)->offset = offset;
	skb->next = next;
	if (!next)
		qp->q.fragments_tail = skb;
	if (prev)
		prev->next = skb;
	else
		qp->q.fragments = skb;
	dev = skb->dev;
	if (dev) {
		qp->iif = dev->ifindex;
		skb->dev = NULL;
	}
	qp->q.stamp = skb->tstamp;
	qp->q.meat += skb->len;
	qp->ecn |= ecn;
	add_frag_mem_limit(qp->q.net, skb->truesize);
	if (offset == 0)
		qp_flags(qp) |= INET_FRAG_FIRST_IN;
	fragsize = skb->len + ihl;
	if (fragsize > qp->q.max_size)
		qp->q.max_size = fragsize;
	if (ip_hdr(skb)->frag_off & htons(IP_DF) &&
	    fragsize > qp->max_df_size)
		qp->max_df_size = fragsize;
	if (qp_flags(qp) == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
	    qp->q.meat == qp->q.len) {
		unsigned long orefdst = skb->_skb_refdst;
		skb->_skb_refdst = 0UL;
		err = ip_frag_reasm(qp, prev, dev);
		skb->_skb_refdst = orefdst;
		return err;
	}
	skb_dst_drop(skb);
	inet_frag_lru_move(&qp->q);
	return -EINPROGRESS;
err:
	kfree_skb(skb);
	return err;
}
static int ip_frag_queue ( struct ipq * qp , struct sk_buff * skb ) { struct sk_buff * prev , * next ; struct net_device * dev ; unsigned int fragsize ; int flags , offset ; int ihl , end ; int err = - ENOENT ; u8 ecn ; if ( qp_flags ( qp ) & INET_FRAG_COMPLETE ) goto err ; if ( ! ( IPCB ( skb ) -> flags & IPSKB_FRAG_COMPLETE ) && unlikely ( ip_frag_too_far ( qp ) ) && unlikely ( err = ip_frag_reinit ( qp ) ) ) { ipq_kill ( qp ) ; goto err ; } ecn = ip4_frag_ecn ( ip_hdr ( skb ) -> tos ) ; offset = ntohs ( ip_hdr ( skb ) -> frag_off ) ; flags = offset & ~ IP_OFFSET ; offset &= IP_OFFSET ; offset <<= 3 ; ihl = ip_hdrlen ( skb ) ; end = offset + skb -> len - skb_network_offset ( skb ) - ihl ; err = - EINVAL ; if ( ( flags & IP_MF ) == 0 ) { if ( end < qp -> q . len || ( ( qp_flags ( qp ) & INET_FRAG_LAST_IN ) && end != qp -> q . len ) ) goto err ; qp_flags ( qp ) |= INET_FRAG_LAST_IN ; qp -> q . len = end ; } else { if ( end & 7 ) { end &= ~ 7 ; if ( skb -> ip_summed != CHECKSUM_UNNECESSARY ) skb -> ip_summed = CHECKSUM_NONE ; } if ( end > qp -> q . len ) { if ( qp_flags ( qp ) & INET_FRAG_LAST_IN ) goto err ; qp -> q . len = end ; } } if ( end == offset ) goto err ; err = - ENOMEM ; if ( ! pskb_pull ( skb , skb_network_offset ( skb ) + ihl ) ) goto err ; err = pskb_trim_rcsum ( skb , end - offset ) ; if ( err ) goto err ; prev = qp -> q . fragments_tail ; if ( ! prev || FRAG_CB ( prev ) -> offset < offset ) { next = NULL ; goto found ; } prev = NULL ; for ( next = qp -> q . fragments ; next != NULL ; next = next -> next ) { if ( FRAG_CB ( next ) -> offset >= offset ) break ; prev = next ; } found : if ( prev ) { int i = ( FRAG_CB ( prev ) -> offset + prev -> len ) - offset ; if ( i > 0 ) { offset += i ; err = - EINVAL ; if ( end <= offset ) goto err ; err = - ENOMEM ; if ( ! pskb_pull ( skb , i ) ) goto err ; if ( skb -> ip_summed != CHECKSUM_UNNECESSARY ) skb -> ip_summed = CHECKSUM_NONE ; } } err = - ENOMEM ; while ( next && FRAG_CB ( next ) -> offset < end ) { int i = end - FRAG_CB ( next ) -> offset ; if ( i < next -> len ) { if ( ! pskb_pull ( next , i ) ) goto err ; FRAG_CB ( next ) -> offset += i ; qp -> q . meat -= i ; if ( next -> ip_summed != CHECKSUM_UNNECESSARY ) next -> ip_summed = CHECKSUM_NONE ; break ; } else { struct sk_buff * free_it = next ; next = next -> next ; if ( prev ) prev -> next = next ; else qp -> q . fragments = next ; qp -> q . meat -= free_it -> len ; sub_frag_mem_limit ( qp -> q . net , free_it -> truesize ) ; kfree_skb ( free_it ) ; } } FRAG_CB ( skb ) -> offset = offset ; skb -> next = next ; if ( ! next ) qp -> q . fragments_tail = skb ; if ( prev ) prev -> next = skb ; else qp -> q . fragments = skb ; dev = skb -> dev ; if ( dev ) { qp -> iif = dev -> ifindex ; skb -> dev = NULL ; } qp -> q . stamp = skb -> tstamp ; qp -> q . meat += skb -> len ; qp -> ecn |= ecn ; add_frag_mem_limit ( qp -> q . net , skb -> truesize ) ; if ( offset == 0 ) qp_flags ( qp ) |= INET_FRAG_FIRST_IN ; fragsize = skb -> len + ihl ; if ( fragsize > qp -> q . max_size ) qp -> q . max_size = fragsize ; if ( ip_hdr ( skb ) -> frag_off & htons ( IP_DF ) && fragsize > qp -> max_df_size ) qp -> max_df_size = fragsize ; if ( qp_flags ( qp ) == ( INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN ) && qp -> q . meat == qp -> q . len ) { unsigned long orefdst = skb -> _skb_refdst ; skb -> _skb_refdst = 0UL ; err = ip_frag_reasm ( qp , prev , dev ) ; skb -> _skb_refdst = orefdst ; return err ; } skb_dst_drop ( skb ) ; inet_frag_lru_move ( & qp -> q ) ; return - EINPROGRESS ; err : kfree_skb ( skb ) ; return err ; }
Add new segment to existing queue.
Add new segment to existing queue .
/* offset is in 8-byte chunks */ /* Determine the position of this fragment. */ /* Is this the final fragment? */ /* If we already have some bits beyond end
		 * or have different end, the segment is corrupted.
		 */ /* Some bits beyond end -> corruption. */ /* 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
	 * this fragment, right?
	 */ /* bingo! */ /* We found where to put this one.  Check for overlap with
	 * preceding fragment, and, if needed, align things so that
	 * any overlaps are eliminated.
	 */ /* overlap is 'i' bytes */ /* Eat head of the next overlapped fragment
			 * and leave the loop. The next ones cannot overlap.
			 */ /* Old fragment is completely overridden with
			 * new one drop it.
			 */ /* Insert this fragment in the chain of fragments. */