xfrm: Assign the inner mode output function to the dst entry
[linux-2.6.git] / net / xfrm / xfrm_output.c
index 40d75ec..47bacd8 100644 (file)
 #include <linux/errno.h>
 #include <linux/module.h>
 #include <linux/netdevice.h>
+#include <linux/netfilter.h>
 #include <linux/skbuff.h>
+#include <linux/slab.h>
 #include <linux/spinlock.h>
-#include <linux/time.h>
 #include <net/dst.h>
 #include <net/xfrm.h>
 
+static int xfrm_output2(struct sk_buff *skb);
+
 static int xfrm_state_check_space(struct xfrm_state *x, struct sk_buff *skb)
 {
-       int nhead = x->props.header_len + LL_RESERVED_SPACE(skb->dst->dev)
+       struct dst_entry *dst = skb_dst(skb);
+       int nhead = dst->header_len + LL_RESERVED_SPACE(dst->dev)
                - skb_headroom(skb);
+       int ntail = dst->dev->needed_tailroom - skb_tailroom(skb);
 
-       if (nhead > 0)
-               return pskb_expand_head(skb, nhead, 0, GFP_ATOMIC);
+       if (nhead <= 0) {
+               if (ntail <= 0)
+                       return 0;
+               nhead = 0;
+       } else if (ntail < 0)
+               ntail = 0;
 
-       /* Check tail too... */
-       return 0;
+       return pskb_expand_head(skb, nhead, ntail, GFP_ATOMIC);
 }
 
-static int xfrm_state_check(struct xfrm_state *x, struct sk_buff *skb)
+static int xfrm_output_one(struct sk_buff *skb, int err)
 {
-       int err = xfrm_state_check_expire(x);
-       if (err < 0)
-               goto err;
-       err = xfrm_state_check_space(x, skb);
-err:
-       return err;
-}
-
-int xfrm_output(struct sk_buff *skb)
-{
-       struct dst_entry *dst = skb->dst;
+       struct dst_entry *dst = skb_dst(skb);
        struct xfrm_state *x = dst->xfrm;
-       int err;
+       struct net *net = xs_net(x);
 
-       if (skb->ip_summed == CHECKSUM_PARTIAL) {
-               err = skb_checksum_help(skb);
-               if (err)
-                       goto error_nolock;
-       }
+       if (err <= 0)
+               goto resume;
 
        do {
-               spin_lock_bh(&x->lock);
-               err = xfrm_state_check(x, skb);
-               if (err)
-                       goto error;
+               err = xfrm_state_check_space(x, skb);
+               if (err) {
+                       XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
+                       goto error_nolock;
+               }
 
-               if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
-                       XFRM_SKB_CB(skb)->seq = ++x->replay.oseq;
-                       if (xfrm_aevent_is_on())
-                               xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
+               err = x->outer_mode->output(x, skb);
+               if (err) {
+                       XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEMODEERROR);
+                       goto error_nolock;
                }
 
-               err = x->mode->output(x, skb);
-               if (err)
+               spin_lock_bh(&x->lock);
+               err = xfrm_state_check_expire(x);
+               if (err) {
+                       XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEEXPIRED);
                        goto error;
+               }
 
-               err = x->type->output(x, skb);
-               if (err)
+               err = x->repl->overflow(x, skb);
+               if (err) {
+                       XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATESEQERROR);
                        goto error;
+               }
 
                x->curlft.bytes += skb->len;
                x->curlft.packets++;
 
-               if (x->props.mode == XFRM_MODE_ROUTEOPTIMIZATION)
-                       x->lastused = get_seconds();
-
                spin_unlock_bh(&x->lock);
 
-               skb_reset_network_header(skb);
+               skb_dst_force(skb);
 
-               if (!(skb->dst = dst_pop(dst))) {
+               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 = skb_dst_pop(skb);
+               if (!dst) {
+                       XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
                        err = -EHOSTUNREACH;
                        goto error_nolock;
                }
-               dst = skb->dst;
+               skb_dst_set(skb, dst);
                x = dst->xfrm;
-       } while (x && (x->props.mode != XFRM_MODE_TUNNEL));
+       } while (x && !(x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL));
 
        err = 0;
 
-error_nolock:
+out_exit:
        return err;
 error:
        spin_unlock_bh(&x->lock);
-       goto error_nolock;
+error_nolock:
+       kfree_skb(skb);
+       goto out_exit;
+}
+
+int xfrm_output_resume(struct sk_buff *skb, int err)
+{
+       while (likely((err = xfrm_output_one(skb, err)) == 0)) {
+               nf_reset(skb);
+
+               err = skb_dst(skb)->ops->local_out(skb);
+               if (unlikely(err != 1))
+                       goto out;
+
+               if (!skb_dst(skb)->xfrm)
+                       return dst_output(skb);
+
+               err = nf_hook(skb_dst(skb)->ops->family,
+                             NF_INET_POST_ROUTING, skb,
+                             NULL, skb_dst(skb)->dev, xfrm_output2);
+               if (unlikely(err != 1))
+                       goto out;
+       }
+
+       if (err == -EINPROGRESS)
+               err = 0;
+
+out:
+       return err;
+}
+EXPORT_SYMBOL_GPL(xfrm_output_resume);
+
+static int xfrm_output2(struct sk_buff *skb)
+{
+       return xfrm_output_resume(skb, 1);
+}
+
+static int xfrm_output_gso(struct sk_buff *skb)
+{
+       struct sk_buff *segs;
+
+       segs = skb_gso_segment(skb, 0);
+       kfree_skb(skb);
+       if (IS_ERR(segs))
+               return PTR_ERR(segs);
+
+       do {
+               struct sk_buff *nskb = segs->next;
+               int err;
+
+               segs->next = NULL;
+               err = xfrm_output2(segs);
+
+               if (unlikely(err)) {
+                       while ((segs = nskb)) {
+                               nskb = segs->next;
+                               segs->next = NULL;
+                               kfree_skb(segs);
+                       }
+                       return err;
+               }
+
+               segs = nskb;
+       } while (segs);
+
+       return 0;
 }
+
+int xfrm_output(struct sk_buff *skb)
+{
+       struct net *net = dev_net(skb_dst(skb)->dev);
+       int err;
+
+       if (skb_is_gso(skb))
+               return xfrm_output_gso(skb);
+
+       if (skb->ip_summed == CHECKSUM_PARTIAL) {
+               err = skb_checksum_help(skb);
+               if (err) {
+                       XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
+                       kfree_skb(skb);
+                       return err;
+               }
+       }
+
+       return xfrm_output2(skb);
+}
+
+int xfrm_inner_extract_output(struct xfrm_state *x, struct sk_buff *skb)
+{
+       struct xfrm_mode *inner_mode;
+       if (x->sel.family == AF_UNSPEC)
+               inner_mode = xfrm_ip2inner_mode(x,
+                               xfrm_af2proto(skb_dst(skb)->ops->family));
+       else
+               inner_mode = x->inner_mode;
+
+       if (inner_mode == NULL)
+               return -EAFNOSUPPORT;
+       return inner_mode->afinfo->extract_output(x, skb);
+}
+
 EXPORT_SYMBOL_GPL(xfrm_output);
+EXPORT_SYMBOL_GPL(xfrm_inner_extract_output);