The amt device binds one AF_INET UDP socket on the relay port, so a relay and its gateways can only talk AMT over IPv4. RFC 7450 defines the protocol over either IP version: the Relay Advertisement carries an IPv4 or an IPv6 relay address, and the relay answers in the IP version of the Discovery (s5.1.2.5), so an IPv6-only access network needs an IPv6 outer transport to reach the relay at all. Add the outer IPv6 local address to struct amt_dev, and amt_v6(), which reports the device's outer family from it. The inner family is independent of the outer one: the bool v6 arguments keep selecting IGMP or MLD, and a device of either outer family carries both IPv4 and IPv6 multicast. amt_create_sock() binds an AF_INET6 socket for an IPv6 device. The socket sends and verifies the UDP checksum, which RFC 8200 s8.1 makes mandatory over IPv6, and it is V6ONLY, so that it does not also claim the IPv4 wildcard port, which an IPv4 amt device in the same netns may hold. The socket is also bound to the underlying link, IFLA_AMT_LINK. The IPv4 paths pass that link as the output interface, and an IPv4 route lookup then only uses routes through it. An IPv6 lookup that has a source address, as every AMT send does, treats the output interface as a preference unless the socket is bound to a device, so without the binding the outer IPv6 traffic could leave through another link while the device's MTU and headroom are derived from this one. The binding also keeps packets that arrive on other links away from the socket. A link-local address is unique only on its link, and relay tunnels are keyed on the gateway's address, so two gateways on different links with the same link-local address would otherwise share a tunnel, and the replies would go out the wrong link. The new socket also hands ICMPv6 errors to amt_err_lookup(). This relies on amt_parse_type() pulling the AMT header behind the transport header, as "amt: pull the AMT header behind the transport header in amt_parse_type()" (eb0c18404c89) makes it do, since on the error path skb->data points at the quoted IPv6 header. Nothing sets local_ipv6 until the netlink attribute added at the end of this series, once every path can use it. No functional change. Assisted-by: LLM Signed-off-by: Omar Ramadan --- drivers/net/amt.c | 34 ++++++++++++++++++++++++++++------ include/net/amt.h | 2 ++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/drivers/net/amt.c b/drivers/net/amt.c index 81d26ef..423ed77 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -80,6 +80,14 @@ static struct in6_addr mld2_all_node = MLD2_ALL_NODE_INIT; static struct mld2_grec mldv2_zero_grec; #endif +/* The outer transport family is fixed when the link is created: an IPv6 + * local address selects IPv6, otherwise the device runs over IPv4. + */ +static bool amt_v6(const struct amt_dev *amt) +{ + return IS_ENABLED(CONFIG_IPV6) && !ipv6_addr_any(&amt->local_ipv6); +} + static void __amt_source_gc_work(void) { struct amt_source_node *snode; @@ -2984,19 +2992,33 @@ drop: return 0; } -static struct sock *amt_create_sock(struct net *net, __be16 port) +static struct sock *amt_create_sock(const struct amt_dev *amt) { struct udp_port_cfg udp_conf; struct socket *sock; int err; memset(&udp_conf, 0, sizeof(udp_conf)); - udp_conf.family = AF_INET; - udp_conf.local_ip.s_addr = htonl(INADDR_ANY); + if (amt_v6(amt)) { + /* Bound to :: by the memset, and to the underlying link, so + * that IPv6 route lookups are strict about the output + * interface, as IPv4 ones are, and a link-local peer is + * unique. V6ONLY leaves the IPv4 wildcard port to an IPv4 + * amt device. + */ + udp_conf.family = AF_INET6; + udp_conf.bind_ifindex = amt->stream_dev->ifindex; + udp_conf.use_udp6_tx_checksums = true; + udp_conf.use_udp6_rx_checksums = true; + udp_conf.ipv6_v6only = true; + } else { + udp_conf.family = AF_INET; + udp_conf.local_ip.s_addr = htonl(INADDR_ANY); + } - udp_conf.local_udp_port = port; + udp_conf.local_udp_port = amt->relay_port; - err = udp_sock_create(net, &udp_conf, &sock); + err = udp_sock_create(amt->net, &udp_conf, &sock); if (err < 0) return ERR_PTR(err); @@ -3008,7 +3030,7 @@ static int amt_socket_create(struct amt_dev *amt) struct udp_tunnel_sock_cfg tunnel_cfg; struct sock *sk; - sk = amt_create_sock(amt->net, amt->relay_port); + sk = amt_create_sock(amt); if (IS_ERR(sk)) return PTR_ERR(sk); diff --git a/include/net/amt.h b/include/net/amt.h index 2846dde..8df7d43 100644 --- a/include/net/amt.h +++ b/include/net/amt.h @@ -344,6 +344,8 @@ struct amt_dev { __be16 gw_port; /* Outer local ip */ __be32 local_ip; + /* Outer local IPv6 address, :: unless the outer transport is IPv6 */ + struct in6_addr local_ipv6; /* Outer remote ip */ __be32 remote_ip; /* Outer discovery ip */ -- 2.43.0