[IPSEC]: Move all calls to xfrm_audit_state_icvfail to xfrm_input
[safe/jmp/linux-2.6] / net / xfrm / xfrm_policy.c
index b6e2e79..280f8de 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
  * xfrm_policy.c
  *
  * Changes:
@@ -13,6 +13,7 @@
  *
  */
 
+#include <linux/err.h>
 #include <linux/slab.h>
 #include <linux/kmod.h>
 #include <linux/list.h>
 #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);
 
@@ -39,7 +52,7 @@ EXPORT_SYMBOL(xfrm_policy_count);
 static DEFINE_RWLOCK(xfrm_policy_afinfo_lock);
 static struct xfrm_policy_afinfo *xfrm_policy_afinfo[NPROTO];
 
-static kmem_cache_t *xfrm_dst_cache __read_mostly;
+static struct kmem_cache *xfrm_dst_cache __read_mostly;
 
 static struct work_struct xfrm_policy_gc_work;
 static HLIST_HEAD(xfrm_policy_gc_list);
@@ -47,179 +60,62 @@ static DEFINE_SPINLOCK(xfrm_policy_gc_lock);
 
 static struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family);
 static void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo);
-static struct xfrm_policy_afinfo *xfrm_policy_lock_afinfo(unsigned int family);
-static void xfrm_policy_unlock_afinfo(struct xfrm_policy_afinfo *afinfo);
+static void xfrm_init_pmtu(struct dst_entry *dst);
 
-int xfrm_register_type(struct xfrm_type *type, unsigned short family)
+static inline int
+__xfrm4_selector_match(struct xfrm_selector *sel, struct flowi *fl)
 {
-       struct xfrm_policy_afinfo *afinfo = xfrm_policy_lock_afinfo(family);
-       struct xfrm_type **typemap;
-       int err = 0;
-
-       if (unlikely(afinfo == NULL))
-               return -EAFNOSUPPORT;
-       typemap = afinfo->type_map;
-
-       if (likely(typemap[type->proto] == NULL))
-               typemap[type->proto] = type;
-       else
-               err = -EEXIST;
-       xfrm_policy_unlock_afinfo(afinfo);
-       return err;
+       return  addr_match(&fl->fl4_dst, &sel->daddr, sel->prefixlen_d) &&
+               addr_match(&fl->fl4_src, &sel->saddr, sel->prefixlen_s) &&
+               !((xfrm_flowi_dport(fl) ^ sel->dport) & sel->dport_mask) &&
+               !((xfrm_flowi_sport(fl) ^ sel->sport) & sel->sport_mask) &&
+               (fl->proto == sel->proto || !sel->proto) &&
+               (fl->oif == sel->ifindex || !sel->ifindex);
 }
-EXPORT_SYMBOL(xfrm_register_type);
-
-int xfrm_unregister_type(struct xfrm_type *type, unsigned short family)
-{
-       struct xfrm_policy_afinfo *afinfo = xfrm_policy_lock_afinfo(family);
-       struct xfrm_type **typemap;
-       int err = 0;
 
-       if (unlikely(afinfo == NULL))
-               return -EAFNOSUPPORT;
-       typemap = afinfo->type_map;
-
-       if (unlikely(typemap[type->proto] != type))
-               err = -ENOENT;
-       else
-               typemap[type->proto] = NULL;
-       xfrm_policy_unlock_afinfo(afinfo);
-       return err;
-}
-EXPORT_SYMBOL(xfrm_unregister_type);
-
-struct xfrm_type *xfrm_get_type(u8 proto, unsigned short family)
+static inline int
+__xfrm6_selector_match(struct xfrm_selector *sel, struct flowi *fl)
 {
-       struct xfrm_policy_afinfo *afinfo;
-       struct xfrm_type **typemap;
-       struct xfrm_type *type;
-       int modload_attempted = 0;
-
-retry:
-       afinfo = xfrm_policy_get_afinfo(family);
-       if (unlikely(afinfo == NULL))
-               return NULL;
-       typemap = afinfo->type_map;
-
-       type = typemap[proto];
-       if (unlikely(type && !try_module_get(type->owner)))
-               type = NULL;
-       if (!type && !modload_attempted) {
-               xfrm_policy_put_afinfo(afinfo);
-               request_module("xfrm-type-%d-%d",
-                              (int) family, (int) proto);
-               modload_attempted = 1;
-               goto retry;
-       }
-
-       xfrm_policy_put_afinfo(afinfo);
-       return type;
+       return  addr_match(&fl->fl6_dst, &sel->daddr, sel->prefixlen_d) &&
+               addr_match(&fl->fl6_src, &sel->saddr, sel->prefixlen_s) &&
+               !((xfrm_flowi_dport(fl) ^ sel->dport) & sel->dport_mask) &&
+               !((xfrm_flowi_sport(fl) ^ sel->sport) & sel->sport_mask) &&
+               (fl->proto == sel->proto || !sel->proto) &&
+               (fl->oif == sel->ifindex || !sel->ifindex);
 }
 
-int xfrm_dst_lookup(struct xfrm_dst **dst, struct flowi *fl, 
+int xfrm_selector_match(struct xfrm_selector *sel, struct flowi *fl,
                    unsigned short family)
 {
-       struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
-       int err = 0;
-
-       if (unlikely(afinfo == NULL))
-               return -EAFNOSUPPORT;
-
-       if (likely(afinfo->dst_lookup != NULL))
-               err = afinfo->dst_lookup(dst, fl);
-       else
-               err = -EINVAL;
-       xfrm_policy_put_afinfo(afinfo);
-       return err;
-}
-EXPORT_SYMBOL(xfrm_dst_lookup);
-
-void xfrm_put_type(struct xfrm_type *type)
-{
-       module_put(type->owner);
-}
-
-int xfrm_register_mode(struct xfrm_mode *mode, int family)
-{
-       struct xfrm_policy_afinfo *afinfo;
-       struct xfrm_mode **modemap;
-       int err;
-
-       if (unlikely(mode->encap >= XFRM_MODE_MAX))
-               return -EINVAL;
-
-       afinfo = xfrm_policy_lock_afinfo(family);
-       if (unlikely(afinfo == NULL))
-               return -EAFNOSUPPORT;
-
-       err = -EEXIST;
-       modemap = afinfo->mode_map;
-       if (likely(modemap[mode->encap] == NULL)) {
-               modemap[mode->encap] = mode;
-               err = 0;
+       switch (family) {
+       case AF_INET:
+               return __xfrm4_selector_match(sel, fl);
+       case AF_INET6:
+               return __xfrm6_selector_match(sel, fl);
        }
-
-       xfrm_policy_unlock_afinfo(afinfo);
-       return err;
-}
-EXPORT_SYMBOL(xfrm_register_mode);
-
-int xfrm_unregister_mode(struct xfrm_mode *mode, int family)
-{
-       struct xfrm_policy_afinfo *afinfo;
-       struct xfrm_mode **modemap;
-       int err;
-
-       if (unlikely(mode->encap >= XFRM_MODE_MAX))
-               return -EINVAL;
-
-       afinfo = xfrm_policy_lock_afinfo(family);
-       if (unlikely(afinfo == NULL))
-               return -EAFNOSUPPORT;
-
-       err = -ENOENT;
-       modemap = afinfo->mode_map;
-       if (likely(modemap[mode->encap] == mode)) {
-               modemap[mode->encap] = NULL;
-               err = 0;
-       }
-
-       xfrm_policy_unlock_afinfo(afinfo);
-       return err;
+       return 0;
 }
-EXPORT_SYMBOL(xfrm_unregister_mode);
 
-struct xfrm_mode *xfrm_get_mode(unsigned int encap, int family)
+static inline struct dst_entry *xfrm_dst_lookup(struct xfrm_state *x, int tos,
+                                               int family)
 {
+       xfrm_address_t *saddr = &x->props.saddr;
+       xfrm_address_t *daddr = &x->id.daddr;
        struct xfrm_policy_afinfo *afinfo;
-       struct xfrm_mode *mode;
-       int modload_attempted = 0;
+       struct dst_entry *dst;
 
-       if (unlikely(encap >= XFRM_MODE_MAX))
-               return NULL;
+       if (x->type->flags & XFRM_TYPE_LOCAL_COADDR)
+               saddr = x->coaddr;
+       if (x->type->flags & XFRM_TYPE_REMOTE_COADDR)
+               daddr = x->coaddr;
 
-retry:
        afinfo = xfrm_policy_get_afinfo(family);
        if (unlikely(afinfo == NULL))
-               return NULL;
-
-       mode = afinfo->mode_map[encap];
-       if (unlikely(mode && !try_module_get(mode->owner)))
-               mode = NULL;
-       if (!mode && !modload_attempted) {
-               xfrm_policy_put_afinfo(afinfo);
-               request_module("xfrm-mode-%d-%d", family, encap);
-               modload_attempted = 1;
-               goto retry;
-       }
+               return ERR_PTR(-EAFNOSUPPORT);
 
+       dst = afinfo->dst_lookup(tos, saddr, daddr);
        xfrm_policy_put_afinfo(afinfo);
-       return mode;
-}
-
-void xfrm_put_mode(struct xfrm_mode *mode)
-{
-       module_put(mode->owner);
+       return dst;
 }
 
 static inline unsigned long make_jiffies(long secs)
@@ -227,13 +123,13 @@ static inline unsigned long make_jiffies(long secs)
        if (secs >= (MAX_SCHEDULE_TIMEOUT-1)/HZ)
                return MAX_SCHEDULE_TIMEOUT-1;
        else
-               return secs*HZ;
+               return secs*HZ;
 }
 
 static void xfrm_policy_timer(unsigned long data)
 {
        struct xfrm_policy *xp = (struct xfrm_policy*)data;
-       unsigned long now = (unsigned long)xtime.tv_sec;
+       unsigned long now = get_seconds();
        long next = LONG_MAX;
        int warn = 0;
        int dir;
@@ -316,9 +212,8 @@ struct xfrm_policy *xfrm_policy_alloc(gfp_t gfp)
                INIT_HLIST_NODE(&policy->byidx);
                rwlock_init(&policy->lock);
                atomic_set(&policy->refcnt, 1);
-               init_timer(&policy->timer);
-               policy->timer.data = (unsigned long)policy;
-               policy->timer.function = xfrm_policy_timer;
+               setup_timer(&policy->timer, xfrm_policy_timer,
+                               (unsigned long)policy);
        }
        return policy;
 }
@@ -358,7 +253,7 @@ static void xfrm_policy_gc_kill(struct xfrm_policy *policy)
        xfrm_pol_put(policy);
 }
 
-static void xfrm_policy_gc_task(void *data)
+static void xfrm_policy_gc_task(struct work_struct *work)
 {
        struct xfrm_policy *policy;
        struct hlist_node *entry, *tmp;
@@ -544,9 +439,23 @@ static inline int xfrm_byidx_should_resize(int total)
        return 0;
 }
 
-static DEFINE_MUTEX(hash_resize_mutex);
+void xfrm_spd_getinfo(struct xfrmk_spdinfo *si)
+{
+       read_lock_bh(&xfrm_policy_lock);
+       si->incnt = xfrm_policy_count[XFRM_POLICY_IN];
+       si->outcnt = xfrm_policy_count[XFRM_POLICY_OUT];
+       si->fwdcnt = xfrm_policy_count[XFRM_POLICY_FWD];
+       si->inscnt = xfrm_policy_count[XFRM_POLICY_IN+XFRM_POLICY_MAX];
+       si->outscnt = xfrm_policy_count[XFRM_POLICY_OUT+XFRM_POLICY_MAX];
+       si->fwdscnt = xfrm_policy_count[XFRM_POLICY_FWD+XFRM_POLICY_MAX];
+       si->spdhcnt = xfrm_idx_hmask;
+       si->spdhmcnt = xfrm_policy_hashmax;
+       read_unlock_bh(&xfrm_policy_lock);
+}
+EXPORT_SYMBOL(xfrm_spd_getinfo);
 
-static void xfrm_hash_resize(void *__unused)
+static DEFINE_MUTEX(hash_resize_mutex);
+static void xfrm_hash_resize(struct work_struct *__unused)
 {
        int dir, total;
 
@@ -563,7 +472,7 @@ static void xfrm_hash_resize(void *__unused)
        mutex_unlock(&hash_resize_mutex);
 }
 
-static DECLARE_WORK(xfrm_hash_work, xfrm_hash_resize, NULL);
+static DECLARE_WORK(xfrm_hash_work, xfrm_hash_resize);
 
 /* Generate new index... KAME seems to generate them ordered by cost
  * of an absolute inpredictability of ordering of rules. This will not pass. */
@@ -615,19 +524,18 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
        struct xfrm_policy *pol;
        struct xfrm_policy *delpol;
        struct hlist_head *chain;
-       struct hlist_node *entry, *newpos, *last;
+       struct hlist_node *entry, *newpos;
        struct dst_entry *gc_list;
 
        write_lock_bh(&xfrm_policy_lock);
        chain = policy_hash_bysel(&policy->selector, policy->family, dir);
        delpol = NULL;
        newpos = NULL;
-       last = NULL;
        hlist_for_each_entry(pol, entry, chain, bydst) {
-               if (!delpol &&
-                   pol->type == policy->type &&
+               if (pol->type == policy->type &&
                    !selector_cmp(&pol->selector, &policy->selector) &&
-                   xfrm_sec_ctx_match(pol->security, policy->security)) {
+                   xfrm_sec_ctx_match(pol->security, policy->security) &&
+                   !WARN_ON(delpol)) {
                        if (excl) {
                                write_unlock_bh(&xfrm_policy_lock);
                                return -EEXIST;
@@ -636,17 +544,12 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
                        if (policy->priority > pol->priority)
                                continue;
                } else if (policy->priority >= pol->priority) {
-                       last = &pol->bydst;
+                       newpos = &pol->bydst;
                        continue;
                }
-               if (!newpos)
-                       newpos = &pol->bydst;
                if (delpol)
                        break;
-               last = &pol->bydst;
        }
-       if (!newpos)
-               newpos = last;
        if (newpos)
                hlist_add_after(newpos, &policy->bydst);
        else
@@ -661,7 +564,7 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
        }
        policy->index = delpol ? delpol->index : xfrm_gen_index(policy->type, dir);
        hlist_add_head(&policy->byidx, xfrm_policy_byidx+idx_hash(policy->index));
-       policy->curlft.add_time = (unsigned long)xtime.tv_sec;
+       policy->curlft.add_time = get_seconds();
        policy->curlft.use_time = 0;
        if (!mod_timer(&policy->timer, jiffies + HZ))
                xfrm_pol_hold(policy);
@@ -706,12 +609,14 @@ EXPORT_SYMBOL(xfrm_policy_insert);
 
 struct xfrm_policy *xfrm_policy_bysel_ctx(u8 type, int dir,
                                          struct xfrm_selector *sel,
-                                         struct xfrm_sec_ctx *ctx, int delete)
+                                         struct xfrm_sec_ctx *ctx, int delete,
+                                         int *err)
 {
        struct xfrm_policy *pol, *ret;
        struct hlist_head *chain;
        struct hlist_node *entry;
 
+       *err = 0;
        write_lock_bh(&xfrm_policy_lock);
        chain = policy_hash_bysel(sel, sel->family, dir);
        ret = NULL;
@@ -721,6 +626,11 @@ 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);
+                               if (*err) {
+                                       write_unlock_bh(&xfrm_policy_lock);
+                                       return pol;
+                               }
                                hlist_del(&pol->bydst);
                                hlist_del(&pol->byidx);
                                xfrm_policy_count[dir]--;
@@ -739,12 +649,18 @@ struct xfrm_policy *xfrm_policy_bysel_ctx(u8 type, int dir,
 }
 EXPORT_SYMBOL(xfrm_policy_bysel_ctx);
 
-struct xfrm_policy *xfrm_policy_byid(u8 type, int dir, u32 id, int delete)
+struct xfrm_policy *xfrm_policy_byid(u8 type, int dir, u32 id, int delete,
+                                    int *err)
 {
        struct xfrm_policy *pol, *ret;
        struct hlist_head *chain;
        struct hlist_node *entry;
 
+       *err = -ENOENT;
+       if (xfrm_policy_id2dir(id) != dir)
+               return NULL;
+
+       *err = 0;
        write_lock_bh(&xfrm_policy_lock);
        chain = xfrm_policy_byidx + idx_hash(id);
        ret = NULL;
@@ -752,6 +668,11 @@ 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);
+                               if (*err) {
+                                       write_unlock_bh(&xfrm_policy_lock);
+                                       return pol;
+                               }
                                hlist_del(&pol->bydst);
                                hlist_del(&pol->byidx);
                                xfrm_policy_count[dir]--;
@@ -770,16 +691,71 @@ struct xfrm_policy *xfrm_policy_byid(u8 type, int dir, u32 id, int delete)
 }
 EXPORT_SYMBOL(xfrm_policy_byid);
 
-void xfrm_policy_flush(u8 type)
+#ifdef CONFIG_SECURITY_NETWORK_XFRM
+static inline int
+xfrm_policy_flush_secctx_check(u8 type, struct xfrm_audit *audit_info)
 {
-       int dir;
+       int dir, err = 0;
 
-       write_lock_bh(&xfrm_policy_lock);
        for (dir = 0; dir < XFRM_POLICY_MAX; dir++) {
                struct xfrm_policy *pol;
                struct hlist_node *entry;
                int i;
 
+               hlist_for_each_entry(pol, entry,
+                                    &xfrm_policy_inexact[dir], bydst) {
+                       if (pol->type != type)
+                               continue;
+                       err = security_xfrm_policy_delete(pol);
+                       if (err) {
+                               xfrm_audit_policy_delete(pol, 0,
+                                                        audit_info->loginuid,
+                                                        audit_info->secid);
+                               return err;
+                       }
+               }
+               for (i = xfrm_policy_bydst[dir].hmask; i >= 0; i--) {
+                       hlist_for_each_entry(pol, entry,
+                                            xfrm_policy_bydst[dir].table + i,
+                                            bydst) {
+                               if (pol->type != type)
+                                       continue;
+                               err = security_xfrm_policy_delete(pol);
+                               if (err) {
+                                       xfrm_audit_policy_delete(pol, 0,
+                                                       audit_info->loginuid,
+                                                       audit_info->secid);
+                                       return err;
+                               }
+                       }
+               }
+       }
+       return err;
+}
+#else
+static inline int
+xfrm_policy_flush_secctx_check(u8 type, struct xfrm_audit *audit_info)
+{
+       return 0;
+}
+#endif
+
+int xfrm_policy_flush(u8 type, struct xfrm_audit *audit_info)
+{
+       int dir, err = 0;
+
+       write_lock_bh(&xfrm_policy_lock);
+
+       err = xfrm_policy_flush_secctx_check(type, audit_info);
+       if (err)
+               goto out;
+
+       for (dir = 0; dir < XFRM_POLICY_MAX; dir++) {
+               struct xfrm_policy *pol;
+               struct hlist_node *entry;
+               int i, killed;
+
+               killed = 0;
        again1:
                hlist_for_each_entry(pol, entry,
                                     &xfrm_policy_inexact[dir], bydst) {
@@ -789,7 +765,11 @@ void xfrm_policy_flush(u8 type)
                        hlist_del(&pol->byidx);
                        write_unlock_bh(&xfrm_policy_lock);
 
+                       xfrm_audit_policy_delete(pol, 1, audit_info->loginuid,
+                                                audit_info->secid);
+
                        xfrm_policy_kill(pol);
+                       killed++;
 
                        write_lock_bh(&xfrm_policy_lock);
                        goto again1;
@@ -806,50 +786,35 @@ void xfrm_policy_flush(u8 type)
                                hlist_del(&pol->byidx);
                                write_unlock_bh(&xfrm_policy_lock);
 
+                               xfrm_audit_policy_delete(pol, 1,
+                                                        audit_info->loginuid,
+                                                        audit_info->secid);
                                xfrm_policy_kill(pol);
+                               killed++;
 
                                write_lock_bh(&xfrm_policy_lock);
                                goto again2;
                        }
                }
 
-               xfrm_policy_count[dir] = 0;
+               xfrm_policy_count[dir] -= killed;
        }
        atomic_inc(&flow_cache_genid);
+out:
        write_unlock_bh(&xfrm_policy_lock);
+       return err;
 }
 EXPORT_SYMBOL(xfrm_policy_flush);
 
 int xfrm_policy_walk(u8 type, int (*func)(struct xfrm_policy *, int, int, void*),
                     void *data)
 {
-       struct xfrm_policy *pol;
+       struct xfrm_policy *pol, *last = NULL;
        struct hlist_node *entry;
-       int dir, count, error;
+       int dir, last_dir = 0, count, error;
 
        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;
-
-               hlist_for_each_entry(pol, entry,
-                                    &xfrm_policy_inexact[dir], bydst) {
-                       if (pol->type == type)
-                               count++;
-               }
-               for (i = xfrm_policy_bydst[dir].hmask; i >= 0; i--) {
-                       hlist_for_each_entry(pol, entry, table + i, bydst) {
-                               if (pol->type == type)
-                                       count++;
-                       }
-               }
-       }
-
-       if (count == 0) {
-               error = -ENOENT;
-               goto out;
-       }
 
        for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) {
                struct hlist_head *table = xfrm_policy_bydst[dir].table;
@@ -859,51 +824,69 @@ int xfrm_policy_walk(u8 type, int (*func)(struct xfrm_policy *, int, int, void*)
                                     &xfrm_policy_inexact[dir], bydst) {
                        if (pol->type != type)
                                continue;
-                       error = func(pol, dir % XFRM_POLICY_MAX, --count, data);
-                       if (error)
-                               goto out;
+                       if (last) {
+                               error = func(last, last_dir % XFRM_POLICY_MAX,
+                                            count, data);
+                               if (error)
+                                       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;
-                               error = func(pol, dir % XFRM_POLICY_MAX, --count, data);
-                               if (error)
-                                       goto out;
+                               if (last) {
+                                       error = func(last, last_dir % XFRM_POLICY_MAX,
+                                                    count, data);
+                                       if (error)
+                                               goto out;
+                               }
+                               last = pol;
+                               last_dir = dir;
+                               count++;
                        }
                }
        }
-       error = 0;
+       if (count == 0) {
+               error = -ENOENT;
+               goto out;
+       }
+       error = func(last, last_dir % XFRM_POLICY_MAX, 0, data);
 out:
        read_unlock_bh(&xfrm_policy_lock);
        return error;
 }
 EXPORT_SYMBOL(xfrm_policy_walk);
 
-/* Find policy to apply to this flow. */
-
+/*
+ * Find policy to apply to this flow.
+ *
+ * Returns 0 if policy found, else an -errno.
+ */
 static int xfrm_policy_match(struct xfrm_policy *pol, struct flowi *fl,
                             u8 type, u16 family, int dir)
 {
        struct xfrm_selector *sel = &pol->selector;
-       int match;
+       int match, ret = -ESRCH;
 
        if (pol->family != family ||
            pol->type != type)
-               return 0;
+               return ret;
 
        match = xfrm_selector_match(sel, fl, family);
-       if (match) {
-               if (!security_xfrm_policy_lookup(pol, fl->secid, dir))
-                       return 1;
-       }
+       if (match)
+               ret = security_xfrm_policy_lookup(pol, fl->secid, dir);
 
-       return 0;
+       return ret;
 }
 
 static struct xfrm_policy *xfrm_policy_lookup_bytype(u8 type, struct flowi *fl,
                                                     u16 family, u8 dir)
 {
+       int err;
        struct xfrm_policy *pol, *ret;
        xfrm_address_t *daddr, *saddr;
        struct hlist_node *entry;
@@ -919,7 +902,15 @@ static struct xfrm_policy *xfrm_policy_lookup_bytype(u8 type, struct flowi *fl,
        chain = policy_hash_direct(daddr, saddr, family, dir);
        ret = NULL;
        hlist_for_each_entry(pol, entry, chain, bydst) {
-               if (xfrm_policy_match(pol, fl, type, family, dir)) {
+               err = xfrm_policy_match(pol, fl, type, family, dir);
+               if (err) {
+                       if (err == -ESRCH)
+                               continue;
+                       else {
+                               ret = ERR_PTR(err);
+                               goto fail;
+                       }
+               } else {
                        ret = pol;
                        priority = ret->priority;
                        break;
@@ -927,53 +918,70 @@ static struct xfrm_policy *xfrm_policy_lookup_bytype(u8 type, struct flowi *fl,
        }
        chain = &xfrm_policy_inexact[dir];
        hlist_for_each_entry(pol, entry, chain, bydst) {
-               if (xfrm_policy_match(pol, fl, type, family, dir) &&
-                   pol->priority < priority) {
+               err = xfrm_policy_match(pol, fl, type, family, dir);
+               if (err) {
+                       if (err == -ESRCH)
+                               continue;
+                       else {
+                               ret = ERR_PTR(err);
+                               goto fail;
+                       }
+               } else if (pol->priority < priority) {
                        ret = pol;
                        break;
                }
        }
        if (ret)
                xfrm_pol_hold(ret);
+fail:
        read_unlock_bh(&xfrm_policy_lock);
 
        return ret;
 }
 
-static void xfrm_policy_lookup(struct flowi *fl, u16 family, u8 dir,
+static int xfrm_policy_lookup(struct flowi *fl, u16 family, u8 dir,
                               void **objp, atomic_t **obj_refp)
 {
        struct xfrm_policy *pol;
+       int err = 0;
 
 #ifdef CONFIG_XFRM_SUB_POLICY
        pol = xfrm_policy_lookup_bytype(XFRM_POLICY_TYPE_SUB, fl, family, dir);
-       if (pol)
+       if (IS_ERR(pol)) {
+               err = PTR_ERR(pol);
+               pol = NULL;
+       }
+       if (pol || err)
                goto end;
 #endif
        pol = xfrm_policy_lookup_bytype(XFRM_POLICY_TYPE_MAIN, fl, family, dir);
-
+       if (IS_ERR(pol)) {
+               err = PTR_ERR(pol);
+               pol = NULL;
+       }
 #ifdef CONFIG_XFRM_SUB_POLICY
 end:
 #endif
        if ((*objp = (void *) pol) != NULL)
                *obj_refp = &pol->refcnt;
+       return err;
 }
 
 static inline int policy_to_flow_dir(int dir)
 {
        if (XFRM_POLICY_IN == FLOW_DIR_IN &&
-           XFRM_POLICY_OUT == FLOW_DIR_OUT &&
-           XFRM_POLICY_FWD == FLOW_DIR_FWD)
-               return dir;
-       switch (dir) {
-       default:
-       case XFRM_POLICY_IN:
-               return FLOW_DIR_IN;
-       case XFRM_POLICY_OUT:
-               return FLOW_DIR_OUT;
-       case XFRM_POLICY_FWD:
-               return FLOW_DIR_FWD;
-       };
+           XFRM_POLICY_OUT == FLOW_DIR_OUT &&
+           XFRM_POLICY_FWD == FLOW_DIR_FWD)
+               return dir;
+       switch (dir) {
+       default:
+       case XFRM_POLICY_IN:
+               return FLOW_DIR_IN;
+       case XFRM_POLICY_OUT:
+               return FLOW_DIR_OUT;
+       case XFRM_POLICY_FWD:
+               return FLOW_DIR_FWD;
+       }
 }
 
 static struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi *fl)
@@ -982,16 +990,20 @@ static struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struc
 
        read_lock_bh(&xfrm_policy_lock);
        if ((pol = sk->sk_policy[dir]) != NULL) {
-               int match = xfrm_selector_match(&pol->selector, fl,
+               int match = xfrm_selector_match(&pol->selector, fl,
                                                sk->sk_family);
-               int err = 0;
-
-               if (match)
-                 err = security_xfrm_policy_lookup(pol, fl->secid, policy_to_flow_dir(dir));
-
-               if (match && !err)
-                       xfrm_pol_hold(pol);
-               else
+               int err = 0;
+
+               if (match) {
+                       err = security_xfrm_policy_lookup(pol, fl->secid,
+                                       policy_to_flow_dir(dir));
+                       if (!err)
+                               xfrm_pol_hold(pol);
+                       else if (err == -ESRCH)
+                               pol = NULL;
+                       else
+                               pol = ERR_PTR(err);
+               } else
                        pol = NULL;
        }
        read_unlock_bh(&xfrm_policy_lock);
@@ -1053,7 +1065,7 @@ int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol)
        old_pol = sk->sk_policy[dir];
        sk->sk_policy[dir] = pol;
        if (pol) {
-               pol->curlft.add_time = (unsigned long)xtime.tv_sec;
+               pol->curlft.add_time = get_seconds();
                pol->index = xfrm_gen_index(pol->type, XFRM_POLICY_MAX+dir);
                __xfrm_policy_link(pol, XFRM_POLICY_MAX+dir);
        }
@@ -1140,9 +1152,11 @@ xfrm_tmpl_resolve_one(struct xfrm_policy *policy, struct flowi *fl,
                xfrm_address_t *local  = saddr;
                struct xfrm_tmpl *tmpl = &policy->xfrm_vec[i];
 
-               if (tmpl->mode == XFRM_MODE_TUNNEL) {
+               if (tmpl->mode == XFRM_MODE_TUNNEL ||
+                   tmpl->mode == XFRM_MODE_BEET) {
                        remote = &tmpl->id.daddr;
                        local = &tmpl->saddr;
+                       family = tmpl->encap_family;
                        if (xfrm_addr_any(local, family)) {
                                error = xfrm_get_saddr(&tmp, remote, family);
                                if (error)
@@ -1231,24 +1245,219 @@ xfrm_find_bundle(struct flowi *fl, struct xfrm_policy *policy, unsigned short fa
        return x;
 }
 
-/* Allocate chain of dst_entry's, attach known xfrm's, calculate
- * all the metrics... Shortly, bundle a bundle.
- */
+static inline int xfrm_get_tos(struct flowi *fl, int family)
+{
+       struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
+       int tos;
 
-static int
-xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx,
-                  struct flowi *fl, struct dst_entry **dst_p,
-                  unsigned short family)
+       if (!afinfo)
+               return -EINVAL;
+
+       tos = afinfo->get_tos(fl);
+
+       xfrm_policy_put_afinfo(afinfo);
+
+       return tos;
+}
+
+static inline struct xfrm_dst *xfrm_alloc_dst(int family)
 {
-       int err;
        struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
-       if (unlikely(afinfo == NULL))
+       struct xfrm_dst *xdst;
+
+       if (!afinfo)
+               return ERR_PTR(-EINVAL);
+
+       xdst = dst_alloc(afinfo->dst_ops) ?: ERR_PTR(-ENOBUFS);
+
+       xfrm_policy_put_afinfo(afinfo);
+
+       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 =
+               xfrm_policy_get_afinfo(xdst->u.dst.ops->family);
+       int err;
+
+       if (!afinfo)
                return -EINVAL;
-       err = afinfo->bundle_create(policy, xfrm, nx, fl, dst_p);
+
+       err = afinfo->fill_dst(xdst, dev);
+
        xfrm_policy_put_afinfo(afinfo);
+
        return err;
 }
 
+/* Allocate chain of dst_entry's, attach known xfrm's, calculate
+ * all the metrics... Shortly, bundle a bundle.
+ */
+
+static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
+                                           struct xfrm_state **xfrm, int nx,
+                                           struct flowi *fl,
+                                           struct dst_entry *dst)
+{
+       unsigned long now = jiffies;
+       struct net_device *dev;
+       struct dst_entry *dst_prev = NULL;
+       struct dst_entry *dst0 = NULL;
+       int i = 0;
+       int err;
+       int header_len = 0;
+       int nfheader_len = 0;
+       int trailer_len = 0;
+       int tos;
+       int family = policy->selector.family;
+
+       tos = xfrm_get_tos(fl, family);
+       err = tos;
+       if (tos < 0)
+               goto put_states;
+
+       dst_hold(dst);
+
+       for (; i < nx; i++) {
+               struct xfrm_dst *xdst = xfrm_alloc_dst(family);
+               struct dst_entry *dst1 = &xdst->u.dst;
+
+               err = PTR_ERR(xdst);
+               if (IS_ERR(xdst)) {
+                       dst_release(dst);
+                       goto put_states;
+               }
+
+               if (!dst_prev)
+                       dst0 = dst1;
+               else {
+                       dst_prev->child = dst_clone(dst1);
+                       dst1->flags |= DST_NOHASH;
+               }
+
+               xdst->route = dst;
+               memcpy(&dst1->metrics, &dst->metrics, sizeof(dst->metrics));
+
+               if (xfrm[i]->props.mode != XFRM_MODE_TRANSPORT) {
+                       family = xfrm[i]->props.family;
+                       dst = xfrm_dst_lookup(xfrm[i], tos, family);
+                       err = PTR_ERR(dst);
+                       if (IS_ERR(dst))
+                               goto put_states;
+               } else
+                       dst_hold(dst);
+
+               dst1->xfrm = xfrm[i];
+               xdst->genid = xfrm[i]->genid;
+
+               dst1->obsolete = -1;
+               dst1->flags |= DST_HOST;
+               dst1->lastuse = now;
+
+               dst1->input = dst_discard;
+               dst1->output = xfrm[i]->outer_mode->afinfo->output;
+
+               dst1->next = dst_prev;
+               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;
+       }
+
+       dst_prev->child = dst;
+       dst0->path = dst;
+
+       err = -ENODEV;
+       dev = dst->dev;
+       if (!dev)
+               goto free_dst;
+
+       /* 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) {
+               struct xfrm_dst *xdst = (struct xfrm_dst *)dst_prev;
+
+               err = xfrm_fill_dst(xdst, dev);
+               if (err)
+                       goto free_dst;
+
+               dst_prev->header_len = header_len;
+               dst_prev->trailer_len = trailer_len;
+               header_len -= xdst->u.dst.xfrm->props.header_len;
+               trailer_len -= xdst->u.dst.xfrm->props.trailer_len;
+       }
+
+out:
+       return dst0;
+
+put_states:
+       for (; i < nx; i++)
+               xfrm_state_put(xfrm[i]);
+free_dst:
+       if (dst0)
+               dst_free(dst0);
+       dst0 = ERR_PTR(err);
+       goto out;
+}
+
+static int inline
+xfrm_dst_alloc_copy(void **target, void *src, int size)
+{
+       if (!*target) {
+               *target = kmalloc(size, GFP_ATOMIC);
+               if (!*target)
+                       return -ENOMEM;
+       }
+       memcpy(*target, src, size);
+       return 0;
+}
+
+static int inline
+xfrm_dst_update_parent(struct dst_entry *dst, struct xfrm_selector *sel)
+{
+#ifdef CONFIG_XFRM_SUB_POLICY
+       struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
+       return xfrm_dst_alloc_copy((void **)&(xdst->partner),
+                                  sel, sizeof(*sel));
+#else
+       return 0;
+#endif
+}
+
+static int inline
+xfrm_dst_update_origin(struct dst_entry *dst, struct flowi *fl)
+{
+#ifdef CONFIG_XFRM_SUB_POLICY
+       struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
+       return xfrm_dst_alloc_copy((void **)&(xdst->origin), fl, sizeof(*fl));
+#else
+       return 0;
+#endif
+}
 
 static int stale_bundle(struct dst_entry *dst);
 
@@ -1257,8 +1466,8 @@ static int stale_bundle(struct dst_entry *dst);
  * At the moment we eat a raw IP route. Mostly to speed up lookups
  * on interfaces with disabled IPsec.
  */
-int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
-               struct sock *sk, int flags)
+int __xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
+                 struct sock *sk, int flags)
 {
        struct xfrm_policy *policy;
        struct xfrm_policy *pols[XFRM_POLICY_TYPE_MAX];
@@ -1283,31 +1492,47 @@ restart:
        pol_dead = 0;
        xfrm_nr = 0;
 
-       if (sk && sk->sk_policy[1])
+       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)) {
+                       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))
+                       goto dropdst;
        }
 
        if (!policy)
-               return 0;
+               goto nopol;
 
        family = dst_orig->ops->family;
-       policy->curlft.use_time = (unsigned long)xtime.tv_sec;
        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;
 
@@ -1327,6 +1552,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;
                }
@@ -1340,7 +1566,13 @@ restart:
                                                            fl, family,
                                                            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;
                                }
@@ -1367,7 +1599,14 @@ restart:
 
                if (unlikely(nx<0)) {
                        err = nx;
-                       if (err == -EAGAIN && flags) {
+                       if (err == -EAGAIN && sysctl_xfrm_larval_drop) {
+                               /* EREMOTE tells the caller to generate
+                                * a one-shot blackhole route.
+                                */
+                               xfrm_pol_put(policy);
+                               return -EREMOTE;
+                       }
+                       if (err == -EAGAIN && (flags & XFRM_LOOKUP_WAIT)) {
                                DECLARE_WAITQUEUE(wait, current);
 
                                add_wait_queue(&km_waitq, &wait);
@@ -1379,6 +1618,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;
                                }
@@ -1389,8 +1629,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. */
@@ -1398,13 +1640,10 @@ restart:
                        return 0;
                }
 
-               dst = dst_orig;
-               err = xfrm_bundle_create(policy, xfrm, nx, fl, &dst, family);
-
-               if (unlikely(err)) {
-                       int i;
-                       for (i=0; i<nx; i++)
-                               xfrm_state_put(xfrm[i]);
+               dst = xfrm_bundle_create(policy, xfrm, nx, fl, dst_orig);
+               err = PTR_ERR(dst);
+               if (IS_ERR(dst)) {
+                       XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLEGENERROR);
                        goto error;
                }
 
@@ -1425,9 +1664,26 @@ restart:
                        if (dst)
                                dst_free(dst);
 
+                       if (pol_dead)
+                               XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLDEAD);
+                       else
+                               XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
                        err = -EHOSTUNREACH;
                        goto error;
                }
+
+               if (npols > 1)
+                       err = xfrm_dst_update_parent(dst, &pols[1]->selector);
+               else
+                       err = xfrm_dst_update_origin(dst, fl);
+               if (unlikely(err)) {
+                       write_unlock_bh(&policy->lock);
+                       if (dst)
+                               dst_free(dst);
+                       XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
+                       goto error;
+               }
+
                dst->next = policy->bundles;
                policy->bundles = dst;
                dst_hold(dst);
@@ -1435,14 +1691,36 @@ restart:
        }
        *dst_p = dst;
        dst_release(dst_orig);
-       xfrm_pols_put(pols, npols);
+       xfrm_pols_put(pols, npols);
        return 0;
 
 error:
-       dst_release(dst_orig);
        xfrm_pols_put(pols, npols);
+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);
+
+int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
+               struct sock *sk, int flags)
+{
+       int err = __xfrm_lookup(dst_p, fl, sk, flags);
+
+       if (err == -EREMOTE) {
+               dst_release(*dst_p);
+               *dst_p = NULL;
+               err = -EAGAIN;
+       }
+
+       return err;
 }
 EXPORT_SYMBOL(xfrm_lookup);
 
@@ -1450,17 +1728,13 @@ static inline int
 xfrm_secpath_reject(int idx, struct sk_buff *skb, struct flowi *fl)
 {
        struct xfrm_state *x;
-       int err;
 
        if (!skb->sp || idx < 0 || idx >= skb->sp->len)
                return 0;
        x = skb->sp->xvec[idx];
        if (!x->type->reject)
                return 0;
-       xfrm_state_hold(x);
-       err = x->type->reject(x, skb, fl);
-       xfrm_state_put(x);
-       return err;
+       return x->type->reject(x, skb, fl);
 }
 
 /* When skb is transformed back to its "native" form, we have to
@@ -1470,11 +1744,11 @@ xfrm_secpath_reject(int idx, struct sk_buff *skb, struct flowi *fl)
  */
 
 static inline int
-xfrm_state_ok(struct xfrm_tmpl *tmpl, struct xfrm_state *x, 
+xfrm_state_ok(struct xfrm_tmpl *tmpl, struct xfrm_state *x,
              unsigned short family)
 {
        if (xfrm_state_kern(x))
-               return tmpl->optional && !xfrm_state_addr_cmp(tmpl, x, family);
+               return tmpl->optional && !xfrm_state_addr_cmp(tmpl, x, tmpl->encap_family);
        return  x->id.proto == tmpl->id.proto &&
                (x->id.spi == tmpl->id.spi || !tmpl->id.spi) &&
                (x->props.reqid == tmpl->reqid || !tmpl->reqid) &&
@@ -1515,8 +1789,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;
@@ -1524,12 +1798,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)
 {
@@ -1543,7 +1817,7 @@ static inline int secpath_has_nontransport(struct sec_path *sp, int k, int *idxp
        return 0;
 }
 
-int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb, 
+int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
                        unsigned short family)
 {
        struct xfrm_policy *pol;
@@ -1551,12 +1825,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. */
@@ -1565,28 +1847,41 @@ 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])
+       if (sk && sk->sk_policy[dir]) {
                pol = xfrm_sk_policy_lookup(sk, dir, &fl);
+               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)) {
+               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;
        }
 
-       pol->curlft.use_time = (unsigned long)xtime.tv_sec;
+       pol->curlft.use_time = get_seconds();
 
        pols[0] = pol;
        npols ++;
@@ -1596,7 +1891,11 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
                                                    &fl, family,
                                                    XFRM_POLICY_IN);
                if (pols[1]) {
-                       pols[1]->curlft.use_time = (unsigned long)xtime.tv_sec;
+                       if (IS_ERR(pols[1])) {
+                               XFRM_INC_STATS(LINUX_MIB_XFRMINPOLERROR);
+                               return 0;
+                       }
+                       pols[1]->curlft.use_time = get_seconds();
                        npols ++;
                }
        }
@@ -1616,10 +1915,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];
                }
@@ -1641,16 +1944,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);
@@ -1664,8 +1971,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;
 }
@@ -1703,14 +2013,14 @@ static struct dst_entry *xfrm_dst_check(struct dst_entry *dst, u32 cookie)
 
 static int stale_bundle(struct dst_entry *dst)
 {
-       return !xfrm_bundle_ok((struct xfrm_dst *)dst, NULL, AF_UNSPEC, 0);
+       return !xfrm_bundle_ok(NULL, (struct xfrm_dst *)dst, NULL, AF_UNSPEC, 0);
 }
 
 void xfrm_dst_ifdown(struct dst_entry *dst, struct net_device *dev)
 {
        while ((dst = dst->child) && dst->xfrm && dst->dev == dev) {
-               dst->dev = &loopback_dev;
-               dev_hold(&loopback_dev);
+               dst->dev = dev->nd_net->loopback_dev;
+               dev_hold(dst->dev);
                dev_put(dev);
        }
 }
@@ -1798,7 +2108,7 @@ static int xfrm_flush_bundles(void)
        return 0;
 }
 
-void xfrm_init_pmtu(struct dst_entry *dst)
+static void xfrm_init_pmtu(struct dst_entry *dst)
 {
        do {
                struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
@@ -1819,13 +2129,12 @@ void xfrm_init_pmtu(struct dst_entry *dst)
        } while ((dst = dst->next));
 }
 
-EXPORT_SYMBOL(xfrm_init_pmtu);
-
 /* Check that the bundle accepts the flow and its components are
  * still valid.
  */
 
-int xfrm_bundle_ok(struct xfrm_dst *first, struct flowi *fl, int family, int strict)
+int xfrm_bundle_ok(struct xfrm_policy *pol, struct xfrm_dst *first,
+               struct flowi *fl, int family, int strict)
 {
        struct dst_entry *dst = &first->u.dst;
        struct xfrm_dst *last;
@@ -1834,6 +2143,15 @@ int xfrm_bundle_ok(struct xfrm_dst *first, struct flowi *fl, int family, int str
        if (!dst_check(dst->path, ((struct xfrm_dst *)dst)->path_cookie) ||
            (dst->dev && !netif_running(dst->dev)))
                return 0;
+#ifdef CONFIG_XFRM_SUB_POLICY
+       if (fl) {
+               if (first->origin && !flow_cache_uli_match(first->origin, fl))
+                       return 0;
+               if (first->partner &&
+                   !xfrm_selector_match(first->partner, fl, family))
+                       return 0;
+       }
+#endif
 
        last = NULL;
 
@@ -1842,14 +2160,16 @@ int xfrm_bundle_ok(struct xfrm_dst *first, struct flowi *fl, int family, int str
 
                if (fl && !xfrm_selector_match(&dst->xfrm->sel, fl, family))
                        return 0;
-               if (fl && !security_xfrm_flow_state_match(fl, dst->xfrm))
+               if (fl && pol &&
+                   !security_xfrm_state_pol_flow_match(dst->xfrm, pol, fl))
                        return 0;
                if (dst->xfrm->km.state != XFRM_STATE_VALID)
                        return 0;
                if (xdst->genid != dst->xfrm->genid)
                        return 0;
 
-               if (strict && fl && dst->xfrm->props.mode != XFRM_MODE_TUNNEL &&
+               if (strict && fl &&
+                   !(dst->xfrm->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL) &&
                    !xfrm_state_addr_flow_check(dst->xfrm, fl, family))
                        return 0;
 
@@ -1885,7 +2205,7 @@ int xfrm_bundle_ok(struct xfrm_dst *first, struct flowi *fl, int family, int str
                if (last == first)
                        break;
 
-               last = last->u.next;
+               last = (struct xfrm_dst *)last->u.dst.next;
                last->child_mtu_cached = mtu;
        }
 
@@ -1966,25 +2286,13 @@ static void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo)
        read_unlock(&xfrm_policy_afinfo_lock);
 }
 
-static struct xfrm_policy_afinfo *xfrm_policy_lock_afinfo(unsigned int family)
+static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
 {
-       struct xfrm_policy_afinfo *afinfo;
-       if (unlikely(family >= NPROTO))
-               return NULL;
-       write_lock_bh(&xfrm_policy_afinfo_lock);
-       afinfo = xfrm_policy_afinfo[family];
-       if (unlikely(!afinfo))
-               write_unlock_bh(&xfrm_policy_afinfo_lock);
-       return afinfo;
-}
+       struct net_device *dev = ptr;
 
-static void xfrm_policy_unlock_afinfo(struct xfrm_policy_afinfo *afinfo)
-{
-       write_unlock_bh(&xfrm_policy_afinfo_lock);
-}
+       if (dev->nd_net != &init_net)
+               return NOTIFY_DONE;
 
-static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
-{
        switch (event) {
        case NETDEV_DOWN:
                xfrm_flush_bundles();
@@ -1998,6 +2306,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;
@@ -2006,7 +2324,7 @@ static void __init xfrm_policy_init(void)
        xfrm_dst_cache = kmem_cache_create("xfrm_dst_cache",
                                           sizeof(struct xfrm_dst),
                                           0, SLAB_HWCACHE_ALIGN|SLAB_PANIC,
-                                          NULL, NULL);
+                                          NULL);
 
        hmask = 8 - 1;
        sz = (hmask+1) * sizeof(struct hlist_head);
@@ -2028,14 +2346,321 @@ static void __init xfrm_policy_init(void)
                        panic("XFRM: failed to allocate bydst hash\n");
        }
 
-       INIT_WORK(&xfrm_policy_gc_work, xfrm_policy_gc_task, NULL);
+       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)
+{
+       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",
+                                ctx->ctx_alg, ctx->ctx_doi, ctx->ctx_str);
+
+       switch(sel->family) {
+       case AF_INET:
+               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:
+               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 secid)
+{
+       struct audit_buffer *audit_buf;
+
+       audit_buf = xfrm_audit_start("SPD-add");
+       if (audit_buf == NULL)
+               return;
+       xfrm_audit_helper_usrinfo(auid, 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 secid)
+{
+       struct audit_buffer *audit_buf;
+
+       audit_buf = xfrm_audit_start("SPD-delete");
+       if (audit_buf == NULL)
+               return;
+       xfrm_audit_helper_usrinfo(auid, 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_delete);
+#endif
+
+#ifdef CONFIG_XFRM_MIGRATE
+static int xfrm_migrate_selector_match(struct xfrm_selector *sel_cmp,
+                                      struct xfrm_selector *sel_tgt)
+{
+       if (sel_cmp->proto == IPSEC_ULPROTO_ANY) {
+               if (sel_tgt->family == sel_cmp->family &&
+                   xfrm_addr_cmp(&sel_tgt->daddr, &sel_cmp->daddr,
+                                 sel_cmp->family) == 0 &&
+                   xfrm_addr_cmp(&sel_tgt->saddr, &sel_cmp->saddr,
+                                 sel_cmp->family) == 0 &&
+                   sel_tgt->prefixlen_d == sel_cmp->prefixlen_d &&
+                   sel_tgt->prefixlen_s == sel_cmp->prefixlen_s) {
+                       return 1;
+               }
+       } else {
+               if (memcmp(sel_tgt, sel_cmp, sizeof(*sel_tgt)) == 0) {
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+static struct xfrm_policy * xfrm_migrate_policy_find(struct xfrm_selector *sel,
+                                                    u8 dir, u8 type)
+{
+       struct xfrm_policy *pol, *ret = NULL;
+       struct hlist_node *entry;
+       struct hlist_head *chain;
+       u32 priority = ~0U;
+
+       read_lock_bh(&xfrm_policy_lock);
+       chain = policy_hash_direct(&sel->daddr, &sel->saddr, sel->family, dir);
+       hlist_for_each_entry(pol, entry, chain, bydst) {
+               if (xfrm_migrate_selector_match(sel, &pol->selector) &&
+                   pol->type == type) {
+                       ret = pol;
+                       priority = ret->priority;
+                       break;
+               }
+       }
+       chain = &xfrm_policy_inexact[dir];
+       hlist_for_each_entry(pol, entry, chain, bydst) {
+               if (xfrm_migrate_selector_match(sel, &pol->selector) &&
+                   pol->type == type &&
+                   pol->priority < priority) {
+                       ret = pol;
+                       break;
+               }
+       }
+
+       if (ret)
+               xfrm_pol_hold(ret);
+
+       read_unlock_bh(&xfrm_policy_lock);
+
+       return ret;
+}
+
+static int migrate_tmpl_match(struct xfrm_migrate *m, struct xfrm_tmpl *t)
+{
+       int match = 0;
+
+       if (t->mode == m->mode && t->id.proto == m->proto &&
+           (m->reqid == 0 || t->reqid == m->reqid)) {
+               switch (t->mode) {
+               case XFRM_MODE_TUNNEL:
+               case XFRM_MODE_BEET:
+                       if (xfrm_addr_cmp(&t->id.daddr, &m->old_daddr,
+                                         m->old_family) == 0 &&
+                           xfrm_addr_cmp(&t->saddr, &m->old_saddr,
+                                         m->old_family) == 0) {
+                               match = 1;
+                       }
+                       break;
+               case XFRM_MODE_TRANSPORT:
+                       /* in case of transport mode, template does not store
+                          any IP addresses, hence we just compare mode and
+                          protocol */
+                       match = 1;
+                       break;
+               default:
+                       break;
+               }
+       }
+       return match;
 }
 
+/* update endpoint address(es) of template(s) */
+static int xfrm_policy_migrate(struct xfrm_policy *pol,
+                              struct xfrm_migrate *m, int num_migrate)
+{
+       struct xfrm_migrate *mp;
+       struct dst_entry *dst;
+       int i, j, n = 0;
+
+       write_lock_bh(&pol->lock);
+       if (unlikely(pol->dead)) {
+               /* target policy has been deleted */
+               write_unlock_bh(&pol->lock);
+               return -ENOENT;
+       }
+
+       for (i = 0; i < pol->xfrm_nr; i++) {
+               for (j = 0, mp = m; j < num_migrate; j++, mp++) {
+                       if (!migrate_tmpl_match(mp, &pol->xfrm_vec[i]))
+                               continue;
+                       n++;
+                       if (pol->xfrm_vec[i].mode != XFRM_MODE_TUNNEL &&
+                           pol->xfrm_vec[i].mode != XFRM_MODE_BEET)
+                               continue;
+                       /* update endpoints */
+                       memcpy(&pol->xfrm_vec[i].id.daddr, &mp->new_daddr,
+                              sizeof(pol->xfrm_vec[i].id.daddr));
+                       memcpy(&pol->xfrm_vec[i].saddr, &mp->new_saddr,
+                              sizeof(pol->xfrm_vec[i].saddr));
+                       pol->xfrm_vec[i].encap_family = mp->new_family;
+                       /* flush bundles */
+                       while ((dst = pol->bundles) != NULL) {
+                               pol->bundles = dst->next;
+                               dst_free(dst);
+                       }
+               }
+       }
+
+       write_unlock_bh(&pol->lock);
+
+       if (!n)
+               return -ENODATA;
+
+       return 0;
+}
+
+static int xfrm_migrate_check(struct xfrm_migrate *m, int num_migrate)
+{
+       int i, j;
+
+       if (num_migrate < 1 || num_migrate > XFRM_MAX_DEPTH)
+               return -EINVAL;
+
+       for (i = 0; i < num_migrate; i++) {
+               if ((xfrm_addr_cmp(&m[i].old_daddr, &m[i].new_daddr,
+                                  m[i].old_family) == 0) &&
+                   (xfrm_addr_cmp(&m[i].old_saddr, &m[i].new_saddr,
+                                  m[i].old_family) == 0))
+                       return -EINVAL;
+               if (xfrm_addr_any(&m[i].new_daddr, m[i].new_family) ||
+                   xfrm_addr_any(&m[i].new_saddr, m[i].new_family))
+                       return -EINVAL;
+
+               /* check if there is any duplicated entry */
+               for (j = i + 1; j < num_migrate; j++) {
+                       if (!memcmp(&m[i].old_daddr, &m[j].old_daddr,
+                                   sizeof(m[i].old_daddr)) &&
+                           !memcmp(&m[i].old_saddr, &m[j].old_saddr,
+                                   sizeof(m[i].old_saddr)) &&
+                           m[i].proto == m[j].proto &&
+                           m[i].mode == m[j].mode &&
+                           m[i].reqid == m[j].reqid &&
+                           m[i].old_family == m[j].old_family)
+                               return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
+int xfrm_migrate(struct xfrm_selector *sel, u8 dir, u8 type,
+                struct xfrm_migrate *m, int num_migrate)
+{
+       int i, err, nx_cur = 0, nx_new = 0;
+       struct xfrm_policy *pol = NULL;
+       struct xfrm_state *x, *xc;
+       struct xfrm_state *x_cur[XFRM_MAX_DEPTH];
+       struct xfrm_state *x_new[XFRM_MAX_DEPTH];
+       struct xfrm_migrate *mp;
+
+       if ((err = xfrm_migrate_check(m, num_migrate)) < 0)
+               goto out;
+
+       /* Stage 1 - find policy */
+       if ((pol = xfrm_migrate_policy_find(sel, dir, type)) == NULL) {
+               err = -ENOENT;
+               goto out;
+       }
+
+       /* Stage 2 - find and update state(s) */
+       for (i = 0, mp = m; i < num_migrate; i++, mp++) {
+               if ((x = xfrm_migrate_state_find(mp))) {
+                       x_cur[nx_cur] = x;
+                       nx_cur++;
+                       if ((xc = xfrm_state_migrate(x, mp))) {
+                               x_new[nx_new] = xc;
+                               nx_new++;
+                       } else {
+                               err = -ENODATA;
+                               goto restore_state;
+                       }
+               }
+       }
+
+       /* Stage 3 - update policy */
+       if ((err = xfrm_policy_migrate(pol, m, num_migrate)) < 0)
+               goto restore_state;
+
+       /* Stage 4 - delete old state(s) */
+       if (nx_cur) {
+               xfrm_states_put(x_cur, nx_cur);
+               xfrm_states_delete(x_cur, nx_cur);
+       }
+
+       /* Stage 5 - announce */
+       km_migrate(sel, dir, type, m, num_migrate);
+
+       xfrm_pol_put(pol);
+
+       return 0;
+out:
+       return err;
+
+restore_state:
+       if (pol)
+               xfrm_pol_put(pol);
+       if (nx_cur)
+               xfrm_states_put(x_cur, nx_cur);
+       if (nx_new)
+               xfrm_states_delete(x_new, nx_new);
+
+       return err;
+}
+EXPORT_SYMBOL(xfrm_migrate);
+#endif