[PATCH net-next 03/13] amt: key relay tunnels on a union amt_addr endpoint

From: Omar Ramadan

Date: Fri Oct 09 2026 - 08:27:37 EST


A relay keeps one struct amt_tunnel_list per gateway and finds it by
the gateway's IPv4 address, stored as __be32 ip4 and compared with
ip_hdr(skb)->saddr. On an IPv6 outer transport that reads bytes 4-7 of
the IPv6 source address as an IPv4 address, and the field cannot hold
the gateway's address anyway.

Store the endpoint as a union amt_addr, which group and source nodes
already use, and move the union above struct amt_tunnel_list so the
tunnel can embed it. Its ip6 member is no longer under CONFIG_IPV6, so
code that only copies, compares or prints an endpoint compiles without
an #if and relies on amt_v6() being false. The tunnel's endpoint grows
from 4 to 16 bytes on every kernel; a kernel without IPv6 also pays 12
more bytes per source node and 24 per group node, which it already
zeroes. The tunnel needs no family of its own: the relay's socket is
bound to one family, so every tunnel has the device's family, which
amt_v6() reports.

amt_outer_saddr() copies the outer source address of a received message
by value into a zeroed union amt_addr. amt_request_handler() takes that
snapshot once and matches and records tunnels with it, comparing with
the existing amt_addr_equal(); the union is zero-padded for IPv4, so
one memcmp() serves both families. As before, a tunnel is matched on
the address alone. When the tunnel limit is reached, an IPv6 device
answers with an ICMPv6 destination unreachable instead of an ICMP one.
It is sent with skb->dev set to the underlying link: amt_rcv() has made
it the amt device, and icmp6_send() routes an error to a link-local
source through skb->dev, where amt_dev_xmit() would drop it.

The response MAC becomes one siphash() over the packed {address, port,
nonce} tuple instead of siphash_3u32() over the IPv4 address. Only the
relay computes and checks the MAC; the gateway echoes it verbatim, so it
cannot tell that the value for an IPv4 endpoint changed.

The IPv4 senders and the relay status debug message are converted to
tunnel->addr.ip4, and the debug message prints an IPv6 endpoint with
%pI6c. No functional change: amt_v6() is still false for every device.

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

diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index a550f84..eaa5637 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -88,6 +88,19 @@ static bool amt_v6(const struct amt_dev *amt)
return IS_ENABLED(CONFIG_IPV6) && !ipv6_addr_any(&amt->local_ipv6);
}

+/* Copy the outer source address of a received message by value, so that
+ * the caller may pull the skb afterwards.
+ */
+static void amt_outer_saddr(const struct amt_dev *amt,
+ const struct sk_buff *skb, union amt_addr *addr)
+{
+ memset(addr, 0, sizeof(*addr));
+ if (amt_v6(amt))
+ addr->ip6 = ipv6_hdr(skb)->saddr;
+ else
+ addr->ip4 = ip_hdr(skb)->saddr;
+}
+
static void __amt_source_gc_work(void)
{
struct amt_source_node *snode;
@@ -594,10 +607,16 @@ static void __amt_update_relay_status(struct amt_tunnel_list *tunnel,
{
if (validate && tunnel->status >= status)
return;
- netdev_dbg(tunnel->amt->dev,
- "Update Tunnel(IP = %pI4, PORT = %u) status %s -> %s",
- &tunnel->ip4, ntohs(tunnel->source_port),
- status_str[tunnel->status], status_str[status]);
+ if (amt_v6(tunnel->amt))
+ netdev_dbg(tunnel->amt->dev,
+ "Update Tunnel(IP = %pI6c, PORT = %u) status %s -> %s",
+ &tunnel->addr.ip6, ntohs(tunnel->source_port),
+ status_str[tunnel->status], status_str[status]);
+ else
+ netdev_dbg(tunnel->amt->dev,
+ "Update Tunnel(IP = %pI4, PORT = %u) status %s -> %s",
+ &tunnel->addr.ip4, ntohs(tunnel->source_port),
+ status_str[tunnel->status], status_str[status]);
tunnel->status = status;
}

@@ -1173,12 +1192,12 @@ static void amt_send_multicast_data(struct amt_dev *amt,

memset(&fl4, 0, sizeof(struct flowi4));
fl4.flowi4_oif = amt->stream_dev->ifindex;
- fl4.daddr = tunnel->ip4;
+ fl4.daddr = tunnel->addr.ip4;
fl4.saddr = amt->local_ip;
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", &tunnel->ip4);
+ netdev_dbg(amt->dev, "no route to %pI4\n", &tunnel->addr.ip4);
kfree_skb(skb);
return;
}
@@ -1228,13 +1247,13 @@ static bool amt_send_membership_query(struct amt_dev *amt,
skb_reset_inner_headers(skb);
memset(&fl4, 0, sizeof(struct flowi4));
fl4.flowi4_oif = amt->stream_dev->ifindex;
- fl4.daddr = tunnel->ip4;
+ fl4.daddr = tunnel->addr.ip4;
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", &tunnel->ip4);
+ netdev_dbg(amt->dev, "no route to %pI4\n", &tunnel->addr.ip4);
return true;
}

@@ -2585,7 +2604,7 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
skb_reset_network_header(skb);

list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list) {
- if (tunnel->ip4 == saddr) {
+ if (tunnel->addr.ip4 == saddr) {
if ((nonce == tunnel->nonce &&
response_mac == tunnel->mac)) {
mod_delayed_work(amt_wq, &tunnel->gc_wq,
@@ -2818,18 +2837,23 @@ static bool amt_discovery_handler(struct amt_dev *amt, struct sk_buff *skb)

static bool amt_request_handler(struct amt_dev *amt, struct sk_buff *skb)
{
+ struct {
+ union amt_addr addr;
+ __be16 port;
+ __be32 nonce;
+ } __packed mac_in;
struct amt_header_request *amtrh;
struct amt_tunnel_list *tunnel;
unsigned long long key;
+ union amt_addr saddr;
struct udphdr *udph;
- struct iphdr *iph;
u64 mac;
int i;

if (!pskb_may_pull(skb, sizeof(*udph) + sizeof(*amtrh)))
return true;

- iph = ip_hdr(skb);
+ amt_outer_saddr(amt, skb, &saddr);
udph = udp_hdr(skb);
amtrh = (struct amt_header_request *)(udp_hdr(skb) + 1);

@@ -2837,12 +2861,24 @@ static bool amt_request_handler(struct amt_dev *amt, struct sk_buff *skb)
return true;

list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list)
- if (tunnel->ip4 == iph->saddr)
+ if (amt_addr_equal(&tunnel->addr, &saddr))
goto send;

spin_lock_bh(&amt->lock);
if (amt->nr_tunnels >= amt->max_tunnels) {
spin_unlock_bh(&amt->lock);
+ if (amt_v6(amt)) {
+ /* amt_rcv() made skb->dev the amt device, but
+ * icmp6_send() routes an error to a link-local
+ * source through skb->dev, and the amt device
+ * drops it. Send it through the underlying link.
+ */
+ skb->dev = amt->stream_dev;
+ icmpv6_ndo_send(skb, ICMPV6_DEST_UNREACH,
+ ICMPV6_ADDR_UNREACH, 0);
+ skb->dev = amt->dev;
+ return true;
+ }
icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
return true;
}
@@ -2856,7 +2892,7 @@ static bool amt_request_handler(struct amt_dev *amt, struct sk_buff *skb)
}

tunnel->source_port = udph->source;
- tunnel->ip4 = iph->saddr;
+ tunnel->addr = saddr;

memcpy(&key, &tunnel->key, sizeof(unsigned long long));
tunnel->amt = amt;
@@ -2876,10 +2912,13 @@ static bool amt_request_handler(struct amt_dev *amt, struct sk_buff *skb)

send:
tunnel->nonce = amtrh->nonce;
- mac = siphash_3u32((__force u32)tunnel->ip4,
- (__force u32)tunnel->source_port,
- (__force u32)tunnel->nonce,
- &tunnel->key);
+ /* The MAC is opaque to the gateway, which only echoes it, so one
+ * siphash over the zero-padded endpoint serves both families.
+ */
+ mac_in.addr = tunnel->addr;
+ mac_in.port = tunnel->source_port;
+ mac_in.nonce = tunnel->nonce;
+ mac = siphash(&mac_in, sizeof(mac_in), &tunnel->key);
tunnel->mac = mac >> 16;

if (!netif_running(amt->dev) || !netif_running(amt->stream_dev))
diff --git a/include/net/amt.h b/include/net/amt.h
index 921944b..77d17fc 100644
--- a/include/net/amt.h
+++ b/include/net/amt.h
@@ -241,6 +241,14 @@ struct amt_relay_headers {
};
} __packed;

+/* ip6 is present without CONFIG_IPV6 too, so that code which only copies
+ * or compares addresses needs no #if; amt_v6() is then false.
+ */
+union amt_addr {
+ __be32 ip4;
+ struct in6_addr ip6;
+};
+
struct amt_tunnel_list {
struct list_head list;
/* Protect All resources under an amt_tunne_list */
@@ -251,7 +259,8 @@ struct amt_tunnel_list {
enum amt_status status;
struct delayed_work gc_wq;
__be16 source_port;
- __be32 ip4;
+ /* Gateway endpoint, in the device's outer family */
+ union amt_addr addr;
__be32 nonce;
siphash_key_t key;
u64 mac:48,
@@ -260,13 +269,6 @@ struct amt_tunnel_list {
struct hlist_head groups[];
};

-union amt_addr {
- __be32 ip4;
-#if IS_ENABLED(CONFIG_IPV6)
- struct in6_addr ip6;
-#endif
-};
-
/* RFC 3810
*
* When the router is in EXCLUDE mode, the router state is represented
--
2.43.0