[RFC PATCH net-next 5/9] amt: add IFLA_AMT_OFFLOAD_MODE

From: Omar Ramadan

Date: Sat Nov 07 2026 - 13:48:31 EST


software (default), auto or required, after
IFLA_AMT_MAX_TUNNELS_PER_SOURCE. An unknown value, or a non-software
mode on a gateway, is refused with extack. required with no usable
provider on the stream device refuses the link with -ENODEV and extack.
Emitted by fill_info.

Assisted-by: LLM
Signed-off-by: Omar Ramadan <omar@xxxxxxxxxxxxx>
---
drivers/net/amt.c | 25 ++++++++++++++++++++++++-
include/uapi/linux/amt.h | 7 +++++++
2 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index 377660e55..58e9da443 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -5719,6 +5719,7 @@ static const struct nla_policy amt_policy[IFLA_AMT_MAX + 1] = {
[IFLA_AMT_NUM_QUEUES] = NLA_POLICY_MAX(NLA_U32, AMT_MAX_QUEUES),
[IFLA_AMT_DISCOVERY_IP6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
[IFLA_AMT_REMOTE_IP6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
+ [IFLA_AMT_OFFLOAD_MODE] = { .type = NLA_U32 },
};

static int amt_validate(struct nlattr *tb[], struct nlattr *data[],
@@ -5745,6 +5746,22 @@ static int amt_validate(struct nlattr *tb[], struct nlattr *data[],
return -EINVAL;
}

+ if (data[IFLA_AMT_OFFLOAD_MODE]) {
+ u32 offload = nla_get_u32(data[IFLA_AMT_OFFLOAD_MODE]);
+
+ if (offload > AMT_OFFLOAD_MODE_MAX) {
+ NL_SET_ERR_MSG_ATTR(extack, data[IFLA_AMT_OFFLOAD_MODE],
+ "Offload mode is not valid");
+ return -EINVAL;
+ }
+ if (offload != AMT_OFFLOAD_MODE_SOFTWARE &&
+ nla_get_u32(data[IFLA_AMT_MODE]) != AMT_MODE_RELAY) {
+ NL_SET_ERR_MSG_ATTR(extack, data[IFLA_AMT_OFFLOAD_MODE],
+ "Offload is only valid in relay mode");
+ return -EINVAL;
+ }
+ }
+
if (!data[IFLA_AMT_LOCAL_IP] && !data[IFLA_AMT_LOCAL_IP6]) {
NL_SET_ERR_MSG_MOD(extack,
"Local IPv4 or IPv6 attribute is required");
@@ -5802,6 +5819,9 @@ static int amt_newlink(struct net_device *dev,

amt->net = link_net;
amt->mode = nla_get_u32(data[IFLA_AMT_MODE]);
+ amt->offload_mode = data[IFLA_AMT_OFFLOAD_MODE] ?
+ nla_get_u32(data[IFLA_AMT_OFFLOAD_MODE]) :
+ AMT_OFFLOAD_MODE_SOFTWARE;

if (data[IFLA_AMT_MAX_TUNNELS] &&
nla_get_u32(data[IFLA_AMT_MAX_TUNNELS]))
@@ -5998,7 +6018,8 @@ static size_t amt_get_size(const struct net_device *dev)
nla_total_size(sizeof(struct iphdr)) + /* IFLA_AMT_LOCAL_IP */
nla_total_size(sizeof(struct in6_addr)) + /* IFLA_AMT_LOCAL_IP6 */
nla_total_size(sizeof(struct in6_addr)) + /* IFLA_AMT_DISCOVERY_IP6 */
- nla_total_size(sizeof(struct in6_addr)); /* IFLA_AMT_REMOTE_IP6 */
+ nla_total_size(sizeof(struct in6_addr)) + /* IFLA_AMT_REMOTE_IP6 */
+ nla_total_size(sizeof(__u32)); /* IFLA_AMT_OFFLOAD_MODE */
}

static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev)
@@ -6068,6 +6089,8 @@ static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev)
goto nla_put_failure;
if (nla_put_u32(skb, IFLA_AMT_NUM_QUEUES, dev->real_num_tx_queues))
goto nla_put_failure;
+ if (nla_put_u32(skb, IFLA_AMT_OFFLOAD_MODE, amt->offload_mode))
+ goto nla_put_failure;

return 0;

diff --git a/include/uapi/linux/amt.h b/include/uapi/linux/amt.h
index de1ae1929..c67a1f763 100644
--- a/include/uapi/linux/amt.h
+++ b/include/uapi/linux/amt.h
@@ -122,6 +122,13 @@ enum {
* AMT_MAX_TUNNELS_PER_SOURCE; raise it on a relay that fronts CGNAT.
*/
IFLA_AMT_MAX_TUNNELS_PER_SOURCE,
+ /* Relay mode: enum ifla_amt_offload_mode, default
+ * AMT_OFFLOAD_MODE_SOFTWARE. Replication offload to a provider
+ * registered on the IFLA_AMT_LINK device (include/net/amt_offload.h).
+ * Required mode refuses creation with -ENODEV when no usable provider
+ * is bound. Gateway mode accepts software only.
+ */
+ IFLA_AMT_OFFLOAD_MODE,
__IFLA_AMT_MAX,
};

--
2.43.0