diff --git a/NEWS b/NEWS index ffd9dc43d9..9c6a7b0a76 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,8 @@ subject to change and not guaranteed to be compatible with the later release. USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! +* A new "prefix-delegation.evict-oldest" configuration option is available to + evict old prefixes rather than dropping new ones when the limit is reached. * Unify the versioning to use everywhere the scheme with the -rcX or -dev suffixes when appropriate. This affects, for example, the URL and filename of the release tarball and the version reported by nmcli and the daemon. diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index f3f09db18c..a8773a9809 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -13026,19 +13026,21 @@ _dev_ipac6_grace_period_start(NMDevice *self, guint32 timeout_sec, gboolean forc static void _dev_ipac6_start(NMDevice *self) { - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - NMConnection *connection; - NMSettingIP6Config *s_ip = NULL; - NMNDiscNodeType node_type; - NMUtilsStableType stable_type; - const char *stable_id; - int max_addresses; - int router_solicitations; - int router_solicitation_interval; - guint32 ra_timeout; - guint32 default_ra_timeout; - NMUtilsIPv6IfaceId iid; - gboolean is_token; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection *connection; + NMSettingIP6Config *s_ip = NULL; + NMSettingPrefixDelegation *s_pd; + bool pd_evict_oldest = FALSE; + NMNDiscNodeType node_type; + NMUtilsStableType stable_type; + const char *stable_id; + int max_addresses; + int router_solicitations; + int router_solicitation_interval; + guint32 ra_timeout; + guint32 default_ra_timeout; + NMUtilsIPv6IfaceId iid; + gboolean is_token; if (priv->ipac6_data.state == NM_DEVICE_IP_STATE_NONE) _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_PENDING); @@ -13083,6 +13085,10 @@ _dev_ipac6_start(NMDevice *self) stable_id = _prop_get_connection_stable_id(self, connection, &stable_type); + s_pd = nm_device_get_applied_setting(self, NM_TYPE_SETTING_PREFIX_DELEGATION); + if (s_pd) + pd_evict_oldest = nm_setting_prefix_delegation_get_evict_oldest(s_pd); + { const NMNDiscConfig config = { .l3cfg = nm_device_get_l3cfg(self), @@ -13096,6 +13102,7 @@ _dev_ipac6_start(NMDevice *self) .router_solicitation_interval = router_solicitation_interval, .ra_timeout = ra_timeout, .ip6_privacy = _prop_get_ipv6_ip6_privacy(self, connection), + .pd_evict_oldest = pd_evict_oldest, }; priv->ipac6_data.ndisc = nm_lndp_ndisc_new(&config); diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c index ed34f088a0..7c4c65fefc 100644 --- a/src/core/ndisc/nm-ndisc.c +++ b/src/core/ndisc/nm-ndisc.c @@ -664,8 +664,33 @@ nm_ndisc_add_address(NMNDisc *ndisc, * what the kernel does, because it considers *all* addresses (including * static and other temporary addresses). **/ - if (rdata->addresses->len >= priv->config.max_addresses) - return FALSE; + if (rdata->addresses->len >= priv->config.max_addresses) { + guint oldest_idx = 0; + gint64 oldest_expiry = G_MAXINT64; + + /* In PD mode with evict_oldest enabled, and the new address is still + * preferred (active), find and evict the address with shortest remaining + * lifetime to make room for the new prefix. */ + if (from_ra || !priv->config.pd_evict_oldest + || new_item->expiry_preferred_msec <= NM_NDISC_EXPIRY_BASE_TIMESTAMP) { + return FALSE; + } + + for (i = 0; i < rdata->addresses->len; i++) { + NMNDiscAddress *item = &nm_g_array_index(rdata->addresses, NMNDiscAddress, i); + if (item->expiry_msec >= oldest_expiry) + continue; + + oldest_expiry = item->expiry_msec; + oldest_idx = i; + } + + _LOGI("add_address: max_addresses=%u reached, evicting oldest " + "(expiry=%" G_GINT64_FORMAT " ms)", + priv->config.max_addresses, + oldest_expiry); + g_array_remove_index(rdata->addresses, oldest_idx); + } if (new_item->expiry_msec <= now_msec) return FALSE; diff --git a/src/core/ndisc/nm-ndisc.h b/src/core/ndisc/nm-ndisc.h index 5b2efde569..94a06194ee 100644 --- a/src/core/ndisc/nm-ndisc.h +++ b/src/core/ndisc/nm-ndisc.h @@ -183,6 +183,7 @@ typedef struct { NMSettingIP6ConfigAddrGenMode addr_gen_mode; NMNDiscNodeType node_type; NMSettingIP6ConfigPrivacy ip6_privacy; + bool pd_evict_oldest; } NMNDiscConfig; struct _NMNDiscPrivate; diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver index e9850ea6e5..7c7418de9b 100644 --- a/src/libnm-client-impl/libnm.ver +++ b/src/libnm-client-impl/libnm.ver @@ -2127,6 +2127,7 @@ global: nm_setting_geneve_new; nm_setting_ip4_config_clat_get_type; nm_setting_ip4_config_get_clat; + nm_setting_prefix_delegation_get_evict_oldest; nm_utils_wifi_6ghz_freqs; nm_utils_wifi_freq_to_band; nm_wifi_band_get_type; 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 7308db4d64..4348df0796 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 @@ -2366,6 +2366,10 @@ + subnet_id; } +/** + * nm_setting_prefix_delegation_get_evict_oldest: + * @setting: the #NMSettingPrefixDelegation + * + * Returns: whether to evict oldest addresses when adding new ones + * + * Since: 1.58 + **/ +gboolean +nm_setting_prefix_delegation_get_evict_oldest(NMSettingPrefixDelegation *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PREFIX_DELEGATION(setting), FALSE); + + return setting->evict_oldest; +} + /*****************************************************************************/ static void @@ -108,6 +125,26 @@ nm_setting_prefix_delegation_class_init(NMSettingPrefixDelegationClass *klass) NMSettingPrefixDelegation, subnet_id); + /** + * NMSettingPrefixDelegation:evict-oldest: + * + * If %TRUE, when the maximum number of addresses is reached and a new + * prefix is delegated, the address with the shortest remaining lifetime + * will be evicted to make room for the new prefix. This is useful for + * scenarios where upstream prefixes change frequently (e.g., unstable + * connections), ensuring the newest working prefix is always advertised. + * + * Since: 1.58 + **/ + _nm_setting_property_define_direct_boolean(properties_override, + obj_properties, + NM_SETTING_PREFIX_DELEGATION_EVICT_OLDEST, + PROP_EVICT_OLDEST, + FALSE, + NM_SETTING_PARAM_NONE, + NMSettingPrefixDelegation, + evict_oldest); + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); _nm_setting_class_commit(setting_class, diff --git a/src/libnm-core-public/nm-setting-prefix-delegation.h b/src/libnm-core-public/nm-setting-prefix-delegation.h index 5361d7c69a..b11437979a 100644 --- a/src/libnm-core-public/nm-setting-prefix-delegation.h +++ b/src/libnm-core-public/nm-setting-prefix-delegation.h @@ -25,7 +25,8 @@ G_BEGIN_DECLS #define NM_SETTING_PREFIX_DELEGATION_SETTING_NAME "prefix-delegation" -#define NM_SETTING_PREFIX_DELEGATION_SUBNET_ID "subnet-id" +#define NM_SETTING_PREFIX_DELEGATION_SUBNET_ID "subnet-id" +#define NM_SETTING_PREFIX_DELEGATION_EVICT_OLDEST "evict-oldest" typedef struct _NMSettingPrefixDelegationClass NMSettingPrefixDelegationClass; @@ -35,6 +36,8 @@ 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); +NM_AVAILABLE_IN_1_58 +gboolean nm_setting_prefix_delegation_get_evict_oldest(NMSettingPrefixDelegation *setting); G_END_DECLS diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in index 97848c8a67..62f5c360de 100644 --- a/src/libnmc-setting/settings-docs.h.in +++ b/src/libnmc-setting/settings-docs.h.in @@ -516,5 +516,6 @@ #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_EVICT_OLDEST N_("If TRUE, when the maximum number of addresses is reached and a new prefix is delegated, the address with the shortest remaining lifetime will be evicted to make room for the new prefix. This is useful for scenarios where upstream prefixes change frequently (e.g., unstable connections), ensuring the newest working prefix is always advertised.") #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.")