Merge branch 'upstream-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mfashe...
[deliverable/linux.git] / net / xfrm / xfrm_policy.c
index 087a5443b0514435dd9c7a2303adae43b691b7cc..64d3938f74c46d1d13753c32a3b37da63fd27918 100644 (file)
 #include <linux/netdevice.h>
 #include <linux/netfilter.h>
 #include <linux/module.h>
-#include <linux/bootmem.h>
-#include <linux/vmalloc.h>
 #include <linux/cache.h>
 #include <net/xfrm.h>
 #include <net/ip.h>
 
+#include "xfrm_hash.h"
+
 DEFINE_MUTEX(xfrm_cfg_mutex);
 EXPORT_SYMBOL(xfrm_cfg_mutex);
 
@@ -50,6 +50,40 @@ 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 inline int
+__xfrm4_selector_match(struct xfrm_selector *sel, struct flowi *fl)
+{
+       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);
+}
+
+static inline int
+__xfrm6_selector_match(struct xfrm_selector *sel, struct flowi *fl)
+{
+       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_selector_match(struct xfrm_selector *sel, struct flowi *fl,
+                   unsigned short family)
+{
+       switch (family) {
+       case AF_INET:
+               return __xfrm4_selector_match(sel, fl);
+       case AF_INET6:
+               return __xfrm6_selector_match(sel, fl);
+       }
+       return 0;
+}
+
 int xfrm_register_type(struct xfrm_type *type, unsigned short family)
 {
        struct xfrm_policy_afinfo *afinfo = xfrm_policy_lock_afinfo(family);
@@ -409,62 +443,11 @@ static struct hlist_head *xfrm_policy_byidx __read_mostly;
 static unsigned int xfrm_idx_hmask __read_mostly;
 static unsigned int xfrm_policy_hashmax __read_mostly = 1 * 1024 * 1024;
 
-static inline unsigned int __idx_hash(u32 index, unsigned int hmask)
-{
-       return (index ^ (index >> 8)) & hmask;
-}
-
 static inline unsigned int idx_hash(u32 index)
 {
        return __idx_hash(index, xfrm_idx_hmask);
 }
 
-static inline unsigned int __sel_hash(struct xfrm_selector *sel, unsigned short family, unsigned int hmask)
-{
-       xfrm_address_t *daddr = &sel->daddr;
-       xfrm_address_t *saddr = &sel->saddr;
-       unsigned int h = 0;
-
-       switch (family) {
-       case AF_INET:
-               if (sel->prefixlen_d != 32 ||
-                   sel->prefixlen_s != 32)
-                       return hmask + 1;
-
-               h = ntohl(daddr->a4 ^ saddr->a4);
-               break;
-
-       case AF_INET6:
-               if (sel->prefixlen_d != 128 ||
-                   sel->prefixlen_s != 128)
-                       return hmask + 1;
-
-               h = ntohl(daddr->a6[2] ^ daddr->a6[3] ^
-                         saddr->a6[2] ^ saddr->a6[3]);
-               break;
-       };
-       h ^= (h >> 16);
-       return h & hmask;
-}
-
-static inline unsigned int __addr_hash(xfrm_address_t *daddr, xfrm_address_t *saddr, unsigned short family, unsigned int hmask)
-{
-       unsigned int h = 0;
-
-       switch (family) {
-       case AF_INET:
-               h = ntohl(daddr->a4 ^ saddr->a4);
-               break;
-
-       case AF_INET6:
-               h = ntohl(daddr->a6[2] ^ daddr->a6[3] ^
-                         saddr->a6[2] ^ saddr->a6[3]);
-               break;
-       };
-       h ^= (h >> 16);
-       return h & hmask;
-}
-
 static struct hlist_head *policy_hash_bysel(struct xfrm_selector *sel, unsigned short family, int dir)
 {
        unsigned int hmask = xfrm_policy_bydst[dir].hmask;
@@ -483,34 +466,6 @@ static struct hlist_head *policy_hash_direct(xfrm_address_t *daddr, xfrm_address
        return xfrm_policy_bydst[dir].table + hash;
 }
 
-static struct hlist_head *xfrm_policy_hash_alloc(unsigned int sz)
-{
-       struct hlist_head *n;
-
-       if (sz <= PAGE_SIZE)
-               n = kmalloc(sz, GFP_KERNEL);
-       else if (hashdist)
-               n = __vmalloc(sz, GFP_KERNEL, PAGE_KERNEL);
-       else
-               n = (struct hlist_head *)
-                       __get_free_pages(GFP_KERNEL, get_order(sz));
-
-       if (n)
-               memset(n, 0, sz);
-
-       return n;
-}
-
-static void xfrm_policy_hash_free(struct hlist_head *n, unsigned int sz)
-{
-       if (sz <= PAGE_SIZE)
-               kfree(n);
-       else if (hashdist)
-               vfree(n);
-       else
-               free_pages((unsigned long)n, get_order(sz));
-}
-
 static void xfrm_dst_hash_transfer(struct hlist_head *list,
                                   struct hlist_head *ndsttable,
                                   unsigned int nhashmask)
@@ -553,7 +508,7 @@ static void xfrm_bydst_resize(int dir)
        unsigned int nhashmask = xfrm_new_hash_mask(hmask);
        unsigned int nsize = (nhashmask + 1) * sizeof(struct hlist_head);
        struct hlist_head *odst = xfrm_policy_bydst[dir].table;
-       struct hlist_head *ndst = xfrm_policy_hash_alloc(nsize);
+       struct hlist_head *ndst = xfrm_hash_alloc(nsize);
        int i;
 
        if (!ndst)
@@ -569,7 +524,7 @@ static void xfrm_bydst_resize(int dir)
 
        write_unlock_bh(&xfrm_policy_lock);
 
-       xfrm_policy_hash_free(odst, (hmask + 1) * sizeof(struct hlist_head));
+       xfrm_hash_free(odst, (hmask + 1) * sizeof(struct hlist_head));
 }
 
 static void xfrm_byidx_resize(int total)
@@ -578,7 +533,7 @@ static void xfrm_byidx_resize(int total)
        unsigned int nhashmask = xfrm_new_hash_mask(hmask);
        unsigned int nsize = (nhashmask + 1) * sizeof(struct hlist_head);
        struct hlist_head *oidx = xfrm_policy_byidx;
-       struct hlist_head *nidx = xfrm_policy_hash_alloc(nsize);
+       struct hlist_head *nidx = xfrm_hash_alloc(nsize);
        int i;
 
        if (!nidx)
@@ -594,7 +549,7 @@ static void xfrm_byidx_resize(int total)
 
        write_unlock_bh(&xfrm_policy_lock);
 
-       xfrm_policy_hash_free(oidx, (hmask + 1) * sizeof(struct hlist_head));
+       xfrm_hash_free(oidx, (hmask + 1) * sizeof(struct hlist_head));
 }
 
 static inline int xfrm_bydst_should_resize(int dir, int *total)
@@ -857,8 +812,9 @@ void xfrm_policy_flush(u8 type)
        for (dir = 0; dir < XFRM_POLICY_MAX; dir++) {
                struct xfrm_policy *pol;
                struct hlist_node *entry;
-               int i;
+               int i, killed;
 
+               killed = 0;
        again1:
                hlist_for_each_entry(pol, entry,
                                     &xfrm_policy_inexact[dir], bydst) {
@@ -869,6 +825,7 @@ void xfrm_policy_flush(u8 type)
                        write_unlock_bh(&xfrm_policy_lock);
 
                        xfrm_policy_kill(pol);
+                       killed++;
 
                        write_lock_bh(&xfrm_policy_lock);
                        goto again1;
@@ -886,13 +843,14 @@ void xfrm_policy_flush(u8 type)
                                write_unlock_bh(&xfrm_policy_lock);
 
                                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);
        write_unlock_bh(&xfrm_policy_lock);
@@ -959,34 +917,37 @@ out:
 }
 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;
        struct hlist_head *chain;
+       u32 priority = ~0U;
 
        daddr = xfrm_flowi_daddr(fl, family);
        saddr = xfrm_flowi_saddr(fl, family);
@@ -997,44 +958,69 @@ 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)) {
-                       xfrm_pol_hold(pol);
+               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;
                }
        }
-       if (!ret) {
-               chain = &xfrm_policy_inexact[dir];
-               hlist_for_each_entry(pol, entry, chain, bydst) {
-                       if (xfrm_policy_match(pol, fl, type, family, dir)) {
-                               xfrm_pol_hold(pol);
-                               ret = pol;
-                               break;
+       chain = &xfrm_policy_inexact[dir];
+       hlist_for_each_entry(pol, entry, chain, bydst) {
+               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)
@@ -1064,12 +1050,16 @@ static struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struc
                                                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
+               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);
@@ -1185,6 +1175,20 @@ int __xfrm_sk_clone_policy(struct sock *sk)
        return 0;
 }
 
+static int
+xfrm_get_saddr(xfrm_address_t *local, xfrm_address_t *remote,
+              unsigned short family)
+{
+       int err;
+       struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
+
+       if (unlikely(afinfo == NULL))
+               return -EINVAL;
+       err = afinfo->get_saddr(local, remote);
+       xfrm_policy_put_afinfo(afinfo);
+       return err;
+}
+
 /* Resolve list of templates for the flow, given policy. */
 
 static int
@@ -1196,6 +1200,7 @@ xfrm_tmpl_resolve_one(struct xfrm_policy *policy, struct flowi *fl,
        int i, error;
        xfrm_address_t *daddr = xfrm_flowi_daddr(fl, family);
        xfrm_address_t *saddr = xfrm_flowi_saddr(fl, family);
+       xfrm_address_t tmp;
 
        for (nx=0, i = 0; i < policy->xfrm_nr; i++) {
                struct xfrm_state *x;
@@ -1206,6 +1211,13 @@ xfrm_tmpl_resolve_one(struct xfrm_policy *policy, struct flowi *fl,
                if (tmpl->mode == XFRM_MODE_TUNNEL) {
                        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)
+                                       goto fail;
+                               local = &tmp;
+                       }
                }
 
                x = xfrm_state_find(remote, local, fl, tmpl, policy, &error, family);
@@ -1340,8 +1352,11 @@ restart:
        pol_dead = 0;
        xfrm_nr = 0;
 
-       if (sk && sk->sk_policy[1])
+       if (sk && sk->sk_policy[1]) {
                policy = xfrm_sk_policy_lookup(sk, XFRM_POLICY_OUT, fl);
+               if (IS_ERR(policy))
+                       return PTR_ERR(policy);
+       }
 
        if (!policy) {
                /* To accelerate a bit...  */
@@ -1351,6 +1366,8 @@ restart:
 
                policy = flow_cache_lookup(fl, dst_orig->ops->family,
                                           dir, xfrm_policy_lookup);
+               if (IS_ERR(policy))
+                       return PTR_ERR(policy);
        }
 
        if (!policy)
@@ -1397,6 +1414,10 @@ restart:
                                                            fl, family,
                                                            XFRM_POLICY_OUT);
                        if (pols[1]) {
+                               if (IS_ERR(pols[1])) {
+                                       err = PTR_ERR(pols[1]);
+                                       goto error;
+                               }
                                if (pols[1]->action == XFRM_POLICY_BLOCK) {
                                        err = -EPERM;
                                        goto error;
@@ -1592,8 +1613,7 @@ static inline int secpath_has_nontransport(struct sec_path *sp, int k, int *idxp
 {
        for (; k < sp->len; k++) {
                if (sp->xvec[k]->props.mode != XFRM_MODE_TRANSPORT) {
-                       if (idxp)
-                               *idxp = k;
+                       *idxp = k;
                        return 1;
                }
        }
@@ -1612,7 +1632,6 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
        struct flowi fl;
        u8 fl_dir = policy_to_flow_dir(dir);
        int xerr_idx = -1;
-       int *xerr_idxp = &xerr_idx;
 
        if (xfrm_decode_session(skb, &fl, family) < 0)
                return 0;
@@ -1630,15 +1649,21 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
        }
 
        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))
+                       return 0;
+       }
 
        if (!pol)
                pol = flow_cache_lookup(&fl, family, fl_dir,
                                        xfrm_policy_lookup);
 
+       if (IS_ERR(pol))
+               return 0;
+
        if (!pol) {
-               if (skb->sp && secpath_has_nontransport(skb->sp, 0, xerr_idxp)) {
+               if (skb->sp && secpath_has_nontransport(skb->sp, 0, &xerr_idx)) {
                        xfrm_secpath_reject(xerr_idx, skb, &fl);
                        return 0;
                }
@@ -1655,6 +1680,8 @@ 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]))
+                               return 0;
                        pols[1]->curlft.use_time = (unsigned long)xtime.tv_sec;
                        npols ++;
                }
@@ -1697,13 +1724,14 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
                for (i = xfrm_nr-1, k = 0; i >= 0; i--) {
                        k = xfrm_policy_ok(tpp[i], sp, k, family);
                        if (k < 0) {
-                               if (k < -1 && xerr_idxp)
-                                       *xerr_idxp = -(2+k);
+                               if (k < -1)
+                                       /* "-2 - errored_index" returned */
+                                       xerr_idx = -(2+k);
                                goto reject;
                        }
                }
 
-               if (secpath_has_nontransport(sp, k, xerr_idxp))
+               if (secpath_has_nontransport(sp, k, &xerr_idx))
                        goto reject;
 
                xfrm_pols_put(pols, npols);
@@ -1761,7 +1789,7 @@ 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)
@@ -1883,7 +1911,8 @@ EXPORT_SYMBOL(xfrm_init_pmtu);
  * 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;
@@ -1900,7 +1929,8 @@ 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;
@@ -2063,15 +2093,13 @@ static void __init xfrm_policy_init(void)
 
        xfrm_dst_cache = kmem_cache_create("xfrm_dst_cache",
                                           sizeof(struct xfrm_dst),
-                                          0, SLAB_HWCACHE_ALIGN,
+                                          0, SLAB_HWCACHE_ALIGN|SLAB_PANIC,
                                           NULL, NULL);
-       if (!xfrm_dst_cache)
-               panic("XFRM: failed to allocate xfrm_dst_cache\n");
 
        hmask = 8 - 1;
        sz = (hmask+1) * sizeof(struct hlist_head);
 
-       xfrm_policy_byidx = xfrm_policy_hash_alloc(sz);
+       xfrm_policy_byidx = xfrm_hash_alloc(sz);
        xfrm_idx_hmask = hmask;
        if (!xfrm_policy_byidx)
                panic("XFRM: failed to allocate byidx hash\n");
@@ -2082,7 +2110,7 @@ static void __init xfrm_policy_init(void)
                INIT_HLIST_HEAD(&xfrm_policy_inexact[dir]);
 
                htab = &xfrm_policy_bydst[dir];
-               htab->table = xfrm_policy_hash_alloc(sz);
+               htab->table = xfrm_hash_alloc(sz);
                htab->hmask = hmask;
                if (!htab->table)
                        panic("XFRM: failed to allocate bydst hash\n");
This page took 0.060457 seconds and 5 git commands to generate.