static int ah_output(struct xfrm_state *x, struct sk_buff *skb)
{
	int err;
	struct iphdr *iph, *top_iph;
	struct ip_auth_hdr *ah;
	struct ah_data *ahp;
	union {
		struct iphdr	iph;
		char 		buf[60];
	} tmp_iph;

	skb_push(skb, -skb_network_offset(skb));
	top_iph = ip_hdr(skb);
	iph = &tmp_iph.iph;

	iph->tos = top_iph->tos;
	iph->ttl = top_iph->ttl;
	iph->frag_off = top_iph->frag_off;

	if (top_iph->ihl != 5) {
		iph->daddr = top_iph->daddr;
		memcpy(iph+1, top_iph+1, top_iph->ihl*4 - sizeof(struct iphdr));
		err = ip_clear_mutable_options(top_iph, &top_iph->daddr);
		if (err)
			goto error;
	}

	ah = ip_auth_hdr(skb);
	ah->nexthdr = *skb_mac_header(skb);
	*skb_mac_header(skb) = IPPROTO_AH;

	top_iph->tos = 0;
	top_iph->tot_len = htons(skb->len);
	top_iph->frag_off = 0;
	top_iph->ttl = 0;
	top_iph->check = 0;

	ahp = x->data;
	ah->hdrlen  = (XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len) >> 2) - 2;

	ah->reserved = 0;
	ah->spi = x->id.spi;
	ah->seq_no = htonl(XFRM_SKB_CB(skb)->seq.output);

	spin_lock_bh(&x->lock);
	err = ah_mac_digest(ahp, skb, ah->auth_data);
	memcpy(ah->auth_data, ahp->work_icv, ahp->icv_trunc_len);
	spin_unlock_bh(&x->lock);

	if (err)
		goto error;

	top_iph->tos = iph->tos;
	top_iph->ttl = iph->ttl;
	top_iph->frag_off = iph->frag_off;
	if (top_iph->ihl != 5) {
		top_iph->daddr = iph->daddr;
		memcpy(top_iph+1, iph+1, top_iph->ihl*4 - sizeof(struct iphdr));
	}

	err = 0;

error:
	return err;
}
static int xfrm_output_one(struct sk_buff *skb, int err)
{
	struct dst_entry *dst = skb_dst(skb);
	struct xfrm_state *x = dst->xfrm;
	struct net *net = xs_net(x);

	if (err <= 0)
		goto resume;

	do {
		err = xfrm_state_check_space(x, skb);
		if (err) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
			goto error_nolock;
		}

		err = x->outer_mode->output(x, skb);
		if (err) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEMODEERROR);
			goto error_nolock;
		}

		spin_lock_bh(&x->lock);
		err = xfrm_state_check_expire(x);
		if (err) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEEXPIRED);
			goto error;
		}

		if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
			XFRM_SKB_CB(skb)->seq.output = ++x->replay.oseq;
			if (unlikely(x->replay.oseq == 0)) {
				XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATESEQERROR);
				x->replay.oseq--;
				xfrm_audit_state_replay_overflow(x, skb);
				err = -EOVERFLOW;
				goto error;
			}
			if (xfrm_aevent_is_on(net))
				xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
		}

		x->curlft.bytes += skb->len;
		x->curlft.packets++;

		spin_unlock_bh(&x->lock);

		err = x->type->output(x, skb);
		if (err == -EINPROGRESS)
			goto out_exit;

resume:
		if (err) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEPROTOERROR);
			goto error_nolock;
		}

		dst = dst_pop(dst);
		if (!dst) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
			err = -EHOSTUNREACH;
			goto error_nolock;
		}
		skb_dst_set(skb, dst);
		x = dst->xfrm;
	} while (x && !(x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL));

	err = 0;

out_exit:
	return err;
error:
	spin_unlock_bh(&x->lock);
error_nolock:
	kfree_skb(skb);
	goto out_exit;
}
int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
{
	struct net *net = dev_net(skb->dev);
	int err;
	__be32 seq;
	__be32 seq_hi;
	struct xfrm_state *x;
	xfrm_address_t *daddr;
	struct xfrm_mode *inner_mode;
	unsigned int family;
	int decaps = 0;
	int async = 0;

	/*                                                   */
	if (encap_type < 0) {
		async = 1;
		x = xfrm_input_state(skb);
		seq = XFRM_SKB_CB(skb)->seq.input.low;
		goto resume;
	}

	/*                                           */
	if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
		struct sec_path *sp;

		sp = secpath_dup(skb->sp);
		if (!sp) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINERROR);
			goto drop;
		}
		if (skb->sp)
			secpath_put(skb->sp);
		skb->sp = sp;
	}

	daddr = (xfrm_address_t *)(skb_network_header(skb) +
				   XFRM_SPI_SKB_CB(skb)->daddroff);
	family = XFRM_SPI_SKB_CB(skb)->family;

	seq = 0;
	if (!spi && (err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != 0) {
		XFRM_INC_STATS(net, LINUX_MIB_XFRMINHDRERROR);
		goto drop;
	}

	do {
		if (skb->sp->len == XFRM_MAX_DEPTH) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINBUFFERERROR);
			goto drop;
		}

		x = xfrm_state_lookup(net, skb->mark, daddr, spi, nexthdr, family);
		if (x == NULL) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOSTATES);
			xfrm_audit_state_notfound(skb, family, spi, seq);
			goto drop;
		}

		skb->sp->xvec[skb->sp->len++] = x;

		spin_lock(&x->lock);
		if (unlikely(x->km.state != XFRM_STATE_VALID)) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEINVALID);
			goto drop_unlock;
		}

		if ((x->encap ? x->encap->encap_type : 0) != encap_type) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMISMATCH);
			goto drop_unlock;
		}

		if (x->repl->check(x, skb, seq)) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATESEQERROR);
			goto drop_unlock;
		}

		if (xfrm_state_check_expire(x)) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEEXPIRED);
			goto drop_unlock;
		}

		spin_unlock(&x->lock);

		seq_hi = htonl(xfrm_replay_seqhi(x, seq));

		XFRM_SKB_CB(skb)->seq.input.low = seq;
		XFRM_SKB_CB(skb)->seq.input.hi = seq_hi;

		skb_dst_force(skb);

		nexthdr = x->type->input(x, skb);

		if (nexthdr == -EINPROGRESS)
			return 0;

resume:
		spin_lock(&x->lock);
		if (nexthdr <= 0) {
			if (nexthdr == -EBADMSG) {
				xfrm_audit_state_icvfail(x, skb,
							 x->type->proto);
				x->stats.integrity_failed++;
			}
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEPROTOERROR);
			goto drop_unlock;
		}

		/*                                         */
		encap_type = 0;

		if (async && x->repl->check(x, skb, seq)) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATESEQERROR);
			goto drop_unlock;
		}

		x->repl->advance(x, seq);

		x->curlft.bytes += skb->len;
		x->curlft.packets++;

		spin_unlock(&x->lock);

		XFRM_MODE_SKB_CB(skb)->protocol = nexthdr;

		inner_mode = x->inner_mode;

		if (x->sel.family == AF_UNSPEC) {
			inner_mode = xfrm_ip2inner_mode(x, XFRM_MODE_SKB_CB(skb)->protocol);
			if (inner_mode == NULL)
				goto drop;
		}

		if (inner_mode->input(x, skb)) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR);
			goto drop;
		}

		if (x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL) {
			decaps = 1;
			break;
		}

		/*
                                                              
                                                      
   */
		daddr = &x->id.daddr;
		family = x->outer_mode->afinfo->family;

		err = xfrm_parse_spi(skb, nexthdr, &spi, &seq);
		if (err < 0) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINHDRERROR);
			goto drop;
		}
	} while (!err);

	nf_reset(skb);

	if (decaps) {
		skb_dst_drop(skb);
		netif_rx(skb);
		return 0;
	} else {
		return x->inner_mode->afinfo->transport_finish(skb, async);
	}

drop_unlock:
	spin_unlock(&x->lock);
drop:
	kfree_skb(skb);
	return 0;
}
Esempio n. 4
0
static int ah6_output(struct xfrm_state *x, struct sk_buff *skb)
{
	int err;
	int extlen;
	struct ipv6hdr *top_iph;
	struct ip_auth_hdr *ah;
	struct ah_data *ahp;
	u8 nexthdr;
	char tmp_base[8];
	struct {
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
		struct in6_addr saddr;
#endif
		struct in6_addr daddr;
		char hdrs[0];
	} *tmp_ext;

	skb_push(skb, -skb_network_offset(skb));
	top_iph = ipv6_hdr(skb);
	top_iph->payload_len = htons(skb->len - sizeof(*top_iph));

	nexthdr = *skb_mac_header(skb);
	*skb_mac_header(skb) = IPPROTO_AH;

	/* When there are no extension headers, we only need to save the first
	 * 8 bytes of the base IP header.
	 */
	memcpy(tmp_base, top_iph, sizeof(tmp_base));

	tmp_ext = NULL;
	extlen = skb_transport_offset(skb) - sizeof(struct ipv6hdr);
	if (extlen) {
		extlen += sizeof(*tmp_ext);
		tmp_ext = kmalloc(extlen, GFP_ATOMIC);
		if (!tmp_ext) {
			err = -ENOMEM;
			goto error;
		}
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
		memcpy(tmp_ext, &top_iph->saddr, extlen);
#else
		memcpy(tmp_ext, &top_iph->daddr, extlen);
#endif
		err = ipv6_clear_mutable_options(top_iph,
						 extlen - sizeof(*tmp_ext) +
						 sizeof(*top_iph),
						 XFRM_POLICY_OUT);
		if (err)
			goto error_free_iph;
	}

	ah = ip_auth_hdr(skb);
	ah->nexthdr = nexthdr;

	top_iph->priority    = 0;
	top_iph->flow_lbl[0] = 0;
	top_iph->flow_lbl[1] = 0;
	top_iph->flow_lbl[2] = 0;
	top_iph->hop_limit   = 0;

	ahp = x->data;
	ah->hdrlen  = (XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len) >> 2) - 2;

	ah->reserved = 0;
	ah->spi = x->id.spi;
	ah->seq_no = htonl(XFRM_SKB_CB(skb)->seq.output);

	spin_lock_bh(&x->lock);
	err = ah_mac_digest(ahp, skb, ah->auth_data);
	memcpy(ah->auth_data, ahp->work_icv, ahp->icv_trunc_len);
	spin_unlock_bh(&x->lock);

	if (err)
		goto error_free_iph;

	memcpy(top_iph, tmp_base, sizeof(tmp_base));
	if (tmp_ext) {
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
		memcpy(&top_iph->saddr, tmp_ext, extlen);
#else
		memcpy(&top_iph->daddr, tmp_ext, extlen);
#endif
error_free_iph:
		kfree(tmp_ext);
	}

error:
	return err;
}
Esempio n. 5
0
int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
{
	struct net *net = dev_net(skb->dev);
	int err;
	__be32 seq;
	__be32 seq_hi;
	struct xfrm_state *x = NULL;
	xfrm_address_t *daddr;
	struct xfrm_mode *inner_mode;
	u32 mark = skb->mark;
	unsigned int family;
	int decaps = 0;
	int async = 0;

	/* A negative encap_type indicates async resumption. */
	if (encap_type < 0) {
		async = 1;
		x = xfrm_input_state(skb);
		seq = XFRM_SKB_CB(skb)->seq.input.low;
		family = x->outer_mode->afinfo->family;
		goto resume;
	}

	daddr = (xfrm_address_t *)(skb_network_header(skb) +
				   XFRM_SPI_SKB_CB(skb)->daddroff);
	family = XFRM_SPI_SKB_CB(skb)->family;

	/* if tunnel is present override skb->mark value with tunnel i_key */
	if (XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4) {
		switch (family) {
		case AF_INET:
			mark = be32_to_cpu(XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4->parms.i_key);
			break;
		case AF_INET6:
			mark = be32_to_cpu(XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6->parms.i_key);
			break;
		}
	}

	/* Allocate new secpath or COW existing one. */
	if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
		struct sec_path *sp;

		sp = secpath_dup(skb->sp);
		if (!sp) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINERROR);
			goto drop;
		}
		if (skb->sp)
			secpath_put(skb->sp);
		skb->sp = sp;
	}

	seq = 0;
	if (!spi && (err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != 0) {
		XFRM_INC_STATS(net, LINUX_MIB_XFRMINHDRERROR);
		goto drop;
	}

	do {
		if (skb->sp->len == XFRM_MAX_DEPTH) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINBUFFERERROR);
			goto drop;
		}

		x = xfrm_state_lookup(net, mark, daddr, spi, nexthdr, family);
		if (x == NULL) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOSTATES);
			xfrm_audit_state_notfound(skb, family, spi, seq);
			goto drop;
		}

		skb->sp->xvec[skb->sp->len++] = x;

		spin_lock(&x->lock);

		if (unlikely(x->km.state != XFRM_STATE_VALID)) {
			if (x->km.state == XFRM_STATE_ACQ)
				XFRM_INC_STATS(net, LINUX_MIB_XFRMACQUIREERROR);
			else
				XFRM_INC_STATS(net,
					       LINUX_MIB_XFRMINSTATEINVALID);
			goto drop_unlock;
		}

		if ((x->encap ? x->encap->encap_type : 0) != encap_type) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMISMATCH);
			goto drop_unlock;
		}

		if (x->repl->check(x, skb, seq)) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATESEQERROR);
			goto drop_unlock;
		}

		if (xfrm_state_check_expire(x)) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEEXPIRED);
			goto drop_unlock;
		}

		spin_unlock(&x->lock);

		if (xfrm_tunnel_check(skb, x, family)) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR);
			goto drop;
		}

		seq_hi = htonl(xfrm_replay_seqhi(x, seq));

		XFRM_SKB_CB(skb)->seq.input.low = seq;
		XFRM_SKB_CB(skb)->seq.input.hi = seq_hi;

		skb_dst_force(skb);

		nexthdr = x->type->input(x, skb);

		if (nexthdr == -EINPROGRESS)
			return 0;
resume:
		spin_lock(&x->lock);
		if (nexthdr <= 0) {
			if (nexthdr == -EBADMSG) {
				xfrm_audit_state_icvfail(x, skb,
							 x->type->proto);
				x->stats.integrity_failed++;
			}
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEPROTOERROR);
			goto drop_unlock;
		}

		/* only the first xfrm gets the encap type */
		encap_type = 0;

		if (async && x->repl->recheck(x, skb, seq)) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATESEQERROR);
			goto drop_unlock;
		}

		x->repl->advance(x, seq);

		x->curlft.bytes += skb->len;
		x->curlft.packets++;

		spin_unlock(&x->lock);

		XFRM_MODE_SKB_CB(skb)->protocol = nexthdr;

		inner_mode = x->inner_mode;

		if (x->sel.family == AF_UNSPEC) {
			inner_mode = xfrm_ip2inner_mode(x, XFRM_MODE_SKB_CB(skb)->protocol);
			if (inner_mode == NULL) {
				XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR);
				goto drop;
			}
		}

		if (inner_mode->input(x, skb)) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR);
			goto drop;
		}

		if (x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL) {
			decaps = 1;
			break;
		}

		/*
		 * We need the inner address.  However, we only get here for
		 * transport mode so the outer address is identical.
		 */
		daddr = &x->id.daddr;
		family = x->outer_mode->afinfo->family;

		err = xfrm_parse_spi(skb, nexthdr, &spi, &seq);
		if (err < 0) {
			XFRM_INC_STATS(net, LINUX_MIB_XFRMINHDRERROR);
			goto drop;
		}
	} while (!err);

	err = xfrm_rcv_cb(skb, family, x->type->proto, 0);
	if (err)
		goto drop;

	nf_reset(skb);

	if (decaps) {
		skb_dst_drop(skb);
		netif_rx(skb);
		return 0;
	} else {
		return x->inner_mode->afinfo->transport_finish(skb, async);
	}

drop_unlock:
	spin_unlock(&x->lock);
drop:
	xfrm_rcv_cb(skb, family, x && x->type ? x->type->proto : nexthdr, -1);
	kfree_skb(skb);
	return 0;
}
Esempio n. 6
0
int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
{
    int err;
    __be32 seq;
    struct xfrm_state *x;
    xfrm_address_t *daddr;
    struct xfrm_mode *inner_mode;
    unsigned int family;
    int decaps = 0;
    int async = 0;

    /* A negative encap_type indicates async resumption. */
    if (encap_type < 0) {
        async = 1;
        x = xfrm_input_state(skb);
        seq = XFRM_SKB_CB(skb)->seq.input;
        goto resume;
    }

    /* Allocate new secpath or COW existing one. */
    if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
        struct sec_path *sp;

        sp = secpath_dup(skb->sp);
        if (!sp) {
            XFRM_INC_STATS(LINUX_MIB_XFRMINERROR);
            goto drop;
        }
        if (skb->sp)
            secpath_put(skb->sp);
        skb->sp = sp;
    }

    daddr = (xfrm_address_t *)(skb_network_header(skb) +
                   XFRM_SPI_SKB_CB(skb)->daddroff);
    family = XFRM_SPI_SKB_CB(skb)->family;

    seq = 0;
    if (!spi && (err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != 0) {
        XFRM_INC_STATS(LINUX_MIB_XFRMINHDRERROR);
        goto drop;
    }

    do {
        if (skb->sp->len == XFRM_MAX_DEPTH) {
            XFRM_INC_STATS(LINUX_MIB_XFRMINBUFFERERROR);
            goto drop;
        }

        x = xfrm_state_lookup(daddr, spi, nexthdr, family);
        if (x == NULL) {
            XFRM_INC_STATS(LINUX_MIB_XFRMINNOSTATES);
            xfrm_audit_state_notfound(skb, family, spi, seq);
            goto drop;
        }

        skb->sp->xvec[skb->sp->len++] = x;

        spin_lock(&x->lock);
        if (unlikely(x->km.state != XFRM_STATE_VALID)) {
            XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEINVALID);
            goto drop_unlock;
        }

        if ((x->encap ? x->encap->encap_type : 0) != encap_type) {
            XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEMISMATCH);
            goto drop_unlock;
        }

        if (x->props.replay_window && xfrm_replay_check(x, skb, seq)) {
            XFRM_INC_STATS(LINUX_MIB_XFRMINSTATESEQERROR);
            goto drop_unlock;
        }

        if (xfrm_state_check_expire(x)) {
            XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEEXPIRED);
            goto drop_unlock;
        }

        spin_unlock(&x->lock);

        XFRM_SKB_CB(skb)->seq.input = seq;

        nexthdr = x->type->input(x, skb);

        if (nexthdr == -EINPROGRESS)
            return 0;

resume:
        spin_lock(&x->lock);
        if (nexthdr <= 0) {
            if (nexthdr == -EBADMSG) {
                xfrm_audit_state_icvfail(x, skb,
                             x->type->proto);
                x->stats.integrity_failed++;
            }
            XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEPROTOERROR);
            goto drop_unlock;
        }

        /* only the first xfrm gets the encap type */
        encap_type = 0;

        if (x->props.replay_window)
            xfrm_replay_advance(x, seq);

        x->curlft.bytes += skb->len;
        x->curlft.packets++;

        spin_unlock(&x->lock);

        XFRM_MODE_SKB_CB(skb)->protocol = nexthdr;

        inner_mode = x->inner_mode;

        if (x->sel.family == AF_UNSPEC) {
            inner_mode = xfrm_ip2inner_mode(x, XFRM_MODE_SKB_CB(skb)->protocol);
            if (inner_mode == NULL)
                goto drop;
        }

        if (inner_mode->input(x, skb)) {
            XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEMODEERROR);
            goto drop;
        }

        if (x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL) {
            decaps = 1;
            break;
        }

        /*
         * We need the inner address.  However, we only get here for
         * transport mode so the outer address is identical.
         */
        daddr = &x->id.daddr;
        family = x->outer_mode->afinfo->family;

        err = xfrm_parse_spi(skb, nexthdr, &spi, &seq);
        if (err < 0) {
            XFRM_INC_STATS(LINUX_MIB_XFRMINHDRERROR);
            goto drop;
        }
    } while (!err);

    nf_reset(skb);

    if (decaps) {
        dst_release(skb->dst);
        skb->dst = NULL;
        netif_rx(skb);
        return 0;
    } else {
        return x->inner_mode->afinfo->transport_finish(skb, async);
    }

drop_unlock:
    spin_unlock(&x->lock);
drop:
    kfree_skb(skb);
    return 0;
}
Esempio n. 7
0
int xfrm6_input_resume(struct sk_buff *skb, int nexthdr)
{
	int err;
	int spi;
	u32 seq;
	struct xfrm_state *x;
	int decaps = 0;
	unsigned int nhoff;

	nhoff = IP6CB(skb)->nhoff;
	x = skb->sp->xvec[skb->sp->len - 1];
	seq = XFRM_SKB_CB(skb)->seq;
	spin_lock(&x->lock);
	goto resume;

	do {
		struct ipv6hdr *iph = skb->nh.ipv6h;

		if (skb->sp->len == XFRM_MAX_DEPTH)
			goto drop;

		x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, nexthdr, AF_INET6);
		if (x == NULL) {
			xfrm_naudit_state_notfound(skb, AF_INET6, spi, seq);
			goto drop;
		}

		skb->sp->xvec[skb->sp->len++] = x;

		spin_lock(&x->lock);
		if (unlikely(x->km.state != XFRM_STATE_VALID))
			goto drop_unlock;

		if (x->props.replay_window && xfrm_replay_check(x, seq)) {
			xfrm_naudit_state_replay(x, skb, seq);
			goto drop_unlock;
		}

		if (xfrm_state_check_expire(x))
			goto drop_unlock;

		XFRM_SKB_CB(skb)->seq = seq;

		nexthdr = x->type->input(x, skb);

resume:
		if (nexthdr <= 0)
			goto drop_unlock;

		skb->nh.raw[nhoff] = nexthdr;

		if (x->props.replay_window)
			xfrm_replay_advance(x, seq);

		x->curlft.bytes += skb->len;
		x->curlft.packets++;

		spin_unlock(&x->lock);

		if (x->mode->input(x, skb))
			goto drop;

		if (x->props.mode) { /* XXX */
			decaps = 1;
			break;
		}

		if ((err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) < 0)
			goto drop;
	} while (!err);

	skb->ip_summed = CHECKSUM_NONE;

	nf_reset(skb);

	if (decaps) {
		if (!(skb->dev->flags&IFF_LOOPBACK)) {
			dst_release(skb->dst);
			skb->dst = NULL;
		}
		netif_rx(skb);
		return -1;
	} else {
		skb->nh.ipv6h->payload_len = htons(skb->len);
		__skb_push(skb, skb->data - skb->nh.raw);

		NF_HOOK(PF_INET6, NF_IP6_PRE_ROUTING, skb, skb->dev, NULL,
		        dst_input);
		return -1;
	}

drop_unlock:
	spin_unlock(&x->lock);
	if (nexthdr == -EINPROGRESS)
		return -1;
drop:
	kfree_skb(skb);
	return -1;
}