From a03a2458197f00a7aa9527cef42e5e637840edf2 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Sat, 7 Mar 2026 10:56:04 +0100 Subject: [PATCH] l3cfg: fix selection of the CLAT IPv6 prefix If the router advertises both ULA and GUA prefixes, the CLAT should select the one that better matches the NAT64 prefix when generating the additional IPv6 address, as recommended by Internet Draft draft-ietf-v6ops-claton. The current implementation just takes the first one, which can cause problems. For example, if the network is using a public NAT64 server, the NAT64 prefix is in the GUA range. Choosing a ULA as source address would not work. Fixes: f0e77a43542c ('Add support for CLAT to l3cfg') --- src/core/nm-l3cfg.c | 93 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 59fc6e4d7f..72288def8c 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -4140,6 +4140,53 @@ update_routes: } } +/** + * _clat_prefix_is_better: + * @best: current best candidate (or %NULL) + * @candidate: the new candidate prefix + * @nat64_pref: the NAT64 prefix + * + * Compare two SLAAC candidate prefixes to be used for CLAT, + * as recommended by draft-ietf-v6ops-claton Section 7. Apply + * rules 6 and 8 of the source address selection algorithm from + * RFC 6724, Section 5. + * + * Returns %TRUE if @candidate is better than @best. + */ +static gboolean +_clat_prefix_is_better(const NMPlatformIP6Address *best, + const NMPlatformIP6Address *candidate, + const struct in6_addr *nat64_pref) +{ + guint nat64_pref_label; + gboolean best_label_match; + gboolean cand_label_match; + guint best_prefix_len; + guint cand_prefix_len; + + if (!best) + return TRUE; + + /* Rule 6: prefer the address whose RFC 6724 label matches + * the label of the NAT64 prefix. */ + nat64_pref_label = nm_ip6_addr_rfc6724_label(nat64_pref); + best_label_match = nm_ip6_addr_rfc6724_label(&best->address) == nat64_pref_label; + cand_label_match = nm_ip6_addr_rfc6724_label(&candidate->address) == nat64_pref_label; + + if (cand_label_match && !best_label_match) + return TRUE; + else if (best_label_match && !cand_label_match) + return FALSE; + + /* Rule 8: longest matching prefix with the NAT64 prefix. */ + best_prefix_len = nm_ip6_addr_common_prefix_len(&best->address, nat64_pref); + cand_prefix_len = nm_ip6_addr_common_prefix_len(&candidate->address, nat64_pref); + if (cand_prefix_len != best_prefix_len) + return cand_prefix_len > best_prefix_len; + + return FALSE; +} + static void _l3cfg_update_clat_config(NML3Cfg *self, NML3ConfigData *l3cd, @@ -4206,29 +4253,37 @@ _l3cfg_update_clat_config(NML3Cfg *self, network_id = nm_l3_config_data_get_network_id(l3cd); if (!self->priv.p->clat_address_6_valid && network_id) { + const NMPlatformIP6Address *best_prefix = NULL; + + /* Select the best SLAAC prefix for the CLAT address per + * draft-ietf-v6ops-claton-14 Section 7 */ nm_l3_config_data_iter_ip6_address_for_each (&iter, l3cd, &ip6_entry) { if (ip6_entry->addr_source == NM_IP_CONFIG_SOURCE_NDISC && ip6_entry->plen == 64) { - ip6 = ip6_entry->address; - - nm_utils_ipv6_addr_set_stable_privacy(NM_UTILS_STABLE_TYPE_CLAT, - &ip6, - nm_l3cfg_get_ifname(self, TRUE), - network_id, - 0); - self->priv.p->clat_address_6 = (NMPlatformIP6Address) { - .ifindex = self->priv.ifindex, - .address = ip6, - .peer_address = ip6, - .addr_source = NM_IP_CONFIG_SOURCE_CLAT, - .plen = ip6_entry->plen, - }; - - _LOGT("clat: using IPv6 address %s", nm_inet6_ntop(&ip6, buf)); - - self->priv.p->clat_address_6_valid = TRUE; - break; + if (_clat_prefix_is_better(best_prefix, ip6_entry, &pref64)) + best_prefix = ip6_entry; } } + + if (best_prefix) { + ip6 = best_prefix->address; + + nm_utils_ipv6_addr_set_stable_privacy(NM_UTILS_STABLE_TYPE_CLAT, + &ip6, + nm_l3cfg_get_ifname(self, TRUE), + network_id, + 0); + self->priv.p->clat_address_6 = (NMPlatformIP6Address) { + .ifindex = self->priv.ifindex, + .address = ip6, + .peer_address = ip6, + .addr_source = NM_IP_CONFIG_SOURCE_CLAT, + .plen = best_prefix->plen, + }; + + _LOGT("clat: using IPv6 address %s", nm_inet6_ntop(&ip6, buf)); + + self->priv.p->clat_address_6_valid = TRUE; + } } /* Don't get a v4 address if we have no v6 address (otherwise, we could