* [PATCH ipsec v2 1/6] xfrm: state: exact mark/mask match for SPI-keyed control-plane SA lookups
2026-09-08 6:48 [PATCH ipsec v2 0/6] xfrm: state: exact mark/mask match for control-plane SA lookups Antony Antony
@ 2026-09-08 6:48 ` Antony Antony
2026-09-08 6:49 ` [PATCH ipsec v2 2/6] xfrm: fix use-after-free of migrated state in xfrm_do_migrate_state() Antony Antony
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Antony Antony @ 2026-09-08 6:48 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc
Add __xfrm_state_lookup_exact(); wire it into DELSA/GETSA, GETAE/NEWAE,
EXPIRE, MIGRATE_STATE. xfrm_state_update() (UPDSA) keeps wildcard match
because ALLOCSPI's larval state has no mark (mask=0), so exact-match UPDSA
can't find it and the ADD fallback trips -EEXIST via xfrm_state_add().
Fixes: 3d6acfa7641f ("xfrm: SA lookups with mark")
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v1->v2: UPDSA keep the wildcard lookup
---
include/net/xfrm.h | 3 +++
net/xfrm/xfrm_state.c | 56 ++++++++++++++++++++++++++++++++++++++++++---------
net/xfrm/xfrm_user.c | 29 +++++++++++++-------------
3 files changed, 64 insertions(+), 24 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index a6d69aaa6cd2..9e053388aa4b 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1748,6 +1748,9 @@ struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark,
const xfrm_address_t *saddr,
u8 proto,
unsigned short family);
+struct xfrm_state *xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,
+ const xfrm_address_t *daddr, __be32 spi,
+ u8 proto, unsigned short family);
#ifdef CONFIG_XFRM_SUB_POLICY
void xfrm_tmpl_sort(struct xfrm_tmpl **dst, struct xfrm_tmpl **src, int n,
unsigned short family);
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index e45aa1ed5b96..b7ea1060dac9 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1177,11 +1177,19 @@ static struct xfrm_state *__xfrm_state_lookup_all(const struct xfrm_hash_state_p
return NULL;
}
-static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs *state_ptrs,
- u32 mark,
- const xfrm_address_t *daddr,
- __be32 spi, u8 proto,
- unsigned short family)
+static bool xfrm_state_mark_matches(const struct xfrm_state *x, u32 mark, u32 mask, bool exact)
+{
+ if (exact)
+ return x->mark.v == (mark & mask) && x->mark.m == mask;
+ return (mark & x->mark.m) == x->mark.v;
+}
+
+static struct xfrm_state *
+__xfrm_state_lookup(const struct xfrm_hash_state_ptrs *state_ptrs,
+ u32 mark, u32 mask, bool exact,
+ const xfrm_address_t *daddr,
+ __be32 spi, u8 proto,
+ unsigned short family)
{
unsigned int h = __xfrm_spi_hash(daddr, spi, proto, family, state_ptrs->hmask);
struct xfrm_state *x;
@@ -1193,7 +1201,7 @@ static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs
!xfrm_addr_equal(&x->id.daddr, daddr, family))
continue;
- if ((mark & x->mark.m) != x->mark.v)
+ if (!xfrm_state_mark_matches(x, mark, mask, exact))
continue;
if (!xfrm_state_hold_rcu(x))
continue;
@@ -1203,6 +1211,17 @@ static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs
return NULL;
}
+static struct xfrm_state *
+__xfrm_state_lookup_exact(const struct xfrm_hash_state_ptrs *state_ptrs,
+ const struct xfrm_mark *mark,
+ const xfrm_address_t *daddr,
+ __be32 spi, u8 proto,
+ unsigned short family)
+{
+ return __xfrm_state_lookup(state_ptrs, mark->v, mark->m, true,
+ daddr, spi, proto, family);
+}
+
struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
const xfrm_address_t *daddr,
__be32 spi, u8 proto,
@@ -1233,7 +1252,7 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
xfrm_hash_ptrs_get(net, &state_ptrs);
- x = __xfrm_state_lookup(&state_ptrs, mark, daddr, spi, proto, family);
+ x = __xfrm_state_lookup(&state_ptrs, mark, 0, false, daddr, spi, proto, family);
if (x) {
spin_lock(&net->xfrm.xfrm_state_lock);
if (x->km.state != XFRM_STATE_VALID) {
@@ -1283,7 +1302,7 @@ static struct xfrm_state *__xfrm_state_lookup_byaddr(const struct xfrm_hash_stat
return NULL;
}
-static inline struct xfrm_state *
+static struct xfrm_state *
__xfrm_state_locate(struct xfrm_state *x, int use_spi, int family)
{
struct xfrm_hash_state_ptrs state_ptrs;
@@ -1293,7 +1312,7 @@ __xfrm_state_locate(struct xfrm_state *x, int use_spi, int family)
xfrm_hash_ptrs_get(net, &state_ptrs);
if (use_spi)
- return __xfrm_state_lookup(&state_ptrs, mark, &x->id.daddr,
+ return __xfrm_state_lookup(&state_ptrs, mark, 0, false, &x->id.daddr,
x->id.spi, x->id.proto, family);
else
return __xfrm_state_lookup_byaddr(&state_ptrs, mark,
@@ -2383,7 +2402,7 @@ xfrm_state_lookup(struct net *net, u32 mark, const xfrm_address_t *daddr, __be32
rcu_read_lock();
xfrm_hash_ptrs_get(net, &state_ptrs);
- x = __xfrm_state_lookup(&state_ptrs, mark, daddr, spi, proto, family);
+ x = __xfrm_state_lookup(&state_ptrs, mark, 0, false, daddr, spi, proto, family);
rcu_read_unlock();
return x;
}
@@ -2407,6 +2426,23 @@ xfrm_state_lookup_byaddr(struct net *net, u32 mark,
}
EXPORT_SYMBOL(xfrm_state_lookup_byaddr);
+struct xfrm_state *
+xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,
+ const xfrm_address_t *daddr, __be32 spi,
+ u8 proto, unsigned short family)
+{
+ struct xfrm_hash_state_ptrs state_ptrs;
+ struct xfrm_state *x;
+
+ rcu_read_lock();
+ xfrm_hash_ptrs_get(net, &state_ptrs);
+
+ x = __xfrm_state_lookup_exact(&state_ptrs, mark, daddr, spi, proto, family);
+ rcu_read_unlock();
+ return x;
+}
+EXPORT_SYMBOL(xfrm_state_lookup_exact);
+
struct xfrm_state *
xfrm_find_acq(struct net *net, const struct xfrm_mark *mark, u8 mode, u32 reqid,
u32 if_id, u32 pcpu_num, u8 proto, const xfrm_address_t *daddr,
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index a2587c7e796b..fffce22389e8 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1089,11 +1089,12 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
struct xfrm_state *x = NULL;
struct xfrm_mark m;
int err;
- u32 mark = xfrm_mark_get(attrs, &m);
+
+ xfrm_mark_get(attrs, &m);
if (xfrm_id_proto_match(p->proto, IPSEC_PROTO_ANY)) {
err = -ESRCH;
- x = xfrm_state_lookup(net, mark, &p->daddr, p->spi, p->proto, p->family);
+ x = xfrm_state_lookup_exact(net, &m, &p->daddr, p->spi, p->proto, p->family);
} else {
xfrm_address_t *saddr = NULL;
@@ -1104,7 +1105,7 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
}
err = -ESRCH;
- x = xfrm_state_lookup_byaddr(net, mark,
+ x = xfrm_state_lookup_byaddr(net, m.v & m.m,
&p->daddr, saddr,
p->proto, p->family);
}
@@ -2795,14 +2796,13 @@ static int xfrm_get_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
struct sk_buff *r_skb;
int err;
struct km_event c;
- u32 mark;
struct xfrm_mark m;
struct xfrm_aevent_id *p = nlmsg_data(nlh);
struct xfrm_usersa_id *id = &p->sa_id;
- mark = xfrm_mark_get(attrs, &m);
+ xfrm_mark_get(attrs, &m);
- x = xfrm_state_lookup(net, mark, &id->daddr, id->spi, id->proto, id->family);
+ x = xfrm_state_lookup_exact(net, &m, &id->daddr, id->spi, id->proto, id->family);
if (x == NULL)
return -ESRCH;
@@ -2843,7 +2843,6 @@ static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
struct xfrm_state *x;
struct km_event c;
int err = -EINVAL;
- u32 mark = 0;
struct xfrm_mark m;
struct xfrm_aevent_id *p = nlmsg_data(nlh);
struct nlattr *rp = attrs[XFRMA_REPLAY_VAL];
@@ -2863,9 +2862,10 @@ static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
return err;
}
- mark = xfrm_mark_get(attrs, &m);
+ xfrm_mark_get(attrs, &m);
- x = xfrm_state_lookup(net, mark, &p->sa_id.daddr, p->sa_id.spi, p->sa_id.proto, p->sa_id.family);
+ x = xfrm_state_lookup_exact(net, &m, &p->sa_id.daddr, p->sa_id.spi,
+ p->sa_id.proto, p->sa_id.family);
if (x == NULL)
return -ESRCH;
@@ -2999,9 +2999,10 @@ static int xfrm_add_sa_expire(struct sk_buff *skb, struct nlmsghdr *nlh,
struct xfrm_user_expire *ue = nlmsg_data(nlh);
struct xfrm_usersa_info *p = &ue->state;
struct xfrm_mark m;
- u32 mark = xfrm_mark_get(attrs, &m);
- x = xfrm_state_lookup(net, mark, &p->id.daddr, p->id.spi, p->id.proto, p->family);
+ xfrm_mark_get(attrs, &m);
+
+ x = xfrm_state_lookup_exact(net, &m, &p->id.daddr, p->id.spi, p->id.proto, p->family);
err = -ENOENT;
if (x == NULL)
@@ -3372,9 +3373,9 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
copy_from_user_migrate_state(&m, um);
- x = xfrm_state_lookup(net, m.old_mark.v & m.old_mark.m,
- &um->id.daddr, um->id.spi,
- um->id.proto, um->id.family);
+ x = xfrm_state_lookup_exact(net, &m.old_mark,
+ &um->id.daddr, um->id.spi,
+ um->id.proto, um->id.family);
if (!x) {
NL_SET_ERR_MSG(extack, "Can not find state");
return -ESRCH;
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH ipsec v2 4/6] xfrm: include mark in MIGRATE_STATE SA collision check
2026-09-08 6:48 [PATCH ipsec v2 0/6] xfrm: state: exact mark/mask match for control-plane SA lookups Antony Antony
` (2 preceding siblings ...)
2026-09-08 6:49 ` [PATCH ipsec v2 3/6] xfrm: fix hw offload state leak on xfrm_do_migrate_state() error path Antony Antony
@ 2026-09-08 6:49 ` Antony Antony
2026-09-08 6:49 ` [PATCH ipsec v2 5/6] xfrm: pass extack through to xfrm_init_replay() from xfrm_init_state() Antony Antony
2026-09-08 6:49 ` [PATCH ipsec v2 6/6] docs: xfrm: include mark in XFRM_MSG_MIGRATE_STATE EEXIST tuple Antony Antony
5 siblings, 0 replies; 7+ messages in thread
From: Antony Antony @ 2026-09-08 6:49 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc, Sashiko
The SA lookup tuple is (daddr, spi, proto, family, mark), but the
EEXIST pre-check and the xfrm_state_insert() vs xfrm_state_add()
decision only considered daddr and family, ignoring mark.
A migration that only changes the mark inserts a duplicate SA tuple
into the hash tables.
The collision check is implemented as xfrm_state_mark_collides(),
which walks the SPI hash bucket for the target tuple and excludes the
state being migrated by pointer, rather than doing a separate
exact-match lookup followed by a wildcard lookup and comparing the
result against self.
Before:
root@west:~# ip xfrm state add src 10.1.1.1 dst 10.1.1.2 proto esp \
spi 0x1000 reqid 100 mode tunnel aead "rfc4106(gcm(aes))" \
0x1111111111111111111111111111111111111111 96 \
mark 0x1 mask 0xff
root@west:~# ip xfrm state add src 10.1.1.1 dst 10.1.1.2 proto esp \
spi 0x1000 reqid 100 mode tunnel aead "rfc4106(gcm(aes))" \
0x1111111111111111111111111111111111111111 96 \
mark 0x2 mask 0xff
root@west:~# ip xfrm state migrate dst 10.1.1.2 proto esp spi 0x1000 \
mark 0x1 mask 0xff \
new-dst 10.1.1.2 new-src 10.1.1.1 new-reqid 100 \
new-mark 0x2 mask 0xff
ip x s
src 10.1.1.1 dst 10.1.1.2
proto esp spi 0x00001000 reqid 100 mode tunnel
replay-window 0
mark 0x2/0xff
aead rfc4106(gcm(aes)) 0x1111111111111111111111111111111111111111 96
anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000
src 10.1.1.1 dst 10.1.1.2
proto esp spi 0x00001000 reqid 100 mode tunnel
replay-window 0
mark 0x2/0xff
aead rfc4106(gcm(aes)) 0x1111111111111111111111111111111111111111 96
anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000
sel src 0.0.0.0/0 dst 0.0.0.0/0
Notice two states with same mark 0x2/0xff.
After:
Error: New SA tuple already occupied.
Fixes: a9d155ea9b44 ("xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v1->v2: pre-check used wildcard lookup; factored the check into
xfrm_state_mark_collides() excluding self directly instead of
lookup_exact()+lookup() with a pointer comparison
---
include/net/xfrm.h | 4 ++++
net/xfrm/xfrm_state.c | 40 +++++++++++++++++++++++++++++++++++++---
net/xfrm/xfrm_user.c | 17 ++++++++---------
3 files changed, 49 insertions(+), 12 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 9e053388aa4b..26dd4b570588 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1751,6 +1751,10 @@ struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark,
struct xfrm_state *xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,
const xfrm_address_t *daddr, __be32 spi,
u8 proto, unsigned short family);
+bool xfrm_state_mark_collides(struct net *net, u32 mark,
+ const xfrm_address_t *daddr, __be32 spi,
+ u8 proto, unsigned short family,
+ const struct xfrm_state *self);
#ifdef CONFIG_XFRM_SUB_POLICY
void xfrm_tmpl_sort(struct xfrm_tmpl **dst, struct xfrm_tmpl **src, int n,
unsigned short family);
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index b7ea1060dac9..977eb004270a 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2205,10 +2205,12 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,
struct netlink_ext_ack *extack)
{
if (m->new_family == m->old_family &&
- xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
+ xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family) &&
+ xc->mark.v == x->mark.v && xc->mark.m == x->mark.m) {
/*
- * Care is needed when the destination address of the state is
- * to be updated as it is a part of triplet.
+ * Care is needed when the destination address or mark of the
+ * state is to be updated, as they are part of the lookup
+ * triplet.
*/
xfrm_state_insert(xc);
} else {
@@ -2443,6 +2445,38 @@ xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,
}
EXPORT_SYMBOL(xfrm_state_lookup_exact);
+/* True if some OTHER state at this tuple would wildcard-match "mark".
+ * Used by MIGRATE_STATE, which must exclude the state being migrated.
+ */
+bool xfrm_state_mark_collides(struct net *net, u32 mark,
+ const xfrm_address_t *daddr, __be32 spi,
+ u8 proto, unsigned short family,
+ const struct xfrm_state *self)
+{
+ struct xfrm_hash_state_ptrs state_ptrs;
+ unsigned int h;
+ struct xfrm_state *x;
+ bool collides = false;
+
+ rcu_read_lock();
+ xfrm_hash_ptrs_get(net, &state_ptrs);
+ h = __xfrm_spi_hash(daddr, spi, proto, family, state_ptrs.hmask);
+
+ hlist_for_each_entry_rcu(x, state_ptrs.byspi + h, byspi) {
+ if (x != self && x->props.family == family &&
+ x->id.spi == spi && x->id.proto == proto &&
+ xfrm_addr_equal(&x->id.daddr, daddr, family) &&
+ (mark & x->mark.m) == x->mark.v) {
+ collides = true;
+ break;
+ }
+ }
+ rcu_read_unlock();
+
+ return collides;
+}
+EXPORT_SYMBOL(xfrm_state_mark_collides);
+
struct xfrm_state *
xfrm_find_acq(struct net *net, const struct xfrm_mark *mark, u8 mode, u32 reqid,
u32 if_id, u32 pcpu_num, u8 proto, const xfrm_address_t *daddr,
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index aac12991d2ca..f8c2b17c6f2b 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3446,15 +3446,14 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
x->nat_keepalive_interval);
if (m.new_family != um->id.family ||
- !xfrm_addr_equal(&m.new_daddr, &um->id.daddr, um->id.family)) {
- u32 new_mark_key = m.new_mark ? m.new_mark->v & m.new_mark->m :
- m.old_mark.v & m.old_mark.m;
- struct xfrm_state *x_new;
-
- x_new = xfrm_state_lookup(net, new_mark_key, &m.new_daddr,
- um->id.spi, um->id.proto, m.new_family);
- if (x_new) {
- xfrm_state_put(x_new);
+ !xfrm_addr_equal(&m.new_daddr, &um->id.daddr, um->id.family) ||
+ (m.new_mark && (m.new_mark->v != x->mark.v ||
+ m.new_mark->m != x->mark.m))) {
+ const struct xfrm_mark *new_mark = m.new_mark ? m.new_mark : &x->mark;
+
+ if (xfrm_state_mark_collides(net, new_mark->v & new_mark->m,
+ &m.new_daddr, um->id.spi,
+ um->id.proto, m.new_family, x)) {
NL_SET_ERR_MSG(extack, "New SA tuple already occupied");
err = -EEXIST;
goto out;
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH ipsec v2 6/6] docs: xfrm: include mark in XFRM_MSG_MIGRATE_STATE EEXIST tuple
2026-09-08 6:48 [PATCH ipsec v2 0/6] xfrm: state: exact mark/mask match for control-plane SA lookups Antony Antony
` (4 preceding siblings ...)
2026-09-08 6:49 ` [PATCH ipsec v2 5/6] xfrm: pass extack through to xfrm_init_replay() from xfrm_init_state() Antony Antony
@ 2026-09-08 6:49 ` Antony Antony
5 siblings, 0 replies; 7+ messages in thread
From: Antony Antony @ 2026-09-08 6:49 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc
Document mark as part of the EEXIST tuple and update the SA lookup
description to match.
Fixes: c13c0cc6f52e ("xfrm: add documentation for XFRM_MSG_MIGRATE_STATE")
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
.../networking/xfrm/xfrm_migrate_state.rst | 23 ++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/Documentation/networking/xfrm/xfrm_migrate_state.rst b/Documentation/networking/xfrm/xfrm_migrate_state.rst
index 9d53cb22b007..0412a3c0ecf7 100644
--- a/Documentation/networking/xfrm/xfrm_migrate_state.rst
+++ b/Documentation/networking/xfrm/xfrm_migrate_state.rst
@@ -27,15 +27,18 @@ SA Identification
=================
The struct is defined in ``include/uapi/linux/xfrm.h``. The SA is looked
-up using ``xfrm_state_lookup()`` with ``id.spi``,
-``id.daddr``, ``id.proto``, ``id.family``, and
-``old_mark.v & old_mark.m`` as the mark key::
+up using ``xfrm_state_lookup_exact()`` with ``id.spi``, ``id.daddr``,
+``id.proto``, ``id.family``, and an exact match against ``old_mark.v``
+and ``old_mark.m``. Unlike the data path, which uses a masked
+comparison, this requires the SA's mark and mask to equal ``old_mark``
+exactly, so a broad-mask SA is never matched when a more specific one
+was intended. If no such SA exists, ``-ESRCH`` is returned.::
struct xfrm_user_migrate_state {
struct xfrm_usersa_id id; /* spi, daddr, proto, family */
xfrm_address_t new_daddr;
xfrm_address_t new_saddr;
- struct xfrm_mark old_mark; /* SA lookup: key = v & m */
+ struct xfrm_mark old_mark; /* SA lookup key (exact v/m match) */
struct xfrm_selector new_sel; /* new selector (see Flags) */
__u32 new_reqid;
__u32 flags; /* XFRM_MIGRATE_STATE_* */
@@ -72,8 +75,8 @@ inherits the value from the existing SA (omit-to-inherit).
- Description
* - ``XFRMA_MARK``
- Mark on the migrated SA (``struct xfrm_mark``). Absent inherits
- ``old_mark``. To use no mark on the new SA, send ``XFRMA_MARK``
- with ``{0, 0}``.
+ the mark of the existing SA. To use no mark on the new SA, send
+ ``XFRMA_MARK`` with ``{0, 0}``.
* - ``XFRMA_ENCAP``
- UDP encapsulation template; only ``UDP_ENCAP_ESPINUDP`` is supported.
Set ``encap_type=0`` to remove encap.
@@ -259,8 +262,12 @@ Attributes in the notification
Error Handling
==============
-If the target SA tuple (new daddr, SPI, proto, new family) is already
-occupied, the operation returns ``-EEXIST`` before the migration begins.
+If the target SA tuple (new daddr, SPI, proto, new family, mark) is
+already occupied, the operation returns ``-EEXIST`` before the migration
+begins. "Occupied" includes wildcard shadowing: an existing SA with a
+broader mask (e.g. mark 0/0) claims every mark value, so it blocks
+migrating to any more specific mark at the same tuple, not just an
+exact mark/mask duplicate.
The old SA remains intact and the operation is safe to retry after
resolving the conflict.
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread