Signed-off-by: Shinta Sugimoto <[EMAIL PROTECTED]>

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index e476541..eb61a99 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -12,6 +12,7 @@ #include <linux/pfkeyv2.h>
 #include <linux/ipsec.h>
 #include <linux/in6.h>
 #include <linux/mutex.h>
+#include <linux/ipsec.h>
 
 #include <net/sock.h>
 #include <net/dst.h>
@@ -29,6 +30,8 @@ extern u32 sysctl_xfrm_aevent_rseqth;
 
 extern struct mutex xfrm_cfg_mutex;
 
+struct xfrm_migrate;
+
 /* Organization of SPD aka "XFRM rules"
    ------------------------------------
 
@@ -385,6 +388,7 @@ struct xfrm_mgr
        int                     (*new_mapping)(struct xfrm_state *x, 
xfrm_address_t *ipaddr, __be16 sport);
        int                     (*notify_policy)(struct xfrm_policy *x, int 
dir, struct km_event *c);
        int                     (*report)(u8 proto, struct xfrm_selector *sel, 
xfrm_address_t *addr);
+       int                     (*migrate)(struct xfrm_selector *sel, u8 dir, 
u8 type, struct xfrm_migrate *m, int num_bundles);
 };
 
 extern int xfrm_register_km(struct xfrm_mgr *km);
@@ -985,6 +989,16 @@ extern int xfrm_bundle_ok(struct xfrm_po
                          struct flowi *fl, int family, int strict);
 extern void xfrm_init_pmtu(struct dst_entry *dst);
 
+#ifdef CONFIG_XFRM_MIGRATE
+extern int km_migrate(struct xfrm_selector *sel, u8 dir, u8 type,
+                     struct xfrm_migrate *m, int num_bundles);
+extern struct xfrm_state * xfrm_migrate_state_find(struct xfrm_migrate *m);
+extern struct xfrm_state * xfrm_state_migrate(struct xfrm_state *x,
+                                             struct xfrm_migrate *m);
+extern int xfrm_migrate(struct xfrm_selector *sel, u8 dir, u8 type,
+                       struct xfrm_migrate *m, int num_bundles);
+#endif
+
 extern wait_queue_head_t km_waitq;
 extern int km_new_mapping(struct xfrm_state *x, xfrm_address_t *ipaddr, __be16 
sport);
 extern void km_policy_expired(struct xfrm_policy *pol, int dir, int hard, u32 
pid);
@@ -1050,5 +1064,64 @@ static inline void xfrm_aevent_doreplay(
                xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
 }
 
+struct xfrm_state_endpoint {
+       u16 family;
+       xfrm_address_t saddr;
+       xfrm_address_t daddr;
+};
+
+struct xfrm_migrate {
+       u8 old_family;
+       u8 new_family;
+       u8 proto;
+       u8 mode;
+       u32 reqid;
+       xfrm_address_t old_daddr;
+       xfrm_address_t old_saddr;
+       xfrm_address_t new_daddr;
+       xfrm_address_t new_saddr;
+};
+
+#ifdef CONFIG_XFRM_MIGRATE
+static inline int __xfrm_addr_any(const xfrm_address_t *a,
+                                 unsigned short family)
+{
+       switch (family) {
+       case AF_INET:
+               return (a->a4 == 0);
+       case AF_INET6:
+               return ipv6_addr_any((const struct in6_addr *)a->a6);
+       }
+       return -1;
+}
+
+static inline int xfrm_migrate_addr_check(struct xfrm_migrate *m) {
+       if ((xfrm_addr_cmp(&m->old_daddr, &m->new_daddr,
+                          m->old_family) == 0) &&
+           (xfrm_addr_cmp(&m->old_saddr, &m->new_saddr,
+                          m->old_family) == 0))
+               return -1;
+
+       if (__xfrm_addr_any(&m->new_daddr, m->new_family) ||
+           __xfrm_addr_any(&m->new_saddr, m->new_family))
+               return -1;
+
+        return 0;
+}
+
+static inline void xfrm_states_put(struct xfrm_state **states, int n)
+{
+       int i;
+       for (i=0; i<n; i++)
+               xfrm_state_put(*(states+i));
+}
+
+static inline void xfrm_states_delete(struct xfrm_state **states, int n)
+{
+       int i;
+       for (i=0; i<n; i++)
+               xfrm_state_delete(*(states+i));
+}
+#endif
 
 #endif /* _NET_XFRM_H */
diff --git a/include/linux/xfrm.h b/include/linux/xfrm.h
index 9529ea1..914654a 100644
--- a/include/linux/xfrm.h
+++ b/include/linux/xfrm.h
@@ -178,6 +178,9 @@ #define XFRM_MSG_GETAE XFRM_MSG_GETAE
        XFRM_MSG_REPORT,
 #define XFRM_MSG_REPORT XFRM_MSG_REPORT
 
+       XFRM_MSG_MIGRATE,
+#define XFRM_MSG_MIGRATE XFRM_MSG_MIGRATE
+
        __XFRM_MSG_MAX
 };
 #define XFRM_MSG_MAX (__XFRM_MSG_MAX - 1)
@@ -256,6 +259,7 @@ enum xfrm_attr_type_t {
        XFRMA_COADDR,           /* xfrm_address_t */
        XFRMA_LASTUSED,
        XFRMA_POLICY_TYPE,      /* struct xfrm_userpolicy_type */
+       XFRMA_MIGRATE,
        __XFRMA_MAX
 
 #define XFRMA_MAX (__XFRMA_MAX - 1)
@@ -351,6 +355,18 @@ struct xfrm_user_report {
        struct xfrm_selector            sel;
 };
 
+struct xfrm_user_migrate {
+       __u8            old_family;
+       __u8            new_family;
+       __u8            proto;
+       __u8            mode;
+       __u32           reqid;
+       xfrm_address_t  old_daddr;
+       xfrm_address_t  old_saddr;
+       xfrm_address_t  new_daddr;
+       xfrm_address_t  new_saddr;
+};
+
 #ifndef __KERNEL__
 /* backwards compatibility for userspace */
 #define XFRMGRP_ACQUIRE                1
@@ -375,6 +391,8 @@ #define XFRMNLGRP_POLICY    XFRMNLGRP_POLIC
 #define XFRMNLGRP_AEVENTS      XFRMNLGRP_AEVENTS
        XFRMNLGRP_REPORT,
 #define XFRMNLGRP_REPORT       XFRMNLGRP_REPORT
+       XFRMNLGRP_MIGRATE,
+#define XFRMNLGRP_MIGRATE      XFRMNLGRP_MIGRATE
        __XFRMNLGRP_MAX
 };
 #define XFRMNLGRP_MAX  (__XFRMNLGRP_MAX - 1)
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index b7e537f..f99e5d3 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -2236,3 +2236,213 @@ void __init xfrm_init(void)
        xfrm_input_init();
 }
 
+#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) {
+                       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 &&
+           (m->reqid == 0 || t->reqid == m->reqid) &&
+           (m->proto == IPSEC_PROTO_ANY || t->id.proto == m->proto)) {
+               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 -EFAULT;
+       }
+
+       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)
+                               continue;
+                       /* update endpoints */
+                       memcpy(&pol->xfrm_vec[i].id.daddr, &mp->new_daddr,
+                              sizeof(xfrm_address_t));
+                       memcpy(&pol->xfrm_vec[i].saddr, &mp->new_saddr,
+                              sizeof(xfrm_address_t));
+                       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 -EFAULT;
+
+       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 (num_migrate < 0 || num_migrate > XFRM_MAX_DEPTH) {
+               err = -EINVAL;
+               goto donothing;
+       }
+
+       if (xfrm_migrate_addr_check(m) < 0) {
+               err = -EINVAL;
+               goto donothing;
+       }
+
+       /* Stage 1 - find policy */
+       if ((pol = xfrm_migrate_policy_find(sel, dir, type)) == NULL) {
+               err = -ENOENT;
+               goto donothing;
+       }
+
+       /* 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 = -EFAULT;
+                               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;
+donothing:
+       if (pol)
+               xfrm_pol_put(pol);
+
+       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;
+}
+#endif
+
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index fdb08d9..40abc8e 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -831,6 +831,177 @@ out:
 }
 EXPORT_SYMBOL(xfrm_state_add);
 
+#ifdef CONFIG_XFRM_MIGRATE
+static struct xfrm_algo *xfrm_algo_solidclone(struct xfrm_algo *orig)
+{
+       struct xfrm_algo *a;
+       a = kmalloc(sizeof(*orig) + orig->alg_key_len, GFP_KERNEL);
+       if (a)
+               memcpy(a, orig, sizeof(*orig) + orig->alg_key_len);
+       return a;
+}
+
+struct xfrm_state *xfrm_state_clone(struct xfrm_state *orig, int *errp)
+{
+       int err = -ENOMEM;
+       struct xfrm_state *x = xfrm_state_alloc();
+       if (!x)
+               goto error;
+
+       memcpy(&x->id, &orig->id, sizeof(x->id));
+       memcpy(&x->sel, &orig->sel, sizeof(x->sel));
+       memcpy(&x->lft, &orig->lft, sizeof(x->lft));
+       x->props.mode = orig->props.mode;
+       x->props.replay_window = orig->props.replay_window;
+       x->props.reqid = orig->props.reqid;
+       x->props.family = orig->props.family;
+       x->props.saddr = orig->props.saddr;
+
+       if (orig->aalg) {
+               x->aalg = xfrm_algo_solidclone(orig->aalg);
+               if (!x->aalg)
+                       goto error;
+       }
+       x->props.aalgo = orig->props.aalgo; 
+
+       if (orig->ealg) {
+               x->ealg = xfrm_algo_solidclone(orig->ealg);
+               if (!x->ealg)
+                       goto error;
+       }
+       x->props.ealgo = orig->props.ealgo;
+
+       if (orig->calg) {
+               x->calg = xfrm_algo_solidclone(orig->calg);
+               if (!x->calg)
+                       goto error;
+       }
+       x->props.calgo = orig->props.calgo;
+
+        if (orig->encap) {
+               x->encap = kmalloc(sizeof(*x->encap), GFP_KERNEL);
+               if (!x->encap)
+                       goto error;
+               memcpy(x->encap, orig->encap, sizeof(*x->encap));
+       }
+
+       if (orig->coaddr) {
+               x->coaddr = kmalloc(sizeof(*x->coaddr), GFP_KERNEL);
+               if (!x->coaddr)
+                       goto error;
+               memcpy(x->coaddr, orig->coaddr, sizeof(*x->coaddr));
+       }
+
+       err = xfrm_init_state(x);
+       if (err)
+               goto error;
+
+       x->props.flags = orig->props.flags;
+       
+       x->curlft.add_time = orig->curlft.add_time;
+       x->km.state = orig->km.state;
+       x->km.seq = orig->km.seq;
+
+       return x;
+
+ error:
+       if (errp)
+               *errp = err;
+       if (x) {
+               if (x->aalg)
+                       kfree(x->aalg);
+               if (x->ealg)
+                       kfree(x->ealg);
+               if (x->ealg)
+                       kfree(x->ealg);
+               if (x->encap)
+                       kfree(x->encap);
+               if (x->coaddr)
+                       kfree(x->coaddr);
+               kfree(x);
+       }
+       return NULL;
+}
+EXPORT_SYMBOL(xfrm_state_clone);
+
+/* xfrm_state_lock is held */
+struct xfrm_state * xfrm_migrate_state_find(struct xfrm_migrate *m)
+{
+       unsigned int h;
+       struct xfrm_state *x;
+       struct hlist_node *entry;
+
+       if (m->reqid) {
+               h = xfrm_dst_hash(&m->old_daddr, &m->old_saddr,
+                                 m->reqid, m->old_family);
+               hlist_for_each_entry(x, entry, xfrm_state_bydst+h, bydst) {
+                       if (x->props.mode != m->mode ||
+                           x->id.proto != m->proto)
+                               continue;
+                       if (m->reqid && x->props.reqid != m->reqid)
+                               continue;
+                       if (xfrm_addr_cmp(&x->id.daddr, &m->old_daddr,
+                                         m->old_family) ||
+                           xfrm_addr_cmp(&x->props.saddr, &m->old_saddr,
+                                         m->old_family))
+                               continue;
+                       xfrm_state_hold(x);
+                       return x;
+               }
+       } else {
+               h = xfrm_src_hash(&m->old_daddr, &m->old_saddr,
+                                 m->old_family);
+               hlist_for_each_entry(x, entry, xfrm_state_bysrc+h, bysrc) {
+                       if (x->props.mode != m->mode ||
+                           x->id.proto != m->proto)
+                               continue;
+                       if (xfrm_addr_cmp(&x->id.daddr, &m->old_daddr,
+                                         m->old_family) ||
+                           xfrm_addr_cmp(&x->props.saddr, &m->old_saddr,
+                                         m->old_family))
+                               continue;
+                       xfrm_state_hold(x);
+                       return x;
+               }
+       }
+
+        return NULL;
+}
+EXPORT_SYMBOL(xfrm_migrate_state_find);
+
+struct xfrm_state * xfrm_state_migrate(struct xfrm_state *x,
+                                      struct xfrm_migrate *m)
+{
+       struct xfrm_state *xc;  /* clone */
+       int err;
+
+       xc = xfrm_state_clone(x, &err);
+       if (!xc)
+               return NULL;
+
+       memcpy(&xc->id.daddr, &m->new_daddr, sizeof(xfrm_address_t));
+       memcpy(&xc->props.saddr, &m->new_saddr, sizeof(xfrm_address_t));
+
+       /* add state */
+       if (!xfrm_addr_cmp(&x->id.daddr, &m->new_daddr,
+                          m->new_family)) {
+               /* a care is needed when destination address of the
+                  state which is part of triplet is to be updated */
+               xfrm_state_insert(xc);
+       } else {
+               if ((err = xfrm_state_add(xc)) < 0)
+                       goto error;
+       }
+       
+       return xc;
+error:
+       if (xc)
+               kfree(xc);
+       return NULL;
+}
+EXPORT_SYMBOL(xfrm_state_migrate);
+#endif
+
 int xfrm_state_update(struct xfrm_state *x)
 {
        struct xfrm_state *x1;
@@ -1345,6 +1516,26 @@ void km_policy_expired(struct xfrm_polic
 }
 EXPORT_SYMBOL(km_policy_expired);
 
+int km_migrate(struct xfrm_selector *sel, u8 dir, u8 type,
+              struct xfrm_migrate *m, int num_migrate)
+{
+       int err = -EINVAL;
+       int ret;
+       struct xfrm_mgr *km;
+
+       read_lock(&xfrm_km_lock);
+       list_for_each_entry(km, &xfrm_km_list, list) {
+               if (km->migrate) {
+                       ret = km->migrate(sel, dir, type, m, num_migrate);
+                       if (!ret)
+                               err = ret;
+               }
+       }
+       read_unlock(&xfrm_km_lock);
+       return err;
+}
+EXPORT_SYMBOL(km_migrate);
+
 int km_report(u8 proto, struct xfrm_selector *sel, xfrm_address_t *addr)
 {
        int err = -EINVAL;

-
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to