Linux Documentation
 help / color / mirror / Atom feed
* Re: [PATCH net-next 4/4] net: make page_pool_get_stats() void
From: Nicolai Buchwitz @ 2026-05-26 19:36 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, corbet,
	tariqt, dtatulea, linux-doc, hawk, ilias.apalodimas
In-Reply-To: <20260526155722.2790742-5-kuba@kernel.org>

On 26.5.2026 17:57, Jakub Kicinski wrote:
> The kdoc for page_pool_get_stats() is missing a Returns: statement.
> Looking at this function, I have no idea what is the purpose of
> the bool it returns. My guess was that maybe the static inline
> stub returns false if CONFIG_PAGE_POOL_STATS=n but such static
> inline helper doesn't exist at all. All callers pass a pointer
> to a struct on the stack. Make this function void.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---

> [...]

Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>

^ permalink raw reply

* Re: [PATCH net-next 3/4] docs: page_pool: drop the mention of the legacy stats API
From: Nicolai Buchwitz @ 2026-05-26 19:25 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, corbet,
	tariqt, dtatulea, linux-doc, hawk, ilias.apalodimas
In-Reply-To: <20260526155722.2790742-4-kuba@kernel.org>

On 26.5.2026 17:57, Jakub Kicinski wrote:
> The Netlink support for querying page pool stats has been
> proven out in production, let's remove the mention of the
> helper meant for dumping page pool stats into ethtool -S
> from the docs.
> 
> Call out in the kdoc that this API is deprecated.
> Some drivers may not be able to use the Netlink API
> (if page pool is shared across netdevs). So the old API
> is not _completely_ dead. But we shouldn't advertise it.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>

> [...]

Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>

^ permalink raw reply

* Re: [PATCH net-next 2/4] docs: clarify page pool NAPI consumer requirement
From: Nicolai Buchwitz @ 2026-05-26 19:22 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, corbet,
	tariqt, dtatulea, linux-doc, hawk, ilias.apalodimas
In-Reply-To: <20260526155722.2790742-3-kuba@kernel.org>

On 26.5.2026 17:57, Jakub Kicinski wrote:
> The comment about requirements when to set the NAPI pointer
> may not be super clear. Add more words.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>

> [...]

Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>

^ permalink raw reply

* Re: [PATCH net-next 1/4] docs: net: page_pool: drop reference to removed PP_FLAG_PAGE_FRAG
From: Nicolai Buchwitz @ 2026-05-26 19:18 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, corbet,
	tariqt, dtatulea, linux-doc, hawk, ilias.apalodimas
In-Reply-To: <20260526155722.2790742-2-kuba@kernel.org>

On 26.5.2026 17:57, Jakub Kicinski wrote:
> The flag was removed in commit 09d96ee5674a ("page_pool: remove
> PP_FLAG_PAGE_FRAG"), but the documentation still mentions it when
> describing fragment usage. Drop the stale reference; the fragment
> API does not require any opt-in flag.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---

> [...]

Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>

^ permalink raw reply

* Re: [PATCH v4 1/4] Introducing pw_lock() and per-cpu queue & flush work
From: Jonathan Corbet @ 2026-05-26 19:15 UTC (permalink / raw)
  To: Leonardo Bras, Shuah Khan, Leonardo Bras, Peter Zijlstra,
	Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long, Andrew Morton,
	David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
	Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
	Jann Horn, Pedro Falcato, Brendan Jackman, Johannes Weiner,
	Zi Yan, Harry Yoo, Hao Li, Christoph Lameter, David Rientjes,
	Roman Gushchin, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Qi Zheng, Shakeel Butt,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Borislav Petkov (AMD),
	Randy Dunlap, Feng Tang, Dapeng Mi, Kees Cook, Marco Elver,
	Jakub Kicinski, Li RongQing, Eric Biggers, Paul E. McKenney,
	Nathan Chancellor, Nicolas Schier, Miguel Ojeda,
	Thomas Weißschuh, Thomas Gleixner, Douglas Anderson,
	Gary Guo, Christian Brauner, Pasha Tatashin, Coiby Xu,
	Masahiro Yamada, Frederic Weisbecker
  Cc: linux-doc, linux-kernel, linux-mm, linux-rt-devel,
	Marcelo Tosatti
In-Reply-To: <20260519012754.240804-2-leobras.c@gmail.com>

Leonardo Bras <leobras.c@gmail.com> writes:

> Some places in the kernel implement a parallel programming strategy
> consisting on local_locks() for most of the work, and some rare remote
> operations are scheduled on target cpu. This keeps cache bouncing low since
> cacheline tends to be mostly local, and avoids the cost of locks in non-RT
> kernels, even though the very few remote operations will be expensive due
> to scheduling overhead.

A couple of documentation-related nits:

> ---
>  MAINTAINERS                                   |   7 +
>  .../admin-guide/kernel-parameters.txt         |  10 +
>  Documentation/locking/pwlocks.rst             |  76 +++++

You have added a new RST file here, but haven't added it to the table of
contents in index.rst.  So it won't be part of the docs build.

>  init/Kconfig                                  |  35 +++
>  kernel/Makefile                               |   2 +
>  include/linux/pwlocks.h                       | 265 ++++++++++++++++++
>  kernel/pwlocks.c                              |  47 ++++
>  7 files changed, 442 insertions(+)
>  create mode 100644 Documentation/locking/pwlocks.rst
>  create mode 100644 include/linux/pwlocks.h
>  create mode 100644 kernel/pwlocks.c

[...]

> diff --git a/Documentation/locking/pwlocks.rst b/Documentation/locking/pwlocks.rst
> new file mode 100644
> index 000000000000..09f4a5417bc1
> --- /dev/null
> +++ b/Documentation/locking/pwlocks.rst
> @@ -0,0 +1,76 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +=========
> +PW (Per-CPU Work) locks
> +=========

The over/underlines should match the text in length.

> +Some places in the kernel implement a parallel programming strategy
> +consisting on local_locks() for most of the work, and some rare remote
> +operations are scheduled on target cpu. This keeps cache bouncing low since
> +cacheline tends to be mostly local, and avoids the cost of locks in non-RT
> +kernels, even though the very few remote operations will be expensive due
> +to scheduling overhead.
> +
> +On the other hand, for RT workloads this can represent a problem:
> +scheduling work on remote cpu that are executing low latency tasks
> +is undesired and can introduce unexpected deadline misses.
> +
> +PW locks help to convert sites that use local_locks (for cpu local operations)
> +and queue_work_on (for queueing work remotely, to be executed
> +locally on the owner cpu of the lock) to a spinlocks.
> +
> +The lock is declared pw_lock_t type.
> +The lock is initialized with pw_lock_init.
> +The lock is locked with pw_lock (takes a lock and cpu as a parameter).
> +The lock is unlocked with pw_unlock (takes a lock and cpu as a parameter).

Did you want that to be an itemized list?  If so, put "- " in front of
each line.

> +The pw_lock_irqsave function disables interrupts and saves current interrupt state,
> +cpu as a parameter.
> +
> +For trylock variant, there is the pw_trylock_t type, initialized with
> +pw_trylock_init. Then the corresponding pw_trylock and pw_trylock_irqsave.
> +
> +work_struct should be replaced by pw_struct, which contains a cpu parameter
> +(owner cpu of the lock), initialized by INIT_PW.
> +
> +The queue work related functions (analogous to queue_work_on and flush_work) are:
> +pw_queue_on and pw_flush.
> +
> +The behaviour of the PW lock functions is as follows:
> +
> +* !CONFIG_PWLOCKS (or CONFIG_PWLOCKS and pwlocks=off kernel boot parameter):
> +        - pw_lock:			local_lock
> +        - pw_lock_irqsave:		local_lock_irqsave
> +        - pw_trylock:			local_trylock
> +        - pw_trylock_irqsave:		local_trylock_irqsave
> +        - pw_unlock:			local_unlock
> +        - pw_lock_local:		local_lock
> +        - pw_trylock_local:		local_trylock
> +        - pw_unlock_local:		local_unlock
> +        - pw_queue_on:         		queue_work_on
> +        - pw_flush:	            	flush_work

This will not render the way you expect it to.  You want a literal block
ere.  So end the text with "...is as follows::"  and indent the entire
literal block.

> +* CONFIG_PWLOCKS (and CONFIG_PWLOCKS_DEFAULT=y or pwlocks=on kernel boot parameter),
> +        - pw_lock:			spin_lock
> +        - pw_lock_irqsave:		spin_lock_irqsave
> +        - pw_trylock:			spin_trylock
> +        - pw_trylock_irqsave:		spin_trylock_irqsave
> +        - pw_unlock:			spin_unlock
> +        - pw_lock_local:		preempt_disable OR migrate_disable + spin_lock
> +        - pw_trylock_local:		preempt_disable OR migrate_disable + spin_trylock
> +        - pw_unlock_local:		preempt_enable OR migrate_enable + spin_unlock
> +        - pw_queue_on:         		executes work function on caller cpu
> +        - pw_flush:            		empty
> +
> +pw_get_cpu(work_struct), to be called from within per-cpu work function,
> +returns the target cpu.
> +
> +On the locking functions above, there are the local locking functions
> +(pw_lock_local, pw_trylock_local and pw_unlock_local) that must only

If you write functions like pw_lock_local(), you'll get automatic cross
links to the kerneldoc documentation ... which I'm sure must exist ...

> +be used to access per-CPU data from the CPU that owns that data,
> +and never remotely. They disable preemption/migration and don't require
> +a cpu parameter, making them a replacement for local_lock functions that
> +does not introduce overhead.
> +
> +These should only be used when accessing per-CPU data of the local CPU.
> +

[...]

> +#else /* CONFIG_PWLOCKS */
> +
> +DECLARE_STATIC_KEY_MAYBE(CONFIG_PWLOCKS_DEFAULT, pw_sl);
> +
> +typedef union {
> +	spinlock_t sl;
> +	local_lock_t ll;
> +} pw_lock_t;
> +
> +typedef union {
> +	spinlock_t sl;
> +	local_trylock_t ll;
> +} pw_trylock_t;
> +
> +struct pw_struct {
> +	struct work_struct work;
> +	int cpu;
> +};
> +
> +#ifdef CONFIG_PREEMPT_RT
> +#define preempt_or_migrate_disable migrate_disable
> +#define preempt_or_migrate_enable migrate_enable
> +#else
> +#define preempt_or_migrate_disable preempt_disable
> +#define preempt_or_migrate_enable preempt_enable
> +#endif
> +
> +#define pw_lock_init(lock)							\
> +do {										\
> +	if (static_branch_maybe(CONFIG_PWLOCKS_DEFAULT, &pw_sl))		\
> +		spin_lock_init(lock.sl);					\
> +	else									\
> +		local_lock_init(lock.ll);					\
> +} while (0)

Sigh, I guess I was over-optimistic about kerneldoc comments.

Is there a reason why these aren't inline functions?

Thanks,

jon

^ permalink raw reply

* [PATCH ipsec-next v9 16/16] xfrm: add documentation for XFRM_MSG_MIGRATE_STATE
From: Antony Antony @ 2026-05-26 19:10 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

Add documentation for the new XFRM_MSG_MIGRATE_STATE netlink message,
which migrates a single SA identified by SPI and mark without involving
policies.

The document covers the motivation and design differences from the
existing XFRM_MSG_MIGRATE, the SA lookup mechanism, supported attributes
with their omit-to-inherit semantics, and usage examples.

Signed-off-by: Antony Antony <antony.antony@secunet.com>

---
v8->v9: document -ESRCH error when SA is deleted before migration completes
	- reject unknown flags with -EINVAL, report bad bits in extack
	- split Migration Steps into Outgoing SA and Incoming SA subsections
	- split migration steps by direction, fix feature detection errors, -EEXIST safe to retry
	- rename flags
v7->v8: unknown flags ignored
v6->v7: update docs to reflect the flags
v5->v6: added this patch
---
 Documentation/networking/xfrm/index.rst            |   1 +
 .../networking/xfrm/xfrm_migrate_state.rst         | 274 +++++++++++++++++++++
 2 files changed, 275 insertions(+)

diff --git a/Documentation/networking/xfrm/index.rst b/Documentation/networking/xfrm/index.rst
index 7d866da836fe..90191848f8db 100644
--- a/Documentation/networking/xfrm/index.rst
+++ b/Documentation/networking/xfrm/index.rst
@@ -9,5 +9,6 @@ XFRM Framework
 
    xfrm_device
    xfrm_proc
+   xfrm_migrate_state
    xfrm_sync
    xfrm_sysctl
diff --git a/Documentation/networking/xfrm/xfrm_migrate_state.rst b/Documentation/networking/xfrm/xfrm_migrate_state.rst
new file mode 100644
index 000000000000..9d53cb22b007
--- /dev/null
+++ b/Documentation/networking/xfrm/xfrm_migrate_state.rst
@@ -0,0 +1,274 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=====================
+XFRM SA Migrate State
+=====================
+
+Overview
+========
+
+``XFRM_MSG_MIGRATE_STATE`` migrates a single SA, looked up using SPI and
+mark, without involving policies. Unlike ``XFRM_MSG_MIGRATE``, which couples
+SA and policy migration and allows migrating multiple SAs in one call, this
+interface identifies the SA unambiguously via SPI and supports changing
+the reqid, addresses, encapsulation, selector, and offload.
+
+Because IKE daemons can manage policies independently of
+the kernel, this interface allows precise per-SA migration without
+requiring policy involvement. Optional netlink attributes follow an
+omit-to-inherit model: omitting an attribute preserves the value from
+the old SA. The ``flags`` field controls two exceptions: hardware offload
+is inherited by default and can be suppressed with
+``XFRM_MIGRATE_STATE_CLEAR_OFFLOAD`` or overridden with ``XFRMA_OFFLOAD_DEV``;
+the new selector is taken from ``new_sel`` by default and can instead be
+derived from the new addresses with ``XFRM_MIGRATE_STATE_UPDATE_H2H_SEL``.
+
+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::
+
+    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_selector   new_sel;  /* new selector (see Flags) */
+        __u32                  new_reqid;
+        __u32                  flags;    /* XFRM_MIGRATE_STATE_* */
+        __u16                  new_family;
+        __u16                  reserved;  /* must be zero */
+    };
+
+The ``reserved`` field must be set to zero; the kernel rejects any
+other value with ``-EINVAL``.
+
+Supported Attributes
+====================
+
+The following fields in ``xfrm_user_migrate_state`` are always explicit
+and are not inherited from the existing SA. Passing zero is not equivalent
+to "keep unchanged" — zero is used as-is:
+
+- ``new_daddr`` - new destination address
+- ``new_saddr`` - new source address
+- ``new_family`` - new address family
+- ``new_reqid`` - new reqid (0 = no reqid)
+- ``new_sel`` - new selector; used when ``XFRM_MIGRATE_STATE_UPDATE_H2H_SEL`` is
+  not set (see `Flags`_ below)
+- ``flags`` - bitmask of ``XFRM_MIGRATE_STATE_*`` flags (see `Flags`_ below)
+
+The following netlink attributes are also accepted. Omitting an attribute
+inherits the value from the existing SA (omit-to-inherit).
+
+.. list-table::
+   :widths: 30 70
+   :header-rows: 1
+
+   * - Attribute
+     - 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}``.
+   * - ``XFRMA_ENCAP``
+     - UDP encapsulation template; only ``UDP_ENCAP_ESPINUDP`` is supported.
+       Set ``encap_type=0`` to remove encap.
+   * - ``XFRMA_OFFLOAD_DEV``
+     - Hardware offload configuration (``struct xfrm_user_offload``). Absent
+       copies offload from the existing SA. When
+       ``XFRM_MIGRATE_STATE_CLEAR_OFFLOAD`` is set in ``flags``, the new SA has
+       no offload; this flag is mutually exclusive with ``XFRMA_OFFLOAD_DEV``
+       and sending both returns ``-EINVAL``.
+   * - ``XFRMA_SET_MARK``
+     - Output mark on the migrated SA; pair with ``XFRMA_SET_MARK_MASK``.
+       Send 0 to clear.
+   * - ``XFRMA_NAT_KEEPALIVE_INTERVAL``
+     - NAT keepalive interval in seconds. Requires encap. Send 0 to clear.
+       Automatically cleared when encap is removed; setting a non-zero
+       value without encap returns ``-EINVAL``.
+   * - ``XFRMA_MTIMER_THRESH``
+     - Mapping maxage threshold. Only valid on input SAs; setting on an
+       output SA returns ``-EINVAL``. Requires encap. Send 0 to clear.
+       Automatically cleared when encap is removed; setting a non-zero
+       value without encap returns ``-EINVAL``.
+
+The following SA properties are immutable and cannot be changed via
+``XFRM_MSG_MIGRATE_STATE``: algorithms (``XFRMA_ALG_*``), replay state,
+direction (``XFRMA_SA_DIR``), and security context (``XFRMA_SEC_CTX``).
+
+Flags
+=====
+
+The ``flags`` field in ``xfrm_user_migrate_state`` controls optional
+migration behaviour. Unknown flag bits are rejected with ``-EINVAL``; the
+extended ACK message identifies the unrecognised bits (e.g. ``"Unknown flags:
+0x4"``). Userspace can use ``XFRM_MIGRATE_STATE_KNOWN_FLAGS`` (defined in
+``<linux/xfrm.h>``) to validate flags before sending; note that this constant
+reflects the flags known to the header version userspace was compiled against,
+which may differ from what the running kernel accepts.
+
+.. list-table::
+   :widths: 40 60
+   :header-rows: 1
+
+   * - Flag
+     - Description
+   * - ``XFRM_MIGRATE_STATE_CLEAR_OFFLOAD``
+     - When set, the new SA has no hardware offload even when
+       ``XFRMA_OFFLOAD_DEV`` is absent. Without this flag, omitting
+       ``XFRMA_OFFLOAD_DEV`` copies the existing offload to the new SA.
+       Mutually exclusive with ``XFRMA_OFFLOAD_DEV``; sending both
+       returns ``-EINVAL``.
+   * - ``XFRM_MIGRATE_STATE_UPDATE_H2H_SEL``
+     - When set, the kernel validates that the existing SA selector is a
+       single-host entry matching the SA addresses (``prefixlen_s ==
+       prefixlen_d`` equal to 32 for IPv4 or 128 for IPv6, and addresses
+       matching ``id.daddr`` and ``props.saddr``). If the check passes,
+       the new selector is derived from ``new_daddr`` and ``new_saddr``
+       with the single-host mask for ``new_family``. A mismatch returns
+       ``-EINVAL``. When this flag is not set, ``new_sel`` is used as-is
+       for the migrated SA.
+
+Migration Steps
+===============
+
+Outgoing SA
+-----------
+
+To prevent cleartext traffic leaks, install a block policy before
+migrating:
+
+#. Install a block policy to drop traffic on the affected selector.
+#. Remove the old policy.
+#. Call ``XFRM_MSG_MIGRATE_STATE`` for each SA.
+#. Reinstall the policies.
+#. Remove the block policy.
+
+If AES-GCM is in use, the block policy also prevents IV reuse during
+the migration window. For other AEADs this step is not required for
+IV safety, but skipping it allows a brief cleartext window.
+
+Incoming SA
+-----------
+
+No block policy is needed. ``XFRM_MSG_MIGRATE_STATE`` atomically
+transfers the sequence number and replay window from the old SA to
+the new SA, so the new SA continues replay protection without a gap.
+Call ``XFRM_MSG_MIGRATE_STATE`` for each SA directly.
+
+When accepting incoming traffic, be liberal during the migration
+window: packets sent by the remote peer before it completed its own
+migration may arrive out of order or slightly late. Dropping them
+unnecessarily causes packet loss. A generous replay window reduces
+the impact of reordering during migration.
+
+Block Policy and IV Safety
+--------------------------
+
+AES-GCM IV uniqueness is critical: reusing a (key, IV) pair allows
+an attacker to recover the authentication subkey and forge
+authentication tags, breaking both confidentiality and integrity.
+This concern applies to outgoing SAs only — the remote peer controls
+IV generation on incoming traffic.
+
+``XFRM_MSG_MIGRATE_STATE`` atomically copies the sequence number and
+replay window from the old SA to the new SA and deletes the old SA.
+The block policy serves two purposes: it prevents cleartext traffic
+leaks during the migration window, and for AES-GCM it prevents IV
+reuse by ensuring no outgoing packets are sent under the same key.
+The atomic copy of the sequence number and replay window complements
+this — together they eliminate both risks during migration.
+The atomic copy also serves incoming SAs, ensuring replay protection
+continues without a gap across the migration.
+
+Feature Detection
+=================
+
+Userspace can probe for kernel support by sending a minimal
+``XFRM_MSG_MIGRATE_STATE`` message with a non-zero non-existent SPI:
+
+- ``-EINVAL``: kernel predates ``XFRM_MSG_MIGRATE_STATE``; message type
+  is out of range
+- ``-ENOPROTOOPT``: message type is known but ``CONFIG_XFRM_MIGRATE``
+  is not enabled
+- ``-ESRCH``: supported (SPI not found)
+
+Userspace Notification on Success
+=================================
+
+On successful migration the kernel multicasts an
+``XFRM_MSG_MIGRATE_STATE`` message to the ``XFRMNLGRP_MIGRATE`` group.
+The fixed header is ``struct xfrm_user_migrate_state`` copied from the
+request, followed by the same set of netlink attributes that are
+accepted as input, with the differences noted below.
+
+Differences from the request
+-----------------------------
+
+.. list-table::
+   :widths: 25 75
+   :header-rows: 1
+
+   * - Field / Attribute
+     - Difference
+   * - ``new_sel``
+     - Contains the actual selector of the newly installed SA, not the
+       ``new_sel`` from the request. When
+       ``XFRM_MIGRATE_STATE_UPDATE_H2H_SEL`` is set the kernel derives the
+       selector from ``new_daddr`` / ``new_saddr``; the caller's
+       ``new_sel`` field is ignored in that case. The notification
+       always carries the real selector of the new SA.
+   * - ``XFRMA_SA_DIR``
+     - Present in the notification (set from the direction of the new
+       SA) but **not accepted as input** — direction is immutable.
+   * - ``flags``
+     - Echoed back as-is. ``XFRM_MIGRATE_STATE_CLEAR_OFFLOAD`` and
+       ``XFRM_MIGRATE_STATE_UPDATE_H2H_SEL`` describe the request that was
+       made, not a property of the resulting SA.
+
+Attributes in the notification
+-------------------------------
+
+.. list-table::
+   :widths: 30 70
+   :header-rows: 1
+
+   * - Attribute
+     - Description
+   * - ``XFRMA_ENCAP``
+     - UDP encapsulation template, if configured on the new SA.
+   * - ``XFRMA_OFFLOAD_DEV``
+     - Hardware offload configuration, if active on the new SA.
+   * - ``XFRMA_MARK``
+     - Mark on the new SA, if set.
+   * - ``XFRMA_SET_MARK``
+     - Output mark on the new SA, if set.
+   * - ``XFRMA_SET_MARK_MASK``
+     - Output mark mask, present together with ``XFRMA_SET_MARK``.
+   * - ``XFRMA_MTIMER_THRESH``
+     - Mapping maxage threshold, if non-zero.
+   * - ``XFRMA_NAT_KEEPALIVE_INTERVAL``
+     - NAT keepalive interval, if non-zero.
+   * - ``XFRMA_SA_DIR``
+     - Direction of the new SA.
+
+Error Handling
+==============
+
+If the target SA tuple (new daddr, SPI, proto, new family) is already
+occupied, the operation returns ``-EEXIST`` before the migration begins.
+The old SA remains intact and the operation is safe to retry after
+resolving the conflict.
+
+If the target SA is deleted before the migration completes, the operation
+returns ``-ESRCH``. No new SA is installed. Userspace should verify the
+current SA state before retrying.
+
+If the multicast notification (``XFRMNLGRP_MIGRATE``) fails to send,
+the migration itself has already completed successfully and the new SA
+is installed. The operation returns success, 0, with an extack warning,
+but listeners will not receive the migration event.

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 15/16] xfrm: restrict netlink attributes for XFRM_MSG_MIGRATE_STATE
From: Antony Antony @ 2026-05-26 19:10 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

Only accept XFRMA used in this method, reject the rest.

Signed-off-by: Antony Antony <antony.antony@secunet.com>

---
v5->v6: added this patch
---
 net/xfrm/xfrm_user.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index fca7b02e0e02..caddd23936dc 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3782,6 +3782,30 @@ static int xfrm_reject_unused_attr(int type, struct nlattr **attrs,
 		}
 	}
 
+	if (type == XFRM_MSG_MIGRATE_STATE) {
+		int i;
+
+		for (i = 0; i <= XFRMA_MAX; i++) {
+			if (!attrs[i])
+				continue;
+
+			switch (i) {
+			case XFRMA_MARK:
+			case XFRMA_ENCAP:
+			case XFRMA_OFFLOAD_DEV:
+			case XFRMA_SET_MARK:
+			case XFRMA_SET_MARK_MASK:
+			case XFRMA_MTIMER_THRESH:
+			case XFRMA_NAT_KEEPALIVE_INTERVAL:
+				break;
+			default:
+				NL_SET_ERR_MSG_ATTR(extack, attrs[i],
+						    "Unsupported attribute in XFRM_MSG_MIGRATE_STATE");
+				return -EINVAL;
+			}
+		}
+	}
+
 	return 0;
 }
 

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 14/16] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
From: Antony Antony @ 2026-05-26 19:09 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

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 SA is looked up via xfrm_usersa_id, which uniquely
identifies it, so old_saddr is not needed. old_daddr is carried in
xfrm_usersa_id.daddr.

The reqid is invariant in the old migration.

Signed-off-by: Antony Antony <antony.antony@secunet.com>

---
v8->v9: - fix smark size estimate (Steffen)
  - pass net from caller in xfrm_send_migrate_state()(Steffen: 7e2a4f7ca095)
  - fix race condition in xfrm_do_migrate_state(): check km.state
    under x->lock before migrate_sync/delete to prevent concurrent
    migrations (sashiko AI review)
  - add XFRM_MIGRATE_STATE_KNOWN_FLAGS to uapi; reject unknown flags with -EINVAL and extack string
  - pre-check new SA tuple before migration
  - add XFRM_MSG_MIGRATE_STATE for single SA migration: SQUASH
  - add xfrm_compat.c support for XFRM_MSG_MIGRATE_STATE
  - fix uninitialized msg_type in PF_KEY migrate path
  - new_mark handling point to x->mark directly, drop new_marks[] array
  - fix UPDATE_SEL family check to use props.family instead of sel.family
  - extract xso_to_xuo() helper, fix duplication
  - drop extra check XFRMA_OFFLOAD_DEV takes precedence
  - rename XFRM_MIGRATE_STATE_NO_OFFLOAD -> XFRM_MIGRATE_STATE_CLEAR_OFFLOAD
  - rename XFRM_MIGRATE_STATE_UPDATE_SEL -> XFRM_MIGRATE_STATE_UPDATE_H2H_SEL
  - use nla_get_u32_default() when possible
  - use verify_selector_prefixlen()
  - split const xfrm_user_offload *xuo cleanup into separate patch (Sabrina)
v7->v8: - removed the unknown-flags validation block
v6->v7: - add flags field to xfrm_user_migrate_state (based on Sabrina's feedback)
  - add XFRM_MIGRATE_STATE_NO_OFFLOAD (bit 0): suppresses offload
  - omit-to-inherit; mutually exclusive with XFRMA_OFFLOAD_DEV
  - zero-initialize struct xfrm_migrate m[XFRM_MAX_DEPTH]
  - add struct xfrm_selector new_sel to xfrm_user_migrate_state
  - add XFRM_MIGRATE_STATE_UPDATE_SEL: derive new selector
    from SA addresses when old selector is a single-host match
v5->v6: - (Feedback from Sabrina's review)
  - reqid change: use xfrm_state_add, not xfrm_state_insert
  - encap and xuo: use nla_data() directly, no kmemdup needed
  - notification failure is non-fatal: set extack warning, return 0
  - drop state direction, x->dir, check, not required
  - reverse xmas tree local variable ordering
  - use NL_SET_ERR_MSG_WEAK for clone failure message
  - fix implicit padding in xfrm_user_migrate_state uapi struct
  - support XFRMA_SET_MARK/XFRMA_SET_MARK_MASK in XFRM_MSG_MIGRATE_STATE
v4->v5: - set portid, seq in XFRM_MSG_MIGRATE_STATE netlink notification
  - rename error label to out for clarity
  - add locking and synchronize after cloning
  - change some if(x) to if(!x) for clarity
  - call __xfrm_state_delete() inside the lock
  - return error from xfrm_send_migrate_state() instead of always returning 0
v3->v4: preserve reqid invariant for each state migrated
v2->v3: free the skb on the error path
v1->v2: merged next patch here to fix use uninitialized value
  - removed unnecessary inline
  - added const when possible

xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration : SQUASH
---
 include/net/xfrm.h          |  10 +-
 include/uapi/linux/xfrm.h   |  25 ++++
 net/xfrm/xfrm_compat.c      |   5 +-
 net/xfrm/xfrm_policy.c      |  17 +++
 net/xfrm/xfrm_state.c       |  29 +++-
 net/xfrm/xfrm_user.c        | 333 +++++++++++++++++++++++++++++++++++++++++++-
 security/selinux/nlmsgtab.c |   3 +-
 7 files changed, 405 insertions(+), 17 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 5515c7b10020..e2cb2d0e5cee 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -684,12 +684,20 @@ struct xfrm_migrate {
 	xfrm_address_t		new_saddr;
 	struct xfrm_encap_tmpl *encap;
 	struct xfrm_user_offload *xuo;
+	struct xfrm_mark        old_mark;
+	const struct xfrm_mark *new_mark;
+	struct xfrm_mark        smark;
 	u8			proto;
 	u8			mode;
-	u16			reserved;
+	u16			msg_type; /* XFRM_MSG_MIGRATE or XFRM_MSG_MIGRATE_STATE */
+	u32			flags;
 	u32			old_reqid;
+	u32			new_reqid;
+	u32			nat_keepalive_interval;
+	u32			mapping_maxage;
 	u16			old_family;
 	u16			new_family;
+	const struct xfrm_selector *new_sel;
 };
 
 #define XFRM_KM_TIMEOUT                30
diff --git a/include/uapi/linux/xfrm.h b/include/uapi/linux/xfrm.h
index a23495c0e0a1..051f8066efd1 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,28 @@ struct xfrm_user_migrate {
 	__u16				new_family;
 };
 
+struct xfrm_user_migrate_state {
+	struct xfrm_usersa_id id;
+	xfrm_address_t new_daddr;
+	xfrm_address_t new_saddr;
+	struct xfrm_mark old_mark;
+	struct xfrm_selector new_sel;
+	__u32 new_reqid;
+	__u32 flags;
+	__u16 new_family;
+	__u16 reserved;
+};
+
+/* Flags for xfrm_user_migrate_state.flags */
+enum xfrm_migrate_state_flags {
+	XFRM_MIGRATE_STATE_CLEAR_OFFLOAD = 1, /* do not inherit offload from existing SA */
+	XFRM_MIGRATE_STATE_UPDATE_H2H_SEL = 2, /* update H2H selector from new daddr/saddr */
+};
+
+/* All flags defined as of this header version; unknown bits are rejected. */
+#define XFRM_MIGRATE_STATE_KNOWN_FLAGS \
+	(XFRM_MIGRATE_STATE_CLEAR_OFFLOAD | XFRM_MIGRATE_STATE_UPDATE_H2H_SEL)
+
 struct xfrm_user_mapping {
 	struct xfrm_usersa_id		id;
 	__u32				reqid;
diff --git a/net/xfrm/xfrm_compat.c b/net/xfrm/xfrm_compat.c
index b8d2e6930041..670e3512fc09 100644
--- a/net/xfrm/xfrm_compat.c
+++ b/net/xfrm/xfrm_compat.c
@@ -94,7 +94,8 @@ static const int compat_msg_min[XFRM_NR_MSGTYPES] = {
 	[XFRM_MSG_GETSADINFO  - XFRM_MSG_BASE] = sizeof(u32),
 	[XFRM_MSG_NEWSPDINFO  - XFRM_MSG_BASE] = sizeof(u32),
 	[XFRM_MSG_GETSPDINFO  - XFRM_MSG_BASE] = sizeof(u32),
-	[XFRM_MSG_MAPPING     - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_mapping)
+	[XFRM_MSG_MAPPING        - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_mapping),
+	[XFRM_MSG_MIGRATE_STATE  - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_migrate_state),
 };
 
 static const struct nla_policy compat_policy[XFRMA_MAX+1] = {
@@ -162,6 +163,7 @@ static struct nlmsghdr *xfrm_nlmsg_put_compat(struct sk_buff *skb,
 	case XFRM_MSG_NEWAE:
 	case XFRM_MSG_REPORT:
 	case XFRM_MSG_MIGRATE:
+	case XFRM_MSG_MIGRATE_STATE:
 	case XFRM_MSG_NEWSADINFO:
 	case XFRM_MSG_NEWSPDINFO:
 	case XFRM_MSG_MAPPING:
@@ -498,6 +500,7 @@ static int xfrm_xlate32(struct nlmsghdr *dst, const struct nlmsghdr *src,
 	case XFRM_MSG_GETAE:
 	case XFRM_MSG_REPORT:
 	case XFRM_MSG_MIGRATE:
+	case XFRM_MSG_MIGRATE_STATE:
 	case XFRM_MSG_NEWSADINFO:
 	case XFRM_MSG_GETSADINFO:
 	case XFRM_MSG_NEWSPDINFO:
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index cf05d778e2dd..1c558362d375 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4643,6 +4643,21 @@ static int xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate,
 	return 0;
 }
 
+/*
+ * Fill migrate fields that are invariant in XFRM_MSG_MIGRATE: inherited
+ * from the existing SA unchanged. XFRM_MSG_MIGRATE_STATE can update these.
+ */
+static void xfrm_migrate_copy_old(const struct xfrm_state *x,
+				  struct xfrm_migrate *mp)
+{
+	mp->msg_type               = XFRM_MSG_MIGRATE;
+	mp->smark                  = x->props.smark;
+	mp->new_reqid              = x->props.reqid;
+	mp->nat_keepalive_interval = x->nat_keepalive_interval;
+	mp->mapping_maxage         = x->mapping_maxage;
+	mp->new_mark               = &x->mark;
+}
+
 int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
 		 struct xfrm_migrate *m, int num_migrate,
 		 struct xfrm_kmaddress *k, struct net *net,
@@ -4682,6 +4697,8 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
 			nx_cur++;
 			mp->encap = encap;
 			mp->xuo = xuo;
+			xfrm_migrate_copy_old(x, mp);
+
 			xc = xfrm_state_migrate(x, mp, net, extack);
 			if (xc) {
 				x_new[nx_new] = xc;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 043e573c4f32..2c738e980b3f 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1974,11 +1974,25 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 		goto out;
 
 	memcpy(&x->id, &orig->id, sizeof(x->id));
-	memcpy(&x->sel, &orig->sel, sizeof(x->sel));
+	if (m->msg_type == XFRM_MSG_MIGRATE_STATE) {
+		if (m->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL) {
+			u8 prefixlen = (m->new_family == AF_INET6) ? 128 : 32;
+
+			x->sel             = orig->sel;
+			x->sel.family      = m->new_family;
+			x->sel.prefixlen_d = prefixlen;
+			x->sel.prefixlen_s = prefixlen;
+			x->sel.daddr       = m->new_daddr;
+			x->sel.saddr       = m->new_saddr;
+		} else {
+			x->sel = *m->new_sel;
+		}
+	} else {
+		x->sel = orig->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;
 
 	if (orig->aalg) {
 		x->aalg = xfrm_algo_auth_clone(orig->aalg);
@@ -2011,8 +2025,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 		x->encap = kmemdup(m->encap, sizeof(*x->encap), GFP_KERNEL);
 		if (!x->encap)
 			goto error;
-		x->mapping_maxage = orig->mapping_maxage;
-		x->nat_keepalive_interval = orig->nat_keepalive_interval;
+		x->mapping_maxage = m->mapping_maxage;
+		x->nat_keepalive_interval = m->nat_keepalive_interval;
 	}
 
 	if (orig->security)
@@ -2029,8 +2043,9 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 	if (xfrm_replay_clone(x, orig))
 		goto error;
 
-	memcpy(&x->mark, &orig->mark, sizeof(x->mark));
-	memcpy(&x->props.smark, &orig->props.smark, sizeof(x->props.smark));
+	x->mark = m->new_mark ? *m->new_mark : m->old_mark;
+
+	x->props.smark = m->smark;
 
 	x->props.flags = orig->props.flags;
 	x->props.extra_flags = orig->props.extra_flags;
@@ -2053,7 +2068,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));
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index c7ae670212a9..fca7b02e0e02 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1195,6 +1195,16 @@ static int copy_sec_ctx(struct xfrm_sec_ctx *s, struct sk_buff *skb)
 	return 0;
 }
 
+static void xso_to_xuo(const struct xfrm_dev_offload *xso,
+		       struct xfrm_user_offload *xuo)
+{
+	xuo->ifindex = xso->dev->ifindex;
+	if (xso->dir == XFRM_DEV_OFFLOAD_IN)
+		xuo->flags = XFRM_OFFLOAD_INBOUND;
+	if (xso->type == XFRM_DEV_OFFLOAD_PACKET)
+		xuo->flags |= XFRM_OFFLOAD_PACKET;
+}
+
 static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
 {
 	struct xfrm_user_offload *xuo;
@@ -1206,11 +1216,7 @@ static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
 
 	xuo = nla_data(attr);
 	memset(xuo, 0, sizeof(*xuo));
-	xuo->ifindex = xso->dev->ifindex;
-	if (xso->dir == XFRM_DEV_OFFLOAD_IN)
-		xuo->flags = XFRM_OFFLOAD_INBOUND;
-	if (xso->type == XFRM_DEV_OFFLOAD_PACKET)
-		xuo->flags |= XFRM_OFFLOAD_PACKET;
+	xso_to_xuo(xso, xuo);
 
 	return 0;
 }
@@ -1335,7 +1341,7 @@ static int copy_to_user_encap(struct xfrm_encap_tmpl *ep, struct sk_buff *skb)
 	return 0;
 }
 
-static int xfrm_smark_put(struct sk_buff *skb, struct xfrm_mark *m)
+static int xfrm_smark_put(struct sk_buff *skb, const struct xfrm_mark *m)
 {
 	int ret = 0;
 
@@ -3084,6 +3090,25 @@ static int xfrm_add_acquire(struct sk_buff *skb, struct nlmsghdr *nlh,
 }
 
 #ifdef CONFIG_XFRM_MIGRATE
+static void 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;
+
+	ma->old_mark = um->old_mark;
+	ma->flags    = um->flags;
+	ma->new_sel  = &um->new_sel;
+	ma->msg_type = XFRM_MSG_MIGRATE_STATE;
+}
+
 static int copy_from_user_migrate(struct xfrm_migrate *ma,
 				  struct xfrm_kmaddress *k,
 				  struct nlattr **attrs, int *num,
@@ -3123,6 +3148,7 @@ static int copy_from_user_migrate(struct xfrm_migrate *ma,
 
 		ma->old_family = um->old_family;
 		ma->new_family = um->new_family;
+		ma->msg_type   = XFRM_MSG_MIGRATE;
 	}
 
 	*num = i;
@@ -3133,7 +3159,7 @@ static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
 			   struct nlattr **attrs, struct netlink_ext_ack *extack)
 {
 	struct xfrm_userpolicy_id *pi = nlmsg_data(nlh);
-	struct xfrm_migrate m[XFRM_MAX_DEPTH];
+	struct xfrm_migrate m[XFRM_MAX_DEPTH] = {};
 	struct xfrm_kmaddress km, *kmp;
 	u8 type;
 	int err;
@@ -3186,7 +3212,298 @@ 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 *um,
+			       const struct xfrm_migrate *m,
+			       u8 dir, u32 portid, u32 seq)
+{
+	int err;
+	struct nlmsghdr *nlh;
+	struct xfrm_user_migrate_state *hdr;
+
+	nlh = nlmsg_put(skb, portid, seq, XFRM_MSG_MIGRATE_STATE,
+			sizeof(struct xfrm_user_migrate_state), 0);
+	if (!nlh)
+		return -EMSGSIZE;
+
+	hdr = nlmsg_data(nlh);
+	*hdr = *um;
+	hdr->new_sel = *m->new_sel;
+
+	if (m->encap) {
+		err = nla_put(skb, XFRMA_ENCAP, sizeof(*m->encap), m->encap);
+		if (err)
+			goto out_cancel;
+	}
+
+	if (m->xuo) {
+		err = nla_put(skb, XFRMA_OFFLOAD_DEV, sizeof(*m->xuo), m->xuo);
+		if (err)
+			goto out_cancel;
+	}
+
+	if (m->new_mark) {
+		err = nla_put(skb, XFRMA_MARK, sizeof(*m->new_mark),
+			      m->new_mark);
+		if (err)
+			goto out_cancel;
+	}
+
+	err = xfrm_smark_put(skb, &m->smark);
+	if (err)
+		goto out_cancel;
+
+	if (m->mapping_maxage) {
+		err = nla_put_u32(skb, XFRMA_MTIMER_THRESH, m->mapping_maxage);
+		if (err)
+			goto out_cancel;
+	}
+
+	if (m->nat_keepalive_interval) {
+		err = nla_put_u32(skb, XFRMA_NAT_KEEPALIVE_INTERVAL,
+				  m->nat_keepalive_interval);
+		if (err)
+			goto out_cancel;
+	}
+
+	if (dir) {
+		err = nla_put_u8(skb, XFRMA_SA_DIR, dir);
+		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(const struct xfrm_migrate *m,
+					       u8 dir)
+{
+	return NLMSG_ALIGN(sizeof(struct xfrm_user_migrate_state)) +
+		(m->encap ? nla_total_size(sizeof(struct xfrm_encap_tmpl)) : 0) +
+		(m->xuo ? nla_total_size(sizeof(struct xfrm_user_offload)) : 0) +
+		(m->new_mark ? nla_total_size(sizeof(struct xfrm_mark)) : 0) +
+		((m->smark.v | m->smark.m) ? nla_total_size(sizeof(u32)) * 2 : 0) +
+		(m->mapping_maxage ? nla_total_size(sizeof(u32)) : 0) +
+		(m->nat_keepalive_interval ? nla_total_size(sizeof(u32)) : 0) +
+		(dir ? nla_total_size(sizeof(u8)) : 0); /* XFRMA_SA_DIR */
+}
+
+static int xfrm_send_migrate_state(struct net *net,
+				   const struct xfrm_user_migrate_state *um,
+				   const struct xfrm_migrate *m,
+				   u8 dir, u32 portid, u32 seq)
+{
+	int err;
+	struct sk_buff *skb;
+
+	skb = nlmsg_new(xfrm_migrate_state_msgsize(m, dir), GFP_ATOMIC);
+	if (!skb)
+		return -ENOMEM;
+
+	err = build_migrate_state(skb, um, m, dir, portid, seq);
+	if (err < 0) {
+		kfree_skb(skb);
+		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)
+{
+	struct xfrm_user_migrate_state *um = nlmsg_data(nlh);
+	struct net *net = sock_net(skb->sk);
+	struct xfrm_user_offload xuo = {};
+	struct xfrm_migrate m = {};
+	struct xfrm_state *xc;
+	struct xfrm_state *x;
+	int err;
+
+	if (!um->id.spi) {
+		NL_SET_ERR_MSG(extack, "Invalid SPI 0x0");
+		return -EINVAL;
+	}
+
+	if (um->reserved) {
+		NL_SET_ERR_MSG(extack, "Reserved field must be zero");
+		return -EINVAL;
+	}
+
+	if (um->flags & ~XFRM_MIGRATE_STATE_KNOWN_FLAGS) {
+		NL_SET_ERR_MSG_FMT(extack, "Unknown flags: 0x%x",
+				   um->flags & ~XFRM_MIGRATE_STATE_KNOWN_FLAGS);
+		return -EINVAL;
+	}
+
+	err = verify_xfrm_family(um->new_family, extack);
+	if (err)
+		return err;
+
+	if (!(um->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL)) {
+		err = verify_selector_prefixlen(um->new_sel.family,
+						&um->new_sel, extack);
+		if (err)
+			return err;
+	}
+
+	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);
+	if (!x) {
+		NL_SET_ERR_MSG(extack, "Can not find state");
+		return -ESRCH;
+	}
+
+	if (um->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL) {
+		u8 prefixlen = (x->props.family == AF_INET6) ? 128 : 32;
+
+		if (x->sel.prefixlen_s != x->sel.prefixlen_d ||
+		    x->sel.prefixlen_d != prefixlen ||
+		    !xfrm_addr_equal(&x->sel.daddr, &x->id.daddr, x->props.family) ||
+		    !xfrm_addr_equal(&x->sel.saddr, &x->props.saddr, x->props.family)) {
+			NL_SET_ERR_MSG(extack,
+				       "SA selector is not a single-host match for SA addresses");
+			err = -EINVAL;
+			goto out;
+		}
+	}
+
+	if (attrs[XFRMA_ENCAP]) {
+		m.encap = nla_data(attrs[XFRMA_ENCAP]);
+		if (m.encap->encap_type == 0) {
+			m.encap = NULL; /* sentinel: remove encap */
+		} else if (m.encap->encap_type != UDP_ENCAP_ESPINUDP) {
+			NL_SET_ERR_MSG(extack, "Unsupported encapsulation type");
+			err = -EINVAL;
+			goto out;
+		}
+	} else {
+		m.encap = x->encap; /* omit-to-inherit */
+	}
+
+	if (attrs[XFRMA_MTIMER_THRESH]) {
+		err = verify_mtimer_thresh(!!m.encap, x->dir, extack);
+		if (err)
+			goto out;
+	}
+
+	if (nla_get_u32_default(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL], 0) && !m.encap) {
+		NL_SET_ERR_MSG(extack,
+			       "NAT_KEEPALIVE_INTERVAL requires encapsulation");
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (attrs[XFRMA_OFFLOAD_DEV]) {
+		m.xuo = nla_data(attrs[XFRMA_OFFLOAD_DEV]);
+	} else {
+		bool inherit_offload = !(um->flags & XFRM_MIGRATE_STATE_CLEAR_OFFLOAD);
+
+		if (inherit_offload && x->xso.dev) {
+			xso_to_xuo(&x->xso, &xuo);
+			m.xuo = &xuo;
+		}
+	}
+
+	if (attrs[XFRMA_MARK])
+		m.new_mark = nla_data(attrs[XFRMA_MARK]);
+
+	if (attrs[XFRMA_SET_MARK])
+		xfrm_smark_init(attrs, &m.smark);
+	else
+		m.smark = x->props.smark;
+
+	m.mapping_maxage = nla_get_u32_default(attrs[XFRMA_MTIMER_THRESH],
+					       x->mapping_maxage);
+	m.nat_keepalive_interval = nla_get_u32_default(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL],
+						       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);
+			NL_SET_ERR_MSG(extack, "New SA tuple already occupied");
+			err = -EEXIST;
+			goto out;
+		}
+	}
+
+	xc = xfrm_state_migrate_create(x, &m, net, extack);
+	if (!xc) {
+		NL_SET_ERR_MSG_WEAK(extack, "State migration clone failed");
+		err = -EINVAL;
+		goto out;
+	}
+
+	spin_lock_bh(&x->lock);
+	if (x->km.state != XFRM_STATE_VALID) {
+		spin_unlock_bh(&x->lock);
+		NL_SET_ERR_MSG(extack, "State already deleted");
+		err = -ESRCH;
+		goto out_xc;
+	}
+	xfrm_migrate_sync(xc, x); /* to prevent SN/IV reuse */
+	__xfrm_state_delete(x);
+	spin_unlock_bh(&x->lock);
+
+	err = xfrm_state_migrate_install(x, xc, &m, extack);
+	if (err < 0) {
+		/*
+		 * Should not occur: pre-check above ensures the new tuple is
+		 * free under xfrm_cfg_mutex. Both SAs are gone if it does;
+		 * restoring x would risk SN/IV reuse.
+		 */
+		goto out;
+	}
+
+	/* Restore encap cleared by sentinel (type=0) during migration. */
+	if (attrs[XFRMA_ENCAP])
+		m.encap = nla_data(attrs[XFRMA_ENCAP]);
+
+	m.new_sel = &xc->sel;
+	m.mapping_maxage = xc->mapping_maxage;
+	m.nat_keepalive_interval = xc->nat_keepalive_interval;
+
+	err = xfrm_send_migrate_state(net, um, &m, xc->dir,
+				      nlh->nlmsg_pid, nlh->nlmsg_seq);
+	if (err < 0) {
+		NL_SET_ERR_MSG(extack, "Failed to send migration notification");
+		err = 0;
+	}
+
+out:
+	xfrm_state_put(x);
+	return err;
+out_xc:
+	xc->km.state = XFRM_STATE_DEAD;
+	xfrm_state_put(xc);
+	xfrm_state_put(x);
+	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)
 {
@@ -3339,6 +3656,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);
 
@@ -3432,6 +3750,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.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 13/16] xfrm: make xfrm_dev_state_add xuo parameter const
From: Antony Antony @ 2026-05-26 19:09 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

The xuo pointer is not modified by xfrm_dev_state_add(); make it const.

Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v8->v9 : added this patch
---
 include/net/xfrm.h     | 6 ++++--
 net/xfrm/xfrm_device.c | 2 +-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 4b29ab92c2a7..5515c7b10020 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -2104,7 +2104,7 @@ void xfrm_dev_resume(struct sk_buff *skb);
 void xfrm_dev_backlog(struct softnet_data *sd);
 struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t features, bool *again);
 int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
-		       struct xfrm_user_offload *xuo,
+		       const struct xfrm_user_offload *xuo,
 		       struct netlink_ext_ack *extack);
 int xfrm_dev_policy_add(struct net *net, struct xfrm_policy *xp,
 			struct xfrm_user_offload *xuo, u8 dir,
@@ -2175,7 +2175,9 @@ static inline struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_fea
 	return skb;
 }
 
-static inline int xfrm_dev_state_add(struct net *net, struct xfrm_state *x, struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack)
+static inline int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
+				     const struct xfrm_user_offload *xuo,
+				     struct netlink_ext_ack *extack)
 {
 	return 0;
 }
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index 550457e4c4f0..630f3dd31cc5 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -229,7 +229,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
 EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
 
 int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
-		       struct xfrm_user_offload *xuo,
+		       const struct xfrm_user_offload *xuo,
 		       struct netlink_ext_ack *extack)
 {
 	int err;

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 12/16] xfrm: extract address family and selector validation helpers
From: Antony Antony @ 2026-05-26 19:09 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

Extract verify_xfrm_family() and verify_selector_prefixlen() from
verify_newsa_info() to allow reuse by other netlink handlers.

verify_xfrm_family() validates that a given address family is AF_INET
or AF_INET6 (with CONFIG_IPV6 guard).

verify_selector_prefixlen() validates that the selector prefix lengths
are within the bounds for the given address family.

No functional change.

Signed-off-by: Antony Antony <antony.antony@secunet.com>

---
v8->v9: added this patch
---
 net/xfrm/xfrm_user.c | 80 +++++++++++++++++++++++++++++-----------------------
 1 file changed, 44 insertions(+), 36 deletions(-)

diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 03fa4cabf601..c7ae670212a9 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -264,66 +264,74 @@ static int verify_mtimer_thresh(bool has_encap, u8 dir,
 	return 0;
 }
 
-static int verify_newsa_info(struct xfrm_usersa_info *p,
-			     struct nlattr **attrs,
-			     struct netlink_ext_ack *extack)
+static int verify_xfrm_family(u16 family, struct netlink_ext_ack *extack)
 {
-	int err;
-	u8 sa_dir = nla_get_u8_default(attrs[XFRMA_SA_DIR], 0);
-	u16 family = p->sel.family;
-
-	err = -EINVAL;
-	switch (p->family) {
+	switch (family) {
 	case AF_INET:
-		break;
-
+		return 0;
 	case AF_INET6:
 #if IS_ENABLED(CONFIG_IPV6)
-		break;
+		return 0;
 #else
-		err = -EAFNOSUPPORT;
 		NL_SET_ERR_MSG(extack, "IPv6 support disabled");
-		goto out;
+		return -EAFNOSUPPORT;
 #endif
-
 	default:
 		NL_SET_ERR_MSG(extack, "Invalid address family");
-		goto out;
+		return -EINVAL;
 	}
+}
 
-	if (!family && !(p->flags & XFRM_STATE_AF_UNSPEC))
-		family = p->family;
-
+static int verify_selector_prefixlen(u16 family,
+				     const struct xfrm_selector *sel,
+				     struct netlink_ext_ack *extack)
+{
 	switch (family) {
 	case AF_UNSPEC:
-		break;
-
+		return 0;
 	case AF_INET:
-		if (p->sel.prefixlen_d > 32 || p->sel.prefixlen_s > 32) {
-			NL_SET_ERR_MSG(extack, "Invalid prefix length in selector (must be <= 32 for IPv4)");
-			goto out;
+		if (sel->prefixlen_d > 32 || sel->prefixlen_s > 32) {
+			NL_SET_ERR_MSG(extack,
+				       "Invalid prefix length in selector (must be <= 32 for IPv4)");
+			return -EINVAL;
 		}
-
-		break;
-
+		return 0;
 	case AF_INET6:
 #if IS_ENABLED(CONFIG_IPV6)
-		if (p->sel.prefixlen_d > 128 || p->sel.prefixlen_s > 128) {
-			NL_SET_ERR_MSG(extack, "Invalid prefix length in selector (must be <= 128 for IPv6)");
-			goto out;
+		if (sel->prefixlen_d > 128 || sel->prefixlen_s > 128) {
+			NL_SET_ERR_MSG(extack,
+				       "Invalid prefix length in selector (must be <= 128 for IPv6)");
+			return -EINVAL;
 		}
-
-		break;
+		return 0;
 #else
 		NL_SET_ERR_MSG(extack, "IPv6 support disabled");
-		err = -EAFNOSUPPORT;
-		goto out;
+		return -EAFNOSUPPORT;
 #endif
-
 	default:
 		NL_SET_ERR_MSG(extack, "Invalid address family in selector");
-		goto out;
+		return -EINVAL;
 	}
+}
+
+static int verify_newsa_info(struct xfrm_usersa_info *p,
+			     struct nlattr **attrs,
+			     struct netlink_ext_ack *extack)
+{
+	int err;
+	u8 sa_dir = nla_get_u8_default(attrs[XFRMA_SA_DIR], 0);
+	u16 family = p->sel.family;
+
+	err = verify_xfrm_family(p->family, extack);
+	if (err)
+		goto out;
+
+	if (!family && !(p->flags & XFRM_STATE_AF_UNSPEC))
+		family = p->family;
+
+	err = verify_selector_prefixlen(family, &p->sel, extack);
+	if (err)
+		goto out;
 
 	err = -EINVAL;
 	switch (p->id.proto) {

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 11/16] xfrm: refactor XFRMA_MTIMER_THRESH validation into a helper
From: Antony Antony @ 2026-05-26 19:08 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

Extract verify_mtimer_thresh() to consolidate the XFRMA_MTIMER_THRESH
validation logic shared between the add_sa and upcoming patch.

Signed-off-by: Antony Antony <antony.antony@secunet.com>

---
v5->v6: added this patch
---
 net/xfrm/xfrm_user.c | 29 ++++++++++++++++++-----------
 1 file changed, 18 insertions(+), 11 deletions(-)

diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 273ea6fdb8ad..03fa4cabf601 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -248,6 +248,22 @@ static inline int verify_replay(struct xfrm_usersa_info *p,
 	return 0;
 }
 
+static int verify_mtimer_thresh(bool has_encap, u8 dir,
+				struct netlink_ext_ack *extack)
+{
+	if (!has_encap) {
+		NL_SET_ERR_MSG(extack,
+			       "MTIMER_THRESH requires encapsulation");
+		return -EINVAL;
+	}
+	if (dir == XFRM_SA_DIR_OUT) {
+		NL_SET_ERR_MSG(extack,
+			       "MTIMER_THRESH should not be set on output SA");
+		return -EINVAL;
+	}
+	return 0;
+}
+
 static int verify_newsa_info(struct xfrm_usersa_info *p,
 			     struct nlattr **attrs,
 			     struct netlink_ext_ack *extack)
@@ -455,18 +471,9 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
 	err = 0;
 
 	if (attrs[XFRMA_MTIMER_THRESH]) {
-		if (!attrs[XFRMA_ENCAP]) {
-			NL_SET_ERR_MSG(extack, "MTIMER_THRESH attribute can only be set on ENCAP states");
-			err = -EINVAL;
-			goto out;
-		}
-
-		if (sa_dir == XFRM_SA_DIR_OUT) {
-			NL_SET_ERR_MSG(extack,
-				       "MTIMER_THRESH attribute should not be set on output SA");
-			err = -EINVAL;
+		err = verify_mtimer_thresh(!!attrs[XFRMA_ENCAP], sa_dir, extack);
+		if (err)
 			goto out;
-		}
 	}
 
 	if (sa_dir == XFRM_SA_DIR_OUT) {

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 10/16] xfrm: move encap and xuo into struct xfrm_migrate
From: Antony Antony @ 2026-05-26 19:08 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

In preparation for an upcoming patch, move the xfrm_encap_tmpl and
xfrm_user_offload pointers from separate parameters into struct
xfrm_migrate, reducing the parameter count of
xfrm_state_migrate_create(), xfrm_state_migrate_install()
and xfrm_state_migrate()

The fields are placed after the four xfrm_address_t members where
the struct is naturally 8-byte aligned, avoiding padding.

No functional change.

Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v8->v9: fixed the commit message
v5->v6: added this patch.
---
 include/net/xfrm.h     |  7 ++-----
 net/xfrm/xfrm_policy.c |  4 +++-
 net/xfrm/xfrm_state.c  | 20 +++++++-------------
 3 files changed, 12 insertions(+), 19 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index be22c26e4661..4b29ab92c2a7 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -682,6 +682,8 @@ struct xfrm_migrate {
 	xfrm_address_t		old_saddr;
 	xfrm_address_t		new_daddr;
 	xfrm_address_t		new_saddr;
+	struct xfrm_encap_tmpl *encap;
+	struct xfrm_user_offload *xuo;
 	u8			proto;
 	u8			mode;
 	u16			reserved;
@@ -1897,20 +1899,15 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n
 						u32 if_id);
 struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
 					     const struct xfrm_migrate *m,
-					     const struct xfrm_encap_tmpl *encap,
 					     struct net *net,
-					     struct xfrm_user_offload *xuo,
 					     struct netlink_ext_ack *extack);
 int xfrm_state_migrate_install(const struct xfrm_state *x,
 			       struct xfrm_state *xc,
 			       const struct xfrm_migrate *m,
-			       struct xfrm_user_offload *xuo,
 			       struct netlink_ext_ack *extack);
 struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
 				      struct xfrm_migrate *m,
-				      struct xfrm_encap_tmpl *encap,
 				      struct net *net,
-				      struct xfrm_user_offload *xuo,
 				      struct netlink_ext_ack *extack);
 int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
 		 struct xfrm_migrate *m, int num_bundles,
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index fd505adf080e..cf05d778e2dd 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4680,7 +4680,9 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
 		if ((x = xfrm_migrate_state_find(mp, net, if_id))) {
 			x_cur[nx_cur] = x;
 			nx_cur++;
-			xc = xfrm_state_migrate(x, mp, encap, net, xuo, extack);
+			mp->encap = encap;
+			mp->xuo = xuo;
+			xc = xfrm_state_migrate(x, mp, net, extack);
 			if (xc) {
 				x_new[nx_new] = xc;
 				nx_new++;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 1db48ecda80d..043e573c4f32 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1966,7 +1966,6 @@ 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,
-					   const struct xfrm_encap_tmpl *encap,
 					   const struct xfrm_migrate *m)
 {
 	struct net *net = xs_net(orig);
@@ -2008,8 +2007,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 	}
 	x->props.calgo = orig->props.calgo;
 
-	if (encap) {
-		x->encap = kmemdup(encap, sizeof(*x->encap), GFP_KERNEL);
+	if (m->encap) {
+		x->encap = kmemdup(m->encap, sizeof(*x->encap), GFP_KERNEL);
 		if (!x->encap)
 			goto error;
 		x->mapping_maxage = orig->mapping_maxage;
@@ -2122,14 +2121,12 @@ EXPORT_SYMBOL(xfrm_migrate_state_find);
 
 struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
 					     const struct xfrm_migrate *m,
-					     const struct xfrm_encap_tmpl *encap,
 					     struct net *net,
-					     struct xfrm_user_offload *xuo,
 					     struct netlink_ext_ack *extack)
 {
 	struct xfrm_state *xc;
 
-	xc = xfrm_state_clone_and_setup(x, encap, m);
+	xc = xfrm_state_clone_and_setup(x, m);
 	if (!xc) {
 		NL_SET_ERR_MSG(extack, "Failed to clone and setup state");
 		return NULL;
@@ -2141,7 +2138,7 @@ struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
 	}
 
 	/* configure the hardware if offload is requested */
-	if (xuo && xfrm_dev_state_add(net, xc, xuo, extack))
+	if (m->xuo && xfrm_dev_state_add(net, xc, m->xuo, extack))
 		goto error;
 
 	return xc;
@@ -2155,7 +2152,6 @@ EXPORT_SYMBOL(xfrm_state_migrate_create);
 int xfrm_state_migrate_install(const struct xfrm_state *x,
 			       struct xfrm_state *xc,
 			       const struct xfrm_migrate *m,
-			       struct xfrm_user_offload *xuo,
 			       struct netlink_ext_ack *extack)
 {
 	if (m->new_family == m->old_family &&
@@ -2168,7 +2164,7 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,
 	} else {
 		if (xfrm_state_add(xc) < 0) {
 			NL_SET_ERR_MSG(extack, "Failed to add migrated state");
-			if (xuo)
+			if (m->xuo)
 				xfrm_dev_state_delete(xc);
 			xc->km.state = XFRM_STATE_DEAD;
 			xfrm_state_put(xc);
@@ -2182,20 +2178,18 @@ EXPORT_SYMBOL(xfrm_state_migrate_install);
 
 struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
 				      struct xfrm_migrate *m,
-				      struct xfrm_encap_tmpl *encap,
 				      struct net *net,
-				      struct xfrm_user_offload *xuo,
 				      struct netlink_ext_ack *extack)
 {
 	struct xfrm_state *xc;
 
-	xc = xfrm_state_migrate_create(x, m, encap, net, xuo, extack);
+	xc = xfrm_state_migrate_create(x, m, net, extack);
 	if (!xc)
 		return NULL;
 
 	xfrm_migrate_sync(xc, x);
 
-	if (xfrm_state_migrate_install(x, xc, m, xuo, extack) < 0)
+	if (xfrm_state_migrate_install(x, xc, m, extack) < 0)
 		return NULL;
 
 	return xc;

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 09/16] xfrm: add error messages to state migration
From: Antony Antony @ 2026-05-26 19:08 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

Add descriptive(extack) error messages for all error paths
in state migration. This improves diagnostics by
providing clear feedback when migration fails.

After xfrm_init_state() use NL_SET_ERR_MSG_WEAK() as fallback for
error paths not yet propagating extack e.g. mode_cbs->init_state()

No functional change.

Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v5->v6: - in case dev_state_add() extack already set
	- after xfrm_init_state() use NL_SET_ERR_MSG_WEAK() as fallback
v4->v5: - added this patch
---
 net/xfrm/xfrm_state.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index fcf6f0c6400d..1db48ecda80d 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2130,11 +2130,15 @@ struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
 	struct xfrm_state *xc;
 
 	xc = xfrm_state_clone_and_setup(x, encap, m);
-	if (!xc)
+	if (!xc) {
+		NL_SET_ERR_MSG(extack, "Failed to clone and setup state");
 		return NULL;
+	}
 
-	if (xfrm_init_state(xc, extack) < 0)
+	if (xfrm_init_state(xc, extack) < 0) {
+		NL_SET_ERR_MSG_WEAK(extack, "Failed to initialize migrated state");
 		goto error;
+	}
 
 	/* configure the hardware if offload is requested */
 	if (xuo && xfrm_dev_state_add(net, xc, xuo, extack))
@@ -2163,6 +2167,7 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,
 		xfrm_state_insert(xc);
 	} else {
 		if (xfrm_state_add(xc) < 0) {
+			NL_SET_ERR_MSG(extack, "Failed to add migrated state");
 			if (xuo)
 				xfrm_dev_state_delete(xc);
 			xc->km.state = XFRM_STATE_DEAD;

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 08/16] xfrm: add state synchronization after migration
From: Antony Antony @ 2026-05-26 19:08 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

Add xfrm_migrate_sync() to copy curlft and replay state from the old SA
to the new one before installation. The function allocates no memory, so
it can be called under a spinlock. In preparation for a subsequent patch
in this series.

A subsequent patch calls this under x->lock, atomically capturing the
latest lifetime counters and replay state from the original SA and
deleting it in the same critical section to prevent SN/IV reuse
for XFRM_MSG_MIGRATE_STATE method.

No functional change.

Signed-off-by: Antony Antony <antony.antony@secunet.com>

---
v6->v7: - rephrase commit message
v5->v6: - move the sync before install to avoid overwriting
v4->v5: - added this patch
---
 include/net/xfrm.h    | 46 +++++++++++++++++++++++++++++++++++++---------
 net/xfrm/xfrm_state.c | 11 ++++-------
 2 files changed, 41 insertions(+), 16 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 4137986f15e2..be22c26e4661 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -2024,23 +2024,51 @@ static inline unsigned int xfrm_replay_state_esn_len(struct xfrm_replay_state_es
 
 #ifdef CONFIG_XFRM_MIGRATE
 static inline int xfrm_replay_clone(struct xfrm_state *x,
-				     struct xfrm_state *orig)
+				    const struct xfrm_state *orig)
 {
+	/* Counters synced later in xfrm_replay_sync() */
 
-	x->replay_esn = kmemdup(orig->replay_esn,
+	x->replay = orig->replay;
+	x->preplay = orig->preplay;
+
+	if (orig->replay_esn) {
+		x->replay_esn = kmemdup(orig->replay_esn,
 				xfrm_replay_state_esn_len(orig->replay_esn),
 				GFP_KERNEL);
-	if (!x->replay_esn)
-		return -ENOMEM;
-	x->preplay_esn = kmemdup(orig->preplay_esn,
-				 xfrm_replay_state_esn_len(orig->preplay_esn),
-				 GFP_KERNEL);
-	if (!x->preplay_esn)
-		return -ENOMEM;
+		if (!x->replay_esn)
+			return -ENOMEM;
+		x->preplay_esn = kmemdup(orig->preplay_esn,
+				xfrm_replay_state_esn_len(orig->preplay_esn),
+				GFP_KERNEL);
+		if (!x->preplay_esn)
+			return -ENOMEM;
+	}
 
 	return 0;
 }
 
+static inline void xfrm_replay_sync(struct xfrm_state *x, const struct xfrm_state *orig)
+{
+	x->replay = orig->replay;
+	x->preplay = orig->preplay;
+
+	if (orig->replay_esn) {
+		memcpy(x->replay_esn, orig->replay_esn,
+				xfrm_replay_state_esn_len(orig->replay_esn));
+
+		memcpy(x->preplay_esn, orig->preplay_esn,
+				xfrm_replay_state_esn_len(orig->preplay_esn));
+	}
+}
+
+static inline void xfrm_migrate_sync(struct xfrm_state *x,
+					  const struct xfrm_state *orig)
+{
+	/* called under lock so no race conditions or mallocs allowed */
+	memcpy(&x->curlft, &orig->curlft, sizeof(x->curlft));
+	xfrm_replay_sync(x, orig);
+}
+
 static inline struct xfrm_algo_aead *xfrm_algo_aead_clone(struct xfrm_algo_aead *orig)
 {
 	return kmemdup(orig, aead_len(orig), GFP_KERNEL);
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 327a855253e6..fcf6f0c6400d 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2027,10 +2027,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 			goto error;
 	}
 
-	if (orig->replay_esn) {
-		if (xfrm_replay_clone(x, orig))
-			goto error;
-	}
+	if (xfrm_replay_clone(x, orig))
+		goto error;
 
 	memcpy(&x->mark, &orig->mark, sizeof(x->mark));
 	memcpy(&x->props.smark, &orig->props.smark, sizeof(x->props.smark));
@@ -2043,11 +2041,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 	x->tfcpad = orig->tfcpad;
 	x->replay_maxdiff = orig->replay_maxdiff;
 	x->replay_maxage = orig->replay_maxage;
-	memcpy(&x->curlft, &orig->curlft, sizeof(x->curlft));
 	x->km.state = orig->km.state;
 	x->km.seq = orig->km.seq;
-	x->replay = orig->replay;
-	x->preplay = orig->preplay;
 	x->lastused = orig->lastused;
 	x->new_mapping = 0;
 	x->new_mapping_sport = 0;
@@ -2193,6 +2188,8 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
 	if (!xc)
 		return NULL;
 
+	xfrm_migrate_sync(xc, x);
+
 	if (xfrm_state_migrate_install(x, xc, m, xuo, extack) < 0)
 		return NULL;
 

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 07/16] xfrm: check family before comparing addresses in migrate
From: Antony Antony @ 2026-05-26 19:07 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

When migrating between different address families, xfrm_addr_equal()
cannot meaningfully compare addresses, different lengths.
Only call xfrm_addr_equal() when families match, and take
the xfrm_state_insert() path when addresses are equal.

Fixes: 80c9abaabf42 ("[XFRM]: Extension for dynamic update of endpoint address(es)")

Signed-off-by: Antony Antony <antony.antony@secunet.com>

---
v8->v9: remove comment reflow, moved to previous patch (Sabrina)
v5->v6: added this patch
---
 net/xfrm/xfrm_state.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 85fd80520184..327a855253e6 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2159,10 +2159,11 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,
 			       struct xfrm_user_offload *xuo,
 			       struct netlink_ext_ack *extack)
 {
-	if (xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
+	if (m->new_family == m->old_family &&
+	    xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
 		/*
-		 * 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 of the state is
+		 * to be updated as it is a part of triplet.
 		 */
 		xfrm_state_insert(xc);
 	} else {

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 06/16] xfrm: split xfrm_state_migrate into create and install functions
From: Antony Antony @ 2026-05-26 19:07 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

To prepare for subsequent patches, split
xfrm_state_migrate() into two functions:
- xfrm_state_migrate_create(): creates the migrated state
- xfrm_state_migrate_install(): installs it into the state table

splitting will help to avoid SN/IV reuse when migrating AEAD SA.

And add const whenever possible.
No functional change.

Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v8->v9: move comment reflow into this patch (Sabrina)
v4->v5: added this patch
---
 include/net/xfrm.h    | 11 ++++++++
 net/xfrm/xfrm_state.c | 73 +++++++++++++++++++++++++++++++++++++--------------
 2 files changed, 64 insertions(+), 20 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 368b1dc22e5c..4137986f15e2 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1895,6 +1895,17 @@ int km_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
 	       const struct xfrm_encap_tmpl *encap);
 struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *net,
 						u32 if_id);
+struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
+					     const struct xfrm_migrate *m,
+					     const struct xfrm_encap_tmpl *encap,
+					     struct net *net,
+					     struct xfrm_user_offload *xuo,
+					     struct netlink_ext_ack *extack);
+int xfrm_state_migrate_install(const struct xfrm_state *x,
+			       struct xfrm_state *xc,
+			       const struct xfrm_migrate *m,
+			       struct xfrm_user_offload *xuo,
+			       struct netlink_ext_ack *extack);
 struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
 				      struct xfrm_migrate *m,
 				      struct xfrm_encap_tmpl *encap,
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 5424f2becbaf..85fd80520184 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1966,8 +1966,8 @@ 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 net *net = xs_net(orig);
 	struct xfrm_state *x = xfrm_state_alloc(net);
@@ -2125,12 +2125,12 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n
 }
 EXPORT_SYMBOL(xfrm_migrate_state_find);
 
-struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
-				      struct xfrm_migrate *m,
-				      struct xfrm_encap_tmpl *encap,
-				      struct net *net,
-				      struct xfrm_user_offload *xuo,
-				      struct netlink_ext_ack *extack)
+struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
+					     const struct xfrm_migrate *m,
+					     const struct xfrm_encap_tmpl *encap,
+					     struct net *net,
+					     struct xfrm_user_offload *xuo,
+					     struct netlink_ext_ack *extack)
 {
 	struct xfrm_state *xc;
 
@@ -2145,24 +2145,57 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
 	if (xuo && xfrm_dev_state_add(net, xc, xuo, extack))
 		goto error;
 
-	/* add state */
+	return xc;
+error:
+	xc->km.state = XFRM_STATE_DEAD;
+	xfrm_state_put(xc);
+	return NULL;
+}
+EXPORT_SYMBOL(xfrm_state_migrate_create);
+
+int xfrm_state_migrate_install(const struct xfrm_state *x,
+			       struct xfrm_state *xc,
+			       const struct xfrm_migrate *m,
+			       struct xfrm_user_offload *xuo,
+			       struct netlink_ext_ack *extack)
+{
 	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 */
+		/*
+		 * Care is needed when the destination address
+		 * 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)
-			goto error_add;
+		if (xfrm_state_add(xc) < 0) {
+			if (xuo)
+				xfrm_dev_state_delete(xc);
+			xc->km.state = XFRM_STATE_DEAD;
+			xfrm_state_put(xc);
+			return -EEXIST;
+		}
 	}
 
+	return 0;
+}
+EXPORT_SYMBOL(xfrm_state_migrate_install);
+
+struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
+				      struct xfrm_migrate *m,
+				      struct xfrm_encap_tmpl *encap,
+				      struct net *net,
+				      struct xfrm_user_offload *xuo,
+				      struct netlink_ext_ack *extack)
+{
+	struct xfrm_state *xc;
+
+	xc = xfrm_state_migrate_create(x, m, encap, net, xuo, extack);
+	if (!xc)
+		return NULL;
+
+	if (xfrm_state_migrate_install(x, xc, m, xuo, extack) < 0)
+		return NULL;
+
 	return xc;
-error_add:
-	if (xuo)
-		xfrm_dev_state_delete(xc);
-error:
-	xc->km.state = XFRM_STATE_DEAD;
-	xfrm_state_put(xc);
-	return NULL;
 }
 EXPORT_SYMBOL(xfrm_state_migrate);
 #endif

-- 
2.47.3


^ permalink raw reply related

* Re: [devel-ipsec] Re: [PATCH ipsec-next v8 12/14] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
From: Antony Antony @ 2026-05-26 19:01 UTC (permalink / raw)
  To: Sabrina Dubroca
  Cc: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan, netdev,
	linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan, devel
In-Reply-To: <agGdwbo2GFhPP78z@krikkit>

On Mon, May 11, 2026 at 11:13:37AM +0200, Sabrina Dubroca wrote:
> 2026-05-05, 06:34:29 +0200, Antony Antony wrote:
>
> [...]
>  include/net/xfrm.h          |  16 ++-
>  include/uapi/linux/xfrm.h   |  21 ++++
>  net/xfrm/xfrm_device.c      |   2 +-
>  net/xfrm/xfrm_policy.c      |  19 +++
>  net/xfrm/xfrm_state.c       |  29 +++--
>  net/xfrm/xfrm_user.c        | 281 +++++++++++++++++++++++++++++++++++++++++++-
>  security/selinux/nlmsgtab.c |   3 +-
>  7 files changed, 357 insertions(+), 14 deletions(-)
>
> If the omission of xfrm_compat.c is intentional, maybe worth
> making a note of that?

Fixed in v9.

> diff --git a/include/net/xfrm.h b/include/net/xfrm.h
> @@ -684,12 +684,20 @@ struct xfrm_migrate {
> +	struct xfrm_mark        old_mark;
> +	struct xfrm_mark       *new_mark;
> +	struct xfrm_mark        smark;
> +	u16			msg_type;
> +	u32			flags;
> +	u32			new_reqid;
> +	u32			nat_keepalive_interval;
> +	u32			mapping_maxage;
> +	const struct xfrm_selector *new_sel;
>
> afkey doesn't zero its array of xfrm_migrate, so those new fields will
> contain garbage there. Hopefully nobody is using it, but...

Fixed in v9: set msg_type = XFRM_MSG_MIGRATE explicitly in
xfrm_migrate_copy_old() so the PF_KEY path always takes the correct
selector branch regardless of whether the caller zeroes the array.

> @@ -2104,7 +2112,7 @@
> -		       struct xfrm_user_offload *xuo,
> +		       const struct xfrm_user_offload *xuo,
>
> nit: unrelated clean up

Split out into a separate patch in v9.

> +/* Flags for xfrm_user_migrate_state.flags */
> +enum xfrm_migrate_state_flags {
> +	XFRM_MIGRATE_STATE_NO_OFFLOAD = 1,
>
> nit: maybe XFRM_MIGRATE_STATE_CLEAR_OFFLOAD?

Done in v9.

> +	XFRM_MIGRATE_STATE_UPDATE_SEL = 2,
>
> "update sel" to me sounds more like "overwrite the whole thing" than
> "copy some bits, fix up others". The name is already long, but maybe
> "XFRM_MIGRATE_STATE_UPDATE_H2H_SEL"?

Done in v9.

> +static void xfrm_migrate_copy_old(struct xfrm_migrate *mp,
> +				  const struct xfrm_state *x,
> +				  struct xfrm_mark *new_mark_buf)
> +{
> +	*new_mark_buf              = x->mark;
> +	mp->new_mark               = new_mark_buf;
>
> Do you really need a separate buffer for that? Or could you just use
>     mp->new_mark = &x->mark;
> and skip the new_marks array in xfrm_migrate()?
> I find that new_marks array quite ugly, so I'd like to get rid of
> it. If that doesn't work, I'd prefer to stuff new_mark_buf directly
> inside struct xfrm_migrate, and then set mp->new_mark pointing to it.

Done in v9: new_marks[] removed, mp->new_mark = &x->mark directly.
new_mark in struct xfrm_migrate changed to const struct xfrm_mark *.

> +			xfrm_migrate_copy_old(mp, x, &new_marks[i]);
>
> nit: maybe swap mp and x, just to match the order of xfrm_state_migrate()?
>
> It would also be a bit easier to review if you split this refactoring
> (and the corresponding changes to xfrm_state_clone_and_setup) into a
> separate patch.

> -	memcpy(&x->sel, &orig->sel, sizeof(x->sel));
> +	...
> +		} else {
> +			x->sel = *m->new_sel;
>
> nit: the mix of copy styles (memcpy and struct assignment) within this
> function, but especially here for x->sel, is a bit unpleasant.

fixed.

> -	struct xfrm_migrate m[XFRM_MAX_DEPTH];
> +	struct xfrm_migrate m[XFRM_MAX_DEPTH] = {};
>
> I'm not really opposed to this change, but what prompted it?

It was prompted after v6 review — when xuo was an embedded struct,
mp->xuo.ifindex could be uninitialized when xuo was NULL. However, in
v9 xuo reverted to a pointer, so the = {} is no longer necessary, kept it
as defensive programming.


> +	if ((um->flags & XFRM_MIGRATE_STATE_NO_OFFLOAD) &&
> +	    attrs[XFRMA_OFFLOAD_DEV]) {
>
> Not a strong objection, but they don't really have to be? "don't
> inherit and set it from the one provided" sounds ok.
> XFRMA_OFFLOAD_DEV with !XFRM_MIGRATE_STATE_NO_OFFLOAD (inherit and
> also set from request) seems more problematic.

Agreed. Exclusivity check dropped in v9. XFRMA_OFFLOAD_DEV takes
precedence via if/else so NO_OFFLOAD is redundant when both are set.
The "inherit AND set" case doesn't exist in the code.

> +		if (x->sel.prefixlen_s != x->sel.prefixlen_d ||
> +		    x->sel.prefixlen_d != prefixlen ||
> +		    !xfrm_addr_equal(&x->sel.daddr, &x->id.daddr, x->sel.family) ||
> +		    !xfrm_addr_equal(&x->sel.saddr, &x->props.saddr, x->sel.family)) {
>
> I think we need to be careful about families here too. id and sel
> could have different ones.

Fixed in v9: use x->props.family for prefixlen and xfrm_addr_equal.
AF_UNSPEC selector falls through to IPv4 comparison — this fixes it.
Mixed-family transport mode sounds odd and hopefully not allowed in
practice; this fix rejects it naturally.

> +	if (attrs[XFRMA_NAT_KEEPALIVE_INTERVAL] &&
> +	    nla_get_u32(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL]) && !m.encap) {
>
> if (nla_get_u32_default(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL], 0) && !m.encap)

> +	} else if (!(um->flags & XFRM_MIGRATE_STATE_NO_OFFLOAD) && x->xso.dev) {
>
> nit: this would be a bit more readable with
>     bool inherit_offload = !(um->flags & XFRM_MIGRATE_STATE_NO_OFFLOAD);
>
> copy_user_offload is doing almost exactly the same thing (copy from
> and xso to an xuo). It would be better to extract some helper
> (xso_to_xuo() ?) and use it in both places, otherwise they'll almost
> certainly get out of sync.

Done in v9.

> +	m.mapping_maxage = attrs[XFRMA_MTIMER_THRESH] ?
> +		nla_get_u32(attrs[XFRMA_MTIMER_THRESH]) : x->mapping_maxage;
>
> m.mapping_maxage = nla_get_u32_default(attrs[XFRMA_MTIMER_THRESH], x->mapping_maxage);

thanks.

> +	m.nat_keepalive_interval = attrs[XFRMA_NAT_KEEPALIVE_INTERVAL] ?
> +		nla_get_u32(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL]) :
> +		x->nat_keepalive_interval;
>
> m.nat_keepalive_interval = nla_get_u32_default(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL], x->nat_keepalive_interval);

fixed in v9

-antony

^ permalink raw reply

* [PATCH ipsec-next v9 05/16] xfrm: rename reqid in xfrm_migrate
From: Antony Antony @ 2026-05-26 19:07 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

In preparation for a later patch in this series s/reqid/old_reqid/.
No functional change.

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 0c035955d87d..368b1dc22e5c 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 842bf5786e3f..1f0201d97b4f 100644
--- a/net/key/af_key.c
+++ b/net/key/af_key.c
@@ -2554,7 +2554,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));
@@ -3655,15 +3655,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 c944327ce66c..fd505adf080e 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4538,7 +4538,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:
@@ -4632,7 +4632,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 b9de931d84c1..5424f2becbaf 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2081,14 +2081,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, xfrm_state_deref_prot(net->xfrm.state_bydst, net) + 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 ae144d1e4a65..273ea6fdb8ad 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3104,7 +3104,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;
@@ -3187,7 +3187,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.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 04/16] xfrm: fix NAT-related field inheritance in SA migration
From: Antony Antony @ 2026-05-26 19:07 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

During SA migration via xfrm_state_clone_and_setup(),
nat_keepalive_interval was silently dropped and never copied to the new
SA. mapping_maxage was unconditionally copied even when migrating to a
non-encapsulated SA.

Both fields are only meaningful when UDP encapsulation (NAT-T) is in
use. Move mapping_maxage and add nat_keepalive_interval inside the
existing if (encap) block, so both are inherited when migrating with
encapsulation and correctly absent when migrating without it.

Fixes: f531d13bdfe3 ("xfrm: support sending NAT keepalives in ESP in UDP states")
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v5->v6: added this patch
---
 net/xfrm/xfrm_state.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 933541bc9093..b9de931d84c1 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2012,6 +2012,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 		x->encap = kmemdup(encap, sizeof(*x->encap), GFP_KERNEL);
 		if (!x->encap)
 			goto error;
+		x->mapping_maxage = orig->mapping_maxage;
+		x->nat_keepalive_interval = orig->nat_keepalive_interval;
 	}
 
 	if (orig->security)
@@ -2046,7 +2048,6 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 	x->km.seq = orig->km.seq;
 	x->replay = orig->replay;
 	x->preplay = orig->preplay;
-	x->mapping_maxage = orig->mapping_maxage;
 	x->lastused = orig->lastused;
 	x->new_mapping = 0;
 	x->new_mapping_sport = 0;

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 03/16] xfrm: allow migration from UDP encapsulated to non-encapsulated ESP
From: Antony Antony @ 2026-05-26 19:06 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

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.

Note: PF_KEY's SADB_X_MIGRATE always passes encap=NULL and never
supported encapsulation in migration. PF_KEY is deprecated and was
in feature freeze when UDP encapsulation was added to xfrm.

Tested-by: Yan Yan <evitayan@google.com>
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
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 53d88b87bdbd..933541bc9093 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.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 02/16] xfrm: add extack to xfrm_init_state
From: Antony Antony @ 2026-05-26 19:06 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

Add a struct extack parameter to xfrm_init_state() and pass it
through to __xfrm_init_state(). This allows validation errors detected
during state initialization to propagate meaningful error messages back
to userspace.

xfrm_state_migrate() now passes extack so that errors from the
XFRM_MSG_MIGRATE_STATE path are properly reported. Callers without an
extack context (af_key, ipcomp4, ipcomp6) pass NULL, preserving their
existing behaviour.

Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v8->v9: fix commit message
v5->v6: added this patch
---
 include/net/xfrm.h    | 2 +-
 net/ipv4/ipcomp.c     | 2 +-
 net/ipv6/ipcomp6.c    | 2 +-
 net/key/af_key.c      | 2 +-
 net/xfrm/xfrm_state.c | 6 +++---
 5 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 10d3edde6b2f..0c035955d87d 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1774,7 +1774,7 @@ u32 xfrm_replay_seqhi(struct xfrm_state *x, __be32 net_seq);
 int xfrm_init_replay(struct xfrm_state *x, struct netlink_ext_ack *extack);
 u32 xfrm_state_mtu(struct xfrm_state *x, int mtu);
 int __xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack);
-int xfrm_init_state(struct xfrm_state *x);
+int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack);
 int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type);
 int xfrm_input_resume(struct sk_buff *skb, int nexthdr);
 int xfrm_trans_queue_net(struct net *net, struct sk_buff *skb,
diff --git a/net/ipv4/ipcomp.c b/net/ipv4/ipcomp.c
index 9a45aed508d1..b1ea2d37e8c5 100644
--- a/net/ipv4/ipcomp.c
+++ b/net/ipv4/ipcomp.c
@@ -77,7 +77,7 @@ static struct xfrm_state *ipcomp_tunnel_create(struct xfrm_state *x)
 	memcpy(&t->mark, &x->mark, sizeof(t->mark));
 	t->if_id = x->if_id;
 
-	if (xfrm_init_state(t))
+	if (xfrm_init_state(t, NULL))
 		goto error;
 
 	atomic_set(&t->tunnel_users, 1);
diff --git a/net/ipv6/ipcomp6.c b/net/ipv6/ipcomp6.c
index 8607569de34f..b340d67eb1d9 100644
--- a/net/ipv6/ipcomp6.c
+++ b/net/ipv6/ipcomp6.c
@@ -95,7 +95,7 @@ static struct xfrm_state *ipcomp6_tunnel_create(struct xfrm_state *x)
 	memcpy(&t->mark, &x->mark, sizeof(t->mark));
 	t->if_id = x->if_id;
 
-	if (xfrm_init_state(t))
+	if (xfrm_init_state(t, NULL))
 		goto error;
 
 	atomic_set(&t->tunnel_users, 1);
diff --git a/net/key/af_key.c b/net/key/af_key.c
index a166a88d8788..842bf5786e3f 100644
--- a/net/key/af_key.c
+++ b/net/key/af_key.c
@@ -1299,7 +1299,7 @@ static struct xfrm_state * pfkey_msg2xfrm_state(struct net *net,
 		}
 	}
 
-	err = xfrm_init_state(x);
+	err = xfrm_init_state(x, NULL);
 	if (err)
 		goto out;
 
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 9417a025270c..53d88b87bdbd 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2143,7 +2143,7 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
 	if (!xc)
 		return NULL;
 
-	if (xfrm_init_state(xc) < 0)
+	if (xfrm_init_state(xc, extack) < 0)
 		goto error;
 
 	/* configure the hardware if offload is requested */
@@ -3238,11 +3238,11 @@ int __xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
 
 EXPORT_SYMBOL(__xfrm_init_state);
 
-int xfrm_init_state(struct xfrm_state *x)
+int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
 {
 	int err;
 
-	err = __xfrm_init_state(x, NULL);
+	err = __xfrm_init_state(x, extack);
 	if (err)
 		return err;
 

-- 
2.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 01/16] xfrm: remove redundant assignments
From: Antony Antony @ 2026-05-26 19:05 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v9-0-ad9947e4ae74@secunet.com>

These 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));

Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
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 1748d374abca..9417a025270c 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.47.3


^ permalink raw reply related

* [PATCH ipsec-next v9 00/16] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message
From: Antony Antony @ 2026-05-26 19:05 UTC (permalink / raw)
  To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
  Cc: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
	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 daemons other than the IKE daemon).

Mandatory SA selector list
The current API requires a non-empty SA selector list, which does not
reflect the 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.

SA Selector, x->sel, can't be changed, especially Transport mode.

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.
This method also allows migtrating SA selectors typically used with
host-to-host in Transport mode.

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.

If the target SA tuple (daddr, SPI, proto, family) is already
occupied, the operation returns -EEXIST. In this case the original
SA is not preserved. Userspace must handle -EEXIST by
re-establishing the SA at the IKE level and manage policies.

---
v8->v9: address v8 review Sabrina and Steffen
	- split const xuo cleanup
	- input validation

Link to v8: https://lore.kernel.org/all/migrate-state-v8-0-4578fb016965@secunet.com/
v7->v8: - removed the unknown-flags validation block

Link to v7: https://patch.msgid.link/migrate-state-v7-14-44eb2440b91c@secunet.com
v6->v7: - add SA selectoor migration
	- fixes to commit messages
	- white space removal

Link to v6: https://lore.kernel.org/r/migrate-state-v6-0-9df9764ddb9e@secunet.com
v5->v6: - add mark to look up SA.
	- restrict netlink attributes in new method
	- address review feedback from Sabrina
	- add new patch to fix existing inter-family address comparison
	- add extack xfrm_state_init()
	- Feedback from Yan : omit-to-inherit add migrating marks
	- Drop missing __rcu annotation on nlsk, Sabrina has a better patch

Link to v5: https://lore.kernel.org/all/cover.1769509130.git.antony.antony@secunet.com/
v4->v5: add synchronize after migrate and delete it inside a lock
	- split xfrm_state_migrate into create and install functions

Link to v4: https://lore.kernel.org/all/cover.1768811736.git.antony.antony@secunet.com/
v3->v4: add patch to fix pre-existing missing __rcu annotation on nlsk

v2->v3: - fix commit message formatting

Link to v2: https://lore.kernel.org/all/cover.1768462955.git.antony.antony@secunet.com/

Lint to v1: https://lore.kernel.org/all/cover.1767964254.git.antony@moon.secunet.de/
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

---
Antony Antony (16):
      xfrm: remove redundant assignments
      xfrm: add extack to xfrm_init_state
      xfrm: allow migration from UDP encapsulated to non-encapsulated ESP
      xfrm: fix NAT-related field inheritance in SA migration
      xfrm: rename reqid in xfrm_migrate
      xfrm: split xfrm_state_migrate into create and install functions
      xfrm: check family before comparing addresses in migrate
      xfrm: add state synchronization after migration
      xfrm: add error messages to state migration
      xfrm: move encap and xuo into struct xfrm_migrate
      xfrm: refactor XFRMA_MTIMER_THRESH validation into a helper
      xfrm: extract address family and selector validation helpers
      xfrm: make xfrm_dev_state_add xuo parameter const
      xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
      xfrm: restrict netlink attributes for XFRM_MSG_MIGRATE_STATE
      xfrm: add documentation for XFRM_MSG_MIGRATE_STATE

 Documentation/networking/xfrm/index.rst            |   1 +
 .../networking/xfrm/xfrm_migrate_state.rst         | 274 ++++++++++++
 include/net/xfrm.h                                 |  78 +++-
 include/uapi/linux/xfrm.h                          |  25 ++
 net/ipv4/ipcomp.c                                  |   2 +-
 net/ipv6/ipcomp6.c                                 |   2 +-
 net/key/af_key.c                                   |  12 +-
 net/xfrm/xfrm_compat.c                             |   5 +-
 net/xfrm/xfrm_device.c                             |   2 +-
 net/xfrm/xfrm_policy.c                             |  25 +-
 net/xfrm/xfrm_state.c                              | 144 ++++---
 net/xfrm/xfrm_user.c                               | 468 ++++++++++++++++++---
 security/selinux/nlmsgtab.c                        |   3 +-
 13 files changed, 903 insertions(+), 138 deletions(-)
---
base-commit: a77d172177f3754ebd70123c78c75a6efa9eec2a
change-id: migrate-state-063ee0342680

Best regards,
--  
Antony Antony <antony.antony@secunet.com>


^ permalink raw reply

* Re: [devel-ipsec] Re: [PATCH ipsec-next v8 14/14] xfrm: add documentation for XFRM_MSG_MIGRATE_STATE
From: Antony Antony @ 2026-05-26 19:02 UTC (permalink / raw)
  To: Sabrina Dubroca
  Cc: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, Jonathan Corbet, Shuah Khan, netdev,
	linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan, devel
In-Reply-To: <agHSPUBZg0tHezM-@krikkit>

On Mon, May 11, 2026 at 02:57:33PM +0200, Sabrina Dubroca wrote:
> Overall a very document, thanks. Some comments:
>
> 2026-05-05, 06:34:55 +0200, Antony Antony wrote:
> > +    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_selector   new_sel;  /* new selector (see Flags) */
> > +        __u32                  new_reqid;
> > +        __u32                  flags;    /* XFRM_MIGRATE_STATE_* */
> > +        __u16                  new_family;
> > +        __u16                  reserved;
> > +    };
>
> Thinking about the UAPI a bit more, maybe this would be a good time to
> start introducing a "proper" netlink API for XFRM? Instead of having
> the main properties in a fixed struct, and a few attributes as an
> afterthought, use attributes as the main way to exchange information?
>
> Then we can start adding the attributes as alternative to the fixed
> headers in some other ops, and later start deprecating the current
> API?
>
> (for some reason this thought only popped up when I had the html
> rendering in front of me, sorry it's so late in the process)

Interesting thought. However, when adding selector support I considered
this, and realized it would require introducing several new XFRMA attributes to
cover the fields currently in several fixed struct. That is a larger
change and out of scope for this series.
In general I go back forward on this.

> [...]
> > +Flags
> > +=====
> > +
> > +The ``flags`` field in ``xfrm_user_migrate_state`` controls optional
> > +migration behaviour. Unknown flag bits are ignored.
>
> Maybe better to reject unknown flag bits (as well as unknown
> attributes beyond XFRMA_MAX), so that we can fully control their
> behavior, and not risk incorrectly migrating an SA if a "too-recent"
> userspace passes attributes we don't know (then if we fail to handle
> them in the kernel, the SA may not handle the traffic).
>
> (Steffen and you will have a much better understanding of the security
> risks here than me)

Good point. Done in v9.

Unknown flag bits are now rejected with -EINVAL. The extended ACK message
reports the specific unrecognised bits, e.g.:

  "Unknown flags: 0x4"

A new UAPI constant XFRM_MIGRATE_STATE_KNOWN_FLAGS is added to
<linux/xfrm.h> so userspace can validate flags before sending:

  if (flags & ~XFRM_MIGRATE_STATE_KNOWN_FLAGS)
          /* flag not known to this kernel header version */

Note: this constant reflects the flags defined in the header userspace
was compiled against, which may differ from what the running kernel
accepts. The documentation is updated accordingly.

-migration behaviour. Unknown flag bits are ignored.
+migration behaviour. Unknown flag bits are rejected with ``-EINVAL``; the
+extended ACK message identifies the unrecognised bits (e.g. ``"Unknown flags:
+0x4"``). Userspace can use ``XFRM_MIGRATE_STATE_KNOWN_FLAGS`` (defined in
+``<linux/xfrm.h>``) to validate flags before sending; note that this constant
+reflects the flags known to the header version userspace was compiled against,
+which may differ from what the running kernel accepts.

>
>
> > +Migration Steps
> > +===============
>
> maybe add:
>
> Userspace is expected to:
>
> > +#. Install a block policy to drop traffic on the affected selector.
> > +#. Remove the old policy.
> > +#. Call ``XFRM_MSG_MIGRATE_STATE`` for each SA.
> > +#. Reinstall the policies.
> > +#. Remove the block policy.

The section is restructured in v9 into Outgoing SA and Incoming SA
subsections. The outgoing procedure is introduced with "To prevent
cleartext traffic leaks, install a block policy before migrating:"
rather than a  mandate - userspace can make an informed choice
depending on the AEAD in use. The incoming section explains that no
block policy is needed and advises being liberal in acceptance to
avoid packet loss during the migration window.

> > +
> > +Block Policy and IV Safety
> > +--------------------------
> > +
> > +Installing a block policy before migration is required to prevent
> > +traffic leaks and IV reuse in counter mode.
> > +
> > +AES-GCM IV uniqueness is critical: reusing a (key, IV) pair allows
> > +an attacker to recover the authentication subkey and forge
> > +authentication tags, breaking both confidentiality and integrity.
> > +
> > +``XFRM_MSG_MIGRATE_STATE`` atomically copies the sequence number and
> > +replay window from the old SA to the new SA and deletes the old SA.
> > +The block policy ensures no outgoing packets are sent in the migration
> > +window, preventing IV reuse under the same key.
>
> Does it matter that the copy is done atomically if we expect userspace
> to install a block policy? (without the block, I'll have to recheck to
> convince myself whether it would be safe, and TBH I'm still a bit
> confused by patch 8)

The Block Policy and IV Safety section is clarified in v9. The block
policy serves two purposes: (1) prevent cleartext traffic leaks during
the migration window, and (2) for AES-GCM, prevent IV reuse by
ensuring no outgoing packets are sent under the same key.

The atomic copy complements the block policy for outgoing — together
they eliminate both risks. For incoming SAs the atomic copy is the
primary mechanism: it ensures replay protection continues without a
gap. The block policy is not needed on the incoming side.
>
> > +Feature Detection
> > +=================
> > +
> > +Userspace can probe for kernel support by sending a minimal
> > +``XFRM_MSG_MIGRATE_STATE`` message with a non-existent SPI:
> > +
> > +- ``-ENOPROTOOPT``: not supported (``CONFIG_XFRM_MIGRATE`` not enabled)
> > +- any other error: supported
>
>
> xfrm_user_rcv_msg
>
> if (type > XFRM_MSG_MAX)
>         return -EINVAL;
>
>
> Userspace will hit that on a kernel that may have migrate but not
> XFRM_MSG_MIGRATE_STATE, no?

Good catch. Fixed in v9: three cases are now documented — -EINVAL
(kernel predates the message type), -ENOPROTOOPT (CONFIG_XFRM_MIGRATE
disabled), -ESRCH (supported). Probe uses a non-zero non-existent SPI
to avoid conflating the SPI=0 validation -EINVAL with the
type-out-of-range -EINVAL.

>
>
> [...]
> > +Error Handling
> > +==============
> > +
> > +If the target SA tuple (daddr, SPI, proto, family) is occupied by an existing
> > +unrelated SA, the operation returns ``-EEXIST``.
>
> All this happens under xfrm_cfg_mutex, so if we did an initial lookup
> for the new SA before starting the operation, we could ensure there's
> no dupe, and the mutex would guarantee no insertion. No?

Done in v9. A pre-check lookup is added before cloning when the SA
tuple changes (new daddr or new family). If the new tuple is already
occupied -EEXIST is returned while the old SA is still intact, safe
to retry. The install failure path comment is updated to reflect it
is now a safety net only.

-antony

^ permalink raw reply

* Re: [PATCH net-next 09/10] docs: net: render the checksum comment in checksum-offloads.rst
From: Willem de Bruijn @ 2026-05-26 18:56 UTC (permalink / raw)
  To: Jakub Kicinski, davem
  Cc: netdev, edumazet, pabeni, andrew+netdev, horms, corbet,
	vladimir.oltean, willemb, sdf.kernel, ecree.xilinx,
	jesse.brandeburg, linux-doc, Jakub Kicinski
In-Reply-To: <20260526160151.2793354-10-kuba@kernel.org>

Jakub Kicinski wrote:
> checksum-offloads.rst seems like a better place to render
> the checksum comment than skbuff.rst.
> 
> Remove the stale references to sections in that comment
> (it no longer has A, B, C, D, E sections).
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>

Reviewed-by: Willem de Bruijn <willemb@google.com>

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox