[PATCH net-next 08/13] amt: send the AMT gateway control plane over IPv6

From: Omar Ramadan

Date: Fri Oct 09 2026 - 09:20:53 EST


A gateway sends three control messages: the Relay Discovery to its
discovery address, then the Request and the Membership Updates to the
relay it learned. All three are built for an IPv4 outer header only, so
a gateway on an IPv6-only access network cannot reach any relay.

Send the Discovery and the Request of a gateway with an IPv6 local
address from amt_send_discovery_v6() and amt_send_request_v6(). Both
messages are a few bytes with no payload, so they are built on the stack
and sent with amt_send_ctrl_v6(), the helper that already sends the
relay's IPv6 Advertisement, rather than with a copy of the IPv4 skb
construction. The Membership Update carries the gateway's IGMP or MLD
report, so amt_send_membership_update() keeps building on that skb: it
sizes the headroom for the outer family and sends through
amt_udp_xmit(), like the relay's Membership Query and Multicast Data,
which avoids an IPv6 copy of the function.

struct amt_dev gains the gateway's IPv6 discovery address and the IPv6
relay address it learns and sends to. The next patch writes the relay
address in process context while the transmit and receive paths read it,
and a struct in6_addr is not read in one access, so it comes with a
seqlock, remote_ipv6_lock: the senders take a snapshot with
amt_get_remote_ipv6(), which retries until the copy is consistent.

Assisted-by: LLM
Signed-off-by: Omar Ramadan <omar@xxxxxxxxxxxxx>
---
drivers/net/amt.c | 91 +++++++++++++++++++++++++++++++++--------------
include/net/amt.h | 5 +++
2 files changed, 69 insertions(+), 27 deletions(-)

diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index 969ecfe..148d1fb 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -715,6 +715,50 @@ out:
return err;
}

+/* The learned IPv6 relay address is written in process context and read
+ * on transmit and receive. A struct in6_addr is not read in one access, so
+ * readers take a snapshot under the seqlock.
+ */
+static struct in6_addr amt_get_remote_ipv6(const struct amt_dev *amt)
+{
+ struct in6_addr addr;
+ unsigned int seq;
+
+ do {
+ seq = read_seqbegin(&amt->remote_ipv6_lock);
+ addr = amt->remote_ipv6;
+ } while (read_seqretry(&amt->remote_ipv6_lock, seq));
+ return addr;
+}
+
+/* IPv6-outer variant of amt_send_discovery(). */
+static void amt_send_discovery_v6(struct amt_dev *amt)
+{
+ struct amt_header_discovery amtd = {
+ .type = AMT_MSG_DISCOVERY,
+ .nonce = amt->nonce,
+ };
+
+ if (!amt_send_ctrl_v6(amt, &amt->local_ipv6, &amt->discovery_ipv6,
+ amt->gw_port, amt->relay_port,
+ &amtd, sizeof(amtd)))
+ amt_update_gw_status(amt, AMT_STATUS_SENT_DISCOVERY, true);
+}
+
+/* IPv6-outer variant of amt_send_request(); @v6 is the inner family. */
+static void amt_send_request_v6(struct amt_dev *amt, bool v6)
+{
+ const struct in6_addr remote = amt_get_remote_ipv6(amt);
+ struct amt_header_request amtrh = {
+ .type = AMT_MSG_REQUEST,
+ .p = v6,
+ .nonce = amt->nonce,
+ };
+
+ amt_send_ctrl_v6(amt, &amt->local_ipv6, &remote, amt->gw_port,
+ amt->relay_port, &amtrh, sizeof(amtrh));
+}
+
static void amt_send_discovery(struct amt_dev *amt)
{
struct amt_header_discovery *amtd;
@@ -728,6 +772,11 @@ static void amt_send_discovery(struct amt_dev *amt)
u32 len;
int err;

+ if (amt_v6(amt)) {
+ amt_send_discovery_v6(amt);
+ return;
+ }
+
rcu_read_lock();
sk = rcu_dereference(amt->sk);
if (!sk)
@@ -818,6 +867,11 @@ static void amt_send_request(struct amt_dev *amt, bool v6)
u32 len;
int err;

+ if (amt_v6(amt)) {
+ amt_send_request_v6(amt, v6);
+ return;
+ }
+
rcu_read_lock();
remote_ip = READ_ONCE(amt->remote_ip);
sk = rcu_dereference(amt->sk);
@@ -1220,9 +1274,7 @@ static bool amt_send_membership_update(struct amt_dev *amt,
{
__be32 remote_ip = READ_ONCE(amt->remote_ip);
struct amt_header_membership_update *amtmu;
- struct iphdr *iph;
- struct flowi4 fl4;
- struct rtable *rt;
+ union amt_addr remote = {};
struct sock *sk;
int err;

@@ -1231,23 +1283,11 @@ static bool amt_send_membership_update(struct amt_dev *amt,
return true;

err = skb_cow_head(skb, LL_RESERVED_SPACE(amt->dev) + sizeof(*amtmu) +
- sizeof(*iph) + sizeof(struct udphdr));
+ amt_ip_hlen(amt) + sizeof(struct udphdr));
if (err)
return true;

skb_reset_inner_headers(skb);
- memset(&fl4, 0, sizeof(struct flowi4));
- fl4.flowi4_oif = amt->stream_dev->ifindex;
- fl4.daddr = remote_ip;
- fl4.saddr = amt->local_ip;
- fl4.flowi4_dscp = inet_dsfield_to_dscp(AMT_TOS);
- fl4.flowi4_proto = IPPROTO_UDP;
- rt = ip_route_output_key(amt->net, &fl4);
- if (IS_ERR(rt)) {
- netdev_dbg(amt->dev, "no route to %pI4\n", &remote_ip);
- return true;
- }
-
amtmu = skb_push(skb, sizeof(*amtmu));
amtmu->version = 0;
amtmu->type = AMT_MSG_MEMBERSHIP_UPDATE;
@@ -1259,17 +1299,13 @@ static bool amt_send_membership_update(struct amt_dev *amt,
skb_set_inner_protocol(skb, htons(ETH_P_IP));
else
skb_set_inner_protocol(skb, htons(ETH_P_IPV6));
- udp_tunnel_xmit_skb(rt, sk, skb,
- fl4.saddr,
- fl4.daddr,
- AMT_TOS,
- ip4_dst_hoplimit(&rt->dst),
- 0,
- amt->gw_port,
- amt->relay_port,
- false,
- false,
- 0);
+ if (amt_v6(amt))
+ remote.ip6 = amt_get_remote_ipv6(amt);
+ else
+ remote.ip4 = remote_ip;
+ if (amt_udp_xmit(amt, sk, skb, &remote, amt->gw_port,
+ amt->relay_port, false))
+ return true;
amt_update_gw_status(amt, AMT_STATUS_SENT_UPDATE, true);
return false;
}
@@ -3481,6 +3517,7 @@ static int amt_newlink(struct net_device *dev,
amt->max_tunnels = AMT_MAX_TUNNELS;

spin_lock_init(&amt->lock);
+ seqlock_init(&amt->remote_ipv6_lock);
amt->max_groups = AMT_MAX_GROUP;
amt->max_sources = AMT_MAX_SOURCE;
amt->hash_buckets = AMT_HSIZE;
diff --git a/include/net/amt.h b/include/net/amt.h
index d8798a9..02c1c33 100644
--- a/include/net/amt.h
+++ b/include/net/amt.h
@@ -360,8 +360,13 @@ struct amt_dev {
struct in6_addr local_ipv6;
/* Outer remote ip */
__be32 remote_ip;
+ /* Outer remote IPv6 address, published under remote_ipv6_lock */
+ struct in6_addr remote_ipv6;
+ seqlock_t remote_ipv6_lock;
/* Outer discovery ip */
__be32 discovery_ip;
+ /* Outer discovery IPv6 address, :: unless an IPv6 gateway */
+ struct in6_addr discovery_ipv6;
/* Only used in gateway mode */
__be32 nonce;
/* Gateway sent request and received query */
--
2.43.0