* [PATCH ipsec-next v2 0/4] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message
@ 2026-01-15 8:03 Antony Antony
2026-01-15 8:04 ` [PATCH ipsec-next v2 1/4] xfrm: remove redundant assignments Antony Antony
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Antony Antony @ 2026-01-15 8:03 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Chiachang Wang, Yan Yan, Shinta Sugimoto, devel
SA migration, and it lacks the information required to reliably migrate
individual SAs. This makes it unsuitable for IKEv2 deployments,
dual-stack setups (IPv4/IPv6), and scenarios where policies are managed
externally (e.g., by other daemons than IKE daemon).
Mandatory SA selector list
The current API requires a non-empty SA selector list, which does not
reflect IKEv2 use case.
A single Child SA may correspond to multiple policies,
and SA discovery already occurs via address and reqid matching. With
dual-stack Child SAs this leads to excessive churn: the current method
would have to be called up to six times (in/out/fwd × v4/v6) on SA,
while the new method only requires two calls. While polices are
migrated, first installing a block policy
Selectors lack SPI (and marks)
XFRM_MSG_MIGRATE cannot uniquely identify an SA when multiple SAs share
the same policies (per-CPU SAs, SELinux label-based SAs, etc.). Without
the SPI, the kernel may update the wrong SA instance.
Reqid cannot be changed
Some implementations allocate reqids based on traffic selectors. In
host-to-host or selector-changing scenarios, the reqid must change,
which the current API cannot express.
Because strongSwan and other implementations manage policies
independently of the kernel, an interface that updates only a specific
SA — with complete and unambiguous identification — is required.
XFRM_MSG_MIGRATE_STATE provides that interface. It supports migration
of a single SA via xfrm_usersa_id (including SPI) and we fix
encap removal in this patch set, reqid updates, address changes,
and other SA-specific parameters. It avoids the structural limitations
of XFRM_MSG_MIGRATE and provides a simpler, extensible mechanism for
precise per-SA migration without involving policies.
Antony Antony (4):
xfrm: remove redundant assignments
xfrm: allow migration from UDP encapsulated to non-encapsulated ESP
xfrm: rename reqid in xfrm_migrate
xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
include/net/xfrm.h | 3 +-
include/uapi/linux/xfrm.h | 11 +++
net/key/af_key.c | 10 +--
net/xfrm/xfrm_policy.c | 4 +-
net/xfrm/xfrm_state.c | 37 ++++----
net/xfrm/xfrm_user.c | 164 +++++++++++++++++++++++++++++++++++-
security/selinux/nlmsgtab.c | 3 +-
7 files changed, 200 insertions(+), 32 deletions(-)
--
2.39.5
---
v1->v2: dropped 6/6. That check is already there where the func is called
- merged patch 4/6 and 5/6, to fix use uninitialized value
- fix commit messages
thanks,
-antony
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH ipsec-next v2 1/4] xfrm: remove redundant assignments
2026-01-15 8:03 [PATCH ipsec-next v2 0/4] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
@ 2026-01-15 8:04 ` Antony Antony
2026-01-15 8:04 ` [PATCH ipsec-next v2 2/4] xfrm: allow migration from UDP encapsulated to non-encapsulated ESP Antony Antony
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Antony Antony @ 2026-01-15 8:04 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Chiachang Wang, Yan Yan, Shinta Sugimoto, devel, Simon Horman,
linux-kernel
This assignments are overwritten within the same function further down
commit e8961c50ee9cc ("xfrm: Refactor migration setup
during the cloning process")
x->props.family = m->new_family;
Which actually moved it in the
commit e03c3bba351f9 ("xfrm: Fix xfrm migrate issues
when address family changes")
And the initial
commit 80c9abaabf428 ("[XFRM]: Extension for dynamic
update of endpoint address(es)")
added x->props.saddr = orig->props.saddr; and
memcpy(&xc->props.saddr, &m->new_saddr, sizeof(xc->props.saddr));
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v1->v2: remove extra saddr copy, previous line
---
net/xfrm/xfrm_state.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 9e14e453b55c..4fd73a970a7a 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1980,8 +1980,6 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
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_auth_clone(orig->aalg);
--
2.39.5
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH ipsec-next v2 2/4] xfrm: allow migration from UDP encapsulated to non-encapsulated ESP
2026-01-15 8:03 [PATCH ipsec-next v2 0/4] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
2026-01-15 8:04 ` [PATCH ipsec-next v2 1/4] xfrm: remove redundant assignments Antony Antony
@ 2026-01-15 8:04 ` Antony Antony
2026-01-15 8:05 ` [PATCH ipsec-next v2 3/4] xfrm: rename reqid in xfrm_migrate Antony Antony
2026-01-15 8:05 ` [PATCH ipsec-next v2 4/4] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration Antony Antony
3 siblings, 0 replies; 6+ messages in thread
From: Antony Antony @ 2026-01-15 8:04 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Chiachang Wang, Yan Yan, Shinta Sugimoto, devel, Simon Horman,
linux-kernel
The current code prevents migrating an SA from UDP encapsulation to
plain ESP. This is needed when moving from a NATed path to a non-NATed
one, for example when switching from IPv4+NAT to IPv6.
Only copy the existing encapsulation during migration if the encap
attribute is explicitly provided.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
net/xfrm/xfrm_state.c | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 4fd73a970a7a..f5f699f5f98e 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2008,14 +2008,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
}
x->props.calgo = orig->props.calgo;
- if (encap || orig->encap) {
- if (encap)
- x->encap = kmemdup(encap, sizeof(*x->encap),
- GFP_KERNEL);
- else
- x->encap = kmemdup(orig->encap, sizeof(*x->encap),
- GFP_KERNEL);
-
+ if (encap) {
+ x->encap = kmemdup(encap, sizeof(*x->encap), GFP_KERNEL);
if (!x->encap)
goto error;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH ipsec-next v2 3/4] xfrm: rename reqid in xfrm_migrate
2026-01-15 8:03 [PATCH ipsec-next v2 0/4] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
2026-01-15 8:04 ` [PATCH ipsec-next v2 1/4] xfrm: remove redundant assignments Antony Antony
2026-01-15 8:04 ` [PATCH ipsec-next v2 2/4] xfrm: allow migration from UDP encapsulated to non-encapsulated ESP Antony Antony
@ 2026-01-15 8:05 ` Antony Antony
2026-01-15 8:05 ` [PATCH ipsec-next v2 4/4] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration Antony Antony
3 siblings, 0 replies; 6+ messages in thread
From: Antony Antony @ 2026-01-15 8:05 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Chiachang Wang, Yan Yan, Shinta Sugimoto, devel, Simon Horman,
linux-kernel
In preparation for the following patch rename s/reqid/old_reqid/.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
include/net/xfrm.h | 2 +-
net/key/af_key.c | 10 +++++-----
net/xfrm/xfrm_policy.c | 4 ++--
net/xfrm/xfrm_state.c | 6 +++---
net/xfrm/xfrm_user.c | 4 ++--
5 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 0a14daaa5dd4..05fa0552523d 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -685,7 +685,7 @@ struct xfrm_migrate {
u8 proto;
u8 mode;
u16 reserved;
- u32 reqid;
+ u32 old_reqid;
u16 old_family;
u16 new_family;
};
diff --git a/net/key/af_key.c b/net/key/af_key.c
index 571200433aa9..a856bdd0c0d7 100644
--- a/net/key/af_key.c
+++ b/net/key/af_key.c
@@ -2538,7 +2538,7 @@ static int ipsecrequests_to_migrate(struct sadb_x_ipsecrequest *rq1, int len,
if ((mode = pfkey_mode_to_xfrm(rq1->sadb_x_ipsecrequest_mode)) < 0)
return -EINVAL;
m->mode = mode;
- m->reqid = rq1->sadb_x_ipsecrequest_reqid;
+ m->old_reqid = rq1->sadb_x_ipsecrequest_reqid;
return ((int)(rq1->sadb_x_ipsecrequest_len +
rq2->sadb_x_ipsecrequest_len));
@@ -3634,15 +3634,15 @@ static int pfkey_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
if (mode < 0)
goto err;
if (set_ipsecrequest(skb, mp->proto, mode,
- (mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
- mp->reqid, mp->old_family,
+ (mp->old_reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
+ mp->old_reqid, mp->old_family,
&mp->old_saddr, &mp->old_daddr) < 0)
goto err;
/* new ipsecrequest */
if (set_ipsecrequest(skb, mp->proto, mode,
- (mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
- mp->reqid, mp->new_family,
+ (mp->old_reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
+ mp->old_reqid, mp->new_family,
&mp->new_saddr, &mp->new_daddr) < 0)
goto err;
}
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 62486f866975..854dfc16ed55 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4530,7 +4530,7 @@ static int migrate_tmpl_match(const struct xfrm_migrate *m, const struct xfrm_tm
int match = 0;
if (t->mode == m->mode && t->id.proto == m->proto &&
- (m->reqid == 0 || t->reqid == m->reqid)) {
+ (m->old_reqid == 0 || t->reqid == m->old_reqid)) {
switch (t->mode) {
case XFRM_MODE_TUNNEL:
case XFRM_MODE_BEET:
@@ -4624,7 +4624,7 @@ static int xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate,
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_reqid == m[j].old_reqid &&
m[i].old_family == m[j].old_family) {
NL_SET_ERR_MSG(extack, "Entries in the MIGRATE attribute's list must be unique");
return -EINVAL;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index f5f699f5f98e..fe595d7f4398 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2080,14 +2080,14 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n
spin_lock_bh(&net->xfrm.xfrm_state_lock);
- if (m->reqid) {
+ if (m->old_reqid) {
h = xfrm_dst_hash(net, &m->old_daddr, &m->old_saddr,
- m->reqid, m->old_family);
+ m->old_reqid, m->old_family);
hlist_for_each_entry(x, net->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)
+ if (m->old_reqid && x->props.reqid != m->old_reqid)
continue;
if (if_id != 0 && x->if_id != if_id)
continue;
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 403b5ecac2c5..26b82d94acc1 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3087,7 +3087,7 @@ static int copy_from_user_migrate(struct xfrm_migrate *ma,
ma->proto = um->proto;
ma->mode = um->mode;
- ma->reqid = um->reqid;
+ ma->old_reqid = um->reqid;
ma->old_family = um->old_family;
ma->new_family = um->new_family;
@@ -3170,7 +3170,7 @@ static int copy_to_user_migrate(const struct xfrm_migrate *m, struct sk_buff *sk
memset(&um, 0, sizeof(um));
um.proto = m->proto;
um.mode = m->mode;
- um.reqid = m->reqid;
+ um.reqid = m->old_reqid;
um.old_family = m->old_family;
memcpy(&um.old_daddr, &m->old_daddr, sizeof(um.old_daddr));
memcpy(&um.old_saddr, &m->old_saddr, sizeof(um.old_saddr));
--
2.39.5
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH ipsec-next v2 4/4] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
2026-01-15 8:03 [PATCH ipsec-next v2 0/4] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (2 preceding siblings ...)
2026-01-15 8:05 ` [PATCH ipsec-next v2 3/4] xfrm: rename reqid in xfrm_migrate Antony Antony
@ 2026-01-15 8:05 ` Antony Antony
3 siblings, 0 replies; 6+ messages in thread
From: Antony Antony @ 2026-01-15 8:05 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Chiachang Wang, Yan Yan, Shinta Sugimoto, devel, Simon Horman,
Paul Moore, Stephen Smalley, Ondrej Mosnacek, linux-kernel,
selinux
Add a new netlink method to migrate a single xfrm_state.
Unlike the existing migration mechanism (SA + policy), this
supports migrating only the SA and allows changing the reqid.
The reqid is invarient in old migration.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v1->v2: merged next patch here to fix use uninitialized value
- removed unnecessary inline
- added const when possible
---
include/net/xfrm.h | 1 +
include/uapi/linux/xfrm.h | 11 +++
net/xfrm/xfrm_state.c | 19 +++--
net/xfrm/xfrm_user.c | 160 ++++++++++++++++++++++++++++++++++++
security/selinux/nlmsgtab.c | 3 +-
5 files changed, 185 insertions(+), 9 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 05fa0552523d..4147c5ba6093 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -686,6 +686,7 @@ struct xfrm_migrate {
u8 mode;
u16 reserved;
u32 old_reqid;
+ u32 new_reqid;
u16 old_family;
u16 new_family;
};
diff --git a/include/uapi/linux/xfrm.h b/include/uapi/linux/xfrm.h
index a23495c0e0a1..60b1f201b237 100644
--- a/include/uapi/linux/xfrm.h
+++ b/include/uapi/linux/xfrm.h
@@ -227,6 +227,9 @@ enum {
#define XFRM_MSG_SETDEFAULT XFRM_MSG_SETDEFAULT
XFRM_MSG_GETDEFAULT,
#define XFRM_MSG_GETDEFAULT XFRM_MSG_GETDEFAULT
+
+ XFRM_MSG_MIGRATE_STATE,
+#define XFRM_MSG_MIGRATE_STATE XFRM_MSG_MIGRATE_STATE
__XFRM_MSG_MAX
};
#define XFRM_MSG_MAX (__XFRM_MSG_MAX - 1)
@@ -507,6 +510,14 @@ struct xfrm_user_migrate {
__u16 new_family;
};
+struct xfrm_user_migrate_state {
+ struct xfrm_usersa_id id;
+ xfrm_address_t new_saddr;
+ xfrm_address_t new_daddr;
+ __u16 new_family;
+ __u32 new_reqid;
+};
+
struct xfrm_user_mapping {
struct xfrm_usersa_id id;
__u32 reqid;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index fe595d7f4398..f35630dea00f 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1966,8 +1966,9 @@ static inline int clone_security(struct xfrm_state *x, struct xfrm_sec_ctx *secu
}
static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
- struct xfrm_encap_tmpl *encap,
- struct xfrm_migrate *m)
+ const struct xfrm_encap_tmpl *encap,
+ const struct xfrm_migrate *m,
+ struct netlink_ext_ack *extack)
{
struct net *net = xs_net(orig);
struct xfrm_state *x = xfrm_state_alloc(net);
@@ -1979,7 +1980,6 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
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;
if (orig->aalg) {
x->aalg = xfrm_algo_auth_clone(orig->aalg);
@@ -2058,7 +2058,7 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
goto error;
}
-
+ x->props.reqid = m->new_reqid;
x->props.family = m->new_family;
memcpy(&x->id.daddr, &m->new_daddr, sizeof(x->id.daddr));
memcpy(&x->props.saddr, &m->new_saddr, sizeof(x->props.saddr));
@@ -2133,7 +2133,7 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
{
struct xfrm_state *xc;
- xc = xfrm_state_clone_and_setup(x, encap, m);
+ xc = xfrm_state_clone_and_setup(x, encap, m, extack);
if (!xc)
return NULL;
@@ -2145,9 +2145,12 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
goto error;
/* add state */
- if (xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
- /* a care is needed when the destination address of the
- state is to be updated as it is a part of triplet */
+ if (xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family) ||
+ x->props.reqid != xc->props.reqid) {
+ /*
+ * a care is needed when the destination address or the reqid
+ * of the state is to be updated as it is a part of triplet
+ */
xfrm_state_insert(xc);
} else {
if (xfrm_state_add(xc) < 0)
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 26b82d94acc1..298d7c1454e5 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3052,6 +3052,22 @@ static int xfrm_add_acquire(struct sk_buff *skb, struct nlmsghdr *nlh,
}
#ifdef CONFIG_XFRM_MIGRATE
+static int copy_from_user_migrate_state(struct xfrm_migrate *ma,
+ const struct xfrm_user_migrate_state *um)
+{
+ memcpy(&ma->old_daddr, &um->id.daddr, sizeof(ma->old_daddr));
+ memcpy(&ma->new_daddr, &um->new_daddr, sizeof(ma->new_daddr));
+ memcpy(&ma->new_saddr, &um->new_saddr, sizeof(ma->new_saddr));
+
+ ma->proto = um->id.proto;
+ ma->new_reqid = um->new_reqid;
+
+ ma->old_family = um->id.family;
+ ma->new_family = um->new_family;
+
+ return 0;
+}
+
static int copy_from_user_migrate(struct xfrm_migrate *ma,
struct xfrm_kmaddress *k,
struct nlattr **attrs, int *num,
@@ -3088,6 +3104,7 @@ static int copy_from_user_migrate(struct xfrm_migrate *ma,
ma->proto = um->proto;
ma->mode = um->mode;
ma->old_reqid = um->reqid;
+ ma->new_reqid = um->reqid; /* reqid is invariant in XFRM_MSG_MIGRATE */
ma->old_family = um->old_family;
ma->new_family = um->new_family;
@@ -3154,7 +3171,148 @@ static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
kfree(xuo);
return err;
}
+
+static int build_migrate_state(struct sk_buff *skb,
+ const struct xfrm_user_migrate_state *m,
+ const struct xfrm_encap_tmpl *encap,
+ const struct xfrm_user_offload *xuo)
+{
+ int err;
+ struct nlmsghdr *nlh;
+ struct xfrm_user_migrate_state *um;
+
+ nlh = nlmsg_put(skb, 0, 0, XFRM_MSG_MIGRATE_STATE,
+ sizeof(struct xfrm_user_migrate_state), 0);
+ if (!nlh)
+ return -EMSGSIZE;
+
+ um = nlmsg_data(nlh);
+ memset(um, 0, sizeof(*um));
+ memcpy(um, m, sizeof(*um));
+
+ if (encap) {
+ err = nla_put(skb, XFRMA_ENCAP, sizeof(*encap), encap);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (xuo) {
+ err = nla_put(skb, XFRMA_OFFLOAD_DEV, sizeof(*xuo), xuo);
+ if (err)
+ goto out_cancel;
+ }
+
+ nlmsg_end(skb, nlh);
+ return 0;
+
+out_cancel:
+ nlmsg_cancel(skb, nlh);
+ return err;
+}
+
+static unsigned int xfrm_migrate_state_msgsize(bool with_encap, bool with_xuo)
+{
+ return NLMSG_ALIGN(sizeof(struct xfrm_user_migrate_state)) +
+ (with_encap ? nla_total_size(sizeof(struct xfrm_encap_tmpl)) : 0) +
+ (with_xuo ? nla_total_size(sizeof(struct xfrm_user_offload)) : 0);
+}
+
+static int xfrm_send_migrate_state(const struct xfrm_user_migrate_state *um,
+ const struct xfrm_encap_tmpl *encap,
+ const struct xfrm_user_offload *xuo)
+{
+ int err;
+ struct sk_buff *skb;
+ struct net *net = &init_net;
+
+ skb = nlmsg_new(xfrm_migrate_state_msgsize(!!encap, !!xuo), GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ err = build_migrate_state(skb, um, encap, xuo);
+ if (err < 0) {
+ WARN_ON(1);
+ return err;
+ }
+
+ return xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE);
+}
+
+static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
+ struct nlattr **attrs, struct netlink_ext_ack *extack)
+{
+ int err = -ESRCH;
+ struct xfrm_state *x;
+ struct net *net = sock_net(skb->sk);
+ struct xfrm_encap_tmpl *encap = NULL;
+ struct xfrm_user_offload *xuo = NULL;
+ struct xfrm_migrate m = { .old_saddr.a4 = 0,};
+ struct xfrm_user_migrate_state *um = nlmsg_data(nlh);
+
+ if (!um->id.spi) {
+ NL_SET_ERR_MSG(extack, "Invalid SPI 0x0");
+ return -EINVAL;
+ }
+
+ err = copy_from_user_migrate_state(&m, um);
+ if (err)
+ return err;
+
+ x = xfrm_user_state_lookup(net, &um->id, attrs, &err);
+
+ if (x) {
+ struct xfrm_state *xc;
+
+ if (!x->dir) {
+ NL_SET_ERR_MSG(extack, "State direction is invalid");
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (attrs[XFRMA_ENCAP]) {
+ encap = kmemdup(nla_data(attrs[XFRMA_ENCAP]),
+ sizeof(*encap), GFP_KERNEL);
+ if (!encap) {
+ err = -ENOMEM;
+ goto error;
+ }
+ }
+ if (attrs[XFRMA_OFFLOAD_DEV]) {
+ xuo = kmemdup(nla_data(attrs[XFRMA_OFFLOAD_DEV]),
+ sizeof(*xuo), GFP_KERNEL);
+ if (!xuo) {
+ err = -ENOMEM;
+ goto error;
+ }
+ }
+ xc = xfrm_state_migrate(x, &m, encap, net, xuo, extack);
+ if (xc) {
+ xfrm_state_delete(x);
+ xfrm_send_migrate_state(um, encap, xuo);
+ } else {
+ if (extack && !extack->_msg)
+ NL_SET_ERR_MSG(extack, "State migration clone failed");
+ err = -EINVAL;
+ }
+ } else {
+ NL_SET_ERR_MSG(extack, "Can not find state");
+ return err;
+ }
+error:
+ xfrm_state_put(x);
+ kfree(encap);
+ kfree(xuo);
+ return err;
+}
+
#else
+static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
+ struct nlattr **attrs, struct netlink_ext_ack *extack)
+{
+ NL_SET_ERR_MSG(extack, "XFRM_MSG_MIGRATE_STATE is not supported");
+ return -ENOPROTOOPT;
+}
+
static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
struct nlattr **attrs, struct netlink_ext_ack *extack)
{
@@ -3307,6 +3465,7 @@ const int xfrm_msg_min[XFRM_NR_MSGTYPES] = {
[XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = sizeof(u32),
[XFRM_MSG_SETDEFAULT - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_default),
[XFRM_MSG_GETDEFAULT - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_default),
+ [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_migrate_state),
};
EXPORT_SYMBOL_GPL(xfrm_msg_min);
@@ -3400,6 +3559,7 @@ static const struct xfrm_link {
[XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = { .doit = xfrm_get_spdinfo },
[XFRM_MSG_SETDEFAULT - XFRM_MSG_BASE] = { .doit = xfrm_set_default },
[XFRM_MSG_GETDEFAULT - XFRM_MSG_BASE] = { .doit = xfrm_get_default },
+ [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = { .doit = xfrm_do_migrate_state },
};
static int xfrm_reject_unused_attr(int type, struct nlattr **attrs,
diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c
index 2c0b07f9fbbd..655d2616c9d2 100644
--- a/security/selinux/nlmsgtab.c
+++ b/security/selinux/nlmsgtab.c
@@ -128,6 +128,7 @@ static const struct nlmsg_perm nlmsg_xfrm_perms[] = {
{ XFRM_MSG_MAPPING, NETLINK_XFRM_SOCKET__NLMSG_READ },
{ XFRM_MSG_SETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
{ XFRM_MSG_GETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_READ },
+ { XFRM_MSG_MIGRATE_STATE, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
};
static const struct nlmsg_perm nlmsg_audit_perms[] = {
@@ -203,7 +204,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm)
* structures at the top of this file with the new mappings
* before updating the BUILD_BUG_ON() macro!
*/
- BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_GETDEFAULT);
+ BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_MIGRATE_STATE);
if (selinux_policycap_netlink_xperm()) {
*perm = NETLINK_XFRM_SOCKET__NLMSG;
--
2.39.5
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH ipsec-next v2 0/4] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message
@ 2026-01-17 20:04 Antony Antony
0 siblings, 0 replies; 6+ messages in thread
From: Antony Antony @ 2026-01-17 20:04 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Chiachang Wang, Yan Yan, devel
The current XFRM_MSG_MIGRATE interface is tightly coupled to policy and
SA migration, and it lacks the information required to reliably migrate
individual SAs. This makes it unsuitable for IKEv2 deployments,
dual-stack setups (IPv4/IPv6), and scenarios where policies are managed
externally (e.g., by other daemons than IKE daemon).
Mandatory SA selector list
The current API requires a non-empty SA selector list, which does not
reflect IKEv2 use case.
A single Child SA may correspond to multiple policies,
and SA discovery already occurs via address and reqid matching. With
dual-stack Child SAs this leads to excessive churn: the current method
would have to be called up to six times (in/out/fwd × v4/v6) on SA,
while the new method only requires two calls.
Selectors lack SPI (and marks)
XFRM_MSG_MIGRATE cannot uniquely identify an SA when multiple SAs share
the same policies (per-CPU SAs, SELinux label-based SAs, etc.). Without
the SPI, the kernel may update the wrong SA instance.
Reqid cannot be changed
Some implementations allocate reqids based on traffic selectors. In
host-to-host or selector-changing scenarios, the reqid must change,
which the current API cannot express.
Because strongSwan and other implementations manage policies
independently of the kernel, an interface that updates only a specific
SA - with complete and unambiguous identification - is required.
XFRM_MSG_MIGRATE_STATE provides that interface. It supports migration
of a single SA via xfrm_usersa_id (including SPI) and we fix
encap removal in this patch set, reqid updates, address changes,
and other SA-specific parameters. It avoids the structural limitations
of XFRM_MSG_MIGRATE and provides a simpler, extensible mechanism for
precise per-SA migration without involving policies.
New migration steps: first install block policy, remove the old policy,
call XFRM_MSG_MIGRATE_STATE for each state, then re-install the
policies and remove the block policy.
Antony Antony (4):
xfrm: remove redundant assignments
xfrm: allow migration from UDP encapsulated to non-encapsulated ESP
xfrm: rename reqid in xfrm_migrate
xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
include/net/xfrm.h | 3 +-
include/uapi/linux/xfrm.h | 11 +++
net/key/af_key.c | 10 +--
net/xfrm/xfrm_policy.c | 4 +-
net/xfrm/xfrm_state.c | 34 +++-----
net/xfrm/xfrm_user.c | 164 +++++++++++++++++++++++++++++++++++-
security/selinux/nlmsgtab.c | 3 +-
7 files changed, 198 insertions(+), 31 deletions(-)
---
v1->v2: dropped 6/6. That check is already there where the func is called
- merged patch 4/6 and 5/6, to fix use uninitialized value
- fix commit messages
v2->v3: fix commit message
- fixes to error path
---
-antony
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-01-17 20:05 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-15 8:03 [PATCH ipsec-next v2 0/4] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
2026-01-15 8:04 ` [PATCH ipsec-next v2 1/4] xfrm: remove redundant assignments Antony Antony
2026-01-15 8:04 ` [PATCH ipsec-next v2 2/4] xfrm: allow migration from UDP encapsulated to non-encapsulated ESP Antony Antony
2026-01-15 8:05 ` [PATCH ipsec-next v2 3/4] xfrm: rename reqid in xfrm_migrate Antony Antony
2026-01-15 8:05 ` [PATCH ipsec-next v2 4/4] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration Antony Antony
-- strict thread matches above, loose matches on Subject: below --
2026-01-17 20:04 [PATCH ipsec-next v2 0/4] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox