From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pg1-f178.google.com (mail-pg1-f178.google.com [209.85.215.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D1DA24DEC0A for ; Fri, 9 Oct 2026 12:24:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.178 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791548701; cv=none; b=Q4VtUb9xN0KfhOsMv5KIRLWmml0IhkLv5AxIfI9P9+obDEaVG7Xr/d8PzT3E2Jt6vqXRgHkSM8Kv0D/4To5cZrV3SoRAEyuLRf/g6B7SWcvf1hKNX6fBSQwZ87y0uIo5C7nWp/lQk3JbOFdrG+AkSEceQTqh79xZWMZvnLrUzYM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791548701; c=relaxed/simple; bh=fY5Grftq988zETCKTD+AxH4y9OWlWICKpXj3Ea0plQ4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GYQv2voXfKxjnBPPMTPZhuy5jhTKuX+IzlLexx61k73Vq+VIdrPiwghJwczrAP0V4RA6RwO8djc5gAJVRrptDlo+muZ8p1DcoZEEBEqXbcti5X3SBFzY4vGzIEkNKVGGTzgGu0g3Cbn3NKViYx8cVo3h5/Glcu9J/yHXwLln8a8= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=blockcast.net; spf=pass smtp.mailfrom=blockcast.net; dkim=pass (2048-bit key) header.d=blockcast.net header.i=@blockcast.net header.b=CDlodfux; arc=none smtp.client-ip=209.85.215.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=blockcast.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=blockcast.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=blockcast.net header.i=@blockcast.net header.b="CDlodfux" Received: by mail-pg1-f178.google.com with SMTP id 41be03b00d2f7-cc73a80ede5so2934189a12.1 for ; Fri, 09 Oct 2026 05:24:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=blockcast.net; s=google; t=1791548691; x=1792153491; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=2LWIHYfSfL38FbGhJOfROiN2A9tR5CGBM+1n/EC/kos=; b=CDlodfuxkk/MQrrSkQLWjmcP3DH4ff/GDNxw1X3ZArXWlXyeo+xAi8qnlzvCeWHKbO /PBC5NXqJsvejJ0D7ZliNKsicirTyaS1PxOEQ8cb/io3A1ns/cEquSODSxOzYGXBSVwI GwM8ee82mI3uc2OcVfxsExe8QnQyaSP5wRt9fgBbVOQVrYvfJhxcH7w8fGSmggf24mBU XR4MbJaN+oZmBn5R2YYjbfdZ/y8EunOkW2eWOO1nNySAPCFw7qYD87htVdcZXGobAkZe YsQmap8G+05MjI6W8AXp+C2pwIFITbop/IfxXL+j69zgWp00ht9KjvLG2CT7gWRE2Kal 2I0Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1791548691; x=1792153491; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=2LWIHYfSfL38FbGhJOfROiN2A9tR5CGBM+1n/EC/kos=; b=TtddlJPKfrqakhDVJ2ukdmEkyaT3kb06ZmHLnmlDzfynXKdiFAAj7yHOVXyMQYaBwd uH86AJ3eG58gGB9GhYtoQrywevEWf/6bW9acu7F/Rqa0waT6z1meM8E+kMsFgt+1z0mv vlJ7FD4vrAjiCKMe7riOy2uRu0Cw4UFqFscpMVxZDwx4mVlVQ9gaRoQzTVLuFYJe4VZL p9KMcPPorubt37auN/6ni1n1RNdcjnzQC41I4UbH/j4VE38GRC2uCj7tDBCTrlJdhbDG 9UJa32IoTYGsqTNNEPQSC64SvGDAXF08U2n3KhzgMRENWNO6+kEK1F0s58zvLhP8T+R7 qLQg== X-Forwarded-Encrypted: i=1; AKwUvByfXNUD5B+wTtKYFNtL8Rj0B88RSBYqhlsVuhCgI6ppGljcJ4l8VUFO6x98YHgHXtNszV0hybPBuie320k=@vger.kernel.org X-Gm-Message-State: AFq9FYJk0q8PAkT7iEmZ2Qz+LsGG5duLlgcYP/+iqUN8Ai/uCVjB3a8f p6ujJABR46++Cf19rtOlsFKFIMEj/L9Pldg3BV5l4eK/WESonwA7r1GmtL4FnRAlE4Y= X-Gm-Gg: AYBFou1zytNfScC1PwTpZ8MwiVW4sGXgzsM+uLI9il/V0vwuEeVvSu673mto6oCi5a/ x2srVg/JtLyDfHt1LLbG9nXrG7CuuxBH7cYmA2TuaBJliVlJEpw0cOtSnemBuKs+jGCoWb4mrGV AAk/+uObl7NqUSdNawZ5BZUUcAsdGsKwYlHtJmOZADhLC1U39iO7bWzyIniu91tPKutDAVj46RE 6gqSxiXm0A+EiW4biQfXmU5xtBu+qr7v9ORxwYgxhG/WTevFeiLxWB0pku/bbzwe0BLfVM0Snig 5LuD8Rq2siCt3mPCgdhbAXn3bUZ5OYgCWh9nb+t2i0ksG5s05oA8SPry1nfxG+1D7H7M8Hs8fDZ R22kIOE06SDsntLy5Ujpkf1lL3fn2VPrUHxKSzQTyk9ORzsh83urxSqyMQhDN4E+fKf6EM80vxS EPGPo7hmSBGS3JueSsLwmOIEERAn4oEDY7HcygFEy9F/13pStrqjVvt1kjdct3vuEHFgoIF58dO jpOOPPTqgo+xwBVYrTB2F8VSWuHzEuv5w1VQAXm X-Received: by 2002:a17:90b:4b:b0:3ab:2c2b:1030 with SMTP id 98e67ed59e1d1-3ab3a747cfcmr1496754a91.22.1791548690909; Fri, 09 Oct 2026 05:24:50 -0700 (PDT) Received: from devbox.ts.blockcast.net ([2602:f74d:1::32]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3ab55e41eefsm1660502a91.10.2026.10.09.05.24.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 09 Oct 2026 05:24:50 -0700 (PDT) From: Omar Ramadan To: Taehee Yoo , Andrew Lunn , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Shuah Khan Cc: Simon Horman , netdev@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH net-next 10/13] amt: add netlink attributes for an IPv6 outer transport Date: Fri, 9 Oct 2026 12:24:23 +0000 Message-ID: <20261009122426.551178-11-omar@blockcast.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20261009122426.551178-1-omar@blockcast.net> References: <20261009122426.551178-1-omar@blockcast.net> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Every path of the relay and the gateway can now run over an IPv6 outer transport, but nothing can create such a device. Add the attributes that do: - IFLA_AMT_LOCAL_IP6, the local IPv6 address. It selects IPv6 as the outer transport and is mutually exclusive with IFLA_AMT_LOCAL_IP. - IFLA_AMT_DISCOVERY_IP6, the IPv6 address a gateway sends its Relay Discovery to. A gateway's discovery address has to be in the family of its local address, as both ends of the tunnel use one outer transport. - IFLA_AMT_REMOTE_IP6, the relay address the gateway learned from the IPv6 Relay Advertisement. Like IFLA_AMT_REMOTE_IP it is only reported; unlike that one, the policy rejects it on input. amt_fill_info() reports the addresses in the device's outer family. IFLA_AMT_LOCAL_IP6 is refused with -EAFNOSUPPORT on a kernel built without IPv6, rather than creating an IPv4 device with no local address. An IPv6 local or discovery address is refused when it is unspecified, loopback or multicast, as the IPv4 ones are, and also when it is IPv4-mapped: the V6ONLY socket could never send from or to such an address, so the device would be created but silently fail every send. The existing IFLA_AMT_LOCAL_IP and IFLA_AMT_DISCOVERY_IP cannot simply carry 16 bytes. Their policy only sets a minimum length of 4, so a kernel without this series would accept a 16-byte value and use its first four bytes as an IPv4 address. A kernel that does not know the new attributes ignores them and fails the request for lack of a local address, so userspace can tell whether IPv6 is supported. vxlan (IFLA_VXLAN_LOCAL6, IFLA_VXLAN_GROUP6) and geneve (IFLA_GENEVE_REMOTE6) add their IPv6 addresses the same way. The attributes are appended to the enum, so the existing values do not change, and strict_start_type makes the policy validate them, and any attribute added after them, strictly. Because of that minimum length, amt_validate() now refuses a 16-byte IFLA_AMT_LOCAL_IP, and a 16-byte IFLA_AMT_DISCOVERY_IP on a gateway. Every iproute2 released before the companion iproute2 patch puts an IPv6 literal in the IPv4 attribute, and the kernel then creates an IPv4 device from the first four bytes of the address, 32.1.13.184 for 2001:db8::. Such a request now fails with an extack message that names the problem instead of creating the wrong device. A relay never reads IFLA_AMT_DISCOVERY_IP, so an IPv4 relay given a 16-byte one still works as before. A relay has always ignored IFLA_AMT_DISCOVERY_IP, and existing users may pass it, so an IPv4 relay still accepts it. An IPv6 relay has no existing users, so it rejects a discovery address of either family, and an IPv4 relay rejects IFLA_AMT_DISCOVERY_IP6. Assisted-by: LLM Signed-off-by: Omar Ramadan --- drivers/net/amt.c | 176 +++++++++++++++++++++++++++++++-------- include/uapi/linux/amt.h | 13 +++ 2 files changed, 155 insertions(+), 34 deletions(-) diff --git a/drivers/net/amt.c b/drivers/net/amt.c index 86f168c..fb199d9 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -3516,6 +3516,7 @@ static void amt_link_setup(struct net_device *dev) } static const struct nla_policy amt_policy[IFLA_AMT_MAX + 1] = { + [IFLA_AMT_UNSPEC] = { .strict_start_type = IFLA_AMT_LOCAL_IP6 }, [IFLA_AMT_MODE] = { .type = NLA_U32 }, [IFLA_AMT_RELAY_PORT] = { .type = NLA_U16 }, [IFLA_AMT_GATEWAY_PORT] = { .type = NLA_U16 }, @@ -3524,8 +3525,25 @@ static const struct nla_policy amt_policy[IFLA_AMT_MAX + 1] = { [IFLA_AMT_REMOTE_IP] = { .len = sizeof_field(struct iphdr, daddr) }, [IFLA_AMT_DISCOVERY_IP] = { .len = sizeof_field(struct iphdr, daddr) }, [IFLA_AMT_MAX_TUNNELS] = { .type = NLA_U32 }, + [IFLA_AMT_LOCAL_IP6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), + [IFLA_AMT_DISCOVERY_IP6] = + NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), + [IFLA_AMT_REMOTE_IP6] = { .type = NLA_REJECT }, }; +/* The policy of the IPv4 address attributes only sets a minimum length, + * and an iproute2 without IPv6 AMT support puts an IPv6 literal in them, + * so the device would take the first four bytes of it as its address. + */ +static bool amt_ip6_in_ip4_attr(const struct nlattr *attr, + struct netlink_ext_ack *extack) +{ + if (!attr || nla_len(attr) != sizeof(struct in6_addr)) + return false; + NL_SET_ERR_MSG_ATTR(extack, attr, "IPv6 address in an IPv4 attribute"); + return true; +} + static int amt_validate(struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack) { @@ -3550,16 +3568,63 @@ static int amt_validate(struct nlattr *tb[], struct nlattr *data[], return -EINVAL; } - if (!data[IFLA_AMT_LOCAL_IP]) { + if (amt_ip6_in_ip4_attr(data[IFLA_AMT_LOCAL_IP], extack)) + return -EINVAL; + + if (!data[IFLA_AMT_LOCAL_IP] && !data[IFLA_AMT_LOCAL_IP6]) { NL_SET_ERR_MSG_ATTR(extack, data[IFLA_AMT_DISCOVERY_IP], "Local attribute is required"); return -EINVAL; } - if (!data[IFLA_AMT_DISCOVERY_IP] && - nla_get_u32(data[IFLA_AMT_MODE]) == AMT_MODE_GATEWAY) { - NL_SET_ERR_MSG_ATTR(extack, data[IFLA_AMT_LOCAL_IP], - "Discovery attribute is required"); + if (data[IFLA_AMT_LOCAL_IP] && data[IFLA_AMT_LOCAL_IP6]) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_AMT_LOCAL_IP6], + "Local IPv4 and IPv6 are mutually exclusive"); + return -EINVAL; + } + + if (data[IFLA_AMT_LOCAL_IP6] && !IS_ENABLED(CONFIG_IPV6)) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_AMT_LOCAL_IP6], + "IPv6 support is disabled"); + return -EAFNOSUPPORT; + } + + if (nla_get_u32(data[IFLA_AMT_MODE]) != AMT_MODE_GATEWAY) { + struct nlattr *disc = data[IFLA_AMT_DISCOVERY_IP6]; + + /* An IPv4 relay has always ignored IFLA_AMT_DISCOVERY_IP, + * and existing users may pass it. An IPv6 relay has no such + * users, so it rejects a discovery address of either family. + */ + if (!disc && data[IFLA_AMT_LOCAL_IP6]) + disc = data[IFLA_AMT_DISCOVERY_IP]; + if (disc) { + NL_SET_ERR_MSG_ATTR(extack, disc, + "Discovery is only valid in gateway mode"); + return -EINVAL; + } + return 0; + } + + /* A relay never reads IFLA_AMT_DISCOVERY_IP, but a gateway would + * take the first four bytes of an IPv6 literal as its relay. + */ + if (amt_ip6_in_ip4_attr(data[IFLA_AMT_DISCOVERY_IP], extack)) + return -EINVAL; + + /* A gateway's discovery address is in the family of its local + * address, since both ends of the tunnel use one outer transport. + */ + if (data[IFLA_AMT_LOCAL_IP6] ? !data[IFLA_AMT_DISCOVERY_IP6] : + !data[IFLA_AMT_DISCOVERY_IP]) { + NL_SET_ERR_MSG_MOD(extack, + "Discovery attribute of the local family is required"); + return -EINVAL; + } + + if (data[IFLA_AMT_DISCOVERY_IP] && data[IFLA_AMT_DISCOVERY_IP6]) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_AMT_DISCOVERY_IP6], + "Discovery IPv4 and IPv6 are mutually exclusive"); return -EINVAL; } @@ -3609,13 +3674,22 @@ static int amt_newlink(struct net_device *dev, goto err; } - amt->local_ip = nla_get_in_addr(data[IFLA_AMT_LOCAL_IP]); - if (ipv4_is_loopback(amt->local_ip) || - ipv4_is_zeronet(amt->local_ip) || - ipv4_is_multicast(amt->local_ip)) { - NL_SET_ERR_MSG_ATTR(extack, tb[IFLA_AMT_LOCAL_IP], - "Invalid Local address"); - goto err; + if (data[IFLA_AMT_LOCAL_IP6]) { + amt->local_ipv6 = nla_get_in6_addr(data[IFLA_AMT_LOCAL_IP6]); + if (amt_ip6_unusable(&amt->local_ipv6)) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_AMT_LOCAL_IP6], + "Invalid Local IPv6 address"); + goto err; + } + } else { + amt->local_ip = nla_get_in_addr(data[IFLA_AMT_LOCAL_IP]); + if (ipv4_is_loopback(amt->local_ip) || + ipv4_is_zeronet(amt->local_ip) || + ipv4_is_multicast(amt->local_ip)) { + NL_SET_ERR_MSG_ATTR(extack, tb[IFLA_AMT_LOCAL_IP], + "Invalid Local address"); + goto err; + } } amt->relay_port = nla_get_be16_default(data[IFLA_AMT_RELAY_PORT], @@ -3633,24 +3707,32 @@ static int amt_newlink(struct net_device *dev, amt->qrv = READ_ONCE(amt->net->ipv4.sysctl_igmp_qrv); amt->qri = 10; } else { - if (!data[IFLA_AMT_DISCOVERY_IP]) { - NL_SET_ERR_MSG_ATTR(extack, tb[IFLA_AMT_DISCOVERY_IP], - "discovery must be set in gateway mode"); - goto err; - } if (!amt->gw_port) { NL_SET_ERR_MSG_ATTR(extack, tb[IFLA_AMT_DISCOVERY_IP], "gateway port must not be 0"); goto err; } - WRITE_ONCE(amt->remote_ip, 0); - amt->discovery_ip = nla_get_in_addr(data[IFLA_AMT_DISCOVERY_IP]); - if (ipv4_is_loopback(amt->discovery_ip) || - ipv4_is_zeronet(amt->discovery_ip) || - ipv4_is_multicast(amt->discovery_ip)) { - NL_SET_ERR_MSG_ATTR(extack, tb[IFLA_AMT_DISCOVERY_IP], - "discovery must be unicast"); - goto err; + if (data[IFLA_AMT_DISCOVERY_IP6]) { + struct nlattr *attr = data[IFLA_AMT_DISCOVERY_IP6]; + + amt->discovery_ipv6 = nla_get_in6_addr(attr); + if (amt_ip6_unusable(&amt->discovery_ipv6)) { + NL_SET_ERR_MSG_ATTR(extack, attr, + "discovery must be unicast"); + goto err; + } + } else { + WRITE_ONCE(amt->remote_ip, 0); + amt->discovery_ip = + nla_get_in_addr(data[IFLA_AMT_DISCOVERY_IP]); + if (ipv4_is_loopback(amt->discovery_ip) || + ipv4_is_zeronet(amt->discovery_ip) || + ipv4_is_multicast(amt->discovery_ip)) { + NL_SET_ERR_MSG_ATTR(extack, + tb[IFLA_AMT_DISCOVERY_IP], + "discovery must be unicast"); + goto err; + } } } dev->needed_headroom = amt->stream_dev->needed_headroom + amt_hlen(amt); @@ -3702,7 +3784,27 @@ static size_t amt_get_size(const struct net_device *dev) nla_total_size(sizeof(__u32)) + /* IFLA_MAX_TUNNELS */ nla_total_size(sizeof(__be32)) + /* IFLA_AMT_DISCOVERY_IP */ nla_total_size(sizeof(__be32)) + /* IFLA_AMT_REMOTE_IP */ - nla_total_size(sizeof(__be32)); /* IFLA_AMT_LOCAL_IP */ + nla_total_size(sizeof(__be32)) + /* IFLA_AMT_LOCAL_IP */ + /* IFLA_AMT_{LOCAL,DISCOVERY,REMOTE}_IP6. Only one address of + * each IPv4/IPv6 pair is emitted, but this sizes for both. + */ + 3 * nla_total_size(sizeof(struct in6_addr)); +} + +/* The IPv6 addresses of an IPv6 device, one of each IPv4/IPv6 pair. */ +static int amt_fill_addr6(struct sk_buff *skb, const struct amt_dev *amt) +{ + const struct in6_addr remote = amt_get_remote_ipv6(amt); + + if (nla_put_in6_addr(skb, IFLA_AMT_LOCAL_IP6, &amt->local_ipv6)) + return -EMSGSIZE; + if (amt->mode == AMT_MODE_GATEWAY && + nla_put_in6_addr(skb, IFLA_AMT_DISCOVERY_IP6, &amt->discovery_ipv6)) + return -EMSGSIZE; + if (!ipv6_addr_any(&remote) && + nla_put_in6_addr(skb, IFLA_AMT_REMOTE_IP6, &remote)) + return -EMSGSIZE; + return 0; } static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev) @@ -3719,15 +3821,21 @@ 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_LINK, amt->stream_dev->ifindex)) goto nla_put_failure; - if (nla_put_in_addr(skb, IFLA_AMT_LOCAL_IP, amt->local_ip)) - goto nla_put_failure; - if (nla_put_in_addr(skb, IFLA_AMT_DISCOVERY_IP, amt->discovery_ip)) - goto nla_put_failure; - - remote_ip = READ_ONCE(amt->remote_ip); - if (remote_ip) - if (nla_put_in_addr(skb, IFLA_AMT_REMOTE_IP, remote_ip)) + if (amt_v6(amt)) { + if (amt_fill_addr6(skb, amt)) + goto nla_put_failure; + } else { + if (nla_put_in_addr(skb, IFLA_AMT_LOCAL_IP, amt->local_ip)) goto nla_put_failure; + if (nla_put_in_addr(skb, IFLA_AMT_DISCOVERY_IP, + amt->discovery_ip)) + goto nla_put_failure; + + remote_ip = READ_ONCE(amt->remote_ip); + if (remote_ip) + if (nla_put_in_addr(skb, IFLA_AMT_REMOTE_IP, remote_ip)) + goto nla_put_failure; + } if (nla_put_u32(skb, IFLA_AMT_MAX_TUNNELS, amt->max_tunnels)) goto nla_put_failure; diff --git a/include/uapi/linux/amt.h b/include/uapi/linux/amt.h index 2dccff4..5290ece 100644 --- a/include/uapi/linux/amt.h +++ b/include/uapi/linux/amt.h @@ -54,6 +54,19 @@ enum { IFLA_AMT_DISCOVERY_IP, /* This attribute specify number of maximum tunnel. */ IFLA_AMT_MAX_TUNNELS, + /* This attribute specifies the local IPv6 address. It selects IPv6 + * as the outer transport and excludes IFLA_AMT_LOCAL_IP. + */ + IFLA_AMT_LOCAL_IP6, + /* This attribute specifies the IPv6 address of the relay a gateway + * sends its Discovery to. It is the IPv6 form of + * IFLA_AMT_DISCOVERY_IP and needs IFLA_AMT_LOCAL_IP6. + */ + IFLA_AMT_DISCOVERY_IP6, + /* This attribute reports the IPv6 relay address a gateway learned + * from the Relay Advertisement. It is read-only. + */ + IFLA_AMT_REMOTE_IP6, __IFLA_AMT_MAX, }; -- 2.43.0