merge: branch 'bg/clat-prefix-selection'

l3cfg: fix selection of the CLAT IPv6 prefix

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2378
This commit is contained in:
Beniamino Galvani 2026-03-18 17:44:41 +00:00
commit 5580b982ac
4 changed files with 219 additions and 19 deletions

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

View file

@ -167,6 +167,92 @@ nm_ip6_addr_is_ula(const struct in6_addr *address)
return (address->s6_addr32[0] & htonl(0xfe000000u)) == htonl(0xfc000000u);
}
/**
* nm_ip6_addr_common_prefix_len:
* @a: first IPv6 address
* @b: second IPv6 address
*
* Returns: the number of leading bits that @a and @b have in common,
* from 0 to 128.
*/
guint
nm_ip6_addr_common_prefix_len(const struct in6_addr *a, const struct in6_addr *b)
{
guint i;
for (i = 0; i < 16; i++) {
guint8 diff = a->s6_addr[i] ^ b->s6_addr[i];
if (diff != 0)
return i * 8u + __builtin_clz((guint) diff) - 24u;
}
return 128;
}
/**
* nm_ip6_addr_rfc6724_label:
* @addr: an IPv6 address
*
* Returns the label for @addr from the default policy table defined
* in RFC 6724, Section 2.1:
*
* Prefix Precedence Label
* ::1/128 50 0
* ::/0 40 1
* ::ffff:0:0/96 35 4
* 2002::/16 30 2
* 2001::/32 5 5
* fc00::/7 3 13
* ::/96 1 3
* fec0::/10 1 11
* 3ffe::/16 1 12
*
* Returns: the label value (0-13). It can be used in the Source
* Address Selection algorithm to prefer a source whose label
* matches with the label of the destination.
*/
guint
nm_ip6_addr_rfc6724_label(const struct in6_addr *addr)
{
/* Checked from most-specific to least-specific prefix length. */
/* ::1/128 (loopback) */
if (IN6_IS_ADDR_LOOPBACK(addr))
return 0;
/* ::ffff:0:0/96 (IPv4-mapped) */
if (IN6_IS_ADDR_V4MAPPED(addr))
return 4;
/* ::/96 (IPv4-compatible, deprecated) */
if (addr->s6_addr32[0] == 0 && addr->s6_addr32[1] == 0 && addr->s6_addr32[2] == 0
&& !IN6_IS_ADDR_UNSPECIFIED(addr))
return 3;
/* 2001::/32 (Teredo) */
if (addr->s6_addr32[0] == htonl(0x20010000u))
return 5;
/* 2002::/16 (6to4) */
if ((addr->s6_addr32[0] & htonl(0xFFFF0000u)) == htonl(0x20020000u))
return 2;
/* 3ffe::/16 (deprecated 6bone) */
if ((addr->s6_addr32[0] & htonl(0xFFFF0000u)) == htonl(0x3FFE0000u))
return 12;
/* fec0::/10 (deprecated site-local) */
if ((addr->s6_addr32[0] & htonl(0xFFC00000u)) == htonl(0xFEC00000u))
return 11;
/* fc00::/7 (ULA) */
if (nm_ip6_addr_is_ula(addr))
return 13;
/* ::/0 (default) */
return 1;
}
/*****************************************************************************/
gconstpointer

View file

@ -280,6 +280,10 @@ nm_ip_addr_same_prefix(int addr_family, gconstpointer addr_a, gconstpointer addr
gboolean nm_ip_addr_is_site_local(int addr_family, const void *address);
gboolean nm_ip6_addr_is_ula(const struct in6_addr *address);
guint nm_ip6_addr_common_prefix_len(const struct in6_addr *a, const struct in6_addr *b);
guint nm_ip6_addr_rfc6724_label(const struct in6_addr *addr);
/*****************************************************************************/
#define NM_IPV4LL_NETWORK ((in_addr_t) htonl(0xA9FE0000lu)) /* 169.254.0.0 */

View file

@ -2380,6 +2380,60 @@ test_inet_utils(void)
/*****************************************************************************/
static void
test_ip6_addr_common_prefix_len(void)
{
struct in6_addr a;
struct in6_addr b;
/* identical addresses -> 128 */
a = nmtst_inet6_from_string("2001:db8::1");
b = nmtst_inet6_from_string("2001:db8::1");
g_assert_cmpuint(nm_ip6_addr_common_prefix_len(&a, &b), ==, 128);
/* completely different -> 0 */
a = nmtst_inet6_from_string("8000::");
b = nmtst_inet6_from_string("::");
g_assert_cmpuint(nm_ip6_addr_common_prefix_len(&a, &b), ==, 0);
/* first 64 bits in common, differ at bit 65 */
a = nmtst_inet6_from_string("2001:db8:abcd:1234:8000::");
b = nmtst_inet6_from_string("2001:db8:abcd:1234::");
g_assert_cmpuint(nm_ip6_addr_common_prefix_len(&a, &b), ==, 64);
/* same /48 prefix */
a = nmtst_inet6_from_string("2001:db8:abcd::");
b = nmtst_inet6_from_string("2001:db8:abcd:ffff::");
g_assert_cmpuint(nm_ip6_addr_common_prefix_len(&a, &b), ==, 48);
/* differ in 5th bit -> 4 common bits */
a = nmtst_inet6_from_string("f800::");
b = nmtst_inet6_from_string("f000::");
g_assert_cmpuint(nm_ip6_addr_common_prefix_len(&a, &b), ==, 4);
/* differ in 2nd bit -> 1 common bit */
a = nmtst_inet6_from_string("c000::");
b = nmtst_inet6_from_string("8000::");
g_assert_cmpuint(nm_ip6_addr_common_prefix_len(&a, &b), ==, 1);
/* both zero -> 128 */
a = nmtst_inet6_from_string("::");
b = nmtst_inet6_from_string("::");
g_assert_cmpuint(nm_ip6_addr_common_prefix_len(&a, &b), ==, 128);
/* first 120 bits in common, differ at MSB of last byte */
a = nmtst_inet6_from_string("2001:db8::80");
b = nmtst_inet6_from_string("2001:db8::");
g_assert_cmpuint(nm_ip6_addr_common_prefix_len(&a, &b), ==, 120);
/* first 127 bits in common, differ only in last bit */
a = nmtst_inet6_from_string("2001:db8::fe");
b = nmtst_inet6_from_string("2001:db8::ff");
g_assert_cmpuint(nm_ip6_addr_common_prefix_len(&a, &b), ==, 127);
}
/*****************************************************************************/
static gboolean
_inet_parse(int addr_family, const char *str, gboolean accept_legacy, gpointer out_addr)
{
@ -2940,6 +2994,7 @@ main(int argc, char **argv)
g_test_add_func("/general/test_path_simplify", test_path_simplify);
g_test_add_func("/general/test_hostname_is_valid", test_hostname_is_valid);
g_test_add_func("/general/test_inet_utils", test_inet_utils);
g_test_add_func("/general/test_ip6_addr_common_prefix_len", test_ip6_addr_common_prefix_len);
g_test_add_func("/general/test_inet_parse_ip4_legacy", test_inet_parse_ip4_legacy);
g_test_add_func("/general/test_garray", test_garray);
g_test_add_func("/general/test_nm_prioq", test_nm_prioq);