From 4ed1d04aff349c0749a6dcf50809483b2eaf978c Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 11 Mar 2025 09:46:28 +0100 Subject: [PATCH 1/6] libnm-glib-aux: add nm_puint64_hash()/nm_puint64_equal() The two new functions can be used as GHashFunc and GEqualFunc when creating hash tables whose keys are pointers to a guint64. --- src/libnm-glib-aux/nm-hash-utils.c | 21 +++++++++++++++++++++ src/libnm-glib-aux/nm-hash-utils.h | 3 +++ 2 files changed, 24 insertions(+) diff --git a/src/libnm-glib-aux/nm-hash-utils.c b/src/libnm-glib-aux/nm-hash-utils.c index 973422a198..7f25e9ac3e 100644 --- a/src/libnm-glib-aux/nm-hash-utils.c +++ b/src/libnm-glib-aux/nm-hash-utils.c @@ -190,6 +190,27 @@ nm_pint_equal(gconstpointer a, gconstpointer b) return s1 == s2 || (s1 && s2 && *s1 == *s2); } +/* GHashFunc for GHashTable keys that are a pointer to a guint64 */ +guint +nm_puint64_hash(gconstpointer p) +{ + const guint64 *s = p; + + if (!s) + return nm_hash_static(298377461u); + return nm_hash_val(1208815757u, *s); +} + +/* GEqualFunc for GHashTable keys that are a pointer to a guint64 */ +gboolean +nm_puint64_equal(gconstpointer a, gconstpointer b) +{ + const guint64 *s1 = a; + const guint64 *s2 = a; + + return s1 == s2 || (s1 && s2 && *s1 == *s2); +} + guint nm_pdirect_hash(gconstpointer p) { diff --git a/src/libnm-glib-aux/nm-hash-utils.h b/src/libnm-glib-aux/nm-hash-utils.h index 6d7cc271bc..34690eabc5 100644 --- a/src/libnm-glib-aux/nm-hash-utils.h +++ b/src/libnm-glib-aux/nm-hash-utils.h @@ -266,6 +266,9 @@ gboolean nm_pstr_equal(gconstpointer a, gconstpointer b); guint nm_pint_hash(gconstpointer p); gboolean nm_pint_equal(gconstpointer a, gconstpointer b); +guint nm_puint64_hash(gconstpointer p); +gboolean nm_puint64_equal(gconstpointer a, gconstpointer b); + G_STATIC_ASSERT(sizeof(int) == sizeof(guint32)); #define nm_puint32_hash nm_pint_hash #define nm_puint32_equal nm_pint_equal From dbc4ff0a1d8f872dbc4676b62b4bb1ae60b98b3e Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 12 Mar 2025 13:55:19 +0100 Subject: [PATCH 2/6] libnm-glib-aux: add nm_ip6_addr_get_subnet_id() Add function nm_ip6_addr_get_subnet_id() to get the subnet ID of a IPv6 address. --- src/libnm-glib-aux/nm-inet-utils.c | 36 ++++++++++++++ src/libnm-glib-aux/nm-inet-utils.h | 2 + .../tests/test-shared-general.c | 48 +++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/src/libnm-glib-aux/nm-inet-utils.c b/src/libnm-glib-aux/nm-inet-utils.c index 2ee73ad5e7..f4f418cf18 100644 --- a/src/libnm-glib-aux/nm-inet-utils.c +++ b/src/libnm-glib-aux/nm-inet-utils.c @@ -238,6 +238,42 @@ nm_ip6_addr_clear_host_address(struct in6_addr *dst, const struct in6_addr *src, return dst; } +/** + * nm_ip6_addr_get_subnet_id: + * @addr: the IPv6 address + * @plen: the prefix length + * + * Given an IPv6 address and a prefix length, returns the subnet ID, + * defined as follows: + * + * | plen bits | (64 - plen) bits | 64 bits | + * +-----------------+------------------+-------------------------------+ + * | prefix | subnet ID | interface ID | + * +-----------------+------------------+-------------------------------+ + * + * The prefix length must be a value between 0 and 64. + */ +guint64 +nm_ip6_addr_get_subnet_id(struct in6_addr *addr, guint8 plen) +{ + int nbits = 64 - plen; + guint64 res = 0; + guint64 tmp; + guint64 i; + + g_return_val_if_fail(addr, 0); + g_return_val_if_fail(plen <= 64, 0); + + for (i = 0; i < 2 && nbits > 0; i++, nbits -= 32) { + tmp = htonl(addr->s6_addr32[1 - i]); + tmp = tmp & ((1ULL << NM_MIN(32U, (guint) nbits)) - 1); + tmp = tmp << (32 * i); + res += tmp; + } + + return res; +} + int nm_ip6_addr_same_prefix_cmp(const struct in6_addr *addr_a, const struct in6_addr *addr_b, diff --git a/src/libnm-glib-aux/nm-inet-utils.h b/src/libnm-glib-aux/nm-inet-utils.h index 489d21dde2..999519f890 100644 --- a/src/libnm-glib-aux/nm-inet-utils.h +++ b/src/libnm-glib-aux/nm-inet-utils.h @@ -218,6 +218,8 @@ nm_ip4_addr_clear_host_address(in_addr_t addr, guint32 plen) const struct in6_addr * nm_ip6_addr_clear_host_address(struct in6_addr *dst, const struct in6_addr *src, guint32 plen); +guint64 nm_ip6_addr_get_subnet_id(struct in6_addr *addr, guint8 plen); + /*****************************************************************************/ static inline int diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c index 2f09a5490d..931c25c667 100644 --- a/src/libnm-glib-aux/tests/test-shared-general.c +++ b/src/libnm-glib-aux/tests/test-shared-general.c @@ -397,6 +397,53 @@ test_nm_ip4_addr_netmask_from_prefix(void) /*****************************************************************************/ +static void +test_nm_ip6_addr_get_subnet_id(void) +{ +#define check_subnet_id(_addrstr, _plen, _subnet_id) \ + { \ + struct in6_addr addr; \ + \ + addr = nmtst_inet6_from_string(_addrstr); \ + g_assert_cmpint(nm_ip6_addr_get_subnet_id(&addr, _plen), ==, _subnet_id); \ + } + + check_subnet_id("aaaa:bbbb:cccc:ffff::", 64, 0); + check_subnet_id("aaaa:bbbb:cccc:fffe::", 63, 0); + check_subnet_id("aaaa:bbbb:cccc:ffff::", 63, 1); + check_subnet_id("aaaa:bbbb:cccc:fffc::", 62, 0); + check_subnet_id("aaaa:bbbb:cccc:fffd::", 62, 1); + check_subnet_id("aaaa:bbbb:cccc:fffe::", 62, 2); + check_subnet_id("aaaa:bbbb:cccc:ffff::", 62, 3); + check_subnet_id("aaaa:bbbb:cccc:fff8::", 61, 0); + check_subnet_id("aaaa:bbbb:cccc:ffff::", 61, 7); + check_subnet_id("aaaa:bbbb:cccc:fff0::", 60, 0); + check_subnet_id("aaaa:bbbb:cccc:fff2::", 60, 2); + check_subnet_id("aaaa:bbbb:cccc:ffff::", 60, 15); + check_subnet_id("aaaa:bbbb:cccc:0000::", 48, 0); + check_subnet_id("aaaa:bbbb:cccc:f000::", 48, 61440); + check_subnet_id("aaaa:bbbb:cccc:ffff::", 48, 65535); + check_subnet_id("aaaa:bbbb:cccc:0000::", 46, 0); + check_subnet_id("aaaa:bbbb:cccc:0001::", 46, 1); + check_subnet_id("aaaa:bbbb:ccce:5555::", 46, 152917); + check_subnet_id("aaaa:bbbb:0000:0000::", 32, 0); + check_subnet_id("aaaa:bbbb:0000:0001::", 32, 1); + check_subnet_id("aaaa:bbbb:0001:0001::", 32, 65537); + check_subnet_id("aaaa:bbbb:ffff:ffff::", 32, 0xffffffff); + check_subnet_id("aaaa:bbb8:0000:0000::", 30, 0); + check_subnet_id("aaaa:bbb8:0000:0001::", 30, 1); + check_subnet_id("aaaa:bbb8:0001:0001::", 30, 65537); + check_subnet_id("aaaa:bbbb:0000:0000::", 30, 0x300000000ULL); + check_subnet_id("aaaa:bbbb:0001:0001::", 30, 0x300010001ULL); + check_subnet_id("aaaa:bbbb:0001:0001::", 8, 0xAABBBB00010001ULL); + check_subnet_id("abaa:bbbb:0001:0001::", 7, 0x1AABBBB00010001ULL); + check_subnet_id("abaa:bbbb:0001:0001::", 0, 0xABAABBBB00010001ULL); + check_subnet_id("abaa:bbbb:0001:0001:5555:5555:5555:5555", 0, 0xABAABBBB00010001ULL); + check_subnet_id("::", 0, 0); +} + +/*****************************************************************************/ + static void test_unaligned(void) { @@ -2864,6 +2911,7 @@ main(int argc, char **argv) g_test_add_func("/general/test_nm_ip4_addr_is_loopback", test_nm_ip4_addr_is_loopback); g_test_add_func("/general/test_nm_ip4_addr_netmask_from_prefix", test_nm_ip4_addr_netmask_from_prefix); + g_test_add_func("/general/test_nm_ip6_addr_get_subnet_id", test_nm_ip6_addr_get_subnet_id); g_test_add_func("/general/test_unaligned", test_unaligned); g_test_add_func("/general/test_strv_cmp", test_strv_cmp); g_test_add_func("/general/test_strstrip_avoid_copy", test_strstrip_avoid_copy); From 037b14965e2953bd73394c762e184617ca834206 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 12 Mar 2025 15:17:06 +0100 Subject: [PATCH 3/6] libnmc-setting: add new flag for property descriptors Add a new flag "print_hex_negative_as_base10" in the property descriptor _NMMetaPropertyTypData. Normally, when a property has "base = 16", it is printed as unsigned even if the gtype is signed. For some properties, we want to print the hexadecimal representation for positive values, and the base10 representation with minus sign for negative values. A typical use case is to encode the default value as "-1" and use positive values as a hexadecimal number. --- src/libnmc-setting/nm-meta-setting-desc.c | 17 +++++++++++++++-- src/libnmc-setting/nm-meta-setting-desc.h | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/libnmc-setting/nm-meta-setting-desc.c b/src/libnmc-setting/nm-meta-setting-desc.c index b234504a07..2e95b0186f 100644 --- a/src/libnmc-setting/nm-meta-setting-desc.c +++ b/src/libnmc-setting/nm-meta-setting-desc.c @@ -1005,8 +1005,16 @@ _get_fcn_gobject_int(ARGS_GET_FCN) case 16: if (is_uint64) return_str = g_strdup_printf("0x%" G_GINT64_MODIFIER "x", v.u64); - else - return_str = g_strdup_printf("0x%" G_GINT64_MODIFIER "x", (guint64) v.i64); + else { + if (property_info->property_typ_data + && property_info->property_typ_data->subtype.gobject_int + .print_hex_negative_as_base10 + && v.i64 < 0) { + return_str = g_strdup_printf("%" G_GINT64_FORMAT, v.i64); + } else { + return_str = g_strdup_printf("0x%" G_GINT64_MODIFIER "x", (guint64) v.i64); + } + } break; default: return_str = NULL; @@ -1422,6 +1430,11 @@ _set_fcn_gobject_int(ARGS_SET_FCN) nm_meta_property_int_get_range(property_info, &min, &max); + /* See the comment on "print_hex_negative_as_base10" */ + nm_assert(!property_info->property_typ_data + || !property_info->property_typ_data->subtype.gobject_int.print_hex_negative_as_base10 + || (!is_uint64 && min.i64 > -10)); + if (is_uint64) v.u64 = _nm_utils_ascii_str_to_uint64(value, base, min.u64, max.u64, 0); else diff --git a/src/libnmc-setting/nm-meta-setting-desc.h b/src/libnmc-setting/nm-meta-setting-desc.h index 46b7f65002..0294b1f706 100644 --- a/src/libnmc-setting/nm-meta-setting-desc.h +++ b/src/libnmc-setting/nm-meta-setting-desc.h @@ -293,9 +293,21 @@ struct _NMMetaPropertyTypData { int value); } gobject_enum; struct { - NMMetaSignUnsignInt64 min; - NMMetaSignUnsignInt64 max; - guint base; + NMMetaSignUnsignInt64 min; + NMMetaSignUnsignInt64 max; + guint base; + + /* Normally, when a property has "base = 16", it is printed + * as unsigned even if the gtype is signed. For some properties, + * we want to print the hexadecimal representation for positive + * values, and the base10 representation with minus sign for negative + * values. A typical use case is to encode the default value as + * "-1" and use positive values as a hexadecimal number. To avoid + * ambiguity when setting the value via nmcli, the property minimum + * allowed value should not be <= -10. + */ + bool print_hex_negative_as_base10; + const NMMetaUtilsIntValueInfo *value_infos; } gobject_int; struct { From 4a8bedcd8980aed7b41a0f76e76e00dab58798c7 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 11 Mar 2025 09:41:47 +0100 Subject: [PATCH 4/6] device: remove the prefix-delegation IP configuration on cleanup When a device in IPv6 shared mode obtains a prefix, it adds a new l3cd of type L3_CONFIG_DATA_TYPE_PD_6 for that prefix. However, that l3cd is never removed later and so the address lingers on the interface even after the connection goes down. Remove the l3cd on cleanup. --- src/core/devices/nm-device.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 3ddd5ffe40..a6d4acc49e 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -13457,6 +13457,8 @@ _dev_ipsharedx_cleanup(NMDevice *self, int addr_family) nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd); _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, NULL, FALSE); + } else { + _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_PD_6, NULL, FALSE); } _dev_ipsharedx_set_state(self, addr_family, NM_DEVICE_IP_STATE_NONE); From b372caf0c4355ff0a9296e4d18bde69444e6474f Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 6 Mar 2025 22:14:02 +0100 Subject: [PATCH 5/6] libnm, nmcli: introduce new "prefix-delegation" setting Introduce a new "prefix-delegation" setting. It contains properties related to the configuration of downstream interfaces using IPv6 prefix-delegation. The only property at the moment is "subnet-id", which specifies which prefix to choose when the delegation contains multiple /64 networks. --- NEWS | 3 + docs/libnm/libnm-docs.xml | 1 + src/libnm-client-impl/libnm.ver | 3 + src/libnm-client-public/NetworkManager.h | 1 + src/libnm-client-public/nm-autoptr.h | 1 + ...gen-metadata-nm-settings-libnm-core.xml.in | 8 ++ src/libnm-core-impl/meson.build | 1 + .../nm-meta-setting-base-impl.c | 9 ++ .../nm-setting-prefix-delegation.c | 118 ++++++++++++++++++ src/libnm-core-intern/nm-core-internal.h | 1 + .../nm-meta-setting-base-impl.h | 1 + src/libnm-core-public/meson.build | 1 + src/libnm-core-public/nm-core-types.h | 1 + .../nm-setting-prefix-delegation.h | 41 ++++++ .../nm-meta-setting-base-impl.c | 9 ++ .../nm-meta-setting-base-impl.h | 1 + src/libnmc-setting/nm-meta-setting-desc.c | 17 +++ src/libnmc-setting/settings-docs.h.in | 1 + src/nmcli/connections.c | 43 +++---- .../gen-metadata-nm-settings-nmcli.xml.in | 6 + vapi/NM-1.0.metadata | 1 + 21 files changed, 247 insertions(+), 21 deletions(-) create mode 100644 src/libnm-core-impl/nm-setting-prefix-delegation.c create mode 100644 src/libnm-core-public/nm-setting-prefix-delegation.h diff --git a/NEWS b/NEWS index bd7d247184..f08d3187c2 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,9 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! * Add systemd services to provide networking in the initrd. * Introduce a new "ovs-dpdk.lsc-interrupt" property to configure the Link State Change (LSC) detection mode for OVS DPDK interfaces. +* Add a new "prefix-delegation" setting containing a "subnet-id" + property that specifies the subnet to choose on the downstream + interface when using IPv6 prefix delegation. ============================================= NetworkManager-1.52 diff --git a/docs/libnm/libnm-docs.xml b/docs/libnm/libnm-docs.xml index 4ed0793937..39f62417d1 100644 --- a/docs/libnm/libnm-docs.xml +++ b/docs/libnm/libnm-docs.xml @@ -341,6 +341,7 @@ print ("NetworkManager version " + client.get_version())]]> + diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver index 920caa4efb..bfcf7f399e 100644 --- a/src/libnm-client-impl/libnm.ver +++ b/src/libnm-client-impl/libnm.ver @@ -2055,4 +2055,7 @@ global: nm_setting_ip_config_get_forwarding; nm_setting_ovs_dpdk_get_lsc_interrupt; nm_setting_ovs_dpdk_lsc_interrupt_get_type; + nm_setting_prefix_delegation_get_subnet_id; + nm_setting_prefix_delegation_get_type; + nm_setting_prefix_delegation_new; } libnm_1_52_0; diff --git a/src/libnm-client-public/NetworkManager.h b/src/libnm-client-public/NetworkManager.h index 3608168f96..9e9a4eb839 100644 --- a/src/libnm-client-public/NetworkManager.h +++ b/src/libnm-client-public/NetworkManager.h @@ -59,6 +59,7 @@ #include "nm-setting-ovs-port.h" #include "nm-setting-ppp.h" #include "nm-setting-pppoe.h" +#include "nm-setting-prefix-delegation.h" #include "nm-setting-proxy.h" #include "nm-setting-serial.h" #include "nm-setting-sriov.h" diff --git a/src/libnm-client-public/nm-autoptr.h b/src/libnm-client-public/nm-autoptr.h index 0f6e59c4a8..f4321ad3a6 100644 --- a/src/libnm-client-public/nm-autoptr.h +++ b/src/libnm-client-public/nm-autoptr.h @@ -104,6 +104,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingOvsPatch, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingOvsPort, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingPpp, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingPppoe, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingPrefixDelegation, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingProxy, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingSerial, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingSriov, g_object_unref) diff --git a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in index 44e338bb9a..d165957179 100644 --- a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in +++ b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in @@ -2315,6 +2315,14 @@ gprop-type="gchararray" /> + + + diff --git a/src/libnm-core-impl/meson.build b/src/libnm-core-impl/meson.build index e068d6bd38..610e22792e 100644 --- a/src/libnm-core-impl/meson.build +++ b/src/libnm-core-impl/meson.build @@ -41,6 +41,7 @@ libnm_core_settings_sources = files( 'nm-setting-ovs-port.c', 'nm-setting-ppp.c', 'nm-setting-pppoe.c', + 'nm-setting-prefix-delegation.c', 'nm-setting-proxy.c', 'nm-setting-serial.c', 'nm-setting-sriov.c', diff --git a/src/libnm-core-impl/nm-meta-setting-base-impl.c b/src/libnm-core-impl/nm-meta-setting-base-impl.c index 37cb61f176..d625c4e8c7 100644 --- a/src/libnm-core-impl/nm-meta-setting-base-impl.c +++ b/src/libnm-core-impl/nm-meta-setting-base-impl.c @@ -51,6 +51,7 @@ #include "nm-setting-ovs-port.h" #include "nm-setting-ppp.h" #include "nm-setting-pppoe.h" +#include "nm-setting-prefix-delegation.h" #include "nm-setting-proxy.h" #include "nm-setting-serial.h" #include "nm-setting-tc-config.h" @@ -484,6 +485,13 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = { .setting_name = NM_SETTING_PPP_SETTING_NAME, .get_setting_gtype = nm_setting_ppp_get_type, }, + [NM_META_SETTING_TYPE_PREFIX_DELEGATION] = + { + .meta_type = NM_META_SETTING_TYPE_PREFIX_DELEGATION, + .setting_priority = NM_SETTING_PRIORITY_IP, + .setting_name = NM_SETTING_PREFIX_DELEGATION_SETTING_NAME, + .get_setting_gtype = nm_setting_prefix_delegation_get_type, + }, [NM_META_SETTING_TYPE_PROXY] = { .meta_type = NM_META_SETTING_TYPE_PROXY, @@ -698,6 +706,7 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = { NM_META_SETTING_TYPE_HOSTNAME, NM_META_SETTING_TYPE_IP4_CONFIG, NM_META_SETTING_TYPE_IP6_CONFIG, + NM_META_SETTING_TYPE_PREFIX_DELEGATION, NM_META_SETTING_TYPE_PROXY, NM_META_SETTING_TYPE_TC_CONFIG, diff --git a/src/libnm-core-impl/nm-setting-prefix-delegation.c b/src/libnm-core-impl/nm-setting-prefix-delegation.c new file mode 100644 index 0000000000..100d315307 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-prefix-delegation.c @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-prefix-delegation.h" + +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-prefix-delegation + * @short_description: Describes connection properties related to IPv6 prefix delegation + * + * The #NMSettingPrefixDelegation object is a #NMSetting subclass that describes the + * configuration of downstream interfaces using IPv6 prefix delegation. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_SUBNET_ID, ); + +/** + * NMSettingPrefixDelegation: + * + * IPv6 prefix delegation settings + * + * Since: 1.54 + */ +struct _NMSettingPrefixDelegation { + NMSetting parent; + gint64 subnet_id; +}; + +struct _NMSettingPrefixDelegationClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingPrefixDelegation, nm_setting_prefix_delegation, NM_TYPE_SETTING) + +/*****************************************************************************/ + +/** + * nm_setting_prefix_delegation_get_subnet_id: + * @setting: the #NMSettingPrefixDelegation + * + * Returns: the subnet ID for prefix delegation + * + * Since: 1.54 + **/ +gint64 +nm_setting_prefix_delegation_get_subnet_id(NMSettingPrefixDelegation *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PREFIX_DELEGATION(setting), 0); + + return setting->subnet_id; +} + +/*****************************************************************************/ + +static void +nm_setting_prefix_delegation_init(NMSettingPrefixDelegation *setting) +{} + +/** + * nm_setting_prefix_delegation_new: + * + * Creates a new #NMSettingPrefixDelegation object with default values. + * + * Returns: (transfer full): the new empty #NMSettingPrefixDelegation object + * + * Since: 1.54 + **/ +NMSetting * +nm_setting_prefix_delegation_new(void) +{ + return g_object_new(NM_TYPE_SETTING_PREFIX_DELEGATION, NULL); +} + +static void +nm_setting_prefix_delegation_class_init(NMSettingPrefixDelegationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray *properties_override = _nm_sett_info_property_override_create_array(); + + object_class->get_property = _nm_setting_property_get_property_direct; + object_class->set_property = _nm_setting_property_set_property_direct; + + /** + * NMSettingPrefixDelegation:subnet-id: + * + * The subnet ID to use on the interface from the prefix delegation received via + * an upstream interface. Set to a value between 0 and 0xffffffff (2^32 - 1) + * to indicate a specific subnet ID; or set to -1 to automatically choose + * an available subnet ID. + * + * Since: 1.54 + **/ + _nm_setting_property_define_direct_int64(properties_override, + obj_properties, + NM_SETTING_PREFIX_DELEGATION_SUBNET_ID, + PROP_SUBNET_ID, + -1, + G_MAXUINT32, + -1, + NM_SETTING_PARAM_NONE, + NMSettingPrefixDelegation, + subnet_id); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, + NM_META_SETTING_TYPE_PREFIX_DELEGATION, + NULL, + properties_override, + 0); +} diff --git a/src/libnm-core-intern/nm-core-internal.h b/src/libnm-core-intern/nm-core-internal.h index aa96c52682..8b23dd44cc 100644 --- a/src/libnm-core-intern/nm-core-internal.h +++ b/src/libnm-core-intern/nm-core-internal.h @@ -58,6 +58,7 @@ #include "nm-setting-ovs-port.h" #include "nm-setting-ppp.h" #include "nm-setting-pppoe.h" +#include "nm-setting-prefix-delegation.h" #include "nm-setting-proxy.h" #include "nm-setting-serial.h" #include "nm-setting-sriov.h" diff --git a/src/libnm-core-intern/nm-meta-setting-base-impl.h b/src/libnm-core-intern/nm-meta-setting-base-impl.h index c03285ebf8..d1535e5e1f 100644 --- a/src/libnm-core-intern/nm-meta-setting-base-impl.h +++ b/src/libnm-core-intern/nm-meta-setting-base-impl.h @@ -143,6 +143,7 @@ typedef enum _nm_packed { NM_META_SETTING_TYPE_OVS_PORT, NM_META_SETTING_TYPE_PPP, NM_META_SETTING_TYPE_PPPOE, + NM_META_SETTING_TYPE_PREFIX_DELEGATION, NM_META_SETTING_TYPE_PROXY, NM_META_SETTING_TYPE_SERIAL, NM_META_SETTING_TYPE_SRIOV, diff --git a/src/libnm-core-public/meson.build b/src/libnm-core-public/meson.build index 97888467e5..086801d96d 100644 --- a/src/libnm-core-public/meson.build +++ b/src/libnm-core-public/meson.build @@ -46,6 +46,7 @@ libnm_core_headers = files( 'nm-setting-ovs-port.h', 'nm-setting-ppp.h', 'nm-setting-pppoe.h', + 'nm-setting-prefix-delegation.h', 'nm-setting-proxy.h', 'nm-setting-serial.h', 'nm-setting-sriov.h', diff --git a/src/libnm-core-public/nm-core-types.h b/src/libnm-core-public/nm-core-types.h index a0d6bc82dc..617d6b7415 100644 --- a/src/libnm-core-public/nm-core-types.h +++ b/src/libnm-core-public/nm-core-types.h @@ -52,6 +52,7 @@ typedef struct _NMSettingOvsPatch NMSettingOvsPatch; typedef struct _NMSettingOvsPort NMSettingOvsPort; typedef struct _NMSettingPpp NMSettingPpp; typedef struct _NMSettingPppoe NMSettingPppoe; +typedef struct _NMSettingPrefixDelegation NMSettingPrefixDelegation; typedef struct _NMSettingProxy NMSettingProxy; typedef struct _NMSettingSerial NMSettingSerial; typedef struct _NMSettingSriov NMSettingSriov; diff --git a/src/libnm-core-public/nm-setting-prefix-delegation.h b/src/libnm-core-public/nm-setting-prefix-delegation.h new file mode 100644 index 0000000000..5361d7c69a --- /dev/null +++ b/src/libnm-core-public/nm-setting-prefix-delegation.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_SETTING_PREFIX_DELEGATION_H__ +#define __NM_SETTING_PREFIX_DELEGATION_H__ + +#if !defined(__NETWORKMANAGER_H_INSIDE__) && !defined(NETWORKMANAGER_COMPILATION) +#error "Only can be included directly." +#endif + +#include "nm-setting.h" + +G_BEGIN_DECLS + +#define NM_TYPE_SETTING_PREFIX_DELEGATION (nm_setting_prefix_delegation_get_type()) +#define NM_SETTING_PREFIX_DELEGATION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTING_PREFIX_DELEGATION, NMSettingVrf)) +#define NM_SETTING_PREFIX_DELEGATION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTING_PREFIX_DELEGATIONCONFIG, NMSettingVrfClass)) +#define NM_IS_SETTING_PREFIX_DELEGATION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTING_PREFIX_DELEGATION)) +#define NM_IS_SETTING_PREFIX_DELEGATION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTING_PREFIX_DELEGATION)) +#define NM_SETTING_PREFIX_DELEGATION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTING_PREFIX_DELEGATION, NMSettingVrfClass)) + +#define NM_SETTING_PREFIX_DELEGATION_SETTING_NAME "prefix-delegation" + +#define NM_SETTING_PREFIX_DELEGATION_SUBNET_ID "subnet-id" + +typedef struct _NMSettingPrefixDelegationClass NMSettingPrefixDelegationClass; + +NM_AVAILABLE_IN_1_54 +GType nm_setting_prefix_delegation_get_type(void); +NM_AVAILABLE_IN_1_54 +NMSetting *nm_setting_prefix_delegation_new(void); +NM_AVAILABLE_IN_1_54 +gint64 nm_setting_prefix_delegation_get_subnet_id(NMSettingPrefixDelegation *setting); + +G_END_DECLS + +#endif /* __NM_SETTING_PREFIX_DELEGATION_H__ */ diff --git a/src/libnmc-setting/nm-meta-setting-base-impl.c b/src/libnmc-setting/nm-meta-setting-base-impl.c index 37cb61f176..d625c4e8c7 100644 --- a/src/libnmc-setting/nm-meta-setting-base-impl.c +++ b/src/libnmc-setting/nm-meta-setting-base-impl.c @@ -51,6 +51,7 @@ #include "nm-setting-ovs-port.h" #include "nm-setting-ppp.h" #include "nm-setting-pppoe.h" +#include "nm-setting-prefix-delegation.h" #include "nm-setting-proxy.h" #include "nm-setting-serial.h" #include "nm-setting-tc-config.h" @@ -484,6 +485,13 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = { .setting_name = NM_SETTING_PPP_SETTING_NAME, .get_setting_gtype = nm_setting_ppp_get_type, }, + [NM_META_SETTING_TYPE_PREFIX_DELEGATION] = + { + .meta_type = NM_META_SETTING_TYPE_PREFIX_DELEGATION, + .setting_priority = NM_SETTING_PRIORITY_IP, + .setting_name = NM_SETTING_PREFIX_DELEGATION_SETTING_NAME, + .get_setting_gtype = nm_setting_prefix_delegation_get_type, + }, [NM_META_SETTING_TYPE_PROXY] = { .meta_type = NM_META_SETTING_TYPE_PROXY, @@ -698,6 +706,7 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = { NM_META_SETTING_TYPE_HOSTNAME, NM_META_SETTING_TYPE_IP4_CONFIG, NM_META_SETTING_TYPE_IP6_CONFIG, + NM_META_SETTING_TYPE_PREFIX_DELEGATION, NM_META_SETTING_TYPE_PROXY, NM_META_SETTING_TYPE_TC_CONFIG, diff --git a/src/libnmc-setting/nm-meta-setting-base-impl.h b/src/libnmc-setting/nm-meta-setting-base-impl.h index c03285ebf8..d1535e5e1f 100644 --- a/src/libnmc-setting/nm-meta-setting-base-impl.h +++ b/src/libnmc-setting/nm-meta-setting-base-impl.h @@ -143,6 +143,7 @@ typedef enum _nm_packed { NM_META_SETTING_TYPE_OVS_PORT, NM_META_SETTING_TYPE_PPP, NM_META_SETTING_TYPE_PPPOE, + NM_META_SETTING_TYPE_PREFIX_DELEGATION, NM_META_SETTING_TYPE_PROXY, NM_META_SETTING_TYPE_SERIAL, NM_META_SETTING_TYPE_SRIOV, diff --git a/src/libnmc-setting/nm-meta-setting-desc.c b/src/libnmc-setting/nm-meta-setting-desc.c index 2e95b0186f..2cae19ba86 100644 --- a/src/libnmc-setting/nm-meta-setting-desc.c +++ b/src/libnmc-setting/nm-meta-setting-desc.c @@ -7494,6 +7494,19 @@ static const NMMetaPropertyInfo *const property_infos_PPPOE[] = { NULL }; +#undef _CURRENT_NM_META_SETTING_TYPE +#define _CURRENT_NM_META_SETTING_TYPE NM_META_SETTING_TYPE_PREFIX_DELEGATION +static const NMMetaPropertyInfo *const property_infos_PREFIX_DELEGATION[] = { + PROPERTY_INFO_WITH_DESC (NM_SETTING_PREFIX_DELEGATION_SUBNET_ID, + .property_type = &_pt_gobject_int, + .property_typ_data = DEFINE_PROPERTY_TYP_DATA_SUBTYPE (gobject_int, + .base = 16, + .print_hex_negative_as_base10 = TRUE, + ), + ), + NULL +}; + #undef _CURRENT_NM_META_SETTING_TYPE #define _CURRENT_NM_META_SETTING_TYPE NM_META_SETTING_TYPE_PROXY static const NMMetaPropertyInfo *const property_infos_PROXY[] = { @@ -8951,6 +8964,7 @@ _setting_init_fcn_wireless (ARGS_SETTING_INIT_FCN) #define SETTING_PRETTY_NAME_OVS_PORT N_("Open vSwitch port settings") #define SETTING_PRETTY_NAME_PPP N_("PPP settings") #define SETTING_PRETTY_NAME_PPPOE N_("PPPoE") +#define SETTING_PRETTY_NAME_PREFIX_DELEGATION N_("Prefix delegation settings") #define SETTING_PRETTY_NAME_PROXY N_("Proxy") #define SETTING_PRETTY_NAME_SERIAL N_("Serial settings") #define SETTING_PRETTY_NAME_SRIOV N_("SR-IOV settings") @@ -9168,6 +9182,7 @@ const NMMetaSettingInfoEditor nm_meta_setting_infos_editor[] = { NM_META_SETTING_VALID_PART_ITEM (OVS_PATCH, FALSE), NM_META_SETTING_VALID_PART_ITEM (IP4_CONFIG, FALSE), NM_META_SETTING_VALID_PART_ITEM (IP6_CONFIG, FALSE), + NM_META_SETTING_VALID_PART_ITEM (PREFIX_DELEGATION, FALSE), NM_META_SETTING_VALID_PART_ITEM (WIRED, FALSE), NM_META_SETTING_VALID_PART_ITEM (ETHTOOL, FALSE), ), @@ -9198,6 +9213,7 @@ const NMMetaSettingInfoEditor nm_meta_setting_infos_editor[] = { ), ), SETTING_INFO (PPP), + SETTING_INFO (PREFIX_DELEGATION), SETTING_INFO (PROXY, .setting_init_fcn = _setting_init_fcn_proxy, ), @@ -9326,6 +9342,7 @@ static const NMMetaSettingValidPartItem *const valid_settings_noport[] = { NM_META_SETTING_VALID_PART_ITEM(MATCH, FALSE), NM_META_SETTING_VALID_PART_ITEM(IP4_CONFIG, FALSE), NM_META_SETTING_VALID_PART_ITEM(IP6_CONFIG, FALSE), + NM_META_SETTING_VALID_PART_ITEM(PREFIX_DELEGATION, FALSE), NM_META_SETTING_VALID_PART_ITEM(HOSTNAME, FALSE), NM_META_SETTING_VALID_PART_ITEM(LINK, FALSE), NM_META_SETTING_VALID_PART_ITEM(TC_CONFIG, FALSE), diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in index 25aed3cfa9..814ca19297 100644 --- a/src/libnmc-setting/settings-docs.h.in +++ b/src/libnmc-setting/settings-docs.h.in @@ -503,4 +503,5 @@ #define DESCRIBE_DOC_NM_SETTING_LOOPBACK_MTU N_("If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames.") #define DESCRIBE_DOC_NM_SETTING_OVS_EXTERNAL_IDS_DATA N_("A dictionary of key/value pairs with external-ids for OVS.") #define DESCRIBE_DOC_NM_SETTING_OVS_OTHER_CONFIG_DATA N_("A dictionary of key/value pairs with other_config settings for OVS. See also \"other_config\" in the \"ovs-vswitchd.conf.db\" manual for the keys that OVS supports.") +#define DESCRIBE_DOC_NM_SETTING_PREFIX_DELEGATION_SUBNET_ID N_("The subnet ID to use on the interface from the prefix delegation received via an upstream interface. Set to a value between 0 and 0xffffffff (2^32 - 1) to indicate a specific subnet ID; or set to -1 to automatically choose an available subnet ID.") #define DESCRIBE_DOC_NM_SETTING_VETH_PEER N_("This property specifies the peer interface name of the veth. This property is mandatory.") diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index 505be6d157..580fab66da 100644 --- a/src/nmcli/connections.c +++ b/src/nmcli/connections.c @@ -1055,27 +1055,28 @@ const NmcMetaGenericInfo "," NM_SETTING_VETH_SETTING_NAME "," NM_SETTING_802_1X_SETTING_NAME \ "," NM_SETTING_WIRELESS_SETTING_NAME "," NM_SETTING_WIRELESS_SECURITY_SETTING_NAME \ "," NM_SETTING_IP4_CONFIG_SETTING_NAME "," NM_SETTING_IP6_CONFIG_SETTING_NAME \ - "," NM_SETTING_SERIAL_SETTING_NAME "," NM_SETTING_WIFI_P2P_SETTING_NAME \ - "," NM_SETTING_PPP_SETTING_NAME "," NM_SETTING_PPPOE_SETTING_NAME \ - "," NM_SETTING_ADSL_SETTING_NAME "," NM_SETTING_GSM_SETTING_NAME \ - "," NM_SETTING_CDMA_SETTING_NAME "," NM_SETTING_BLUETOOTH_SETTING_NAME \ - "," NM_SETTING_OLPC_MESH_SETTING_NAME "," NM_SETTING_VPN_SETTING_NAME \ - "," NM_SETTING_INFINIBAND_SETTING_NAME "," NM_SETTING_BOND_SETTING_NAME \ - "," NM_SETTING_BOND_PORT_SETTING_NAME "," NM_SETTING_VLAN_SETTING_NAME \ - "," NM_SETTING_BRIDGE_SETTING_NAME "," NM_SETTING_BRIDGE_PORT_SETTING_NAME \ - "," NM_SETTING_TEAM_SETTING_NAME "," NM_SETTING_TEAM_PORT_SETTING_NAME \ - "," NM_SETTING_OVS_BRIDGE_SETTING_NAME "," NM_SETTING_OVS_INTERFACE_SETTING_NAME \ - "," NM_SETTING_OVS_PATCH_SETTING_NAME "," NM_SETTING_OVS_PORT_SETTING_NAME \ - "," NM_SETTING_GENERIC_SETTING_NAME "," NM_SETTING_DCB_SETTING_NAME \ - "," NM_SETTING_TUN_SETTING_NAME "," NM_SETTING_IP_TUNNEL_SETTING_NAME \ - "," NM_SETTING_MACSEC_SETTING_NAME "," NM_SETTING_MACVLAN_SETTING_NAME \ - "," NM_SETTING_VXLAN_SETTING_NAME "," NM_SETTING_VRF_SETTING_NAME \ - "," NM_SETTING_WPAN_SETTING_NAME "," NM_SETTING_6LOWPAN_SETTING_NAME \ - "," NM_SETTING_WIREGUARD_SETTING_NAME "," NM_SETTING_LINK_SETTING_NAME \ - "," NM_SETTING_PROXY_SETTING_NAME "," NM_SETTING_TC_CONFIG_SETTING_NAME \ - "," NM_SETTING_SRIOV_SETTING_NAME "," NM_SETTING_ETHTOOL_SETTING_NAME \ - "," NM_SETTING_OVS_DPDK_SETTING_NAME "," NM_SETTING_HOSTNAME_SETTING_NAME \ - "," NM_SETTING_HSR_SETTING_NAME "," NM_SETTING_IPVLAN_SETTING_NAME + "," NM_SETTING_PREFIX_DELEGATION_SETTING_NAME "," NM_SETTING_SERIAL_SETTING_NAME \ + "," NM_SETTING_WIFI_P2P_SETTING_NAME "," NM_SETTING_PPP_SETTING_NAME \ + "," NM_SETTING_PPPOE_SETTING_NAME "," NM_SETTING_ADSL_SETTING_NAME \ + "," NM_SETTING_GSM_SETTING_NAME "," NM_SETTING_CDMA_SETTING_NAME \ + "," NM_SETTING_BLUETOOTH_SETTING_NAME "," NM_SETTING_OLPC_MESH_SETTING_NAME \ + "," NM_SETTING_VPN_SETTING_NAME "," NM_SETTING_INFINIBAND_SETTING_NAME \ + "," NM_SETTING_BOND_SETTING_NAME "," NM_SETTING_BOND_PORT_SETTING_NAME \ + "," NM_SETTING_VLAN_SETTING_NAME "," NM_SETTING_BRIDGE_SETTING_NAME \ + "," NM_SETTING_BRIDGE_PORT_SETTING_NAME "," NM_SETTING_TEAM_SETTING_NAME \ + "," NM_SETTING_TEAM_PORT_SETTING_NAME "," NM_SETTING_OVS_BRIDGE_SETTING_NAME \ + "," NM_SETTING_OVS_INTERFACE_SETTING_NAME "," NM_SETTING_OVS_PATCH_SETTING_NAME \ + "," NM_SETTING_OVS_PORT_SETTING_NAME "," NM_SETTING_GENERIC_SETTING_NAME \ + "," NM_SETTING_DCB_SETTING_NAME "," NM_SETTING_TUN_SETTING_NAME \ + "," NM_SETTING_IP_TUNNEL_SETTING_NAME "," NM_SETTING_MACSEC_SETTING_NAME \ + "," NM_SETTING_MACVLAN_SETTING_NAME "," NM_SETTING_VXLAN_SETTING_NAME \ + "," NM_SETTING_VRF_SETTING_NAME "," NM_SETTING_WPAN_SETTING_NAME \ + "," NM_SETTING_6LOWPAN_SETTING_NAME "," NM_SETTING_WIREGUARD_SETTING_NAME \ + "," NM_SETTING_LINK_SETTING_NAME "," NM_SETTING_PROXY_SETTING_NAME \ + "," NM_SETTING_TC_CONFIG_SETTING_NAME "," NM_SETTING_SRIOV_SETTING_NAME \ + "," NM_SETTING_ETHTOOL_SETTING_NAME "," NM_SETTING_OVS_DPDK_SETTING_NAME \ + "," NM_SETTING_HOSTNAME_SETTING_NAME "," NM_SETTING_HSR_SETTING_NAME \ + "," NM_SETTING_IPVLAN_SETTING_NAME /* NM_SETTING_DUMMY_SETTING_NAME NM_SETTING_WIMAX_SETTING_NAME */ const NmcMetaGenericInfo *const nmc_fields_con_active_details_groups[] = { diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in index cf902a7e56..5b3ba3e7dc 100644 --- a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in +++ b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in @@ -1912,6 +1912,12 @@ format="flags (NMSettingSecretFlags)" values="none (0x0), agent-owned (0x1), not-saved (0x2), not-required (0x4)" /> + + + Date: Mon, 10 Mar 2025 16:39:58 +0100 Subject: [PATCH 6/6] core: support prefix-delegation.subnet-id --- src/core/devices/nm-device.c | 7 ++ src/core/nm-policy.c | 154 +++++++++++++++++++++++++---------- 2 files changed, 120 insertions(+), 41 deletions(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index a6d4acc49e..6e798aaf16 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -11355,6 +11355,13 @@ _dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_d switch (notify_data->notify_type) { case NM_DHCP_CLIENT_NOTIFY_TYPE_PREFIX_DELEGATED: nm_assert(!IS_IPv4); + if (notify_data->prefix_delegated.prefix->plen == 0 + || notify_data->prefix_delegated.prefix->plen > 64) { + _LOGW_ipdhcp(addr_family, + "ignoring invalid prefix-delegation with length %u", + notify_data->prefix_delegated.prefix->plen); + return; + } /* Just re-emit. The device just contributes the prefix to the * pool in NMPolicy, which decides about subnet allocation * on the shared devices. */ diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c index 4be796d247..fbcee40dfb 100644 --- a/src/core/nm-policy.c +++ b/src/core/nm-policy.c @@ -155,17 +155,15 @@ static gboolean hostname_retry_cb(gpointer user_data); typedef struct { NMPlatformIP6Address prefix; - NMDevice *device; /* The requesting ("uplink") device */ - guint64 next_subnet; /* Cache of the next subnet number to be - * assigned from this prefix */ - GHashTable *subnets; /* ifindex -> NMPlatformIP6Address */ + NMDevice *device; /* The requesting ("uplink") device */ + GHashTable *map_subnet_id_to_ifindex; /* (guint64 *) subnet_id -> int ifindex */ + GHashTable *map_ifindex_to_subnet; /* int ifindex -> (NMPlatformIP6Address *) prefix */ } IP6PrefixDelegation; static void -_clear_ip6_subnet(gpointer key, gpointer value, gpointer user_data) +clear_ip6_subnet(int ifindex, NMPlatformIP6Address *subnet) { - NMPlatformIP6Address *subnet = value; - NMDevice *device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, GPOINTER_TO_INT(key)); + NMDevice *device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, ifindex); if (device) { /* We can not remove a subnet we already started announcing. @@ -176,6 +174,12 @@ _clear_ip6_subnet(gpointer key, gpointer value, gpointer user_data) g_slice_free(NMPlatformIP6Address, subnet); } +static void +clear_ip6_subnet_entry(gpointer key, gpointer value, gpointer user_data) +{ + clear_ip6_subnet(GPOINTER_TO_INT(key), value); +} + static void clear_ip6_prefix_delegation(gpointer data) { @@ -187,8 +191,9 @@ clear_ip6_prefix_delegation(gpointer data) nm_inet6_ntop(&delegation->prefix.address, sbuf), delegation->prefix.plen); - g_hash_table_foreach(delegation->subnets, _clear_ip6_subnet, NULL); - g_hash_table_destroy(delegation->subnets); + g_hash_table_foreach(delegation->map_ifindex_to_subnet, clear_ip6_subnet_entry, NULL); + g_hash_table_destroy(delegation->map_ifindex_to_subnet); + g_hash_table_destroy(delegation->map_subnet_id_to_ifindex); } static void @@ -215,46 +220,112 @@ expire_ip6_delegations(NMPolicy *self) static gboolean ip6_subnet_from_delegation(IP6PrefixDelegation *delegation, NMDevice *device) { - NMPlatformIP6Address *subnet; - int ifindex = nm_device_get_ifindex(device); - char sbuf[NM_INET_ADDRSTRLEN]; + NMPlatformIP6Address *subnet; + int ifindex = nm_device_get_ifindex(device); + char sbuf[NM_INET_ADDRSTRLEN]; + NMSettingPrefixDelegation *s_pd; + gint64 wanted_subnet_id = -1; + guint64 num_subnets; + guint64 old_subnet_id; - subnet = g_hash_table_lookup(delegation->subnets, GINT_TO_POINTER(ifindex)); - if (!subnet) { - /* Check for out-of-prefixes condition. */ - if (delegation->next_subnet >= (1 << (64 - delegation->prefix.plen))) { - _LOGD(LOGD_IP6, - "ipv6-pd: no more prefixes in %s/%d", - nm_inet6_ntop(&delegation->prefix.address, sbuf), - delegation->prefix.plen); - return FALSE; - } + nm_assert(delegation->prefix.plen > 0 && delegation->prefix.plen <= 64); - /* Allocate a new subnet. */ - subnet = g_slice_new0(NMPlatformIP6Address); - g_hash_table_insert(delegation->subnets, GINT_TO_POINTER(ifindex), subnet); - - subnet->plen = 64; - subnet->address.s6_addr32[0] = - delegation->prefix.address.s6_addr32[0] | htonl(delegation->next_subnet >> 32); - subnet->address.s6_addr32[1] = - delegation->prefix.address.s6_addr32[1] | htonl(delegation->next_subnet); - - /* Out subnet pool management is pretty unsophisticated. We only add - * the subnets and index them by ifindex. That keeps the implementation - * simple and the dead entries make it easy to reuse the same subnet on - * subsequent activations. On the other hand they may waste the subnet - * space. */ - delegation->next_subnet++; + s_pd = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PREFIX_DELEGATION); + if (s_pd) { + wanted_subnet_id = nm_setting_prefix_delegation_get_subnet_id(s_pd); } + /* Try to use the cached subnet assigned to the interface */ + subnet = g_hash_table_lookup(delegation->map_ifindex_to_subnet, GINT_TO_POINTER(ifindex)); + if (subnet) { + old_subnet_id = nm_ip6_addr_get_subnet_id(&subnet->address, delegation->prefix.plen); + if (wanted_subnet_id != -1 && wanted_subnet_id != old_subnet_id) { + /* The device had a subnet assigned before, but now wants a + * different subnet-id. Release the old subnet and continue below + * to get a new one. */ + clear_ip6_subnet(ifindex, subnet); + subnet = NULL; + g_hash_table_remove(delegation->map_ifindex_to_subnet, GINT_TO_POINTER(ifindex)); + g_hash_table_remove(delegation->map_subnet_id_to_ifindex, &old_subnet_id); + } else { + goto subnet_found; + } + } + + /* Check for out-of-prefixes condition */ + num_subnets = 1 << (64 - delegation->prefix.plen); + if (nm_g_hash_table_size(delegation->map_subnet_id_to_ifindex) >= num_subnets) { + _LOGD(LOGD_IP6, + "ipv6-pd: no more prefixes in %s/%u", + nm_inet6_ntop(&delegation->prefix.address, sbuf), + delegation->prefix.plen); + return FALSE; + } + + /* Try to honor the "prefix-delegation.subnet-id" property */ + if (wanted_subnet_id >= 0) { + gpointer value; + NMDevice *other_device; + + if (g_hash_table_lookup_extended(delegation->map_subnet_id_to_ifindex, + &wanted_subnet_id, + NULL, + &value)) { + other_device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, GPOINTER_TO_INT(value)); + _LOGW(LOGD_IP6, + "ipv6-pd: subnet-id 0x%" G_GINT64_MODIFIER + "x wanted by device %s is already in use by " + "device %s (ifindex %d)", + (guint64) wanted_subnet_id, + nm_device_get_iface(device), + other_device ? nm_device_get_ip_iface(other_device) : NULL, + GPOINTER_TO_INT(value)); + wanted_subnet_id = -1; + } + } + + /* If we don't have a subnet-id yet, find the first one available */ + if (wanted_subnet_id < 0) { + guint64 i; + + for (i = 0; i < num_subnets; i++) { + if (!g_hash_table_lookup_extended(delegation->map_subnet_id_to_ifindex, + &i, + NULL, + NULL)) { + wanted_subnet_id = (gint64) i; + break; + } + } + + if (wanted_subnet_id < 0) { + /* We already verified that there are available subnets, this should not happen */ + return nm_assert_unreachable_val(FALSE); + } + } + + /* Allocate a new subnet */ + subnet = g_slice_new0(NMPlatformIP6Address); + g_hash_table_insert(delegation->map_ifindex_to_subnet, GINT_TO_POINTER(ifindex), subnet); + g_hash_table_insert(delegation->map_subnet_id_to_ifindex, + nm_memdup(&wanted_subnet_id, sizeof(guint64)), + GINT_TO_POINTER(ifindex)); + + subnet->plen = 64; + subnet->address.s6_addr32[0] = + delegation->prefix.address.s6_addr32[0] | htonl(wanted_subnet_id >> 32); + subnet->address.s6_addr32[1] = + delegation->prefix.address.s6_addr32[1] | htonl(wanted_subnet_id); + +subnet_found: subnet->timestamp = delegation->prefix.timestamp; subnet->lifetime = delegation->prefix.lifetime; subnet->preferred = delegation->prefix.preferred; _LOGD(LOGD_IP6, - "ipv6-pd: %s allocated from a /%d prefix on %s", + "ipv6-pd: %s/64 (subnet-id 0x%" G_GINT64_MODIFIER "x) allocated from a /%d prefix on %s", nm_inet6_ntop(&subnet->address, sbuf), + (guint64) wanted_subnet_id, delegation->prefix.plen, nm_device_get_iface(device)); @@ -345,8 +416,9 @@ device_ip6_prefix_delegated(NMDevice *device, if (i == priv->ip6_prefix_delegations->len) { /* Allocate a delegation for new prefix. */ delegation = nm_g_array_append_new(priv->ip6_prefix_delegations, IP6PrefixDelegation); - delegation->subnets = g_hash_table_new(nm_direct_hash, NULL); - delegation->next_subnet = 0; + delegation->map_subnet_id_to_ifindex = + g_hash_table_new_full(nm_puint64_hash, nm_puint64_equal, g_free, NULL); + delegation->map_ifindex_to_subnet = g_hash_table_new(nm_direct_hash, NULL); } delegation->device = device;