Merge branch 'core/xen' into x86/xen
[safe/jmp/linux-2.6] / net / xfrm / xfrm_policy.c
index b153f74..b7754b1 100644 (file)
 #include <linux/netfilter.h>
 #include <linux/module.h>
 #include <linux/cache.h>
+#include <linux/audit.h>
 #include <net/dst.h>
 #include <net/xfrm.h>
 #include <net/ip.h>
+#ifdef CONFIG_XFRM_STATISTICS
+#include <net/snmp.h>
+#endif
 
 #include "xfrm_hash.h"
 
 int sysctl_xfrm_larval_drop __read_mostly;
 
+#ifdef CONFIG_XFRM_STATISTICS
+DEFINE_SNMP_STAT(struct linux_xfrm_mib, xfrm_statistics) __read_mostly;
+EXPORT_SYMBOL(xfrm_statistics);
+#endif
+
 DEFINE_MUTEX(xfrm_cfg_mutex);
 EXPORT_SYMBOL(xfrm_cfg_mutex);
 
 static DEFINE_RWLOCK(xfrm_policy_lock);
 
+static struct list_head xfrm_policy_bytype[XFRM_POLICY_TYPE_MAX];
 unsigned int xfrm_policy_count[XFRM_POLICY_MAX*2];
 EXPORT_SYMBOL(xfrm_policy_count);
 
@@ -87,25 +97,52 @@ int xfrm_selector_match(struct xfrm_selector *sel, struct flowi *fl,
        return 0;
 }
 
+static inline struct dst_entry *__xfrm_dst_lookup(int tos,
+                                                 xfrm_address_t *saddr,
+                                                 xfrm_address_t *daddr,
+                                                 int family)
+{
+       struct xfrm_policy_afinfo *afinfo;
+       struct dst_entry *dst;
+
+       afinfo = xfrm_policy_get_afinfo(family);
+       if (unlikely(afinfo == NULL))
+               return ERR_PTR(-EAFNOSUPPORT);
+
+       dst = afinfo->dst_lookup(tos, saddr, daddr);
+
+       xfrm_policy_put_afinfo(afinfo);
+
+       return dst;
+}
+
 static inline struct dst_entry *xfrm_dst_lookup(struct xfrm_state *x, int tos,
+                                               xfrm_address_t *prev_saddr,
+                                               xfrm_address_t *prev_daddr,
                                                int family)
 {
        xfrm_address_t *saddr = &x->props.saddr;
        xfrm_address_t *daddr = &x->id.daddr;
-       struct xfrm_policy_afinfo *afinfo;
        struct dst_entry *dst;
 
-       if (x->type->flags & XFRM_TYPE_LOCAL_COADDR)
+       if (x->type->flags & XFRM_TYPE_LOCAL_COADDR) {
                saddr = x->coaddr;
-       if (x->type->flags & XFRM_TYPE_REMOTE_COADDR)
+               daddr = prev_daddr;
+       }
+       if (x->type->flags & XFRM_TYPE_REMOTE_COADDR) {
+               saddr = prev_saddr;
                daddr = x->coaddr;
+       }
 
-       afinfo = xfrm_policy_get_afinfo(family);
-       if (unlikely(afinfo == NULL))
-               return ERR_PTR(-EAFNOSUPPORT);
+       dst = __xfrm_dst_lookup(tos, saddr, daddr, family);
+
+       if (!IS_ERR(dst)) {
+               if (prev_saddr != saddr)
+                       memcpy(prev_saddr, saddr,  sizeof(*prev_saddr));
+               if (prev_daddr != daddr)
+                       memcpy(prev_daddr, daddr,  sizeof(*prev_daddr));
+       }
 
-       dst = afinfo->dst_lookup(tos, saddr, daddr);
-       xfrm_policy_put_afinfo(afinfo);
        return dst;
 }
 
@@ -199,6 +236,7 @@ struct xfrm_policy *xfrm_policy_alloc(gfp_t gfp)
        policy = kzalloc(sizeof(struct xfrm_policy), gfp);
 
        if (policy) {
+               INIT_LIST_HEAD(&policy->bytype);
                INIT_HLIST_NODE(&policy->bydst);
                INIT_HLIST_NODE(&policy->byidx);
                rwlock_init(&policy->lock);
@@ -212,7 +250,7 @@ EXPORT_SYMBOL(xfrm_policy_alloc);
 
 /* Destroy xfrm_policy: descendant resources must be released to this moment. */
 
-void __xfrm_policy_destroy(struct xfrm_policy *policy)
+void xfrm_policy_destroy(struct xfrm_policy *policy)
 {
        BUG_ON(!policy->dead);
 
@@ -221,10 +259,14 @@ void __xfrm_policy_destroy(struct xfrm_policy *policy)
        if (del_timer(&policy->timer))
                BUG();
 
-       security_xfrm_policy_free(policy);
+       write_lock_bh(&xfrm_policy_lock);
+       list_del(&policy->bytype);
+       write_unlock_bh(&xfrm_policy_lock);
+
+       security_xfrm_policy_free(policy->security);
        kfree(policy);
 }
-EXPORT_SYMBOL(__xfrm_policy_destroy);
+EXPORT_SYMBOL(xfrm_policy_destroy);
 
 static void xfrm_policy_gc_kill(struct xfrm_policy *policy)
 {
@@ -322,15 +364,31 @@ static void xfrm_dst_hash_transfer(struct hlist_head *list,
                                   struct hlist_head *ndsttable,
                                   unsigned int nhashmask)
 {
-       struct hlist_node *entry, *tmp;
+       struct hlist_node *entry, *tmp, *entry0 = NULL;
        struct xfrm_policy *pol;
+       unsigned int h0 = 0;
 
+redo:
        hlist_for_each_entry_safe(pol, entry, tmp, list, bydst) {
                unsigned int h;
 
                h = __addr_hash(&pol->selector.daddr, &pol->selector.saddr,
                                pol->family, nhashmask);
-               hlist_add_head(&pol->bydst, ndsttable+h);
+               if (!entry0) {
+                       hlist_del(entry);
+                       hlist_add_head(&pol->bydst, ndsttable+h);
+                       h0 = h;
+               } else {
+                       if (h != h0)
+                               continue;
+                       hlist_del(entry);
+                       hlist_add_after(entry0, &pol->bydst);
+               }
+               entry0 = entry;
+       }
+       if (!hlist_empty(list)) {
+               entry0 = NULL;
+               goto redo;
        }
 }
 
@@ -559,6 +617,7 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
        policy->curlft.use_time = 0;
        if (!mod_timer(&policy->timer, jiffies + HZ))
                xfrm_pol_hold(policy);
+       list_add_tail(&policy->bytype, &xfrm_policy_bytype[policy->type]);
        write_unlock_bh(&xfrm_policy_lock);
 
        if (delpol)
@@ -617,7 +676,8 @@ struct xfrm_policy *xfrm_policy_bysel_ctx(u8 type, int dir,
                    xfrm_sec_ctx_match(ctx, pol->security)) {
                        xfrm_pol_hold(pol);
                        if (delete) {
-                               *err = security_xfrm_policy_delete(pol);
+                               *err = security_xfrm_policy_delete(
+                                                               pol->security);
                                if (*err) {
                                        write_unlock_bh(&xfrm_policy_lock);
                                        return pol;
@@ -659,7 +719,8 @@ struct xfrm_policy *xfrm_policy_byid(u8 type, int dir, u32 id, int delete,
                if (pol->type == type && pol->index == id) {
                        xfrm_pol_hold(pol);
                        if (delete) {
-                               *err = security_xfrm_policy_delete(pol);
+                               *err = security_xfrm_policy_delete(
+                                                               pol->security);
                                if (*err) {
                                        write_unlock_bh(&xfrm_policy_lock);
                                        return pol;
@@ -697,10 +758,11 @@ xfrm_policy_flush_secctx_check(u8 type, struct xfrm_audit *audit_info)
                                     &xfrm_policy_inexact[dir], bydst) {
                        if (pol->type != type)
                                continue;
-                       err = security_xfrm_policy_delete(pol);
+                       err = security_xfrm_policy_delete(pol->security);
                        if (err) {
                                xfrm_audit_policy_delete(pol, 0,
                                                         audit_info->loginuid,
+                                                        audit_info->sessionid,
                                                         audit_info->secid);
                                return err;
                        }
@@ -711,10 +773,12 @@ xfrm_policy_flush_secctx_check(u8 type, struct xfrm_audit *audit_info)
                                             bydst) {
                                if (pol->type != type)
                                        continue;
-                               err = security_xfrm_policy_delete(pol);
+                               err = security_xfrm_policy_delete(
+                                                               pol->security);
                                if (err) {
                                        xfrm_audit_policy_delete(pol, 0,
                                                        audit_info->loginuid,
+                                                       audit_info->sessionid,
                                                        audit_info->secid);
                                        return err;
                                }
@@ -757,6 +821,7 @@ int xfrm_policy_flush(u8 type, struct xfrm_audit *audit_info)
                        write_unlock_bh(&xfrm_policy_lock);
 
                        xfrm_audit_policy_delete(pol, 1, audit_info->loginuid,
+                                                audit_info->sessionid,
                                                 audit_info->secid);
 
                        xfrm_policy_kill(pol);
@@ -779,6 +844,7 @@ int xfrm_policy_flush(u8 type, struct xfrm_audit *audit_info)
 
                                xfrm_audit_policy_delete(pol, 1,
                                                         audit_info->loginuid,
+                                                        audit_info->sessionid,
                                                         audit_info->secid);
                                xfrm_policy_kill(pol);
                                killed++;
@@ -797,57 +863,60 @@ out:
 }
 EXPORT_SYMBOL(xfrm_policy_flush);
 
-int xfrm_policy_walk(u8 type, int (*func)(struct xfrm_policy *, int, int, void*),
+int xfrm_policy_walk(struct xfrm_policy_walk *walk,
+                    int (*func)(struct xfrm_policy *, int, int, void*),
                     void *data)
 {
-       struct xfrm_policy *pol, *last = NULL;
-       struct hlist_node *entry;
-       int dir, last_dir = 0, count, error;
+       struct xfrm_policy *old, *pol, *last = NULL;
+       int error = 0;
+
+       if (walk->type >= XFRM_POLICY_TYPE_MAX &&
+           walk->type != XFRM_POLICY_TYPE_ANY)
+               return -EINVAL;
 
+       if (walk->policy == NULL && walk->count != 0)
+               return 0;
+
+       old = pol = walk->policy;
+       walk->policy = NULL;
        read_lock_bh(&xfrm_policy_lock);
-       count = 0;
 
-       for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) {
-               struct hlist_head *table = xfrm_policy_bydst[dir].table;
-               int i;
+       for (; walk->cur_type < XFRM_POLICY_TYPE_MAX; walk->cur_type++) {
+               if (walk->type != walk->cur_type &&
+                   walk->type != XFRM_POLICY_TYPE_ANY)
+                       continue;
 
-               hlist_for_each_entry(pol, entry,
-                                    &xfrm_policy_inexact[dir], bydst) {
-                       if (pol->type != type)
+               if (pol == NULL) {
+                       pol = list_first_entry(&xfrm_policy_bytype[walk->cur_type],
+                                              struct xfrm_policy, bytype);
+               }
+               list_for_each_entry_from(pol, &xfrm_policy_bytype[walk->cur_type], bytype) {
+                       if (pol->dead)
                                continue;
                        if (last) {
-                               error = func(last, last_dir % XFRM_POLICY_MAX,
-                                            count, data);
-                               if (error)
+                               error = func(last, xfrm_policy_id2dir(last->index),
+                                            walk->count, data);
+                               if (error) {
+                                       xfrm_pol_hold(last);
+                                       walk->policy = last;
                                        goto out;
-                       }
-                       last = pol;
-                       last_dir = dir;
-                       count++;
-               }
-               for (i = xfrm_policy_bydst[dir].hmask; i >= 0; i--) {
-                       hlist_for_each_entry(pol, entry, table + i, bydst) {
-                               if (pol->type != type)
-                                       continue;
-                               if (last) {
-                                       error = func(last, last_dir % XFRM_POLICY_MAX,
-                                                    count, data);
-                                       if (error)
-                                               goto out;
                                }
-                               last = pol;
-                               last_dir = dir;
-                               count++;
                        }
+                       last = pol;
+                       walk->count++;
                }
+               pol = NULL;
        }
-       if (count == 0) {
+       if (walk->count == 0) {
                error = -ENOENT;
                goto out;
        }
-       error = func(last, last_dir % XFRM_POLICY_MAX, 0, data);
+       if (last)
+               error = func(last, xfrm_policy_id2dir(last->index), 0, data);
 out:
        read_unlock_bh(&xfrm_policy_lock);
+       if (old != NULL)
+               xfrm_pol_put(old);
        return error;
 }
 EXPORT_SYMBOL(xfrm_policy_walk);
@@ -869,7 +938,8 @@ static int xfrm_policy_match(struct xfrm_policy *pol, struct flowi *fl,
 
        match = xfrm_selector_match(sel, fl, family);
        if (match)
-               ret = security_xfrm_policy_lookup(pol, fl->secid, dir);
+               ret = security_xfrm_policy_lookup(pol->security, fl->secid,
+                                                 dir);
 
        return ret;
 }
@@ -986,8 +1056,9 @@ static struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struc
                int err = 0;
 
                if (match) {
-                       err = security_xfrm_policy_lookup(pol, fl->secid,
-                                       policy_to_flow_dir(dir));
+                       err = security_xfrm_policy_lookup(pol->security,
+                                                     fl->secid,
+                                                     policy_to_flow_dir(dir));
                        if (!err)
                                xfrm_pol_hold(pol);
                        else if (err == -ESRCH)
@@ -1006,6 +1077,7 @@ static void __xfrm_policy_link(struct xfrm_policy *pol, int dir)
        struct hlist_head *chain = policy_hash_bysel(&pol->selector,
                                                     pol->family, dir);
 
+       list_add_tail(&pol->bytype, &xfrm_policy_bytype[pol->type]);
        hlist_add_head(&pol->bydst, chain);
        hlist_add_head(&pol->byidx, xfrm_policy_byidx+idx_hash(pol->index));
        xfrm_policy_count[dir]++;
@@ -1076,7 +1148,8 @@ static struct xfrm_policy *clone_policy(struct xfrm_policy *old, int dir)
 
        if (newp) {
                newp->selector = old->selector;
-               if (security_xfrm_policy_clone(old, newp)) {
+               if (security_xfrm_policy_clone(old->security,
+                                              &newp->security)) {
                        kfree(newp);
                        return NULL;  /* ENOMEM */
                }
@@ -1266,6 +1339,23 @@ static inline struct xfrm_dst *xfrm_alloc_dst(int family)
        return xdst;
 }
 
+static inline int xfrm_init_path(struct xfrm_dst *path, struct dst_entry *dst,
+                                int nfheader_len)
+{
+       struct xfrm_policy_afinfo *afinfo =
+               xfrm_policy_get_afinfo(dst->ops->family);
+       int err;
+
+       if (!afinfo)
+               return -EINVAL;
+
+       err = afinfo->init_path(path, dst, nfheader_len);
+
+       xfrm_policy_put_afinfo(afinfo);
+
+       return err;
+}
+
 static inline int xfrm_fill_dst(struct xfrm_dst *xdst, struct net_device *dev)
 {
        struct xfrm_policy_afinfo *afinfo =
@@ -1298,9 +1388,13 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
        int i = 0;
        int err;
        int header_len = 0;
+       int nfheader_len = 0;
        int trailer_len = 0;
        int tos;
        int family = policy->selector.family;
+       xfrm_address_t saddr, daddr;
+
+       xfrm_flowi_addr_get(fl, &saddr, &daddr, family);
 
        tos = xfrm_get_tos(fl, family);
        err = tos;
@@ -1331,7 +1425,8 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
 
                if (xfrm[i]->props.mode != XFRM_MODE_TRANSPORT) {
                        family = xfrm[i]->props.family;
-                       dst = xfrm_dst_lookup(xfrm[i], tos, family);
+                       dst = xfrm_dst_lookup(xfrm[i], tos, &saddr, &daddr,
+                                             family);
                        err = PTR_ERR(dst);
                        if (IS_ERR(dst))
                                goto put_states;
@@ -1352,6 +1447,8 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
                dst_prev = dst1;
 
                header_len += xfrm[i]->props.header_len;
+               if (xfrm[i]->type->flags & XFRM_TYPE_NON_FRAGMENT)
+                       nfheader_len += xfrm[i]->props.header_len;
                trailer_len += xfrm[i]->props.trailer_len;
        }
 
@@ -1366,6 +1463,7 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
        /* Copy neighbout for reachability confirmation */
        dst0->neighbour = neigh_clone(dst->neighbour);
 
+       xfrm_init_path((struct xfrm_dst *)dst0, dst, nfheader_len);
        xfrm_init_pmtu(dst_prev);
 
        for (dst_prev = dst0; dst_prev != dst; dst_prev = dst_prev->child) {
@@ -1465,36 +1563,46 @@ restart:
        if (sk && sk->sk_policy[XFRM_POLICY_OUT]) {
                policy = xfrm_sk_policy_lookup(sk, XFRM_POLICY_OUT, fl);
                err = PTR_ERR(policy);
-               if (IS_ERR(policy))
+               if (IS_ERR(policy)) {
+                       XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLERROR);
                        goto dropdst;
+               }
        }
 
        if (!policy) {
                /* To accelerate a bit...  */
                if ((dst_orig->flags & DST_NOXFRM) ||
                    !xfrm_policy_count[XFRM_POLICY_OUT])
-                       return 0;
+                       goto nopol;
 
                policy = flow_cache_lookup(fl, dst_orig->ops->family,
                                           dir, xfrm_policy_lookup);
                err = PTR_ERR(policy);
-               if (IS_ERR(policy))
+               if (IS_ERR(policy)) {
+                       XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLERROR);
                        goto dropdst;
+               }
        }
 
        if (!policy)
-               return 0;
+               goto nopol;
 
        family = dst_orig->ops->family;
-       policy->curlft.use_time = get_seconds();
        pols[0] = policy;
        npols ++;
        xfrm_nr += pols[0]->xfrm_nr;
 
+       err = -ENOENT;
+       if ((flags & XFRM_LOOKUP_ICMP) && !(policy->flags & XFRM_POLICY_ICMP))
+               goto error;
+
+       policy->curlft.use_time = get_seconds();
+
        switch (policy->action) {
        default:
        case XFRM_POLICY_BLOCK:
                /* Prohibit the flow */
+               XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLBLOCK);
                err = -EPERM;
                goto error;
 
@@ -1514,6 +1622,7 @@ restart:
                 */
                dst = xfrm_find_bundle(fl, policy, family);
                if (IS_ERR(dst)) {
+                       XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
                        err = PTR_ERR(dst);
                        goto error;
                }
@@ -1528,10 +1637,12 @@ restart:
                                                            XFRM_POLICY_OUT);
                        if (pols[1]) {
                                if (IS_ERR(pols[1])) {
+                                       XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLERROR);
                                        err = PTR_ERR(pols[1]);
                                        goto error;
                                }
                                if (pols[1]->action == XFRM_POLICY_BLOCK) {
+                                       XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLBLOCK);
                                        err = -EPERM;
                                        goto error;
                                }
@@ -1562,10 +1673,11 @@ restart:
                                /* EREMOTE tells the caller to generate
                                 * a one-shot blackhole route.
                                 */
+                               XFRM_INC_STATS(LINUX_MIB_XFRMOUTNOSTATES);
                                xfrm_pol_put(policy);
                                return -EREMOTE;
                        }
-                       if (err == -EAGAIN && flags) {
+                       if (err == -EAGAIN && (flags & XFRM_LOOKUP_WAIT)) {
                                DECLARE_WAITQUEUE(wait, current);
 
                                add_wait_queue(&km_waitq, &wait);
@@ -1577,6 +1689,7 @@ restart:
                                nx = xfrm_tmpl_resolve(pols, npols, fl, xfrm, family);
 
                                if (nx == -EAGAIN && signal_pending(current)) {
+                                       XFRM_INC_STATS(LINUX_MIB_XFRMOUTNOSTATES);
                                        err = -ERESTART;
                                        goto error;
                                }
@@ -1587,8 +1700,10 @@ restart:
                                }
                                err = nx;
                        }
-                       if (err < 0)
+                       if (err < 0) {
+                               XFRM_INC_STATS(LINUX_MIB_XFRMOUTNOSTATES);
                                goto error;
+                       }
                }
                if (nx == 0) {
                        /* Flow passes not transformed. */
@@ -1598,8 +1713,10 @@ restart:
 
                dst = xfrm_bundle_create(policy, xfrm, nx, fl, dst_orig);
                err = PTR_ERR(dst);
-               if (IS_ERR(dst))
+               if (IS_ERR(dst)) {
+                       XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLEGENERROR);
                        goto error;
+               }
 
                for (pi = 0; pi < npols; pi++) {
                        read_lock_bh(&pols[pi]->lock);
@@ -1615,9 +1732,12 @@ restart:
                         * We can't enlist stable bundles either.
                         */
                        write_unlock_bh(&policy->lock);
-                       if (dst)
-                               dst_free(dst);
+                       dst_free(dst);
 
+                       if (pol_dead)
+                               XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLDEAD);
+                       else
+                               XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
                        err = -EHOSTUNREACH;
                        goto error;
                }
@@ -1628,8 +1748,8 @@ restart:
                        err = xfrm_dst_update_origin(dst, fl);
                if (unlikely(err)) {
                        write_unlock_bh(&policy->lock);
-                       if (dst)
-                               dst_free(dst);
+                       dst_free(dst);
+                       XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
                        goto error;
                }
 
@@ -1649,6 +1769,12 @@ dropdst:
        dst_release(dst_orig);
        *dst_p = NULL;
        return err;
+
+nopol:
+       err = -ENOENT;
+       if (flags & XFRM_LOOKUP_ICMP)
+               goto dropdst;
+       return 0;
 }
 EXPORT_SYMBOL(__xfrm_lookup);
 
@@ -1696,7 +1822,7 @@ xfrm_state_ok(struct xfrm_tmpl *tmpl, struct xfrm_state *x,
                (x->id.spi == tmpl->id.spi || !tmpl->id.spi) &&
                (x->props.reqid == tmpl->reqid || !tmpl->reqid) &&
                x->props.mode == tmpl->mode &&
-               ((tmpl->aalgos & (1<<x->props.aalgo)) ||
+               (tmpl->allalgs || (tmpl->aalgos & (1<<x->props.aalgo)) ||
                 !(xfrm_id_proto_match(tmpl->id.proto, IPSEC_PROTO_ANY))) &&
                !(x->props.mode != XFRM_MODE_TRANSPORT &&
                  xfrm_state_addr_cmp(tmpl, x, family));
@@ -1732,8 +1858,8 @@ xfrm_policy_ok(struct xfrm_tmpl *tmpl, struct sec_path *sp, int start,
        return start;
 }
 
-int
-xfrm_decode_session(struct sk_buff *skb, struct flowi *fl, unsigned short family)
+int __xfrm_decode_session(struct sk_buff *skb, struct flowi *fl,
+                         unsigned int family, int reverse)
 {
        struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
        int err;
@@ -1741,12 +1867,12 @@ xfrm_decode_session(struct sk_buff *skb, struct flowi *fl, unsigned short family
        if (unlikely(afinfo == NULL))
                return -EAFNOSUPPORT;
 
-       afinfo->decode_session(skb, fl);
+       afinfo->decode_session(skb, fl, reverse);
        err = security_xfrm_decode_session(skb, &fl->secid);
        xfrm_policy_put_afinfo(afinfo);
        return err;
 }
-EXPORT_SYMBOL(xfrm_decode_session);
+EXPORT_SYMBOL(__xfrm_decode_session);
 
 static inline int secpath_has_nontransport(struct sec_path *sp, int k, int *idxp)
 {
@@ -1768,12 +1894,20 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
        int npols = 0;
        int xfrm_nr;
        int pi;
+       int reverse;
        struct flowi fl;
-       u8 fl_dir = policy_to_flow_dir(dir);
+       u8 fl_dir;
        int xerr_idx = -1;
 
-       if (xfrm_decode_session(skb, &fl, family) < 0)
+       reverse = dir & ~XFRM_POLICY_MASK;
+       dir &= XFRM_POLICY_MASK;
+       fl_dir = policy_to_flow_dir(dir);
+
+       if (__xfrm_decode_session(skb, &fl, family, reverse) < 0) {
+               XFRM_INC_STATS(LINUX_MIB_XFRMINHDRERROR);
                return 0;
+       }
+
        nf_nat_decode_session(skb, &fl, family);
 
        /* First, check used SA against their selectors. */
@@ -1782,28 +1916,35 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
 
                for (i=skb->sp->len-1; i>=0; i--) {
                        struct xfrm_state *x = skb->sp->xvec[i];
-                       if (!xfrm_selector_match(&x->sel, &fl, family))
+                       if (!xfrm_selector_match(&x->sel, &fl, family)) {
+                               XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEMISMATCH);
                                return 0;
+                       }
                }
        }
 
        pol = NULL;
        if (sk && sk->sk_policy[dir]) {
                pol = xfrm_sk_policy_lookup(sk, dir, &fl);
-               if (IS_ERR(pol))
+               if (IS_ERR(pol)) {
+                       XFRM_INC_STATS(LINUX_MIB_XFRMINPOLERROR);
                        return 0;
+               }
        }
 
        if (!pol)
                pol = flow_cache_lookup(&fl, family, fl_dir,
                                        xfrm_policy_lookup);
 
-       if (IS_ERR(pol))
+       if (IS_ERR(pol)) {
+               XFRM_INC_STATS(LINUX_MIB_XFRMINPOLERROR);
                return 0;
+       }
 
        if (!pol) {
                if (skb->sp && secpath_has_nontransport(skb->sp, 0, &xerr_idx)) {
                        xfrm_secpath_reject(xerr_idx, skb, &fl);
+                       XFRM_INC_STATS(LINUX_MIB_XFRMINNOPOLS);
                        return 0;
                }
                return 1;
@@ -1819,8 +1960,10 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
                                                    &fl, family,
                                                    XFRM_POLICY_IN);
                if (pols[1]) {
-                       if (IS_ERR(pols[1]))
+                       if (IS_ERR(pols[1])) {
+                               XFRM_INC_STATS(LINUX_MIB_XFRMINPOLERROR);
                                return 0;
+                       }
                        pols[1]->curlft.use_time = get_seconds();
                        npols ++;
                }
@@ -1841,10 +1984,14 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
 
                for (pi = 0; pi < npols; pi++) {
                        if (pols[pi] != pol &&
-                           pols[pi]->action != XFRM_POLICY_ALLOW)
+                           pols[pi]->action != XFRM_POLICY_ALLOW) {
+                               XFRM_INC_STATS(LINUX_MIB_XFRMINPOLBLOCK);
                                goto reject;
-                       if (ti + pols[pi]->xfrm_nr >= XFRM_MAX_DEPTH)
+                       }
+                       if (ti + pols[pi]->xfrm_nr >= XFRM_MAX_DEPTH) {
+                               XFRM_INC_STATS(LINUX_MIB_XFRMINBUFFERERROR);
                                goto reject_error;
+                       }
                        for (i = 0; i < pols[pi]->xfrm_nr; i++)
                                tpp[ti++] = &pols[pi]->xfrm_vec[i];
                }
@@ -1866,16 +2013,20 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
                                if (k < -1)
                                        /* "-2 - errored_index" returned */
                                        xerr_idx = -(2+k);
+                               XFRM_INC_STATS(LINUX_MIB_XFRMINTMPLMISMATCH);
                                goto reject;
                        }
                }
 
-               if (secpath_has_nontransport(sp, k, &xerr_idx))
+               if (secpath_has_nontransport(sp, k, &xerr_idx)) {
+                       XFRM_INC_STATS(LINUX_MIB_XFRMINTMPLMISMATCH);
                        goto reject;
+               }
 
                xfrm_pols_put(pols, npols);
                return 1;
        }
+       XFRM_INC_STATS(LINUX_MIB_XFRMINPOLBLOCK);
 
 reject:
        xfrm_secpath_reject(xerr_idx, skb, &fl);
@@ -1889,8 +2040,11 @@ int __xfrm_route_forward(struct sk_buff *skb, unsigned short family)
 {
        struct flowi fl;
 
-       if (xfrm_decode_session(skb, &fl, family) < 0)
+       if (xfrm_decode_session(skb, &fl, family) < 0) {
+               /* XXX: we should have something like FWDHDRERROR here. */
+               XFRM_INC_STATS(LINUX_MIB_XFRMINHDRERROR);
                return 0;
+       }
 
        return xfrm_lookup(&skb->dst, &fl, NULL, 0) == 0;
 }
@@ -1934,7 +2088,7 @@ static int stale_bundle(struct dst_entry *dst)
 void xfrm_dst_ifdown(struct dst_entry *dst, struct net_device *dev)
 {
        while ((dst = dst->child) && dst->xfrm && dst->dev == dev) {
-               dst->dev = init_net.loopback_dev;
+               dst->dev = dev_net(dev)->loopback_dev;
                dev_hold(dst->dev);
                dev_put(dev);
        }
@@ -2205,7 +2359,7 @@ static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void
 {
        struct net_device *dev = ptr;
 
-       if (dev->nd_net != &init_net)
+       if (!net_eq(dev_net(dev), &init_net))
                return NOTIFY_DONE;
 
        switch (event) {
@@ -2221,6 +2375,16 @@ static struct notifier_block xfrm_dev_notifier = {
        0
 };
 
+#ifdef CONFIG_XFRM_STATISTICS
+static int __init xfrm_statistics_init(void)
+{
+       if (snmp_mib_init((void **)xfrm_statistics,
+                         sizeof(struct linux_xfrm_mib)) < 0)
+               return -ENOMEM;
+       return 0;
+}
+#endif
+
 static void __init xfrm_policy_init(void)
 {
        unsigned int hmask, sz;
@@ -2251,77 +2415,90 @@ static void __init xfrm_policy_init(void)
                        panic("XFRM: failed to allocate bydst hash\n");
        }
 
+       for (dir = 0; dir < XFRM_POLICY_TYPE_MAX; dir++)
+               INIT_LIST_HEAD(&xfrm_policy_bytype[dir]);
+
        INIT_WORK(&xfrm_policy_gc_work, xfrm_policy_gc_task);
        register_netdevice_notifier(&xfrm_dev_notifier);
 }
 
 void __init xfrm_init(void)
 {
+#ifdef CONFIG_XFRM_STATISTICS
+       xfrm_statistics_init();
+#endif
        xfrm_state_init();
        xfrm_policy_init();
        xfrm_input_init();
+#ifdef CONFIG_XFRM_STATISTICS
+       xfrm_proc_init();
+#endif
 }
 
 #ifdef CONFIG_AUDITSYSCALL
-static inline void xfrm_audit_common_policyinfo(struct xfrm_policy *xp,
-                                               struct audit_buffer *audit_buf)
+static void xfrm_audit_common_policyinfo(struct xfrm_policy *xp,
+                                        struct audit_buffer *audit_buf)
 {
-       if (xp->security)
+       struct xfrm_sec_ctx *ctx = xp->security;
+       struct xfrm_selector *sel = &xp->selector;
+
+       if (ctx)
                audit_log_format(audit_buf, " sec_alg=%u sec_doi=%u sec_obj=%s",
-                                xp->security->ctx_alg, xp->security->ctx_doi,
-                                xp->security->ctx_str);
+                                ctx->ctx_alg, ctx->ctx_doi, ctx->ctx_str);
 
-       switch(xp->selector.family) {
+       switch(sel->family) {
        case AF_INET:
-               audit_log_format(audit_buf, " src=%u.%u.%u.%u dst=%u.%u.%u.%u",
-                                NIPQUAD(xp->selector.saddr.a4),
-                                NIPQUAD(xp->selector.daddr.a4));
+               audit_log_format(audit_buf, " src=" NIPQUAD_FMT,
+                                NIPQUAD(sel->saddr.a4));
+               if (sel->prefixlen_s != 32)
+                       audit_log_format(audit_buf, " src_prefixlen=%d",
+                                        sel->prefixlen_s);
+               audit_log_format(audit_buf, " dst=" NIPQUAD_FMT,
+                                NIPQUAD(sel->daddr.a4));
+               if (sel->prefixlen_d != 32)
+                       audit_log_format(audit_buf, " dst_prefixlen=%d",
+                                        sel->prefixlen_d);
                break;
        case AF_INET6:
-               {
-                       struct in6_addr saddr6, daddr6;
-
-                       memcpy(&saddr6, xp->selector.saddr.a6,
-                               sizeof(struct in6_addr));
-                       memcpy(&daddr6, xp->selector.daddr.a6,
-                               sizeof(struct in6_addr));
-                       audit_log_format(audit_buf,
-                               " src=" NIP6_FMT " dst=" NIP6_FMT,
-                               NIP6(saddr6), NIP6(daddr6));
-               }
+               audit_log_format(audit_buf, " src=" NIP6_FMT,
+                                NIP6(*(struct in6_addr *)sel->saddr.a6));
+               if (sel->prefixlen_s != 128)
+                       audit_log_format(audit_buf, " src_prefixlen=%d",
+                                        sel->prefixlen_s);
+               audit_log_format(audit_buf, " dst=" NIP6_FMT,
+                                NIP6(*(struct in6_addr *)sel->daddr.a6));
+               if (sel->prefixlen_d != 128)
+                       audit_log_format(audit_buf, " dst_prefixlen=%d",
+                                        sel->prefixlen_d);
                break;
        }
 }
 
-void
-xfrm_audit_policy_add(struct xfrm_policy *xp, int result, u32 auid, u32 sid)
+void xfrm_audit_policy_add(struct xfrm_policy *xp, int result,
+                          uid_t auid, u32 sessionid, u32 secid)
 {
        struct audit_buffer *audit_buf;
-       extern int audit_enabled;
 
-       if (audit_enabled == 0)
-               return;
-       audit_buf = xfrm_audit_start(auid, sid);
+       audit_buf = xfrm_audit_start("SPD-add");
        if (audit_buf == NULL)
                return;
-       audit_log_format(audit_buf, " op=SPD-add res=%u", result);
+       xfrm_audit_helper_usrinfo(auid, sessionid, secid, audit_buf);
+       audit_log_format(audit_buf, " res=%u", result);
        xfrm_audit_common_policyinfo(xp, audit_buf);
        audit_log_end(audit_buf);
 }
 EXPORT_SYMBOL_GPL(xfrm_audit_policy_add);
 
-void
-xfrm_audit_policy_delete(struct xfrm_policy *xp, int result, u32 auid, u32 sid)
+void xfrm_audit_policy_delete(struct xfrm_policy *xp, int result,
+                             uid_t auid, u32 sessionid, u32 secid)
 {
        struct audit_buffer *audit_buf;
-       extern int audit_enabled;
 
-       if (audit_enabled == 0)
-               return;
-       audit_buf = xfrm_audit_start(auid, sid);
+       audit_buf = xfrm_audit_start("SPD-delete");
        if (audit_buf == NULL)
                return;
-       audit_log_format(audit_buf, " op=SPD-delete res=%u", result);
+       xfrm_audit_helper_usrinfo(auid, sessionid, secid, audit_buf);
+       audit_log_format(audit_buf, " res=%u", result);
        xfrm_audit_common_policyinfo(xp, audit_buf);
        audit_log_end(audit_buf);
 }