|
@@ -37,6 +37,8 @@
|
|
|
DEFINE_MUTEX(xfrm_cfg_mutex);
|
|
|
EXPORT_SYMBOL(xfrm_cfg_mutex);
|
|
|
|
|
|
+static DEFINE_SPINLOCK(xfrm_policy_sk_bundle_lock);
|
|
|
+static struct dst_entry *xfrm_policy_sk_bundles;
|
|
|
static DEFINE_RWLOCK(xfrm_policy_lock);
|
|
|
|
|
|
static DEFINE_RWLOCK(xfrm_policy_afinfo_lock);
|
|
@@ -50,6 +52,7 @@ 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 void xfrm_init_pmtu(struct dst_entry *dst);
|
|
|
+static int stale_bundle(struct dst_entry *dst);
|
|
|
|
|
|
static struct xfrm_policy *__xfrm_policy_unlink(struct xfrm_policy *pol,
|
|
|
int dir);
|
|
@@ -277,8 +280,6 @@ void xfrm_policy_destroy(struct xfrm_policy *policy)
|
|
|
{
|
|
|
BUG_ON(!policy->walk.dead);
|
|
|
|
|
|
- BUG_ON(policy->bundles);
|
|
|
-
|
|
|
if (del_timer(&policy->timer))
|
|
|
BUG();
|
|
|
|
|
@@ -289,12 +290,7 @@ EXPORT_SYMBOL(xfrm_policy_destroy);
|
|
|
|
|
|
static void xfrm_policy_gc_kill(struct xfrm_policy *policy)
|
|
|
{
|
|
|
- struct dst_entry *dst;
|
|
|
-
|
|
|
- while ((dst = policy->bundles) != NULL) {
|
|
|
- policy->bundles = dst->next;
|
|
|
- dst_free(dst);
|
|
|
- }
|
|
|
+ atomic_inc(&policy->genid);
|
|
|
|
|
|
if (del_timer(&policy->timer))
|
|
|
atomic_dec(&policy->refcnt);
|
|
@@ -572,7 +568,6 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
|
|
|
struct xfrm_policy *delpol;
|
|
|
struct hlist_head *chain;
|
|
|
struct hlist_node *entry, *newpos;
|
|
|
- struct dst_entry *gc_list;
|
|
|
u32 mark = policy->mark.v & policy->mark.m;
|
|
|
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
|
@@ -622,34 +617,6 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
|
|
|
else if (xfrm_bydst_should_resize(net, dir, NULL))
|
|
|
schedule_work(&net->xfrm.policy_hash_work);
|
|
|
|
|
|
- read_lock_bh(&xfrm_policy_lock);
|
|
|
- gc_list = NULL;
|
|
|
- entry = &policy->bydst;
|
|
|
- hlist_for_each_entry_continue(policy, entry, bydst) {
|
|
|
- struct dst_entry *dst;
|
|
|
-
|
|
|
- write_lock(&policy->lock);
|
|
|
- dst = policy->bundles;
|
|
|
- if (dst) {
|
|
|
- struct dst_entry *tail = dst;
|
|
|
- while (tail->next)
|
|
|
- tail = tail->next;
|
|
|
- tail->next = gc_list;
|
|
|
- gc_list = dst;
|
|
|
-
|
|
|
- policy->bundles = NULL;
|
|
|
- }
|
|
|
- write_unlock(&policy->lock);
|
|
|
- }
|
|
|
- read_unlock_bh(&xfrm_policy_lock);
|
|
|
-
|
|
|
- while (gc_list) {
|
|
|
- struct dst_entry *dst = gc_list;
|
|
|
-
|
|
|
- gc_list = dst->next;
|
|
|
- dst_free(dst);
|
|
|
- }
|
|
|
-
|
|
|
return 0;
|
|
|
}
|
|
|
EXPORT_SYMBOL(xfrm_policy_insert);
|
|
@@ -998,6 +965,19 @@ fail:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static struct xfrm_policy *
|
|
|
+__xfrm_policy_lookup(struct net *net, struct flowi *fl, u16 family, u8 dir)
|
|
|
+{
|
|
|
+#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
+ struct xfrm_policy *pol;
|
|
|
+
|
|
|
+ pol = xfrm_policy_lookup_bytype(net, XFRM_POLICY_TYPE_SUB, fl, family, dir);
|
|
|
+ if (pol != NULL)
|
|
|
+ return pol;
|
|
|
+#endif
|
|
|
+ return xfrm_policy_lookup_bytype(net, XFRM_POLICY_TYPE_MAIN, fl, family, dir);
|
|
|
+}
|
|
|
+
|
|
|
static struct flow_cache_object *
|
|
|
xfrm_policy_lookup(struct net *net, struct flowi *fl, u16 family,
|
|
|
u8 dir, struct flow_cache_object *old_obj, void *ctx)
|
|
@@ -1007,21 +987,10 @@ xfrm_policy_lookup(struct net *net, struct flowi *fl, u16 family,
|
|
|
if (old_obj)
|
|
|
xfrm_pol_put(container_of(old_obj, struct xfrm_policy, flo));
|
|
|
|
|
|
-#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
- pol = xfrm_policy_lookup_bytype(net, XFRM_POLICY_TYPE_SUB, fl, family, dir);
|
|
|
- if (IS_ERR(pol))
|
|
|
+ pol = __xfrm_policy_lookup(net, fl, family, dir);
|
|
|
+ if (IS_ERR_OR_NULL(pol))
|
|
|
return ERR_CAST(pol);
|
|
|
- if (pol)
|
|
|
- goto found;
|
|
|
-#endif
|
|
|
- pol = xfrm_policy_lookup_bytype(net, XFRM_POLICY_TYPE_MAIN, fl, family, dir);
|
|
|
- if (IS_ERR(pol))
|
|
|
- return ERR_CAST(pol);
|
|
|
- if (pol)
|
|
|
- goto found;
|
|
|
- return NULL;
|
|
|
|
|
|
-found:
|
|
|
/* Resolver returns two references:
|
|
|
* one for cache and one for caller of flow_cache_lookup() */
|
|
|
xfrm_pol_hold(pol);
|
|
@@ -1313,18 +1282,6 @@ xfrm_tmpl_resolve(struct xfrm_policy **pols, int npols, struct flowi *fl,
|
|
|
* still valid.
|
|
|
*/
|
|
|
|
|
|
-static struct dst_entry *
|
|
|
-xfrm_find_bundle(struct flowi *fl, struct xfrm_policy *policy, unsigned short family)
|
|
|
-{
|
|
|
- struct dst_entry *x;
|
|
|
- struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
|
|
|
- if (unlikely(afinfo == NULL))
|
|
|
- return ERR_PTR(-EINVAL);
|
|
|
- x = afinfo->find_bundle(fl, policy);
|
|
|
- xfrm_policy_put_afinfo(afinfo);
|
|
|
- return x;
|
|
|
-}
|
|
|
-
|
|
|
static inline int xfrm_get_tos(struct flowi *fl, int family)
|
|
|
{
|
|
|
struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
|
|
@@ -1340,6 +1297,54 @@ static inline int xfrm_get_tos(struct flowi *fl, int family)
|
|
|
return tos;
|
|
|
}
|
|
|
|
|
|
+static struct flow_cache_object *xfrm_bundle_flo_get(struct flow_cache_object *flo)
|
|
|
+{
|
|
|
+ struct xfrm_dst *xdst = container_of(flo, struct xfrm_dst, flo);
|
|
|
+ struct dst_entry *dst = &xdst->u.dst;
|
|
|
+
|
|
|
+ if (xdst->route == NULL) {
|
|
|
+ /* Dummy bundle - if it has xfrms we were not
|
|
|
+ * able to build bundle as template resolution failed.
|
|
|
+ * It means we need to try again resolving. */
|
|
|
+ if (xdst->num_xfrms > 0)
|
|
|
+ return NULL;
|
|
|
+ } else {
|
|
|
+ /* Real bundle */
|
|
|
+ if (stale_bundle(dst))
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ dst_hold(dst);
|
|
|
+ return flo;
|
|
|
+}
|
|
|
+
|
|
|
+static int xfrm_bundle_flo_check(struct flow_cache_object *flo)
|
|
|
+{
|
|
|
+ struct xfrm_dst *xdst = container_of(flo, struct xfrm_dst, flo);
|
|
|
+ struct dst_entry *dst = &xdst->u.dst;
|
|
|
+
|
|
|
+ if (!xdst->route)
|
|
|
+ return 0;
|
|
|
+ if (stale_bundle(dst))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+static void xfrm_bundle_flo_delete(struct flow_cache_object *flo)
|
|
|
+{
|
|
|
+ struct xfrm_dst *xdst = container_of(flo, struct xfrm_dst, flo);
|
|
|
+ struct dst_entry *dst = &xdst->u.dst;
|
|
|
+
|
|
|
+ dst_free(dst);
|
|
|
+}
|
|
|
+
|
|
|
+static const struct flow_cache_ops xfrm_bundle_fc_ops = {
|
|
|
+ .get = xfrm_bundle_flo_get,
|
|
|
+ .check = xfrm_bundle_flo_check,
|
|
|
+ .delete = xfrm_bundle_flo_delete,
|
|
|
+};
|
|
|
+
|
|
|
static inline struct xfrm_dst *xfrm_alloc_dst(struct net *net, int family)
|
|
|
{
|
|
|
struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
|
|
@@ -1362,9 +1367,10 @@ static inline struct xfrm_dst *xfrm_alloc_dst(struct net *net, int family)
|
|
|
BUG();
|
|
|
}
|
|
|
xdst = dst_alloc(dst_ops) ?: ERR_PTR(-ENOBUFS);
|
|
|
-
|
|
|
xfrm_policy_put_afinfo(afinfo);
|
|
|
|
|
|
+ xdst->flo.ops = &xfrm_bundle_fc_ops;
|
|
|
+
|
|
|
return xdst;
|
|
|
}
|
|
|
|
|
@@ -1402,6 +1408,7 @@ static inline int xfrm_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/* Allocate chain of dst_entry's, attach known xfrm's, calculate
|
|
|
* all the metrics... Shortly, bundle a bundle.
|
|
|
*/
|
|
@@ -1465,7 +1472,7 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
|
|
|
dst_hold(dst);
|
|
|
|
|
|
dst1->xfrm = xfrm[i];
|
|
|
- xdst->genid = xfrm[i]->genid;
|
|
|
+ xdst->xfrm_genid = xfrm[i]->genid;
|
|
|
|
|
|
dst1->obsolete = -1;
|
|
|
dst1->flags |= DST_HOST;
|
|
@@ -1558,7 +1565,186 @@ xfrm_dst_update_origin(struct dst_entry *dst, struct flowi *fl)
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
-static int stale_bundle(struct dst_entry *dst);
|
|
|
+static int xfrm_expand_policies(struct flowi *fl, u16 family,
|
|
|
+ struct xfrm_policy **pols,
|
|
|
+ int *num_pols, int *num_xfrms)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (*num_pols == 0 || !pols[0]) {
|
|
|
+ *num_pols = 0;
|
|
|
+ *num_xfrms = 0;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if (IS_ERR(pols[0]))
|
|
|
+ return PTR_ERR(pols[0]);
|
|
|
+
|
|
|
+ *num_xfrms = pols[0]->xfrm_nr;
|
|
|
+
|
|
|
+#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
+ if (pols[0] && pols[0]->action == XFRM_POLICY_ALLOW &&
|
|
|
+ pols[0]->type != XFRM_POLICY_TYPE_MAIN) {
|
|
|
+ pols[1] = xfrm_policy_lookup_bytype(xp_net(pols[0]),
|
|
|
+ XFRM_POLICY_TYPE_MAIN,
|
|
|
+ fl, family,
|
|
|
+ XFRM_POLICY_OUT);
|
|
|
+ if (pols[1]) {
|
|
|
+ if (IS_ERR(pols[1])) {
|
|
|
+ xfrm_pols_put(pols, *num_pols);
|
|
|
+ return PTR_ERR(pols[1]);
|
|
|
+ }
|
|
|
+ (*num_pols) ++;
|
|
|
+ (*num_xfrms) += pols[1]->xfrm_nr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+ for (i = 0; i < *num_pols; i++) {
|
|
|
+ if (pols[i]->action != XFRM_POLICY_ALLOW) {
|
|
|
+ *num_xfrms = -1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+static struct xfrm_dst *
|
|
|
+xfrm_resolve_and_create_bundle(struct xfrm_policy **pols, int num_pols,
|
|
|
+ struct flowi *fl, u16 family,
|
|
|
+ struct dst_entry *dst_orig)
|
|
|
+{
|
|
|
+ struct net *net = xp_net(pols[0]);
|
|
|
+ struct xfrm_state *xfrm[XFRM_MAX_DEPTH];
|
|
|
+ struct dst_entry *dst;
|
|
|
+ struct xfrm_dst *xdst;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ /* Try to instantiate a bundle */
|
|
|
+ err = xfrm_tmpl_resolve(pols, num_pols, fl, xfrm, family);
|
|
|
+ if (err < 0) {
|
|
|
+ if (err != -EAGAIN)
|
|
|
+ XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTPOLERROR);
|
|
|
+ return ERR_PTR(err);
|
|
|
+ }
|
|
|
+
|
|
|
+ dst = xfrm_bundle_create(pols[0], xfrm, err, fl, dst_orig);
|
|
|
+ if (IS_ERR(dst)) {
|
|
|
+ XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTBUNDLEGENERROR);
|
|
|
+ return ERR_CAST(dst);
|
|
|
+ }
|
|
|
+
|
|
|
+ xdst = (struct xfrm_dst *)dst;
|
|
|
+ xdst->num_xfrms = err;
|
|
|
+ if (num_pols > 1)
|
|
|
+ err = xfrm_dst_update_parent(dst, &pols[1]->selector);
|
|
|
+ else
|
|
|
+ err = xfrm_dst_update_origin(dst, fl);
|
|
|
+ if (unlikely(err)) {
|
|
|
+ dst_free(dst);
|
|
|
+ XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
|
|
|
+ return ERR_PTR(err);
|
|
|
+ }
|
|
|
+
|
|
|
+ xdst->num_pols = num_pols;
|
|
|
+ memcpy(xdst->pols, pols, sizeof(struct xfrm_policy*) * num_pols);
|
|
|
+ xdst->policy_genid = atomic_read(&pols[0]->genid);
|
|
|
+
|
|
|
+ return xdst;
|
|
|
+}
|
|
|
+
|
|
|
+static struct flow_cache_object *
|
|
|
+xfrm_bundle_lookup(struct net *net, struct flowi *fl, u16 family, u8 dir,
|
|
|
+ struct flow_cache_object *oldflo, void *ctx)
|
|
|
+{
|
|
|
+ struct dst_entry *dst_orig = (struct dst_entry *)ctx;
|
|
|
+ struct xfrm_policy *pols[XFRM_POLICY_TYPE_MAX];
|
|
|
+ struct xfrm_dst *xdst, *new_xdst;
|
|
|
+ int num_pols = 0, num_xfrms = 0, i, err, pol_dead;
|
|
|
+
|
|
|
+ /* Check if the policies from old bundle are usable */
|
|
|
+ xdst = NULL;
|
|
|
+ if (oldflo) {
|
|
|
+ xdst = container_of(oldflo, struct xfrm_dst, flo);
|
|
|
+ num_pols = xdst->num_pols;
|
|
|
+ num_xfrms = xdst->num_xfrms;
|
|
|
+ pol_dead = 0;
|
|
|
+ for (i = 0; i < num_pols; i++) {
|
|
|
+ pols[i] = xdst->pols[i];
|
|
|
+ pol_dead |= pols[i]->walk.dead;
|
|
|
+ }
|
|
|
+ if (pol_dead) {
|
|
|
+ dst_free(&xdst->u.dst);
|
|
|
+ xdst = NULL;
|
|
|
+ num_pols = 0;
|
|
|
+ num_xfrms = 0;
|
|
|
+ oldflo = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Resolve policies to use if we couldn't get them from
|
|
|
+ * previous cache entry */
|
|
|
+ if (xdst == NULL) {
|
|
|
+ num_pols = 1;
|
|
|
+ pols[0] = __xfrm_policy_lookup(net, fl, family, dir);
|
|
|
+ err = xfrm_expand_policies(fl, family, pols,
|
|
|
+ &num_pols, &num_xfrms);
|
|
|
+ if (err < 0)
|
|
|
+ goto inc_error;
|
|
|
+ if (num_pols == 0)
|
|
|
+ return NULL;
|
|
|
+ if (num_xfrms <= 0)
|
|
|
+ goto make_dummy_bundle;
|
|
|
+ }
|
|
|
+
|
|
|
+ new_xdst = xfrm_resolve_and_create_bundle(pols, num_pols, fl, family, dst_orig);
|
|
|
+ if (IS_ERR(new_xdst)) {
|
|
|
+ err = PTR_ERR(new_xdst);
|
|
|
+ if (err != -EAGAIN)
|
|
|
+ goto error;
|
|
|
+ if (oldflo == NULL)
|
|
|
+ goto make_dummy_bundle;
|
|
|
+ dst_hold(&xdst->u.dst);
|
|
|
+ return oldflo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Kill the previous bundle */
|
|
|
+ if (xdst) {
|
|
|
+ /* The policies were stolen for newly generated bundle */
|
|
|
+ xdst->num_pols = 0;
|
|
|
+ dst_free(&xdst->u.dst);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Flow cache does not have reference, it dst_free()'s,
|
|
|
+ * but we do need to return one reference for original caller */
|
|
|
+ dst_hold(&new_xdst->u.dst);
|
|
|
+ return &new_xdst->flo;
|
|
|
+
|
|
|
+make_dummy_bundle:
|
|
|
+ /* We found policies, but there's no bundles to instantiate:
|
|
|
+ * either because the policy blocks, has no transformations or
|
|
|
+ * we could not build template (no xfrm_states).*/
|
|
|
+ xdst = xfrm_alloc_dst(net, family);
|
|
|
+ if (IS_ERR(xdst)) {
|
|
|
+ xfrm_pols_put(pols, num_pols);
|
|
|
+ return ERR_CAST(xdst);
|
|
|
+ }
|
|
|
+ xdst->num_pols = num_pols;
|
|
|
+ xdst->num_xfrms = num_xfrms;
|
|
|
+ memcpy(xdst->pols, pols, sizeof(struct xfrm_policy*) * num_pols);
|
|
|
+
|
|
|
+ dst_hold(&xdst->u.dst);
|
|
|
+ return &xdst->flo;
|
|
|
+
|
|
|
+inc_error:
|
|
|
+ XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTPOLERROR);
|
|
|
+error:
|
|
|
+ if (xdst != NULL)
|
|
|
+ dst_free(&xdst->u.dst);
|
|
|
+ else
|
|
|
+ xfrm_pols_put(pols, num_pols);
|
|
|
+ return ERR_PTR(err);
|
|
|
+}
|
|
|
|
|
|
/* Main function: finds/creates a bundle for given flow.
|
|
|
*
|
|
@@ -1568,248 +1754,152 @@ static int stale_bundle(struct dst_entry *dst);
|
|
|
int __xfrm_lookup(struct net *net, 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];
|
|
|
- int npols;
|
|
|
- int pol_dead;
|
|
|
- int xfrm_nr;
|
|
|
- int pi;
|
|
|
- struct xfrm_state *xfrm[XFRM_MAX_DEPTH];
|
|
|
- struct dst_entry *dst, *dst_orig = *dst_p;
|
|
|
- int nx = 0;
|
|
|
- int err;
|
|
|
- u32 genid;
|
|
|
- u16 family;
|
|
|
+ struct flow_cache_object *flo;
|
|
|
+ struct xfrm_dst *xdst;
|
|
|
+ struct dst_entry *dst, *dst_orig = *dst_p, *route;
|
|
|
+ u16 family = dst_orig->ops->family;
|
|
|
u8 dir = policy_to_flow_dir(XFRM_POLICY_OUT);
|
|
|
+ int i, err, num_pols, num_xfrms, drop_pols = 0;
|
|
|
|
|
|
restart:
|
|
|
- genid = atomic_read(&flow_cache_genid);
|
|
|
- policy = NULL;
|
|
|
- for (pi = 0; pi < ARRAY_SIZE(pols); pi++)
|
|
|
- pols[pi] = NULL;
|
|
|
- npols = 0;
|
|
|
- pol_dead = 0;
|
|
|
- xfrm_nr = 0;
|
|
|
+ dst = NULL;
|
|
|
+ xdst = NULL;
|
|
|
+ route = NULL;
|
|
|
|
|
|
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(net, LINUX_MIB_XFRMOUTPOLERROR);
|
|
|
+ num_pols = 1;
|
|
|
+ pols[0] = xfrm_sk_policy_lookup(sk, XFRM_POLICY_OUT, fl);
|
|
|
+ err = xfrm_expand_policies(fl, family, pols,
|
|
|
+ &num_pols, &num_xfrms);
|
|
|
+ if (err < 0)
|
|
|
goto dropdst;
|
|
|
+
|
|
|
+ if (num_pols) {
|
|
|
+ if (num_xfrms <= 0) {
|
|
|
+ drop_pols = num_pols;
|
|
|
+ goto no_transform;
|
|
|
+ }
|
|
|
+
|
|
|
+ xdst = xfrm_resolve_and_create_bundle(
|
|
|
+ pols, num_pols, fl,
|
|
|
+ family, dst_orig);
|
|
|
+ if (IS_ERR(xdst)) {
|
|
|
+ xfrm_pols_put(pols, num_pols);
|
|
|
+ err = PTR_ERR(xdst);
|
|
|
+ goto dropdst;
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock_bh(&xfrm_policy_sk_bundle_lock);
|
|
|
+ xdst->u.dst.next = xfrm_policy_sk_bundles;
|
|
|
+ xfrm_policy_sk_bundles = &xdst->u.dst;
|
|
|
+ spin_unlock_bh(&xfrm_policy_sk_bundle_lock);
|
|
|
+
|
|
|
+ route = xdst->route;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (!policy) {
|
|
|
- struct flow_cache_object *flo;
|
|
|
-
|
|
|
+ if (xdst == NULL) {
|
|
|
/* To accelerate a bit... */
|
|
|
if ((dst_orig->flags & DST_NOXFRM) ||
|
|
|
!net->xfrm.policy_count[XFRM_POLICY_OUT])
|
|
|
goto nopol;
|
|
|
|
|
|
- flo = flow_cache_lookup(net, fl, dst_orig->ops->family,
|
|
|
- dir, xfrm_policy_lookup, NULL);
|
|
|
- err = PTR_ERR(flo);
|
|
|
+ flo = flow_cache_lookup(net, fl, family, dir,
|
|
|
+ xfrm_bundle_lookup, dst_orig);
|
|
|
+ if (flo == NULL)
|
|
|
+ goto nopol;
|
|
|
if (IS_ERR(flo)) {
|
|
|
- XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTPOLERROR);
|
|
|
+ err = PTR_ERR(flo);
|
|
|
goto dropdst;
|
|
|
}
|
|
|
- if (flo)
|
|
|
- policy = container_of(flo, struct xfrm_policy, flo);
|
|
|
- else
|
|
|
- policy = NULL;
|
|
|
+ xdst = container_of(flo, struct xfrm_dst, flo);
|
|
|
+
|
|
|
+ num_pols = xdst->num_pols;
|
|
|
+ num_xfrms = xdst->num_xfrms;
|
|
|
+ memcpy(pols, xdst->pols, sizeof(struct xfrm_policy*) * num_pols);
|
|
|
+ route = xdst->route;
|
|
|
+ }
|
|
|
+
|
|
|
+ dst = &xdst->u.dst;
|
|
|
+ if (route == NULL && num_xfrms > 0) {
|
|
|
+ /* The only case when xfrm_bundle_lookup() returns a
|
|
|
+ * bundle with null route, is when the template could
|
|
|
+ * not be resolved. It means policies are there, but
|
|
|
+ * bundle could not be created, since we don't yet
|
|
|
+ * have the xfrm_state's. We need to wait for KM to
|
|
|
+ * negotiate new SA's or bail out with error.*/
|
|
|
+ if (net->xfrm.sysctl_larval_drop) {
|
|
|
+ /* EREMOTE tells the caller to generate
|
|
|
+ * a one-shot blackhole route. */
|
|
|
+ dst_release(dst);
|
|
|
+ xfrm_pols_put(pols, num_pols);
|
|
|
+ XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTNOSTATES);
|
|
|
+ return -EREMOTE;
|
|
|
+ }
|
|
|
+ if (flags & XFRM_LOOKUP_WAIT) {
|
|
|
+ DECLARE_WAITQUEUE(wait, current);
|
|
|
+
|
|
|
+ add_wait_queue(&net->xfrm.km_waitq, &wait);
|
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
|
+ schedule();
|
|
|
+ set_current_state(TASK_RUNNING);
|
|
|
+ remove_wait_queue(&net->xfrm.km_waitq, &wait);
|
|
|
+
|
|
|
+ if (!signal_pending(current)) {
|
|
|
+ dst_release(dst);
|
|
|
+ goto restart;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = -ERESTART;
|
|
|
+ } else
|
|
|
+ err = -EAGAIN;
|
|
|
+
|
|
|
+ XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTNOSTATES);
|
|
|
+ goto error;
|
|
|
}
|
|
|
|
|
|
- if (!policy)
|
|
|
+no_transform:
|
|
|
+ if (num_pols == 0)
|
|
|
goto nopol;
|
|
|
|
|
|
- family = dst_orig->ops->family;
|
|
|
- pols[0] = policy;
|
|
|
- npols ++;
|
|
|
- xfrm_nr += pols[0]->xfrm_nr;
|
|
|
-
|
|
|
- err = -ENOENT;
|
|
|
- if ((flags & XFRM_LOOKUP_ICMP) && !(policy->flags & XFRM_POLICY_ICMP))
|
|
|
+ if ((flags & XFRM_LOOKUP_ICMP) &&
|
|
|
+ !(pols[0]->flags & XFRM_POLICY_ICMP)) {
|
|
|
+ err = -ENOENT;
|
|
|
goto error;
|
|
|
+ }
|
|
|
|
|
|
- policy->curlft.use_time = get_seconds();
|
|
|
+ for (i = 0; i < num_pols; i++)
|
|
|
+ pols[i]->curlft.use_time = get_seconds();
|
|
|
|
|
|
- switch (policy->action) {
|
|
|
- default:
|
|
|
- case XFRM_POLICY_BLOCK:
|
|
|
+ if (num_xfrms < 0) {
|
|
|
/* Prohibit the flow */
|
|
|
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTPOLBLOCK);
|
|
|
err = -EPERM;
|
|
|
goto error;
|
|
|
-
|
|
|
- case XFRM_POLICY_ALLOW:
|
|
|
-#ifndef CONFIG_XFRM_SUB_POLICY
|
|
|
- if (policy->xfrm_nr == 0) {
|
|
|
- /* Flow passes not transformed. */
|
|
|
- xfrm_pol_put(policy);
|
|
|
- return 0;
|
|
|
- }
|
|
|
-#endif
|
|
|
-
|
|
|
- /* Try to find matching bundle.
|
|
|
- *
|
|
|
- * LATER: help from flow cache. It is optional, this
|
|
|
- * is required only for output policy.
|
|
|
- */
|
|
|
- dst = xfrm_find_bundle(fl, policy, family);
|
|
|
- if (IS_ERR(dst)) {
|
|
|
- XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
|
|
|
- err = PTR_ERR(dst);
|
|
|
- goto error;
|
|
|
- }
|
|
|
-
|
|
|
- if (dst)
|
|
|
- break;
|
|
|
-
|
|
|
-#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
- if (pols[0]->type != XFRM_POLICY_TYPE_MAIN) {
|
|
|
- pols[1] = xfrm_policy_lookup_bytype(net,
|
|
|
- XFRM_POLICY_TYPE_MAIN,
|
|
|
- fl, family,
|
|
|
- XFRM_POLICY_OUT);
|
|
|
- if (pols[1]) {
|
|
|
- if (IS_ERR(pols[1])) {
|
|
|
- XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTPOLERROR);
|
|
|
- err = PTR_ERR(pols[1]);
|
|
|
- goto error;
|
|
|
- }
|
|
|
- if (pols[1]->action == XFRM_POLICY_BLOCK) {
|
|
|
- XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTPOLBLOCK);
|
|
|
- err = -EPERM;
|
|
|
- goto error;
|
|
|
- }
|
|
|
- npols ++;
|
|
|
- xfrm_nr += pols[1]->xfrm_nr;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /*
|
|
|
- * Because neither flowi nor bundle information knows about
|
|
|
- * transformation template size. On more than one policy usage
|
|
|
- * we can realize whether all of them is bypass or not after
|
|
|
- * they are searched. See above not-transformed bypass
|
|
|
- * is surrounded by non-sub policy configuration, too.
|
|
|
- */
|
|
|
- if (xfrm_nr == 0) {
|
|
|
- /* Flow passes not transformed. */
|
|
|
- xfrm_pols_put(pols, npols);
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
-#endif
|
|
|
- nx = xfrm_tmpl_resolve(pols, npols, fl, xfrm, family);
|
|
|
-
|
|
|
- if (unlikely(nx<0)) {
|
|
|
- err = nx;
|
|
|
- if (err == -EAGAIN && net->xfrm.sysctl_larval_drop) {
|
|
|
- /* EREMOTE tells the caller to generate
|
|
|
- * a one-shot blackhole route.
|
|
|
- */
|
|
|
- XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTNOSTATES);
|
|
|
- xfrm_pol_put(policy);
|
|
|
- return -EREMOTE;
|
|
|
- }
|
|
|
- if (err == -EAGAIN && (flags & XFRM_LOOKUP_WAIT)) {
|
|
|
- DECLARE_WAITQUEUE(wait, current);
|
|
|
-
|
|
|
- add_wait_queue(&net->xfrm.km_waitq, &wait);
|
|
|
- set_current_state(TASK_INTERRUPTIBLE);
|
|
|
- schedule();
|
|
|
- set_current_state(TASK_RUNNING);
|
|
|
- remove_wait_queue(&net->xfrm.km_waitq, &wait);
|
|
|
-
|
|
|
- nx = xfrm_tmpl_resolve(pols, npols, fl, xfrm, family);
|
|
|
-
|
|
|
- if (nx == -EAGAIN && signal_pending(current)) {
|
|
|
- XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTNOSTATES);
|
|
|
- err = -ERESTART;
|
|
|
- goto error;
|
|
|
- }
|
|
|
- if (nx == -EAGAIN ||
|
|
|
- genid != atomic_read(&flow_cache_genid)) {
|
|
|
- xfrm_pols_put(pols, npols);
|
|
|
- goto restart;
|
|
|
- }
|
|
|
- err = nx;
|
|
|
- }
|
|
|
- if (err < 0) {
|
|
|
- XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTNOSTATES);
|
|
|
- goto error;
|
|
|
- }
|
|
|
- }
|
|
|
- if (nx == 0) {
|
|
|
- /* Flow passes not transformed. */
|
|
|
- xfrm_pols_put(pols, npols);
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- dst = xfrm_bundle_create(policy, xfrm, nx, fl, dst_orig);
|
|
|
- err = PTR_ERR(dst);
|
|
|
- if (IS_ERR(dst)) {
|
|
|
- XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTBUNDLEGENERROR);
|
|
|
- goto error;
|
|
|
- }
|
|
|
-
|
|
|
- for (pi = 0; pi < npols; pi++)
|
|
|
- pol_dead |= pols[pi]->walk.dead;
|
|
|
-
|
|
|
- write_lock_bh(&policy->lock);
|
|
|
- if (unlikely(pol_dead || stale_bundle(dst))) {
|
|
|
- /* Wow! While we worked on resolving, this
|
|
|
- * policy has gone. Retry. It is not paranoia,
|
|
|
- * we just cannot enlist new bundle to dead object.
|
|
|
- * We can't enlist stable bundles either.
|
|
|
- */
|
|
|
- write_unlock_bh(&policy->lock);
|
|
|
- dst_free(dst);
|
|
|
-
|
|
|
- if (pol_dead)
|
|
|
- XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTPOLDEAD);
|
|
|
- else
|
|
|
- XFRM_INC_STATS(net, 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);
|
|
|
- dst_free(dst);
|
|
|
- XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
|
|
|
- goto error;
|
|
|
- }
|
|
|
-
|
|
|
- dst->next = policy->bundles;
|
|
|
- policy->bundles = dst;
|
|
|
- dst_hold(dst);
|
|
|
- write_unlock_bh(&policy->lock);
|
|
|
+ } else if (num_xfrms > 0) {
|
|
|
+ /* Flow transformed */
|
|
|
+ *dst_p = dst;
|
|
|
+ dst_release(dst_orig);
|
|
|
+ } else {
|
|
|
+ /* Flow passes untransformed */
|
|
|
+ dst_release(dst);
|
|
|
}
|
|
|
- *dst_p = dst;
|
|
|
- dst_release(dst_orig);
|
|
|
- xfrm_pols_put(pols, npols);
|
|
|
+ok:
|
|
|
+ xfrm_pols_put(pols, drop_pols);
|
|
|
return 0;
|
|
|
|
|
|
+nopol:
|
|
|
+ if (!(flags & XFRM_LOOKUP_ICMP))
|
|
|
+ goto ok;
|
|
|
+ err = -ENOENT;
|
|
|
error:
|
|
|
- xfrm_pols_put(pols, npols);
|
|
|
+ dst_release(dst);
|
|
|
dropdst:
|
|
|
dst_release(dst_orig);
|
|
|
*dst_p = NULL;
|
|
|
+ xfrm_pols_put(pols, drop_pols);
|
|
|
return err;
|
|
|
-
|
|
|
-nopol:
|
|
|
- err = -ENOENT;
|
|
|
- if (flags & XFRM_LOOKUP_ICMP)
|
|
|
- goto dropdst;
|
|
|
- return 0;
|
|
|
}
|
|
|
EXPORT_SYMBOL(__xfrm_lookup);
|
|
|
|
|
@@ -2161,71 +2251,24 @@ static struct dst_entry *xfrm_negative_advice(struct dst_entry *dst)
|
|
|
return dst;
|
|
|
}
|
|
|
|
|
|
-static void prune_one_bundle(struct xfrm_policy *pol, int (*func)(struct dst_entry *), struct dst_entry **gc_list_p)
|
|
|
-{
|
|
|
- struct dst_entry *dst, **dstp;
|
|
|
-
|
|
|
- write_lock(&pol->lock);
|
|
|
- dstp = &pol->bundles;
|
|
|
- while ((dst=*dstp) != NULL) {
|
|
|
- if (func(dst)) {
|
|
|
- *dstp = dst->next;
|
|
|
- dst->next = *gc_list_p;
|
|
|
- *gc_list_p = dst;
|
|
|
- } else {
|
|
|
- dstp = &dst->next;
|
|
|
- }
|
|
|
- }
|
|
|
- write_unlock(&pol->lock);
|
|
|
-}
|
|
|
-
|
|
|
-static void xfrm_prune_bundles(struct net *net, int (*func)(struct dst_entry *))
|
|
|
+static void __xfrm_garbage_collect(struct net *net)
|
|
|
{
|
|
|
- struct dst_entry *gc_list = NULL;
|
|
|
- int dir;
|
|
|
+ struct dst_entry *head, *next;
|
|
|
|
|
|
- read_lock_bh(&xfrm_policy_lock);
|
|
|
- for (dir = 0; dir < XFRM_POLICY_MAX * 2; dir++) {
|
|
|
- struct xfrm_policy *pol;
|
|
|
- struct hlist_node *entry;
|
|
|
- struct hlist_head *table;
|
|
|
- int i;
|
|
|
+ flow_cache_flush();
|
|
|
|
|
|
- hlist_for_each_entry(pol, entry,
|
|
|
- &net->xfrm.policy_inexact[dir], bydst)
|
|
|
- prune_one_bundle(pol, func, &gc_list);
|
|
|
+ spin_lock_bh(&xfrm_policy_sk_bundle_lock);
|
|
|
+ head = xfrm_policy_sk_bundles;
|
|
|
+ xfrm_policy_sk_bundles = NULL;
|
|
|
+ spin_unlock_bh(&xfrm_policy_sk_bundle_lock);
|
|
|
|
|
|
- table = net->xfrm.policy_bydst[dir].table;
|
|
|
- for (i = net->xfrm.policy_bydst[dir].hmask; i >= 0; i--) {
|
|
|
- hlist_for_each_entry(pol, entry, table + i, bydst)
|
|
|
- prune_one_bundle(pol, func, &gc_list);
|
|
|
- }
|
|
|
- }
|
|
|
- read_unlock_bh(&xfrm_policy_lock);
|
|
|
-
|
|
|
- while (gc_list) {
|
|
|
- struct dst_entry *dst = gc_list;
|
|
|
- gc_list = dst->next;
|
|
|
- dst_free(dst);
|
|
|
+ while (head) {
|
|
|
+ next = head->next;
|
|
|
+ dst_free(head);
|
|
|
+ head = next;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static int unused_bundle(struct dst_entry *dst)
|
|
|
-{
|
|
|
- return !atomic_read(&dst->__refcnt);
|
|
|
-}
|
|
|
-
|
|
|
-static void __xfrm_garbage_collect(struct net *net)
|
|
|
-{
|
|
|
- xfrm_prune_bundles(net, unused_bundle);
|
|
|
-}
|
|
|
-
|
|
|
-static int xfrm_flush_bundles(struct net *net)
|
|
|
-{
|
|
|
- xfrm_prune_bundles(net, stale_bundle);
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
static void xfrm_init_pmtu(struct dst_entry *dst)
|
|
|
{
|
|
|
do {
|
|
@@ -2283,7 +2326,9 @@ int xfrm_bundle_ok(struct xfrm_policy *pol, struct xfrm_dst *first,
|
|
|
return 0;
|
|
|
if (dst->xfrm->km.state != XFRM_STATE_VALID)
|
|
|
return 0;
|
|
|
- if (xdst->genid != dst->xfrm->genid)
|
|
|
+ if (xdst->xfrm_genid != dst->xfrm->genid)
|
|
|
+ return 0;
|
|
|
+ if (xdst->policy_genid != atomic_read(&xdst->pols[0]->genid))
|
|
|
return 0;
|
|
|
|
|
|
if (strict && fl &&
|
|
@@ -2448,7 +2493,7 @@ static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void
|
|
|
|
|
|
switch (event) {
|
|
|
case NETDEV_DOWN:
|
|
|
- xfrm_flush_bundles(dev_net(dev));
|
|
|
+ __xfrm_garbage_collect(dev_net(dev));
|
|
|
}
|
|
|
return NOTIFY_DONE;
|
|
|
}
|
|
@@ -2780,7 +2825,6 @@ 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);
|
|
@@ -2805,10 +2849,7 @@ static int xfrm_policy_migrate(struct xfrm_policy *pol,
|
|
|
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);
|
|
|
- }
|
|
|
+ atomic_inc(&pol->genid);
|
|
|
}
|
|
|
}
|
|
|
|