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: f0e77a4354 ('Add support for CLAT to l3cfg')
This commit is contained in:
Beniamino Galvani 2026-03-07 10:56:04 +01:00
parent 7f2745f3b1
commit a03a245819

View file

@ -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