summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2023-03-21 16:01:24 +0100
committerThomas Haller <thaller@redhat.com>2023-03-21 16:01:24 +0100
commit2dfeec9aea2ba5a1635f550c44a0c67224b21f91 (patch)
tree1fc45a9b78cad58af324c0bcd5140b260c65604b
parent575e35d1ca1bb8d17001e8f50acb825e4f3d816d (diff)
parent1feaf427d2bcbf5b618bbe38a82d76cfe621d203 (diff)
platform,core: merge branch 'th/platform-rt-prefsrc'
https://bugzilla.redhat.com/show_bug.cgi?id=2046293 https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1550
-rw-r--r--.clang-format1
-rw-r--r--src/core/devices/nm-device.c43
-rw-r--r--src/core/nm-l3-config-data.c29
-rw-r--r--src/core/nm-l3-config-data.h2
-rw-r--r--src/core/nm-l3cfg.c731
-rw-r--r--src/core/nm-l3cfg.h8
-rw-r--r--src/core/nm-netns.c593
-rw-r--r--src/core/nm-netns.h42
-rw-r--r--src/libnm-glib-aux/nm-inet-utils.h40
-rw-r--r--src/libnm-glib-aux/nm-prioq.c189
-rw-r--r--src/libnm-glib-aux/nm-prioq.h4
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.c45
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.h6
-rw-r--r--src/libnm-glib-aux/tests/test-shared-general.c55
-rw-r--r--src/libnm-lldp/nm-lldp-neighbor.c1
-rw-r--r--src/libnm-platform/nm-linux-platform.c1
-rw-r--r--src/libnm-platform/nm-platform.c267
-rw-r--r--src/libnm-platform/nm-platform.h22
-rw-r--r--src/libnm-platform/nmp-base.h9
-rw-r--r--src/libnm-platform/nmp-object.h15
-rw-r--r--src/libnm-platform/nmp-plobj.h1
21 files changed, 1474 insertions, 630 deletions
diff --git a/.clang-format b/.clang-format
index 4099440415..fb100bb1cd 100644
--- a/.clang-format
+++ b/.clang-format
@@ -120,6 +120,7 @@ ForEachMacros: [
'nm_manager_for_each_device',
'nm_manager_for_each_device_safe',
'nm_platform_iter_obj_for_each',
+ 'nm_prioq_for_each',
'nmp_cache_iter_for_each',
'nmp_cache_iter_for_each_link',
'nmp_cache_iter_for_each_reverse',
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 939a9b2995..497108b1c1 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -3522,7 +3522,7 @@ _dev_ip_state_check(NMDevice *self, int addr_family)
&s_is_pending,
&s_is_failed);
- has_tna = priv->l3cfg && nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, addr_family);
+ has_tna = priv->l3cfg && nm_l3cfg_has_failedobj_pending(priv->l3cfg, addr_family);
if (has_tna)
s_is_pending = TRUE;
@@ -3967,9 +3967,7 @@ after_merge_flags:
}
static gboolean
-_dev_l3_register_l3cds_add_config(NMDevice *self,
- L3ConfigDataType l3cd_type,
- NML3CfgConfigFlags flags)
+_dev_l3_register_l3cds_add_config(NMDevice *self, L3ConfigDataType l3cd_type)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NML3ConfigMergeFlags merge_flags;
@@ -3992,7 +3990,7 @@ _dev_l3_register_l3cds_add_config(NMDevice *self,
_prop_get_ipvx_dns_priority(self, AF_INET6),
acd_defend_type,
acd_timeout_msec,
- flags,
+ NM_L3CFG_CONFIG_FLAGS_NONE,
merge_flags);
}
@@ -4000,7 +3998,6 @@ static gboolean
_dev_l3_register_l3cds_set_one_full(NMDevice *self,
L3ConfigDataType l3cd_type,
const NML3ConfigData *l3cd,
- NML3CfgConfigFlags flags,
NMTernary commit_sync)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
@@ -4024,7 +4021,7 @@ _dev_l3_register_l3cds_set_one_full(NMDevice *self,
if (priv->l3cfg) {
if (priv->l3cds[l3cd_type].d) {
- if (_dev_l3_register_l3cds_add_config(self, l3cd_type, flags))
+ if (_dev_l3_register_l3cds_add_config(self, l3cd_type))
changed = TRUE;
}
@@ -4048,11 +4045,7 @@ _dev_l3_register_l3cds_set_one(NMDevice *self,
const NML3ConfigData *l3cd,
NMTernary commit_sync)
{
- return _dev_l3_register_l3cds_set_one_full(self,
- l3cd_type,
- l3cd,
- NM_L3CFG_CONFIG_FLAGS_NONE,
- commit_sync);
+ return _dev_l3_register_l3cds_set_one_full(self, l3cd_type, l3cd, commit_sync);
}
static void
@@ -4107,7 +4100,7 @@ _dev_l3_register_l3cds(NMDevice *self,
}
if (is_external)
continue;
- if (_dev_l3_register_l3cds_add_config(self, i, NM_L3CFG_CONFIG_FLAGS_NONE))
+ if (_dev_l3_register_l3cds_add_config(self, i))
changed = TRUE;
}
@@ -4261,6 +4254,7 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
_dev_ipshared4_spawn_dnsmasq(self);
nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
}
+ _dev_ip_state_check_async(self, AF_UNSPEC);
_dev_ipmanual_check_ready(self);
return;
case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT:
@@ -4270,10 +4264,6 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
return;
case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE:
return;
- case NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED:
- /* we commit again. This way we try to configure the routes.*/
- _dev_l3_cfg_commit(self, FALSE);
- return;
case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE:
if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags,
nmp_object_type_to_flags(NMP_OBJECT_TYPE_LINK)
@@ -4305,9 +4295,6 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
* synchronously to update the current state and schedule a commit. */
nm_ndisc_dad_failed(priv->ipac6_data.ndisc, conflicts, TRUE);
} else if (ready) {
- if (nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, AF_INET6))
- _dev_l3_cfg_commit(self, FALSE);
-
nm_clear_l3cd(&priv->ipac6_data.l3cd);
_dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY);
_dev_ip_state_check_async(self, AF_INET6);
@@ -10314,14 +10301,6 @@ _dev_ipmanual_check_ready(NMDevice *self)
_dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_FAILED);
_dev_ip_state_check_async(self, AF_UNSPEC);
} else if (ready) {
- if (priv->ipmanual_data.state_x[IS_IPv4] != NM_DEVICE_IP_STATE_READY
- && nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, addr_family)) {
- /* Addresses with pending ACD/DAD are a possible cause for the
- * presence of temporarily-not-available objects. Once all addresses
- * are ready, retry to commit those unavailable objects. */
- _dev_l3_cfg_commit(self, FALSE);
- }
-
_dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY);
_dev_ip_state_check_async(self, AF_UNSPEC);
}
@@ -10487,7 +10466,6 @@ _dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_d
_dev_l3_register_l3cds_set_one_full(self,
L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4),
notify_data->lease_update.l3cd,
- NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
FALSE);
if (notify_data->lease_update.accepted) {
@@ -10708,7 +10686,6 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
_dev_l3_register_l3cds_set_one_full(self,
L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4),
previous_lease,
- NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
FALSE);
}
@@ -11641,11 +11618,7 @@ _dev_ipac6_ndisc_config_changed(NMNDisc *ndisc,
_dev_ipac6_grace_period_start(self, 0, TRUE);
- _dev_l3_register_l3cds_set_one_full(self,
- L3_CONFIG_DATA_TYPE_AC_6,
- l3cd,
- NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
- FALSE);
+ _dev_l3_register_l3cds_set_one_full(self, L3_CONFIG_DATA_TYPE_AC_6, l3cd, FALSE);
nm_clear_l3cd(&priv->ipac6_data.l3cd);
ready = nm_l3cfg_check_ready(priv->l3cfg,
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index 17bb9db87d..4a9dd78bcf 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -2604,7 +2604,6 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self,
int addr_family,
guint32 route_table,
guint32 route_metric,
- gboolean force_commit,
const NML3ConfigData *source)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
@@ -2649,7 +2648,6 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self,
self->ifindex,
route_table,
route_metric,
- force_commit,
&r_stack.r4);
if (r)
nm_l3_config_data_add_route(self, addr_family, NULL, r);
@@ -2685,13 +2683,12 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self,
}
rx.r6 = (NMPlatformIP6Route){
- .ifindex = self->ifindex,
- .rt_source = NM_IP_CONFIG_SOURCE_KERNEL,
- .table_coerced = nm_platform_route_table_coerce(route_table),
- .metric = route_metric,
- .network = *a6,
- .plen = plen,
- .r_force_commit = force_commit,
+ .ifindex = self->ifindex,
+ .rt_source = NM_IP_CONFIG_SOURCE_KERNEL,
+ .table_coerced = nm_platform_route_table_coerce(route_table),
+ .metric = route_metric,
+ .network = *a6,
+ .plen = plen,
};
nm_platform_ip_route_normalize(addr_family, &rx.rx);
@@ -3197,7 +3194,6 @@ nm_l3_config_data_merge(NML3ConfigData *self,
NMPlatformIPXAddress a;
NML3ConfigMergeHookResult hook_result = {
.ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT,
- .force_commit = NM_OPTION_BOOL_DEFAULT,
};
#define _ensure_a() \
@@ -3230,12 +3226,6 @@ nm_l3_config_data_merge(NML3ConfigData *self,
a.a4.a_acd_not_ready = (!!hook_result.ip4acd_not_ready);
}
- if (hook_result.force_commit != NM_OPTION_BOOL_DEFAULT
- && (!!hook_result.force_commit) != a_src->a_force_commit) {
- _ensure_a();
- a.ax.a_force_commit = (!!hook_result.force_commit);
- }
-
nm_l3_config_data_add_address_full(self,
addr_family,
a_src == &a.ax ? NULL : obj,
@@ -3255,7 +3245,6 @@ nm_l3_config_data_merge(NML3ConfigData *self,
NMPlatformIPXRoute r;
NML3ConfigMergeHookResult hook_result = {
.ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT,
- .force_commit = NM_OPTION_BOOL_DEFAULT,
};
#define _ensure_r() \
@@ -3281,12 +3270,6 @@ nm_l3_config_data_merge(NML3ConfigData *self,
r.rx.ifindex = self->ifindex;
}
- if (hook_result.force_commit != NM_OPTION_BOOL_DEFAULT
- && (!!hook_result.force_commit) != r_src->r_force_commit) {
- _ensure_r();
- r.rx.r_force_commit = (!!hook_result.force_commit);
- }
-
if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) {
if (r_src->table_any) {
_ensure_r();
diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h
index bfab04d9ce..80abb00d88 100644
--- a/src/core/nm-l3-config-data.h
+++ b/src/core/nm-l3-config-data.h
@@ -137,7 +137,6 @@ NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex *mu
typedef struct {
NMOptionBool ip4acd_not_ready;
- NMOptionBool force_commit;
} NML3ConfigMergeHookResult;
typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData *l3cd,
@@ -164,7 +163,6 @@ void nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self,
int addr_family,
guint32 route_table,
guint32 route_metric,
- gboolean force_commit,
const NML3ConfigData *source);
/*****************************************************************************/
diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c
index a49654feb5..4a49c4f8d9 100644
--- a/src/core/nm-l3cfg.c
+++ b/src/core/nm-l3cfg.c
@@ -11,6 +11,7 @@
#include <linux/if_ether.h>
#include <linux/rtnetlink.h>
+#include "libnm-glib-aux/nm-prioq.h"
#include "libnm-glib-aux/nm-time-utils.h"
#include "libnm-platform/nm-platform.h"
#include "libnm-platform/nmp-object.h"
@@ -123,25 +124,34 @@ typedef struct {
CList os_lst;
- /* If we have a timeout pending, we link the instance to
- * self->priv.p->obj_state_temporary_not_available_lst_head. */
- CList os_temporary_not_available_lst;
-
/* If a NMPObject is no longer to be configured (but was configured
* during a previous commit), then we need to remember it so that the
* next commit can delete the address/route in kernel. It becomes a zombie. */
CList os_zombie_lst;
- /* We might want to configure "obj" in platform, but it's currently not possible.
- * For example, certain IPv6 routes can only be added after the IPv6 address
- * becomes non-tentative (*sigh*). In such a case, we need to remember that, and
- * retry later. If this timestamp is set to a non-zero value, then it means
- * we tried to configure the obj (at that timestamp) and failed, but we are
- * waiting to retry.
+ /* Used by _handle_routes_failed() mechanism. If "os_plobj" is set, then
+ * this is meaningless but should be set to zero.
+ *
+ * If set to a non-zero value, this means adding the object failed. Until
+ * "os_failedobj_expiry_msec" we are still waiting whether we would be able to
+ * configure the object. Afterwards, we consider the element failed.
*
- * See also self->priv.p->obj_state_temporary_not_available_lst_head
- * and self->priv.p->obj_state_temporary_not_available_timeout_source. */
- gint64 os_temporary_not_available_timestamp_msec;
+ * Depending on "os_failedobj_prioq_idx", we are currently waiting whether the
+ * condition can resolve itself or becomes a failure. */
+ gint64 os_failedobj_expiry_msec;
+
+ /* The index into the "priv->failedobj_prioq" queue for objects that are failed.
+ * - this field is meaningless in case "os_plobj" is set (but it should be
+ * set to NM_PRIOQ_IDX_NULL).
+ * - otherwise, if "os_failedobj_expiry_msec" is 0, no error was detected so
+ * far. The index should be set to NM_PRIOQ_IDX_NULL.
+ * - otherwise, if the index is NM_PRIOQ_IDX_NULL it means that the object
+ * is not tracked by the queue, no grace timer is pending, and the object
+ * is considered failed.
+ * - otherwise, the index is used for tracking the element in the queue.
+ * It means, we are currently waiting to decide whether this will be a
+ * failure or not. */
+ guint os_failedobj_prioq_idx;
/* When the obj is a zombie (that means, it was previously configured by NML3Cfg, but
* now no longer), it needs to be deleted from platform. This ratelimits the time
@@ -206,7 +216,6 @@ typedef struct {
guint32 acd_timeout_msec_confdata;
NML3AcdDefendType acd_defend_type_confdata : 3;
bool dirty_confdata : 1;
- gboolean force_commit_once : 1;
} L3ConfigData;
struct _NML3CfgBlockHandle {
@@ -241,7 +250,6 @@ typedef struct _NML3CfgPrivate {
CList obj_state_lst_head;
CList obj_state_zombie_lst_head;
- CList obj_state_temporary_not_available_lst_head;
GHashTable *acd_ipv4_addresses_on_link;
@@ -288,12 +296,22 @@ typedef struct _NML3CfgPrivate {
guint64 pseudo_timestamp_counter;
- GSource *obj_state_temporary_not_available_timeout_source;
+ NMPrioq failedobj_prioq;
+ GSource *failedobj_timeout_source;
+ gint64 failedobj_timeout_expiry_msec;
NML3CfgCommitType commit_on_idle_type;
gint8 commit_reentrant_count;
+ union {
+ struct {
+ gint8 commit_reentrant_count_ip_address_sync_6;
+ gint8 commit_reentrant_count_ip_address_sync_4;
+ };
+ gint8 commit_reentrant_count_ip_address_sync_x[2];
+ };
+
/* The value that was set before we touched the sysctl (this only is
* meaningful if "ip6_privacy_set" is true. At the end, we want to restore
* this value. */
@@ -340,6 +358,9 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT)
#define _MPTCP_TAG(self, IS_IPv4) ((gconstpointer) (&(((const char *) (self))[2 + (!(IS_IPv4))])))
+#define _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family) \
+ ((gconstpointer) & (((char *) self)[1 + NM_IS_IPv4(addr_family)]))
+
/*****************************************************************************/
#define _NMLOG_DOMAIN LOGD_CORE
@@ -410,8 +431,6 @@ static NM_UTILS_ENUM2STR_DEFINE(
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT, "pre-commit"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"),
- NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED,
- "routes-temporary-not-available-expired"),
NM_UTILS_ENUM2STR_IGNORE(_NM_L3_CONFIG_NOTIFY_TYPE_NUM), );
static NM_UTILS_ENUM2STR_DEFINE(_l3_acd_defend_type_to_string,
@@ -754,51 +773,51 @@ _nm_n_acd_data_probe_new(NML3Cfg *self, in_addr_t addr, guint32 timeout_msec, gp
/*****************************************************************************/
-#define nm_assert_obj_state(self, obj_state) \
- G_STMT_START \
- { \
- if (NM_MORE_ASSERTS > 0) { \
- const NML3Cfg *_self = (self); \
- const ObjStateData *_obj_state = (obj_state); \
- \
- nm_assert(_obj_state); \
- nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj), \
- NMP_OBJECT_TYPE_IP4_ADDRESS, \
- NMP_OBJECT_TYPE_IP6_ADDRESS, \
- NMP_OBJECT_TYPE_IP4_ROUTE, \
- NMP_OBJECT_TYPE_IP6_ROUTE)); \
- nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform); \
- nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0) \
- == c_list_is_empty(&_obj_state->os_temporary_not_available_lst)); \
- if (_self) { \
- if (c_list_is_empty(&_obj_state->os_zombie_lst)) { \
- nm_assert(_self->priv.p->combined_l3cd_commited); \
- \
- if (NM_MORE_ASSERTS > 5) { \
- nm_assert(c_list_contains(&_self->priv.p->obj_state_lst_head, \
- &_obj_state->os_lst)); \
- nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0) \
- || c_list_contains( \
- &_self->priv.p->obj_state_temporary_not_available_lst_head, \
- &_obj_state->os_temporary_not_available_lst)); \
- nm_assert(_obj_state->os_plobj \
- == nm_platform_lookup_obj(_self->priv.platform, \
- NMP_CACHE_ID_TYPE_OBJECT_TYPE, \
- _obj_state->obj)); \
- nm_assert( \
- c_list_is_empty(&obj_state->os_zombie_lst) \
- ? (_obj_state->obj \
- == nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj( \
- _self->priv.p->combined_l3cd_commited, \
- _obj_state->obj))) \
- : (!nm_l3_config_data_lookup_obj( \
- _self->priv.p->combined_l3cd_commited, \
- _obj_state->obj))); \
- } \
- } \
- } \
- } \
- } \
+#define nm_assert_obj_state(self, obj_state) \
+ G_STMT_START \
+ { \
+ if (NM_MORE_ASSERTS > 0) { \
+ const NML3Cfg *_self = (self); \
+ const ObjStateData *_obj_state = (obj_state); \
+ \
+ nm_assert(_obj_state); \
+ nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj), \
+ NMP_OBJECT_TYPE_IP4_ADDRESS, \
+ NMP_OBJECT_TYPE_IP6_ADDRESS, \
+ NMP_OBJECT_TYPE_IP4_ROUTE, \
+ NMP_OBJECT_TYPE_IP6_ROUTE)); \
+ nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform); \
+ nm_assert(_obj_state->os_failedobj_expiry_msec != 0 \
+ || _obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); \
+ nm_assert(_obj_state->os_failedobj_expiry_msec == 0 || !_obj_state->os_plobj); \
+ nm_assert(_obj_state->os_failedobj_expiry_msec == 0 \
+ || c_list_is_empty(&_obj_state->os_zombie_lst)); \
+ nm_assert(_obj_state->os_failedobj_expiry_msec == 0 || _obj_state->obj); \
+ if (_self) { \
+ if (c_list_is_empty(&_obj_state->os_zombie_lst)) { \
+ nm_assert(_self->priv.p->combined_l3cd_commited); \
+ \
+ if (NM_MORE_ASSERTS > 5) { \
+ nm_assert(c_list_contains(&_self->priv.p->obj_state_lst_head, \
+ &_obj_state->os_lst)); \
+ nm_assert(_obj_state->os_plobj \
+ == nm_platform_lookup_obj(_self->priv.platform, \
+ NMP_CACHE_ID_TYPE_OBJECT_TYPE, \
+ _obj_state->obj)); \
+ nm_assert( \
+ c_list_is_empty(&obj_state->os_zombie_lst) \
+ ? (_obj_state->obj \
+ == nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj( \
+ _self->priv.p->combined_l3cd_commited, \
+ _obj_state->obj))) \
+ : (!nm_l3_config_data_lookup_obj( \
+ _self->priv.p->combined_l3cd_commited, \
+ _obj_state->obj))); \
+ } \
+ } \
+ } \
+ } \
+ } \
G_STMT_END
static ObjStateData *
@@ -808,13 +827,14 @@ _obj_state_data_new(const NMPObject *obj, const NMPObject *plobj)
obj_state = g_slice_new(ObjStateData);
*obj_state = (ObjStateData){
- .obj = nmp_object_ref(obj),
- .os_plobj = nmp_object_ref(plobj),
- .os_was_in_platform = !!plobj,
- .os_nm_configured = FALSE,
- .os_dirty = FALSE,
- .os_temporary_not_available_lst = C_LIST_INIT(obj_state->os_temporary_not_available_lst),
- .os_zombie_lst = C_LIST_INIT(obj_state->os_zombie_lst),
+ .obj = nmp_object_ref(obj),
+ .os_plobj = nmp_object_ref(plobj),
+ .os_was_in_platform = !!plobj,
+ .os_nm_configured = FALSE,
+ .os_dirty = FALSE,
+ .os_failedobj_expiry_msec = 0,
+ .os_failedobj_prioq_idx = NM_PRIOQ_IDX_NULL,
+ .os_zombie_lst = C_LIST_INIT(obj_state->os_zombie_lst),
};
return obj_state;
}
@@ -824,9 +844,10 @@ _obj_state_data_free(gpointer data)
{
ObjStateData *obj_state = data;
+ nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
+
c_list_unlink_stale(&obj_state->os_lst);
c_list_unlink_stale(&obj_state->os_zombie_lst);
- c_list_unlink_stale(&obj_state->os_temporary_not_available_lst);
nmp_object_unref(obj_state->obj);
nmp_object_unref(obj_state->os_plobj);
nm_g_slice_free(obj_state);
@@ -864,15 +885,17 @@ _obj_state_data_to_string(const ObjStateData *obj_state, char *buf, gsize buf_si
} else if (obj_state->os_was_in_platform)
nm_strbuf_append_str(&buf, &buf_size, ", was-in-platform");
- if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
+ if (obj_state->os_failedobj_expiry_msec > 0) {
nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
- nm_strbuf_append(
- &buf,
- &buf_size,
- ", temporary-not-available-since=%" G_GINT64_FORMAT ".%03d",
- (now_msec - obj_state->os_temporary_not_available_timestamp_msec) / 1000,
- (int) ((now_msec - obj_state->os_temporary_not_available_timestamp_msec) % 1000));
- }
+ nm_strbuf_append(&buf,
+ &buf_size,
+ ", %s-since=%" G_GINT64_FORMAT ".%03d",
+ (obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL) ? "failed"
+ : "failed-wait",
+ (obj_state->os_failedobj_expiry_msec - now_msec) / 1000,
+ (int) ((obj_state->os_failedobj_expiry_msec - now_msec) % 1000));
+ } else
+ nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
return buf0;
}
@@ -934,6 +957,7 @@ _obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboole
if (!in_platform && !c_list_is_empty(&obj_state->os_zombie_lst)) {
/* this is a zombie. We can forget about it.*/
+ nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
nm_clear_nmp_object(&obj_state->os_plobj);
c_list_unlink(&obj_state->os_zombie_lst);
_LOGD("obj-state: zombie gone (untrack): %s",
@@ -949,8 +973,23 @@ _obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboole
if (in_platform) {
nmp_object_ref_set(&obj_state->os_plobj, obj);
obj_state->os_was_in_platform = TRUE;
- _LOGD("obj-state: appeared in platform: %s",
- _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ if (obj_state->os_failedobj_expiry_msec != 0) {
+ obj_state->os_failedobj_expiry_msec = 0;
+ if (obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL) {
+ _LOGT("obj-state: failed-obj: object now configured after failed earlier: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ } else {
+ nm_prioq_remove(&self->priv.p->failedobj_prioq,
+ obj_state,
+ &obj_state->os_failedobj_prioq_idx);
+ _LOGT("obj-state: failed-obj: object now configured after waiting: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ }
+ } else {
+ _LOGD("obj-state: appeared in platform: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ }
+ nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
goto out;
}
@@ -1039,6 +1078,7 @@ _obj_states_update_all(NML3Cfg *self)
continue;
if (obj_state->os_plobj && obj_state->os_nm_configured) {
+ nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
c_list_link_tail(&self->priv.p->obj_state_zombie_lst_head,
&obj_state->os_zombie_lst);
obj_state->os_zombie_count = ZOMBIE_COUNT_START;
@@ -1049,6 +1089,9 @@ _obj_states_update_all(NML3Cfg *self)
_LOGD("obj-state: untrack: %s",
_obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ nm_prioq_remove(&self->priv.p->failedobj_prioq,
+ obj_state,
+ &obj_state->os_failedobj_prioq_idx);
g_hash_table_iter_remove(&h_iter);
}
}
@@ -1086,16 +1129,32 @@ _obj_states_sync_filter(NML3Cfg *self, const NMPObject *obj, NML3CfgCommitType c
return TRUE;
}
- if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
- /* we currently try to configure this address (but failed earlier).
- * Definitely retry. */
- return TRUE;
- }
-
- if (!obj_state->os_plobj && commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
- && !nmp_object_get_force_commit(obj))
- return FALSE;
-
+ /* One goal would be that we don't forcefully re-add routes which were
+ * externally removed (e.g. by the user via `ip route del`).
+ *
+ * However,
+ *
+ * - some routes get automatically deleted by kernel (for example,
+ * when we have an IPv4 route with RTA_PREFSRC set and the referenced
+ * IPv4 address gets removed). The absence of such a route does not
+ * mean that the user doesn't want the route there. It means, kernel
+ * removed it because of some consistency check, but we want it back.
+ * - a route with a non-zero gateway requires that the gateway is
+ * directly reachable via an onlink route. The rules for this are
+ * complex, but kernel will reject adding a route which has such a
+ * gateway. If the user manually removed the needed onlink route, the
+ * gateway route cannot be added in kernel ("Nexthop has invalid
+ * gateway"). To handle that is a nightmare, so we always ensure that
+ * the onlink route is there.
+ * - a route with RTA_PREFSRC requires that such an address is
+ * configured otherwise kernel rejects adding the route with "Invalid
+ * prefsrc address"/"Invalid source address". Removing an address can
+ * thus prevent adding the route, which is a problem for us.
+ *
+ * So the goal is not tenable and causes problems. NetworkManager will
+ * try hard to re-add routes and address that it thinks should be
+ * present. If you externally remove them, then you are starting a
+ * fight where NetworkManager tries to re-add them on every commit. */
return TRUE;
}
@@ -1246,6 +1305,7 @@ _obj_state_zombie_lst_get_prune_lists(NML3Cfg *self,
if (--obj_state->os_zombie_count == 0) {
_LOGD("obj-state: prune zombie (untrack): %s",
_obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
g_hash_table_remove(self->priv.p->obj_state_hash, obj_state);
continue;
}
@@ -1280,6 +1340,7 @@ _obj_state_zombie_lst_prune_all(NML3Cfg *self, int addr_family)
if (--obj_state->os_zombie_count == 0) {
_LOGD("obj-state: zombie pruned during reapply (untrack): %s",
_obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
g_hash_table_remove(self->priv.p->obj_state_hash, obj_state);
continue;
}
@@ -3017,16 +3078,14 @@ nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr)
/*****************************************************************************/
gboolean
-nm_l3cfg_has_temp_not_available_obj(NML3Cfg *self, int addr_family)
+nm_l3cfg_has_failedobj_pending(NML3Cfg *self, int addr_family)
{
ObjStateData *obj_state;
nm_assert(NM_IS_L3CFG(self));
nm_assert_addr_family(addr_family);
- c_list_for_each_entry (obj_state,
- &self->priv.p->obj_state_temporary_not_available_lst_head,
- os_temporary_not_available_lst) {
+ nm_prioq_for_each (&self->priv.p->failedobj_prioq, obj_state) {
if (NMP_OBJECT_GET_ADDR_FAMILY(obj_state->obj) == addr_family)
return TRUE;
}
@@ -3413,8 +3472,7 @@ nm_l3cfg_add_config(NML3Cfg *self,
.acd_timeout_msec_confdata = acd_timeout_msec,
.priority_confdata = priority,
.pseudo_timestamp_confdata = ++self->priv.p->pseudo_timestamp_counter,
- .force_commit_once = NM_FLAGS_HAS(config_flags, NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE),
- .dirty_confdata = FALSE,
+ .dirty_confdata = FALSE,
};
changed = TRUE;
} else {
@@ -3611,7 +3669,6 @@ typedef struct {
NML3Cfg *self;
gconstpointer tag;
bool to_commit;
- bool force_commit_once;
} L3ConfigMergeHookAddObjData;
static gboolean
@@ -3629,9 +3686,6 @@ _l3_hook_add_obj_cb(const NML3ConfigData *l3cd,
nm_assert(obj);
nm_assert(hook_result);
nm_assert(hook_result->ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT);
- nm_assert(hook_result->force_commit == NM_OPTION_BOOL_DEFAULT);
-
- hook_result->force_commit = hook_data->force_commit_once;
switch (NMP_OBJECT_GET_TYPE(obj)) {
case NMP_OBJECT_TYPE_IP4_ADDRESS:
@@ -3787,8 +3841,7 @@ _l3cfg_update_combined_config(NML3Cfg *self,
if (NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD))
continue;
- hook_data.tag = l3cd_data->tag_confdata;
- hook_data.force_commit_once = l3cd_data->force_commit_once;
+ hook_data.tag = l3cd_data->tag_confdata;
nm_l3_config_data_merge(l3cd,
l3cd_data->l3cd,
@@ -3846,7 +3899,6 @@ _l3cfg_update_combined_config(NML3Cfg *self,
IS_IPv4 ? AF_INET : AF_INET6,
l3cd_data->default_route_table_x[IS_IPv4],
l3cd_data->default_route_metric_x[IS_IPv4],
- l3cd_data->force_commit_once,
l3cd_data->l3cd);
}
}
@@ -3921,79 +3973,91 @@ out:
/*****************************************************************************/
static gboolean
-_routes_temporary_not_available_timeout(gpointer user_data)
+_failedobj_timeout_cb(gpointer user_data)
{
- NML3Cfg *self = NM_L3CFG(user_data);
- ObjStateData *obj_state;
- gint64 now_msec;
- gint64 expiry_msec;
+ NML3Cfg *self = NM_L3CFG(user_data);
- nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
+ _LOGT("obj-state: failed-obj: handle timeout");
- obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head,
- ObjStateData,
- os_temporary_not_available_lst);
+ nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source);
- if (!obj_state)
- return G_SOURCE_CONTINUE;
+ nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO);
+
+ return G_SOURCE_CONTINUE;
+}
- now_msec = nm_utils_get_monotonic_timestamp_msec();
+static void
+_failedobj_reschedule(NML3Cfg *self, gint64 now_msec)
+{
+ char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+ ObjStateData *obj_state;
- expiry_msec = obj_state->os_temporary_not_available_timestamp_msec
- + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC;
+ nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
- if (now_msec < expiry_msec) {
- /* the timeout is not yet reached. Restart the timer... */
- self->priv.p->obj_state_temporary_not_available_timeout_source =
- nm_g_timeout_add_source(expiry_msec - now_msec,
- _routes_temporary_not_available_timeout,
- self);
- return G_SOURCE_CONTINUE;
+again:
+ obj_state = nm_prioq_peek(&self->priv.p->failedobj_prioq);
+
+ if (obj_state && obj_state->os_failedobj_expiry_msec <= now_msec) {
+ /* The object is already expired... */
+
+ /* we shouldn't have a "os_plobj", because if we had, we should have
+ * removed "obj_state" from the queue. */
+ nm_assert(!obj_state->os_plobj);
+
+ /* we need to have an "obj", otherwise the "obj_state" instance
+ * shouldn't exist (as it also has not "os_plobj"). */
+ nm_assert(obj_state->obj);
+
+ /* It seems that nm_platform_ip_route_sync() signaled success and did
+ * not report the route as missing. Regardless, it is still not
+ * configured and the timeout expired. */
+ nm_prioq_remove(&self->priv.p->failedobj_prioq,
+ obj_state,
+ &obj_state->os_failedobj_prioq_idx);
+ _LOGW(
+ "missing IPv%c route: %s",
+ nm_utils_addr_family_to_char(NMP_OBJECT_GET_TYPE(obj_state->obj)),
+ nmp_object_to_string(obj_state->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+ goto again;
}
- /* One (or several) routes expired. We emit a signal, but we don't schedule it again.
- * We expect the callers to commit again, which will one last time try to configure
- * the route. If that again fails, we detect the timeout, log a warning and don't
- * track the object as not temporary-not-available anymore. */
- _nm_l3cfg_emit_signal_notify_simple(
- self,
- NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED);
- return G_SOURCE_CONTINUE;
+ if (!obj_state) {
+ if (nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source))
+ _LOGT("obj-state: failed-obj: cancel timeout");
+ return;
+ }
+
+ if (nm_g_timeout_reschedule(&self->priv.p->failedobj_timeout_source,
+ &self->priv.p->failedobj_timeout_expiry_msec,
+ obj_state->os_failedobj_expiry_msec,
+ _failedobj_timeout_cb,
+ self)) {
+ _LOGT(
+ "obj-state: failed-obj: schedule timeout in %" G_GINT64_FORMAT " msec",
+ NM_MAX((gint64) 0,
+ obj_state->os_failedobj_expiry_msec - nm_utils_get_monotonic_timestamp_msec()));
+ }
}
-static gboolean
-_routes_temporary_not_available_update(NML3Cfg *self,
- int addr_family,
- GPtrArray *routes_temporary_not_available_arr)
+static void
+_failedobj_handle_routes(NML3Cfg *self, int addr_family, GPtrArray *routes_failed)
{
- ObjStateData *obj_state;
- ObjStateData *obj_state_safe;
- gint64 now_msec;
- gboolean prune_all = FALSE;
- gboolean success = TRUE;
- guint i;
- const NMPClass *klass;
-
- klass = nmp_class_from_type(NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)));
- now_msec = nm_utils_get_monotonic_timestamp_msec();
-
- if (nm_g_ptr_array_len(routes_temporary_not_available_arr) <= 0) {
- prune_all = TRUE;
- goto out_prune;
- }
+ const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
+ char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+ ObjStateData *obj_state;
+ guint i;
- c_list_for_each_entry (obj_state,
- &self->priv.p->obj_state_temporary_not_available_lst_head,
- os_temporary_not_available_lst) {
- if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) {
- nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0);
- obj_state->os_tna_dirty = TRUE;
- }
- }
+ if (!routes_failed)
+ return;
- for (i = 0; i < routes_temporary_not_available_arr->len; i++) {
- const NMPObject *o = routes_temporary_not_available_arr->pdata[i];
- char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+ for (i = 0; i < routes_failed->len; i++) {
+ const NMPObject *o = routes_failed->pdata[i];
+ const NMPlatformIPXRoute *rt = NMP_OBJECT_CAST_IPX_ROUTE(o);
+ gboolean just_started_to_fail = FALSE;
+ gboolean just_failed = FALSE;
+ gboolean arm_timer = FALSE;
+ int grace_timeout_msec;
+ gint64 grace_expiry_mesc;
nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)));
@@ -4005,70 +4069,83 @@ _routes_temporary_not_available_update(NML3Cfg *self,
continue;
}
- if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
- nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0
- && obj_state->os_temporary_not_available_timestamp_msec <= now_msec);
-
- if (!obj_state->os_tna_dirty) {
- /* Odd, this only can happen if routes_temporary_not_available_arr contains duplicates.
- * It should not. */
- nm_assert_not_reached();
- continue;
- }
-
- if (now_msec > obj_state->os_temporary_not_available_timestamp_msec
- + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) {
- /* Timeout. Could not add this address.
- *
- * For now, keep it obj_state->os_tna_dirty and prune it below. */
- _LOGW("failure to add IPv%c route: %s",
- nm_utils_addr_family_to_char(addr_family),
- nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
- success = FALSE;
- continue;
- }
-
- obj_state->os_tna_dirty = FALSE;
+ if (obj_state->os_plobj) {
+ /* This object is apparently present in platform. Not sure what this failure report
+ * is about. Probably some harmless glitch. Ignore. */
continue;
}
- _LOGT("(temporarily) unable to add IPv%c route: %s",
- nm_utils_addr_family_to_char(addr_family),
- nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+ /* This route failed, but why? That determines the grace time that we
+ * give before considering it bad. */
+ if (!nm_ip_addr_is_null(addr_family,
+ nm_platform_ip_route_get_pref_src(addr_family, &rt->rx))) {
+ /* This route has a pref_src. A common cause for being unable to
+ * configure such routes, is that the referenced IP address is not
+ * configured/ready (yet). Give a longer timeout to this case. */
+ grace_timeout_msec = 10000;
+ } else {
+ /* Other route don't have any grace time. There is no retry/wait,
+ * they are a failure right away. */
+ grace_timeout_msec = 0;
+ }
- obj_state->os_tna_dirty = FALSE;
- obj_state->os_temporary_not_available_timestamp_msec = now_msec;
- c_list_link_tail(&self->priv.p->obj_state_temporary_not_available_lst_head,
- &obj_state->os_temporary_not_available_lst);
- }
+ grace_expiry_mesc = now_msec + grace_timeout_msec;
-out_prune:
- c_list_for_each_entry_safe (obj_state,
- obj_state_safe,
- &self->priv.p->obj_state_temporary_not_available_lst_head,
- os_temporary_not_available_lst) {
- if (prune_all || obj_state->os_tna_dirty) {
- if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) {
- obj_state->os_temporary_not_available_timestamp_msec = 0;
- c_list_unlink(&obj_state->os_temporary_not_available_lst);
+ if (obj_state->os_failedobj_expiry_msec == 0) {
+ /* This is a new failure that we didn't see before... */
+ obj_state->os_failedobj_expiry_msec = grace_expiry_mesc;
+ if (grace_timeout_msec == 0)
+ just_failed = TRUE;
+ else {
+ arm_timer = TRUE;
+ just_started_to_fail = TRUE;
+ }
+ } else {
+ if (obj_state->os_failedobj_expiry_msec > grace_expiry_mesc) {
+ /* Shorten the grace timeout. We anyway rearm below... */
+ obj_state->os_failedobj_expiry_msec = grace_expiry_mesc;
}
+ if (obj_state->os_failedobj_expiry_msec <= now_msec) {
+ /* The grace period is (already) expired. */
+ if (obj_state->os_failedobj_prioq_idx != NM_PRIOQ_IDX_NULL) {
+ /* We are still tracking the element. It just is about to become failed. */
+ just_failed = TRUE;
+ }
+ } else
+ arm_timer = TRUE;
}
- }
- nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
-
- obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head,
- ObjStateData,
- os_temporary_not_available_lst);
- if (obj_state) {
- self->priv.p->obj_state_temporary_not_available_timeout_source =
- nm_g_timeout_add_source((obj_state->os_temporary_not_available_timestamp_msec
- + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec),
- _routes_temporary_not_available_timeout,
- self);
+ nm_prioq_update(&self->priv.p->failedobj_prioq,
+ obj_state,
+ &obj_state->os_failedobj_prioq_idx,
+ arm_timer);
+
+ if (just_failed) {
+ _LOGW("unable to configure IPv%c route: %s",
+ nm_utils_addr_family_to_char(addr_family),
+ nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+ } else if (just_started_to_fail) {
+ _LOGT("obj-state: failed-obj: unable to configure %s. Wait for %d msec",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)),
+ grace_timeout_msec);
+ }
}
+}
- return success;
+static int
+_failedobj_prioq_cmp(gconstpointer a, gconstpointer b)
+{
+ const ObjStateData *object_state_a = a;
+ const ObjStateData *object_state_b = b;
+
+ nm_assert(object_state_a);
+ nm_assert(object_state_a->os_failedobj_expiry_msec > 0);
+ nm_assert(object_state_b);
+ nm_assert(object_state_b->os_failedobj_expiry_msec > 0);
+
+ NM_CMP_SELF(object_state_a, object_state_b);
+ NM_CMP_FIELD(object_state_a, object_state_b, os_failedobj_expiry_msec);
+ return 0;
}
/*****************************************************************************/
@@ -4391,6 +4468,147 @@ _rp_filter_update(NML3Cfg *self, gboolean reapply)
/*****************************************************************************/
+static void
+_routes_watch_ip_addrs_cb(NMNetns *netns,
+ NMNetnsWatcherType watcher_type,
+ const NMNetnsWatcherData *watcher_data,
+ gconstpointer tag,
+ const NMNetnsWatcherEventData *event_data,
+ gpointer user_data)
+{
+ const int IS_IPv4 = NM_IS_IPv4(watcher_data->ip_addr.addr.addr_family);
+ NML3Cfg *self = user_data;
+ char sbuf[NM_INET_ADDRSTRLEN];
+
+ if (NMP_OBJECT_CAST_IP_ADDRESS(event_data->ip_addr.obj)->ifindex == self->priv.ifindex) {
+ if (self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4] > 0) {
+ /* We are currently commiting IP addresses on this very interface.
+ * We can ignore the event. Also, because we will sync the routes
+ * immediately after already. So even if somebody externally added
+ * the address just this very moment, we would still do the commit
+ * at the right time to ensure our routes are there. */
+ return;
+ }
+ }
+
+ if (event_data->ip_addr.change_type == NM_PLATFORM_SIGNAL_REMOVED)
+ return;
+
+ _LOGT("watched ip-address %s changed. Schedule an idle commit",
+ nm_inet_ntop(watcher_data->ip_addr.addr.addr_family,
+ &watcher_data->ip_addr.addr.addr,
+ sbuf));
+ nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO);
+}
+
+static void
+_routes_watch_ip_addrs(NML3Cfg *self, int addr_family, GPtrArray *addresses, GPtrArray *routes)
+{
+ gconstpointer const TAG = _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family);
+ NMNetnsWatcherData watcher_data = {
+ .ip_addr =
+ {
+ .addr =
+ {
+ .addr_family = addr_family,
+ },
+ },
+ };
+ guint i;
+ guint j;
+
+ /* IP routes that have a pref_src, can only be configured in kernel if that
+ * address exists (and is non-tentative, in case of IPv6). That address
+ * might be on another interface. So we actually watch all other
+ * interfaces.
+ *
+ * Note that while we track failure to configure routes via "failedobj"
+ * mechanism, we eagerly register watchers, even if the route is already
+ * successfully configured or if the route is to be configure the first
+ * time. Maybe that could be improved, but
+ * - watchers should be cheap unless they notify the event.
+ * - upon change we do an async commit, which is maybe not entirely cheap
+ * but cheap enough. More importantly, committing is something that
+ * *always* should be permissible -- because NML3Cfg has multiple,
+ * independent users, that don't know about each other and which
+ * independently are allowed to issue a commit when they think something
+ * relevant changed. If there are really too many, unnecessary commits,
+ * then the cause needs to be understood and addressed explicitly. */
+
+ if (!routes)
+ goto out;
+
+ for (i = 0; i < routes->len; i++) {
+ const NMPlatformIPRoute *rt = NMP_OBJECT_CAST_IP_ROUTE(routes->pdata[i]);
+ gconstpointer pref_src;
+
+ nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(routes->pdata[i]) == addr_family);
+
+ pref_src = nm_platform_ip_route_get_pref_src(addr_family, rt);
+
+ if (nm_ip_addr_is_null(addr_family, pref_src))
+ continue;
+
+ if (NM_IS_IPv4(addr_family)) {
+ if (addresses) {
+ /* This nested loop makes the whole operation O(n*m). We still
+ * do it that way, because it's probably faster to just iterate
+ * over the few addresses instead of building a lookup index to
+ * get it in O(n+m). */
+ for (j = 0; j < addresses->len; j++) {
+ const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(addresses->pdata[j]);
+
+ nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(addresses->pdata[j]) == addr_family);
+
+ if (nm_ip_addr_equal(addr_family, pref_src, a->address_ptr)) {
+ /* We optimize for the case where the required address
+ * is about be configured in the same commit. That is a
+ * common case, because our DHCP routes have prefsrc
+ * set, and we commonly have the respective IP address
+ * ready. Otherwise, the very common DHCP case would
+ * also require the overhead of registering a watcher
+ * (every time).
+ */
+ goto next;
+ }
+ }
+ }
+ } else {
+ /* For IPv6, the prefsrc address must also be non-tentative (or
+ * IFA_F_OPTIMISTIC). So by only looking at the addresses we are
+ * about to configure, it's not clear whether we will be able to
+ * configure the route too.
+ *
+ * Maybe we could check current platform, whether the address
+ * exists there as non-tentative, but that seems fragile.
+ *
+ * Maybe we should only register watchers, after we encountered a
+ * failure to configure a route, but that seems complicated (and
+ * has the potential to be wrong).
+ *
+ * The overhead for always watching the IPv6 address should be
+ * acceptably small. So just do that.
+ */
+ }
+
+ nm_assert(watcher_data.ip_addr.addr.addr_family == addr_family);
+ nm_ip_addr_set(addr_family, &watcher_data.ip_addr.addr.addr, pref_src);
+
+ nm_netns_watcher_add(self->priv.netns,
+ NM_NETNS_WATCHER_TYPE_IP_ADDR,
+ &watcher_data,
+ TAG,
+ _routes_watch_ip_addrs_cb,
+ self);
+next:
+ (void) 0;
+ }
+
+out:
+ nm_netns_watcher_remove_all(self->priv.netns, TAG, FALSE);
+}
+/*****************************************************************************/
+
static gboolean
_global_tracker_mptcp_untrack(NML3Cfg *self, int addr_family)
{
@@ -4576,24 +4794,22 @@ _l3_commit_mptcp(NML3Cfg *self, NML3CfgCommitType commit_type)
_rp_filter_update(self, reapply);
}
-static gboolean
+static void
_l3_commit_one(NML3Cfg *self,
int addr_family,
NML3CfgCommitType commit_type,
gboolean changed_combined_l3cd,
const NML3ConfigData *l3cd_old)
{
- const int IS_IPv4 = NM_IS_IPv4(addr_family);
- gs_unref_ptrarray GPtrArray *addresses = NULL;
- gs_unref_ptrarray GPtrArray *routes = NULL;
- gs_unref_ptrarray GPtrArray *routes_nodev = NULL;
- gs_unref_ptrarray GPtrArray *addresses_prune = NULL;
- gs_unref_ptrarray GPtrArray *routes_prune = NULL;
- gs_unref_ptrarray GPtrArray *routes_temporary_not_available_arr = NULL;
+ const int IS_IPv4 = NM_IS_IPv4(addr_family);
+ gs_unref_ptrarray GPtrArray *addresses = NULL;
+ gs_unref_ptrarray GPtrArray *routes = NULL;
+ gs_unref_ptrarray GPtrArray *routes_nodev = NULL;
+ gs_unref_ptrarray GPtrArray *addresses_prune = NULL;
+ gs_unref_ptrarray GPtrArray *routes_prune = NULL;
+ gs_unref_ptrarray GPtrArray *routes_failed = NULL;
NMIPRouteTableSyncMode route_table_sync;
- gboolean final_failure_for_temporary_not_available = FALSE;
char sbuf_commit_type[50];
- gboolean success = TRUE;
guint i;
nm_assert(NM_IS_L3CFG(self));
@@ -4689,9 +4905,14 @@ _l3_commit_one(NML3Cfg *self,
}
}
}
+
+ _routes_watch_ip_addrs(self, addr_family, addresses, routes);
+
/* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ndisc_*(). */
/* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_mtu(). */
+ self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]++;
+
nm_platform_ip_address_sync(self->priv.platform,
addr_family,
self->priv.ifindex,
@@ -4701,26 +4922,18 @@ _l3_commit_one(NML3Cfg *self,
? NMP_IP_ADDRESS_SYNC_FLAGS_NONE
: NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE);
- _nodev_routes_sync(self, addr_family, commit_type, routes_nodev);
-
- if (!nm_platform_ip_route_sync(self->priv.platform,
- addr_family,
- self->priv.ifindex,
- routes,
- routes_prune,
- &routes_temporary_not_available_arr))
- success = FALSE;
+ self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]--;
- final_failure_for_temporary_not_available = FALSE;
- if (!_routes_temporary_not_available_update(self,
- addr_family,
- routes_temporary_not_available_arr))
- final_failure_for_temporary_not_available = TRUE;
+ _nodev_routes_sync(self, addr_family, commit_type, routes_nodev);
- /* FIXME(l3cfg) */
- (void) final_failure_for_temporary_not_available;
+ nm_platform_ip_route_sync(self->priv.platform,
+ addr_family,
+ self->priv.ifindex,
+ routes,
+ routes_prune,
+ &routes_failed);
- return success;
+ _failedobj_handle_routes(self, addr_family, routes_failed);
}
static void
@@ -4733,7 +4946,6 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
gboolean is_sticky_update = FALSE;
char sbuf_ct[30];
gboolean changed_combined_l3cd;
- guint i;
g_return_if_fail(NM_IS_L3CFG(self));
nm_assert(NM_IN_SET(commit_type,
@@ -4794,19 +5006,12 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
_l3_commit_one(self, AF_INET, commit_type, changed_combined_l3cd, l3cd_old);
_l3_commit_one(self, AF_INET6, commit_type, changed_combined_l3cd, l3cd_old);
+ _failedobj_reschedule(self, 0);
+
_l3_commit_mptcp(self, commit_type);
_l3_acd_data_process_changes(self);
- if (self->priv.p->l3_config_datas) {
- for (i = 0; i < self->priv.p->l3_config_datas->len; i++) {
- L3ConfigData *l3_config_data = _l3_config_datas_at(self->priv.p->l3_config_datas, i);
-
- if (l3_config_data->force_commit_once)
- l3_config_data->force_commit_once = FALSE;
- }
- }
-
nm_assert(self->priv.p->commit_reentrant_count == 1);
self->priv.p->commit_reentrant_count--;
@@ -5164,7 +5369,6 @@ nm_l3cfg_init(NML3Cfg *self)
c_list_init(&self->priv.p->acd_event_notify_lst_head);
c_list_init(&self->priv.p->commit_type_lst_head);
c_list_init(&self->priv.p->obj_state_lst_head);
- c_list_init(&self->priv.p->obj_state_temporary_not_available_lst_head);
c_list_init(&self->priv.p->obj_state_zombie_lst_head);
c_list_init(&self->priv.p->blocked_lst_head_4);
c_list_init(&self->priv.p->blocked_lst_head_6);
@@ -5176,6 +5380,8 @@ nm_l3cfg_init(NML3Cfg *self)
nmp_object_indirect_id_equal,
_obj_state_data_free,
NULL);
+
+ nm_prioq_init(&self->priv.p->failedobj_prioq, _failedobj_prioq_cmp);
}
static void
@@ -5214,6 +5420,18 @@ finalize(GObject *object)
NML3Cfg *self = NM_L3CFG(object);
gboolean changed;
+ if (self->priv.netns) {
+ nm_netns_watcher_remove_all(self->priv.netns,
+ _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET),
+ TRUE);
+ nm_netns_watcher_remove_all(self->priv.netns,
+ _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET6),
+ TRUE);
+ }
+
+ nm_prioq_destroy(&self->priv.p->failedobj_prioq);
+ nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source);
+
nm_assert(c_list_is_empty(&self->internal_netns.signal_pending_lst));
nm_assert(c_list_is_empty(&self->internal_netns.ecmp_track_ifindex_lst_head));
@@ -5241,11 +5459,8 @@ finalize(GObject *object)
nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry);
nm_clear_g_source_inst(&self->priv.p->nacd_event_down_source);
- nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
-
nm_clear_pointer(&self->priv.p->obj_state_hash, g_hash_table_destroy);
nm_assert(c_list_is_empty(&self->priv.p->obj_state_lst_head));
- nm_assert(c_list_is_empty(&self->priv.p->obj_state_temporary_not_available_lst_head));
nm_assert(c_list_is_empty(&self->priv.p->obj_state_zombie_lst_head));
if (_nodev_routes_untrack(self, AF_INET))
diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h
index 9d622b4a01..5ee201e7a2 100644
--- a/src/core/nm-l3cfg.h
+++ b/src/core/nm-l3cfg.h
@@ -55,15 +55,11 @@ typedef enum _nm_packed {
* "don't change" behavior. At least once. If the address/route
* is still not (no longer) configured on the subsequent
* commit, it's not getting added again.
- * @NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE: if set, objects in the
- * NML3ConfigData are committed to platform even if they were
- * removed externally.
*/
typedef enum _nm_packed {
NM_L3CFG_CONFIG_FLAGS_NONE = 0,
NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD = (1LL << 0),
NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE = (1LL << 1),
- NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE = (1LL << 2),
} NML3CfgConfigFlags;
typedef enum _nm_packed {
@@ -132,8 +128,6 @@ typedef enum {
* and neither should you call into NML3Cfg again (reentrancy). */
NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED,
- NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED,
-
NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT,
/* emitted before the merged l3cd is committed to platform.
@@ -412,7 +406,7 @@ gboolean nm_l3cfg_check_ready(NML3Cfg *self,
NML3CfgCheckReadyFlags flags,
GArray **conflicts);
-gboolean nm_l3cfg_has_temp_not_available_obj(NML3Cfg *self, int addr_family);
+gboolean nm_l3cfg_has_failedobj_pending(NML3Cfg *self, int addr_family);
/*****************************************************************************/
diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c
index 88a4d66001..ad156f99ed 100644
--- a/src/core/nm-netns.c
+++ b/src/core/nm-netns.c
@@ -22,6 +22,44 @@
/*****************************************************************************/
+typedef struct {
+ gconstpointer tag;
+ CList watcher_by_tag_lst_head;
+} WatcherByTag;
+
+typedef struct {
+ NMIPAddrTyped addr;
+ CList watcher_ip_addr_lst_head;
+} WatcherDataIPAddr;
+
+struct _NMNetnsWatcherHandle {
+ NMNetnsWatcherType watcher_type;
+ NMNetnsWatcherData watcher_data;
+ gconstpointer tag;
+ NMNetnsWatcherCallback callback;
+ gpointer callback_user_data;
+
+ /* This is linked to "WatcherByTag.watcher_by_tag_lst_head" in
+ * "priv->watcher_by_tag_idx". */
+ CList watcher_tag_lst;
+
+ /* The registration data, which depends on the "watcher_type". */
+ union {
+ struct {
+ CList watcher_ip_addr_lst;
+ } ip_addr;
+ } reg_data;
+
+ /* nm_netns_watcher_add() will mark the handle as non-dirty, while
+ * nm_netns_watcher_remove_all() can delete only dirty handles (while
+ * leaving non-dirty handles alive, but marking them as dirty).
+ *
+ * That allows a pattern where you just add the new handles that you want
+ * now, and then call nm_netns_watcher_remove_all() to remove those that
+ * should no longer be present. */
+ bool watcher_dirty : 1;
+};
+
NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PLATFORM, );
typedef struct {
@@ -33,8 +71,20 @@ typedef struct {
GHashTable *shared_ips;
GHashTable *ecmp_track_by_obj;
GHashTable *ecmp_track_by_ecmpid;
- CList l3cfg_signal_pending_lst_head;
- GSource *signal_pending_idle_source;
+
+ /* Indexes the watcher handles. */
+ GHashTable *watcher_idx;
+
+ /* An index of WatcherByTag. It allows to lookup watcher handles by tag.
+ * Handles without tag are not indexed. */
+ GHashTable *watcher_by_tag_idx;
+
+ /* Index for WatcherDataIPAddr instances. Allows to lookup all subscribers
+ * by IP address. */
+ GHashTable *watcher_ip_data_idx;
+
+ CList l3cfg_signal_pending_lst_head;
+ GSource *signal_pending_idle_source;
} NMNetnsPrivate;
struct _NMNetns {
@@ -87,6 +137,24 @@ NM_DEFINE_SINGLETON_GETTER(NMNetns, nm_netns_get, NM_TYPE_NETNS);
/*****************************************************************************/
+static WatcherDataIPAddr *
+_watcher_ip_data_lookup(NMNetns *self, int addr_family, gconstpointer addr);
+static void _watcher_handle_notify(NMNetns *self,
+ NMNetnsWatcherHandle *handle,
+ const NMNetnsWatcherEventData *event_data);
+static const char *
+_watcher_handle_to_string(const NMNetnsWatcherHandle *handle, char *buf, gsize buf_size);
+
+/*****************************************************************************/
+
+static gboolean
+NM_NETNS_WATCHER_TYPE_VALID(NMNetnsWatcherType watcher_type)
+{
+ return NM_IN_SET(watcher_type, NM_NETNS_WATCHER_TYPE_IP_ADDR);
+}
+
+/*****************************************************************************/
+
typedef struct {
const NMPObject *representative_obj;
const NMPObject *merged_obj;
@@ -437,7 +505,7 @@ _platform_signal_cb(NMPlatform *platform,
l3cfg = nm_netns_l3cfg_get(self, ifindex);
if (!l3cfg)
- return;
+ goto notify_watcher;
l3cfg->internal_netns.signal_pending_obj_type_flags |= nmp_object_type_to_flags(obj_type);
@@ -450,6 +518,55 @@ _platform_signal_cb(NMPlatform *platform,
}
_nm_l3cfg_notify_platform_change(l3cfg, change_type, NMP_OBJECT_UP_CAST(platform_object));
+
+notify_watcher:
+ switch (obj_type) {
+ case NMP_OBJECT_TYPE_IP4_ADDRESS:
+ case NMP_OBJECT_TYPE_IP6_ADDRESS:
+ {
+ NMNetnsWatcherHandle *handle;
+ NMNetnsWatcherHandle *handle_safe;
+ WatcherDataIPAddr *data;
+
+ data =
+ _watcher_ip_data_lookup(self,
+ obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS ? AF_INET : AF_INET6,
+ ((const NMPlatformIPAddress *) platform_object)->address_ptr);
+
+ if (data) {
+ const NMNetnsWatcherEventData event_data = {
+ .ip_addr =
+ {
+ .change_type = change_type,
+ .obj = NMP_OBJECT_UP_CAST(platform_object),
+ },
+ };
+ char sbuf[500];
+
+ c_list_for_each_entry_safe (handle,
+ handle_safe,
+ &data->watcher_ip_addr_lst_head,
+ reg_data.ip_addr.watcher_ip_addr_lst) {
+ _LOGT("netns-watcher: %s %s",
+ "notify",
+ _watcher_handle_to_string(handle, sbuf, sizeof(sbuf)));
+
+ /* Note that we dispatch these events directly from the platform event
+ * and while iterating over "data".
+ *
+ * From the callback, it's probably a bad idea to do anything in platform
+ * that might change anything (emit new signals) or to nm_netns_watcher_remove*()
+ * any other watcher.
+ *
+ * The callee needs to be careful. */
+ _watcher_handle_notify(self, handle, &event_data);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
}
/*****************************************************************************/
@@ -823,6 +940,460 @@ nm_netns_ip_route_ecmp_commit(NMNetns *self,
/*****************************************************************************/
static void
+_watcher_data_set(NMNetnsWatcherData *dst,
+ NMNetnsWatcherType watcher_type,
+ const NMNetnsWatcherData *src)
+{
+ nm_assert(dst);
+ nm_assert(src);
+
+ switch (watcher_type) {
+ case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+ dst->ip_addr = src->ip_addr;
+ return;
+ }
+ nm_assert_not_reached();
+}
+
+static void
+_watcher_data_hash(NMHashState *h, NMNetnsWatcherType watcher_type, const NMNetnsWatcherData *data)
+{
+ nm_assert(h);
+ nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type));
+ nm_assert(data);
+
+ switch (watcher_type) {
+ case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+ nm_ip_addr_typed_hash_update(h, &data->ip_addr.addr);
+ return;
+ }
+ nm_assert_not_reached();
+}
+
+static gboolean
+_watcher_data_equal(NMNetnsWatcherType watcher_type,
+ const NMNetnsWatcherData *a,
+ const NMNetnsWatcherData *b)
+{
+ nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type));
+ nm_assert(a);
+ nm_assert(b);
+
+ switch (watcher_type) {
+ case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+ return nm_ip_addr_typed_equal(&a->ip_addr.addr, &b->ip_addr.addr);
+ }
+ return nm_assert_unreachable_val(FALSE);
+}
+
+static void
+_watcher_by_tag_destroy(WatcherByTag *watcher_by_tag)
+{
+ c_list_unlink_stale(&watcher_by_tag->watcher_by_tag_lst_head);
+ nm_g_slice_free(watcher_by_tag);
+}
+
+static void
+_watcher_handle_init(NMNetnsWatcherHandle *handle,
+ NMNetnsWatcherType watcher_type,
+ const NMNetnsWatcherData *watcher_data,
+ gconstpointer tag)
+{
+ nm_assert(handle);
+ nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type));
+
+ *handle = (NMNetnsWatcherHandle){
+ .watcher_type = watcher_type,
+ .tag = tag,
+ .watcher_tag_lst = C_LIST_INIT(handle->watcher_tag_lst),
+ };
+ _watcher_data_set(&handle->watcher_data, watcher_type, watcher_data);
+}
+
+static guint
+_watcher_handle_hash(gconstpointer data)
+{
+ const NMNetnsWatcherHandle *watcher = data;
+ NMHashState h;
+
+ nm_assert(watcher);
+ nm_assert(watcher->tag);
+
+ nm_hash_init(&h, 2696278447u);
+ nm_hash_update_vals(&h, watcher->tag, watcher->watcher_type);
+ _watcher_data_hash(&h, watcher->watcher_type, &watcher->watcher_data);
+ return nm_hash_complete(&h);
+}
+
+static gboolean
+_watcher_handle_equal(gconstpointer a, gconstpointer b)
+{
+ const NMNetnsWatcherHandle *ha = a;
+ const NMNetnsWatcherHandle *hb = b;
+
+ nm_assert(ha);
+ nm_assert(hb);
+ nm_assert(ha->tag);
+ nm_assert(hb->tag);
+
+ if (ha == hb)
+ return TRUE;
+
+ return (ha->tag == hb->tag) && (ha->watcher_type == hb->watcher_type)
+ && _watcher_data_equal(ha->watcher_type, &ha->watcher_data, &hb->watcher_data);
+}
+
+static const char *
+_watcher_handle_to_string(const NMNetnsWatcherHandle *handle, char *buf, gsize buf_size)
+{
+ const char *buf0 = buf;
+ char sbuf[NM_INET_ADDRSTRLEN];
+
+ nm_strbuf_append(&buf,
+ &buf_size,
+ "h:" NM_HASH_OBFUSCATE_PTR_FMT "[",
+ NM_HASH_OBFUSCATE_PTR(handle));
+
+ if (handle->tag) {
+ nm_strbuf_append(&buf,
+ &buf_size,
+ "tag:" NM_HASH_OBFUSCATE_PTR_FMT ",",
+ NM_HASH_OBFUSCATE_PTR(handle->tag));
+ }
+
+ switch (handle->watcher_type) {
+ case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+ nm_strbuf_append_str(&buf, &buf_size, "ip-addr:");
+ nm_strbuf_append_str(&buf,
+ &buf_size,
+ nm_inet_ntop(handle->watcher_data.ip_addr.addr.addr_family,
+ &handle->watcher_data.ip_addr.addr.addr,
+ sbuf));
+ goto out;
+ }
+ nm_assert_not_reached();
+ nm_strbuf_append_str(&buf, &buf_size, "unknown");
+
+out:
+ nm_strbuf_append_c(&buf, &buf_size, ']');
+ return buf0;
+}
+
+static void
+_watcher_handle_notify(NMNetns *self,
+ NMNetnsWatcherHandle *handle,
+ const NMNetnsWatcherEventData *event_data)
+{
+ nm_assert(NM_IS_NETNS(self));
+ nm_assert(handle);
+ nm_assert(handle->callback);
+
+ handle->callback(self,
+ handle->watcher_type,
+ &handle->watcher_data,
+ handle->tag,
+ event_data,
+ handle->callback_user_data);
+}
+
+static WatcherDataIPAddr *
+_watcher_ip_data_lookup(NMNetns *self, int addr_family, gconstpointer addr)
+{
+ WatcherDataIPAddr needle;
+
+ needle.addr.addr_family = addr_family;
+ nm_ip_addr_set(addr_family, &needle.addr.addr, addr);
+ return g_hash_table_lookup(NM_NETNS_GET_PRIVATE(self)->watcher_ip_data_idx, &needle);
+}
+
+static WatcherDataIPAddr *
+_watcher_ip_data_lookup_addr(NMNetns *self, const NMIPAddrTyped *addr)
+{
+ return _watcher_ip_data_lookup(self, addr->addr_family, &addr->addr);
+}
+
+static guint
+_watcher_ip_data_hash(gconstpointer _data)
+{
+ const WatcherDataIPAddr *data = _data;
+ NMHashState h;
+
+ nm_assert(data);
+
+ nm_hash_init(&h, 3152126191u);
+ nm_ip_addr_typed_hash_update(&h, &data->addr);
+ return nm_hash_complete(&h);
+}
+
+static gboolean
+_watcher_ip_data_equal(gconstpointer a, gconstpointer b)
+{
+ const WatcherDataIPAddr *data_a = a;
+ const WatcherDataIPAddr *data_b = b;
+
+ nm_assert(data_a);
+ nm_assert(data_b);
+
+ return nm_ip_addr_typed_equal(&data_a->addr, &data_b->addr);
+}
+
+static NMNetnsWatcherHandle *
+_watcher_lookup_handle(NMNetns *self,
+ NMNetnsWatcherType watcher_type,
+ const NMNetnsWatcherData *watcher_data,
+ gconstpointer tag)
+{
+ NMNetnsWatcherHandle handle_needle;
+
+ nm_assert(NM_IS_NETNS(self));
+ nm_assert(tag);
+
+ _watcher_handle_init(&handle_needle, watcher_type, watcher_data, tag);
+ return g_hash_table_lookup(NM_NETNS_GET_PRIVATE(self)->watcher_idx, &handle_needle);
+}
+
+static void
+_watcher_register_handle(NMNetns *self, NMNetnsWatcherHandle *handle)
+{
+ NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
+
+ switch (handle->watcher_type) {
+ case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+ {
+ WatcherDataIPAddr *data;
+
+ data = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr);
+ if (!data) {
+ data = g_slice_new(WatcherDataIPAddr);
+ *data = (WatcherDataIPAddr){
+ .addr = handle->watcher_data.ip_addr.addr,
+ .watcher_ip_addr_lst_head = C_LIST_INIT(data->watcher_ip_addr_lst_head),
+ };
+ if (!g_hash_table_add(priv->watcher_ip_data_idx, data))
+ nm_assert_not_reached();
+ }
+
+ c_list_link_tail(&data->watcher_ip_addr_lst_head,
+ &handle->reg_data.ip_addr.watcher_ip_addr_lst);
+ return;
+ }
+ }
+ nm_assert_not_reached();
+}
+
+static void
+_watcher_unregister_handle(NMNetns *self, NMNetnsWatcherHandle *handle)
+{
+ NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
+
+ switch (handle->watcher_type) {
+ case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+ {
+ gboolean is_last;
+
+ nm_assert(({
+ WatcherDataIPAddr *d;
+
+ d = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr);
+ d &&c_list_contains(&d->watcher_ip_addr_lst_head,
+ &handle->reg_data.ip_addr.watcher_ip_addr_lst);
+ }));
+
+ is_last = c_list_is_empty_or_single(&handle->reg_data.ip_addr.watcher_ip_addr_lst);
+
+ c_list_unlink(&handle->reg_data.ip_addr.watcher_ip_addr_lst);
+
+ if (is_last) {
+ WatcherDataIPAddr *data;
+
+ data = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr);
+ nm_assert(data);
+ nm_assert(c_list_is_empty(&data->watcher_ip_addr_lst_head));
+
+ if (!g_hash_table_remove(priv->watcher_ip_data_idx, data))
+ nm_assert_not_reached();
+
+ nm_g_slice_free(data);
+ }
+ return;
+ }
+ }
+ nm_assert_not_reached();
+}
+
+void
+nm_netns_watcher_add(NMNetns *self,
+ NMNetnsWatcherType watcher_type,
+ const NMNetnsWatcherData *watcher_data,
+ gconstpointer tag,
+ NMNetnsWatcherCallback callback,
+ gpointer user_data)
+{
+ NMNetnsPrivate *priv;
+ NMNetnsWatcherHandle *handle;
+ gboolean is_new = FALSE;
+ char sbuf[500];
+
+ g_return_if_fail(NM_IS_NETNS(self));
+ g_return_if_fail(NM_NETNS_WATCHER_TYPE_VALID(watcher_type));
+ g_return_if_fail(callback);
+ g_return_if_fail(tag);
+
+ priv = NM_NETNS_GET_PRIVATE(self);
+
+ handle = _watcher_lookup_handle(self, watcher_type, watcher_data, tag);
+
+ if (!handle) {
+ WatcherByTag *watcher_by_tag;
+
+ if (G_UNLIKELY(g_hash_table_size(priv->watcher_idx) == 0))
+ g_object_ref(self);
+
+ handle = g_slice_new(NMNetnsWatcherHandle);
+ _watcher_handle_init(handle, watcher_type, watcher_data, tag);
+
+ if (!g_hash_table_add(priv->watcher_idx, handle))
+ nm_assert_not_reached();
+
+ watcher_by_tag = g_hash_table_lookup(priv->watcher_by_tag_idx, &tag);
+
+ if (!watcher_by_tag) {
+ watcher_by_tag = g_slice_new(WatcherByTag);
+ *watcher_by_tag = (WatcherByTag){
+ .tag = tag,
+ .watcher_by_tag_lst_head = C_LIST_INIT(watcher_by_tag->watcher_by_tag_lst_head),
+ };
+ g_hash_table_add(priv->watcher_by_tag_idx, watcher_by_tag);
+ }
+
+ c_list_link_tail(&watcher_by_tag->watcher_by_tag_lst_head, &handle->watcher_tag_lst);
+
+ is_new = TRUE;
+ } else {
+ /* Handles are deduplicated/shared. Hence it is error prone (and likely
+ * a bug) to provide different callback/user_data. Such usage is
+ * rejected here.
+ *
+ * This could be made to work, for example by now allowing handles to
+ * be merged or simply requiring the caller to be careful to not get
+ * this wrong. But that is currently not implemented nor needed.
+ */
+ nm_assert(!tag
+ || (handle->callback == callback && handle->callback_user_data == user_data));
+ }
+
+ if (_LOGT_ENABLED()
+ && (is_new || handle->callback != callback || handle->callback_user_data != user_data)) {
+ _LOGT("netns-watcher: %s %s",
+ is_new ? "register" : "update",
+ _watcher_handle_to_string(handle, sbuf, sizeof(sbuf)));
+ }
+
+ handle->callback = callback;
+ handle->callback_user_data = user_data;
+ handle->watcher_dirty = FALSE;
+
+ if (is_new)
+ _watcher_register_handle(self, handle);
+
+ /* We cannot return a handle here, because handles are deduplicated via the priv->watchers_idx dictionary.
+ * The usage pattern is to use nm_netns_watcher_remove_all(), and not remove them one by one.
+ * As nm_netns_watcher_add() can return the same handle more than once, the user
+ * wouldn't know when it's safe to call nm_netns_watcher_remove_handle().
+ *
+ * This could be extended by adding a ref-count to the handles. But that is not
+ * used currently, so it's not possible to remove watcher by their handle. */
+}
+
+static void
+nm_netns_watcher_remove_handle(NMNetns *self, NMNetnsWatcherHandle *handle)
+{
+ NMNetnsPrivate *priv;
+ char sbuf[500];
+
+ g_return_if_fail(NM_IS_NETNS(self));
+ g_return_if_fail(handle);
+ nm_assert(handle->tag);
+
+ priv = NM_NETNS_GET_PRIVATE(self);
+
+ nm_assert(g_hash_table_lookup(priv->watcher_idx, handle) == handle);
+
+ _LOGT("netns-watcher: %s %s",
+ "unregister",
+ _watcher_handle_to_string(handle, sbuf, sizeof(sbuf)));
+
+ _watcher_unregister_handle(self, handle);
+
+ if (!g_hash_table_remove(priv->watcher_idx, handle))
+ nm_assert_not_reached();
+
+ if (c_list_is_empty_or_single(&handle->watcher_tag_lst)) {
+ if (!g_hash_table_remove(priv->watcher_by_tag_idx, &handle->tag))
+ nm_assert_not_reached();
+ }
+
+ c_list_unlink_stale(&handle->watcher_tag_lst);
+ nm_g_slice_free(handle);
+
+ if (G_UNLIKELY(g_hash_table_size(priv->watcher_idx) == 0))
+ g_object_unref(self);
+}
+
+void
+nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all)
+{
+ NMNetnsPrivate *priv;
+ WatcherByTag *watcher_by_tag;
+ NMNetnsWatcherHandle *handle;
+ NMNetnsWatcherHandle *handle_safe;
+
+ g_return_if_fail(NM_IS_NETNS(self));
+
+ /* remove-all only works with handles that have a tag associated.
+ * Since NMNetns can have multiple users that are unknown to each
+ * other, it makes no sense to have a remove-all function which
+ * would remove all of them. */
+ g_return_if_fail(tag);
+
+ priv = NM_NETNS_GET_PRIVATE(self);
+
+ watcher_by_tag = g_hash_table_lookup(priv->watcher_by_tag_idx, &tag);
+ if (!watcher_by_tag)
+ return;
+
+ c_list_for_each_entry_safe (handle,
+ handle_safe,
+ &watcher_by_tag->watcher_by_tag_lst_head,
+ watcher_tag_lst) {
+ gboolean is_last;
+
+ if (!all && !handle->watcher_dirty) {
+ /* Survivors are marked as dirty. This enables a pattern where you
+ * call nm_netns_watcher_add() on the elements you care about
+ * (which clears the dirty flag), and then remove all dirty ones
+ * with nm_netns_watcher_remove_all() (which marks the remaining
+ * handles as dirty for the next time). */
+ handle->watcher_dirty = TRUE;
+ continue;
+ }
+
+ is_last = c_list_is_empty_or_single(&watcher_by_tag->watcher_by_tag_lst_head);
+ nm_netns_watcher_remove_handle(self, handle);
+
+ if (is_last) {
+ /* Removing the last handle destroys the "watcher_by_tag" and may even
+ * destroy "self". We must not touch those pointers hereafter.
+ *
+ * If you ever *not* return here, make sure to handle that! */
+ return;
+ }
+ }
+}
+
+/*****************************************************************************/
+
+static void
set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
NMNetns *self = NM_NETNS(object);
@@ -850,6 +1421,7 @@ nm_netns_init(NMNetns *self)
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
priv->_self_signal_user_data = self;
+
c_list_init(&priv->l3cfg_signal_pending_lst_head);
G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(EcmpTrackObj, obj) == 0);
@@ -859,6 +1431,14 @@ nm_netns_init(NMNetns *self)
_ecmp_routes_by_ecmpid_equal,
_ecmp_routes_by_ecmpid_free,
NULL);
+
+ priv->watcher_idx = g_hash_table_new(_watcher_handle_hash, _watcher_handle_equal);
+ G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(WatcherByTag, tag) == 0);
+ priv->watcher_by_tag_idx = g_hash_table_new_full(nm_pdirect_hash,
+ nm_pdirect_equal,
+ (GDestroyNotify) _watcher_by_tag_destroy,
+ NULL);
+ priv->watcher_ip_data_idx = g_hash_table_new(_watcher_ip_data_hash, _watcher_ip_data_equal);
}
static void
@@ -937,10 +1517,17 @@ dispose(GObject *object)
nm_assert(nm_g_hash_table_size(priv->l3cfgs) == 0);
nm_assert(c_list_is_empty(&priv->l3cfg_signal_pending_lst_head));
nm_assert(!priv->shared_ips);
+ nm_assert(nm_g_hash_table_size(priv->watcher_idx) == 0);
+ nm_assert(nm_g_hash_table_size(priv->watcher_by_tag_idx) == 0);
+ nm_assert(nm_g_hash_table_size(priv->watcher_ip_data_idx) == 0);
nm_clear_pointer(&priv->ecmp_track_by_obj, g_hash_table_destroy);
nm_clear_pointer(&priv->ecmp_track_by_ecmpid, g_hash_table_destroy);
+ nm_clear_pointer(&priv->watcher_idx, g_hash_table_destroy);
+ nm_clear_pointer(&priv->watcher_by_tag_idx, g_hash_table_destroy);
+ nm_clear_pointer(&priv->watcher_ip_data_idx, g_hash_table_destroy);
+
nm_clear_g_source_inst(&priv->signal_pending_idle_source);
if (priv->platform)
diff --git a/src/core/nm-netns.h b/src/core/nm-netns.h
index 84a78f8363..7725ae79b4 100644
--- a/src/core/nm-netns.h
+++ b/src/core/nm-netns.h
@@ -59,4 +59,46 @@ void nm_netns_ip_route_ecmp_commit(NMNetns *self,
GPtrArray **routes,
gboolean is_reapply);
+/*****************************************************************************/
+
+typedef enum {
+ NM_NETNS_WATCHER_TYPE_IP_ADDR,
+} NMNetnsWatcherType;
+
+typedef struct {
+ union {
+ struct {
+ NMIPAddrTyped addr;
+ } ip_addr;
+ };
+} NMNetnsWatcherData;
+
+typedef struct {
+ union {
+ struct {
+ const NMPObject *obj;
+ NMPlatformSignalChangeType change_type;
+ } ip_addr;
+ };
+} NMNetnsWatcherEventData;
+
+typedef struct _NMNetnsWatcherHandle NMNetnsWatcherHandle;
+
+typedef void (*NMNetnsWatcherCallback)(NMNetns *self,
+ NMNetnsWatcherType watcher_type,
+ const NMNetnsWatcherData *watcher_data,
+ gconstpointer tag,
+ const NMNetnsWatcherEventData *event_data,
+ gpointer user_data);
+
+void nm_netns_watcher_add(NMNetns *self,
+ NMNetnsWatcherType watcher_type,
+ const NMNetnsWatcherData *watcher_data,
+ gconstpointer tag,
+ NMNetnsWatcherCallback callback,
+ gpointer user_data);
+
+void
+nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all /* or only dirty */);
+
#endif /* __NM_NETNS_H__ */
diff --git a/src/libnm-glib-aux/nm-inet-utils.h b/src/libnm-glib-aux/nm-inet-utils.h
index 40ba60c653..087a8af179 100644
--- a/src/libnm-glib-aux/nm-inet-utils.h
+++ b/src/libnm-glib-aux/nm-inet-utils.h
@@ -3,6 +3,8 @@
#ifndef __NM_INET_UTILS_H__
#define __NM_INET_UTILS_H__
+#include "libnm-std-aux/unaligned-fundamental.h"
+
typedef union _NMIPAddr {
guint8 addr_ptr[sizeof(struct in6_addr)];
in_addr_t addr4;
@@ -15,6 +17,11 @@ typedef union _NMIPAddr {
NMEtherAddr _ether_addr;
} NMIPAddr;
+typedef struct _NMIPAddrTyped {
+ NMIPAddr addr;
+ gint8 addr_family;
+} NMIPAddrTyped;
+
#define NM_IP_ADDR_INIT \
{ \
.addr_ptr = { 0 } \
@@ -85,14 +92,15 @@ nm_ip_addr_set(int addr_family, gpointer dst, gconstpointer src)
static inline gboolean
nm_ip_addr_is_null(int addr_family, gconstpointer addr)
{
- NMIPAddr a;
+ struct in6_addr a6;
- nm_ip_addr_set(addr_family, &a, addr);
+ nm_assert(addr);
if (NM_IS_IPv4(addr_family))
- return a.addr4 == 0;
+ return unaligned_read_ne32(addr) == 0;
- return IN6_IS_ADDR_UNSPECIFIED(&a.addr6);
+ memcpy(&a6, addr, sizeof(struct in6_addr));
+ return IN6_IS_ADDR_UNSPECIFIED(&a6);
}
static inline NMIPAddr
@@ -138,6 +146,30 @@ nm_ip_addr_from_packed_array(int addr_family, gconstpointer ipaddr_arr, gsize id
/*****************************************************************************/
+static inline int
+nm_ip_addr_typed_cmp(const NMIPAddrTyped *a, const NMIPAddrTyped *b)
+{
+ NM_CMP_SELF(a, b);
+ NM_CMP_FIELD(a, b, addr_family);
+ NM_CMP_DIRECT_MEMCMP(&a->addr, &b->addr, nm_utils_addr_family_to_size(a->addr_family));
+ return 0;
+}
+
+static inline gboolean
+nm_ip_addr_typed_equal(const NMIPAddrTyped *a, const NMIPAddrTyped *b)
+{
+ return nm_ip_addr_typed_cmp(a, b) == 0;
+}
+
+static inline void
+nm_ip_addr_typed_hash_update(NMHashState *h, const NMIPAddrTyped *addr)
+{
+ nm_hash_update_vals(h, addr->addr_family);
+ nm_hash_update_mem(h, &addr->addr, nm_utils_addr_family_to_size(addr->addr_family));
+}
+
+/*****************************************************************************/
+
static inline guint32
nm_ip4_addr_netmask_to_prefix(in_addr_t subnetmask)
{
diff --git a/src/libnm-glib-aux/nm-prioq.c b/src/libnm-glib-aux/nm-prioq.c
index 3448dcd969..897d65dd51 100644
--- a/src/libnm-glib-aux/nm-prioq.c
+++ b/src/libnm-glib-aux/nm-prioq.c
@@ -21,10 +21,36 @@
/*****************************************************************************/
-struct _NMPrioqItem {
+typedef struct _NMPrioqItem {
void *data;
unsigned *idx;
-};
+} PrioqItem;
+
+/*****************************************************************************/
+
+#define _nm_assert_q(q) \
+ G_STMT_START \
+ { \
+ const NMPrioq *const _q2 = (q); \
+ \
+ nm_assert(_q2); \
+ nm_assert(_q2->_priv.n_items == 0 || _q2->_priv.items); \
+ nm_assert(_q2->_priv.compare_func); \
+ } \
+ G_STMT_END
+
+#define _nm_assert_item(q, item) \
+ G_STMT_START \
+ { \
+ const NMPrioq *const _q = (q); \
+ const PrioqItem *const _item = (item); \
+ \
+ _nm_assert_q(_q); \
+ \
+ nm_assert(_item >= _q->_priv.items); \
+ nm_assert(_item < &_q->_priv.items[_q->_priv.n_items]); \
+ } \
+ G_STMT_END
/*****************************************************************************/
@@ -72,6 +98,15 @@ nm_prioq_destroy(NMPrioq *q)
if (!q || !q->_priv.compare_func)
return;
+ _nm_assert_q(q);
+
+ while (q->_priv.n_items > 0) {
+ PrioqItem *i = &q->_priv.items[--q->_priv.n_items];
+
+ if (i->idx)
+ *i->idx = NM_PRIOQ_IDX_NULL;
+ }
+
free(q->_priv.items);
q->_priv.compare_func = NULL;
}
@@ -81,8 +116,7 @@ nm_prioq_destroy(NMPrioq *q)
static int
compare(NMPrioq *q, unsigned a, unsigned b)
{
- nm_assert(q);
- nm_assert(q->_priv.compare_func);
+ _nm_assert_q(q);
nm_assert(a != b);
nm_assert(a < q->_priv.n_items);
nm_assert(b < q->_priv.n_items);
@@ -99,15 +133,13 @@ compare(NMPrioq *q, unsigned a, unsigned b)
static void
swap(NMPrioq *q, unsigned j, unsigned k)
{
- nm_assert(q);
+ _nm_assert_q(q);
nm_assert(j < q->_priv.n_items);
nm_assert(k < q->_priv.n_items);
-
nm_assert(!q->_priv.items[j].idx || *(q->_priv.items[j].idx) == j);
nm_assert(!q->_priv.items[k].idx || *(q->_priv.items[k].idx) == k);
- NM_SWAP(&q->_priv.items[j].data, &q->_priv.items[k].data);
- NM_SWAP(&q->_priv.items[j].idx, &q->_priv.items[k].idx);
+ NM_SWAP(&q->_priv.items[j], &q->_priv.items[k]);
if (q->_priv.items[j].idx)
*q->_priv.items[j].idx = j;
@@ -119,7 +151,7 @@ swap(NMPrioq *q, unsigned j, unsigned k)
static unsigned
shuffle_up(NMPrioq *q, unsigned idx)
{
- nm_assert(q);
+ _nm_assert_q(q);
nm_assert(idx < q->_priv.n_items);
while (idx > 0) {
@@ -140,7 +172,7 @@ shuffle_up(NMPrioq *q, unsigned idx)
static unsigned
shuffle_down(NMPrioq *q, unsigned idx)
{
- nm_assert(q);
+ _nm_assert_q(q);
for (;;) {
unsigned j;
@@ -184,19 +216,21 @@ nm_prioq_put(NMPrioq *q, void *data, unsigned *idx)
{
unsigned k;
- nm_assert(q);
+ _nm_assert_q(q);
+ nm_assert(q->_priv.n_items < G_MAXUINT);
- if (q->_priv.n_items >= q->_priv.n_allocated) {
+ if (G_UNLIKELY(q->_priv.n_items >= q->_priv.n_allocated)) {
q->_priv.n_allocated = NM_MAX((q->_priv.n_items + 1u) * 2u, 16u);
- q->_priv.items = g_renew(struct _NMPrioqItem, q->_priv.items, q->_priv.n_allocated);
+ q->_priv.items = g_renew(PrioqItem, q->_priv.items, q->_priv.n_allocated);
}
k = q->_priv.n_items++;
- q->_priv.items[k] = (struct _NMPrioqItem){
+ q->_priv.items[k] = (PrioqItem){
.data = data,
.idx = idx,
};
+
if (idx)
*idx = k;
@@ -204,71 +238,88 @@ nm_prioq_put(NMPrioq *q, void *data, unsigned *idx)
}
static void
-remove_item(NMPrioq *q, struct _NMPrioqItem *i)
+remove_item(NMPrioq *q, PrioqItem *i)
{
- struct _NMPrioqItem *l;
- unsigned k;
+ PrioqItem *l;
+ unsigned k;
- nm_assert(q);
- nm_assert(i);
- nm_assert(q->_priv.n_items > 0);
- nm_assert(i >= q->_priv.items);
- nm_assert(i < &q->_priv.items[q->_priv.n_items]);
+ _nm_assert_item(q, i);
+
+ if (i->idx)
+ *i->idx = NM_PRIOQ_IDX_NULL;
+
+ q->_priv.n_items--;
- l = &q->_priv.items[q->_priv.n_items - 1u];
+ l = &q->_priv.items[q->_priv.n_items];
if (i == l) {
- /* Last entry, let's just remove it */
- q->_priv.n_items--;
+ /* Last entry, nothing to do. */
return;
}
- /* Not last entry, let's replace the last entry with
- * this one, and reshuffle */
+ /* Not last entry, let's replace this entry with the last one, and
+ * reshuffle */
+
k = i - q->_priv.items;
*i = *l;
+
if (i->idx)
*i->idx = k;
- q->_priv.n_items--;
k = shuffle_down(q, k);
shuffle_up(q, k);
}
-_nm_pure static struct _NMPrioqItem *
+static PrioqItem *
find_item(NMPrioq *q, void *data, unsigned *idx)
{
- struct _NMPrioqItem *i;
-
- nm_assert(q);
+ PrioqItem *i;
- if (q->_priv.n_items <= 0)
- return NULL;
+ _nm_assert_q(q);
- if (idx) {
- if (*idx == NM_PRIOQ_IDX_NULL || *idx >= q->_priv.n_items)
- return NULL;
-
- i = &q->_priv.items[*idx];
- if (i->data == data)
- return i;
- } else {
+ if (G_UNLIKELY(!idx)) {
+ /* We allow using NMPrioq without "idx". In that case, it does a linear
+ * search for the data. */
for (i = q->_priv.items; i < &q->_priv.items[q->_priv.n_items]; i++) {
if (i->data == data)
return i;
}
+ return NULL;
}
- return NULL;
+ /* If the user however provides an "idx" pointer, then we assert that it is
+ * consistent. That is, if data is not in the queue, then we require that
+ * "*idx" is NM_PRIOQ_IDX_NULL, and otherwise we require that we really
+ * find "data" at index "*idx".
+ *
+ * This means, when the user calls nm_prioq_{remove,update,reshuffle}()
+ * with an "idx", then they must make sure that the index is consistent.
+ * Usually this means they are required to initialize the index to
+ * NM_PRIOQ_IDX_NULL while the data is not in the heap.
+ *
+ * This is done to assert more, and requires a stricter usage of the API
+ * (in the hope to find misuses of the index). */
+
+ if (*idx >= q->_priv.n_items) {
+ nm_assert(*idx == NM_PRIOQ_IDX_NULL);
+ return NULL;
+ }
+
+ i = &q->_priv.items[*idx];
+
+ if (i->data != data)
+ return nm_assert_unreachable_val(NULL);
+
+ return i;
}
gboolean
nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx)
{
- struct _NMPrioqItem *i;
+ PrioqItem *i;
- nm_assert(q);
+ _nm_assert_q(q);
i = find_item(q, data, idx);
if (!i)
@@ -278,28 +329,60 @@ nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx)
return TRUE;
}
+static void
+reshuffle_item(NMPrioq *q, PrioqItem *i)
+{
+ unsigned k;
+
+ _nm_assert_item(q, i);
+
+ k = i - q->_priv.items;
+ k = shuffle_down(q, k);
+ shuffle_up(q, k);
+}
+
gboolean
nm_prioq_reshuffle(NMPrioq *q, void *data, unsigned *idx)
{
- struct _NMPrioqItem *i;
- unsigned k;
+ PrioqItem *i;
- nm_assert(q);
+ _nm_assert_q(q);
i = find_item(q, data, idx);
if (!i)
return FALSE;
- k = i - q->_priv.items;
- k = shuffle_down(q, k);
- shuffle_up(q, k);
+ reshuffle_item(q, i);
return TRUE;
}
+void
+nm_prioq_update(NMPrioq *q, void *data, unsigned *idx, bool queued /* or else remove */)
+{
+ PrioqItem *i;
+
+ _nm_assert_q(q);
+
+ i = find_item(q, data, idx);
+
+ if (!i) {
+ if (queued)
+ nm_prioq_put(q, data, idx);
+ return;
+ }
+
+ if (!queued) {
+ remove_item(q, i);
+ return;
+ }
+
+ reshuffle_item(q, i);
+}
+
void *
nm_prioq_peek_by_index(NMPrioq *q, unsigned idx)
{
- nm_assert(q);
+ _nm_assert_q(q);
if (idx >= q->_priv.n_items)
return NULL;
@@ -312,7 +395,7 @@ nm_prioq_pop(NMPrioq *q)
{
void *data;
- nm_assert(q);
+ _nm_assert_q(q);
if (q->_priv.n_items <= 0)
return NULL;
diff --git a/src/libnm-glib-aux/nm-prioq.h b/src/libnm-glib-aux/nm-prioq.h
index 918c644724..51e5b059fb 100644
--- a/src/libnm-glib-aux/nm-prioq.h
+++ b/src/libnm-glib-aux/nm-prioq.h
@@ -43,6 +43,8 @@ void nm_prioq_put(NMPrioq *q, void *data, unsigned *idx);
gboolean nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx);
gboolean nm_prioq_reshuffle(NMPrioq *q, void *data, unsigned *idx);
+void nm_prioq_update(NMPrioq *q, void *data, unsigned *idx, bool queued /* or else remove */);
+
void *nm_prioq_peek_by_index(NMPrioq *q, unsigned idx) _nm_pure;
static inline void *
@@ -53,7 +55,7 @@ nm_prioq_peek(NMPrioq *q)
void *nm_prioq_pop(NMPrioq *q);
-#define NM_PRIOQ_FOREACH_ITEM(q, p) for (unsigned _i = 0; (p = nm_prioq_peek_by_index(q, _i)); _i++)
+#define nm_prioq_for_each(q, p) for (unsigned _i = 0; (p = nm_prioq_peek_by_index((q), _i)); _i++)
_nm_pure static inline unsigned
nm_prioq_size(NMPrioq *q)
diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c
index c71d3dc190..8b255101b3 100644
--- a/src/libnm-glib-aux/nm-shared-utils.c
+++ b/src/libnm-glib-aux/nm-shared-utils.c
@@ -20,6 +20,7 @@
#include "c-list/src/c-list.h"
#include "nm-errno.h"
+#include "nm-time-utils.h"
#include "nm-str-buf.h"
#include "nm-time-utils.h"
@@ -4840,6 +4841,50 @@ nm_g_child_watch_source_new(GPid pid,
return source;
}
+gboolean
+nm_g_timeout_reschedule(GSource **src,
+ gint64 *p_expiry_msec,
+ gint64 expiry_msec,
+ GSourceFunc func,
+ gpointer user_data)
+{
+ gint64 now_msec;
+ gint64 timeout_msec;
+
+ /* (Re-)Schedules a timeout at "expiry_msec" (in
+ * nm_utils_get_monotonic_timestamp_msec() scale).
+ *
+ * If a source is already scheduled in "*src" and "*p_expiry_msec" is
+ * identical to "expiry_msec", then we assume the timer is already ticking,
+ * and nothing is rescheduled.
+ *
+ * Otherwise, "*src" gets cancelled (if any), a new timer is scheduled
+ * (assigned to "*src") and the new expiry is written to "*p_expiry_msec".
+ */
+
+ nm_assert(src);
+ nm_assert(p_expiry_msec);
+
+ if (*src) {
+ if (*p_expiry_msec == expiry_msec) {
+ /* already scheduled with same expiry. */
+ return FALSE;
+ }
+ nm_clear_g_source_inst(src);
+ }
+
+ now_msec = nm_utils_get_monotonic_timestamp_msec();
+
+ if (expiry_msec <= now_msec)
+ timeout_msec = 0;
+ else
+ timeout_msec = NM_MIN(expiry_msec - now_msec, (gint64) G_MAXUINT);
+
+ *p_expiry_msec = expiry_msec;
+ *src = nm_g_timeout_add_source(timeout_msec, func, user_data);
+ return TRUE;
+}
+
/*****************************************************************************/
#define _CTX_LOG(fmt, ...) \
diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h
index 05d96cbd7e..d9f1d7b008 100644
--- a/src/libnm-glib-aux/nm-shared-utils.h
+++ b/src/libnm-glib-aux/nm-shared-utils.h
@@ -1543,6 +1543,12 @@ nm_g_timeout_add_source(guint timeout_msec, GSourceFunc func, gpointer user_data
NULL);
}
+gboolean nm_g_timeout_reschedule(GSource **src,
+ gint64 *p_expiry_msec,
+ gint64 expiry_msec,
+ GSourceFunc func,
+ gpointer user_data);
+
static inline GSource *
nm_g_timeout_add_seconds_source(guint timeout_sec, GSourceFunc func, gpointer user_data)
{
diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c
index 3eaca5474a..563c370243 100644
--- a/src/libnm-glib-aux/tests/test-shared-general.c
+++ b/src/libnm-glib-aux/tests/test-shared-general.c
@@ -2348,7 +2348,7 @@ test_garray(void)
static int
_prioq_cmp(gconstpointer a, gconstpointer b)
{
- NM_CMP_DIRECT(GPOINTER_TO_UINT(a), GPOINTER_TO_UINT(b));
+ NM_CMP_DIRECT(*((const guint32 *) a), *((const guint32 *) b));
return 0;
}
@@ -2362,11 +2362,12 @@ static void
test_nm_prioq(void)
{
nm_auto_prioq NMPrioq q = NM_PRIOQ_ZERO;
- gpointer data[200];
- gpointer data_pop[200];
+ guint32 data[200];
+ const guint32 *data_pop[200];
guint data_idx[G_N_ELEMENTS(data)];
guint i;
guint n;
+ guint m;
gpointer p;
if (nmtst_get_rand_one_case_in(10))
@@ -2379,37 +2380,67 @@ test_nm_prioq(void)
g_assert(nm_prioq_size(&q) == 0);
- if (nmtst_get_rand_one_case_in(10))
+ if (nmtst_get_rand_one_case_in(100))
return;
for (i = 0; i < G_N_ELEMENTS(data); i++) {
- data[i] = GUINT_TO_POINTER((nmtst_get_rand_uint32() % G_N_ELEMENTS(data)) + 1u);
+ data[i] = nmtst_get_rand_uint32() % G_N_ELEMENTS(data);
data_idx[i] = NM_PRIOQ_IDX_NULL;
}
- nm_prioq_put(&q, data[0], NULL);
+ nm_prioq_put(&q, &data[0], NULL);
g_assert(nm_prioq_size(&q) == 1);
p = nm_prioq_pop(&q);
- g_assert(p == data[0]);
+ g_assert(p == &data[0]);
g_assert(nm_prioq_size(&q) == 0);
g_assert(!nm_prioq_pop(&q));
n = nmtst_get_rand_uint32() % G_N_ELEMENTS(data);
for (i = 0; i < n; i++)
- nm_prioq_put(&q, data[i], &data_idx[i]);
+ nm_prioq_put(&q, &data[i], &data_idx[i]);
- g_assert_cmpint(nm_prioq_size(&q), ==, n);
+ m = n;
+ for (i = 0; i < n; i++) {
+ if (!nmtst_get_rand_bool())
+ continue;
- if (nmtst_get_rand_one_case_in(10))
+ data[i] = nmtst_get_rand_uint32() % G_N_ELEMENTS(data);
+ switch (nmtst_get_rand_uint32() % 4) {
+ case 0:
+ nm_prioq_reshuffle(&q, &data[i], &data_idx[i]);
+ break;
+ case 1:
+ nm_prioq_remove(&q, &data[i], nmtst_get_rand_bool() ? &data_idx[i] : NULL);
+ m--;
+ break;
+ case 2:
+ nm_prioq_update(&q, &data[i], &data_idx[i], TRUE);
+ break;
+ case 3:
+ nm_prioq_update(&q, &data[i], nmtst_get_rand_bool() ? &data_idx[i] : NULL, FALSE);
+ m--;
+ break;
+ }
+ }
+
+ g_assert_cmpint(nm_prioq_size(&q), ==, m);
+
+ if (nmtst_get_rand_one_case_in(50))
return;
- for (i = 0; i < n; i++) {
+ for (i = 0; i < m; i++) {
data_pop[i] = nm_prioq_pop(&q);
g_assert(data_pop[i]);
- if (i > 0)
+ g_assert_cmpint(*data_pop[i], >=, 0);
+ g_assert_cmpint(*data_pop[i], <, G_N_ELEMENTS(data));
+ g_assert(data_pop[i] >= &data[0]);
+ g_assert(data_pop[i] < &data[n]);
+ if (i > 0) {
g_assert(_prioq_cmp(data_pop[i - 1], data_pop[i]) <= 0);
+ g_assert_cmpint(*data_pop[i - 1], <=, *data_pop[i]);
+ }
}
g_assert(!nm_prioq_pop(&q));
diff --git a/src/libnm-lldp/nm-lldp-neighbor.c b/src/libnm-lldp/nm-lldp-neighbor.c
index f1e2d42eb0..a2a9695e85 100644
--- a/src/libnm-lldp/nm-lldp-neighbor.c
+++ b/src/libnm-lldp/nm-lldp-neighbor.c
@@ -735,6 +735,7 @@ nm_lldp_neighbor_new(size_t raw_size)
n->raw_size = raw_size;
n->ref_count = 1;
+ n->prioq_idx = NM_PRIOQ_IDX_NULL;
return n;
}
diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c
index c75fb3bf81..b6c2902d20 100644
--- a/src/libnm-platform/nm-linux-platform.c
+++ b/src/libnm-platform/nm-linux-platform.c
@@ -8149,6 +8149,7 @@ do_add_addrroute(NMPlatform *platform,
do_request_one_type_by_needle_object(platform, obj_id);
}
+ NM_SET_OUT(out_extack_msg, g_steal_pointer(&extack_msg));
return wait_for_nl_response_to_nmerr(seq_result);
}
diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c
index eddbaba376..deaf72acab 100644
--- a/src/libnm-platform/nm-platform.c
+++ b/src/libnm-platform/nm-platform.c
@@ -4593,53 +4593,6 @@ nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex)
/*****************************************************************************/
-static gboolean
-_route_is_temporary_not_available(NMPlatform *self,
- int err,
- const NMPObject *obj,
- const char **out_err_reason)
-{
- const NMPlatformIPXRoute *rx;
- const NMPlatformIP6Address *a;
-
- nm_assert(NM_IS_PLATFORM(self));
- nm_assert(NMP_OBJECT_IS_VALID(obj));
-
- if (err != -EINVAL)
- return FALSE;
-
- rx = NMP_OBJECT_CAST_IPX_ROUTE(obj);
-
- if (rx->rx.rt_source != NM_IP_CONFIG_SOURCE_USER) {
- /* we only allow this workaround for routes added manually by the user. */
- return FALSE;
- }
-
- if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ROUTE) {
- return FALSE;
- } else {
- const NMPlatformIP6Route *r = &rx->r6;
-
- /* trying to add an IPv6 route with pref-src fails, if the address is
- * still tentative (rh#1452684). We need to hack around that.
- *
- * Detect it, by guessing whether that's the case. */
-
- if (IN6_IS_ADDR_UNSPECIFIED(&r->pref_src))
- return FALSE;
-
- a = nm_platform_ip6_address_get(self, r->ifindex, &r->pref_src);
- if (!a)
- return FALSE;
- if (!NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TENTATIVE)
- || NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_DADFAILED))
- return FALSE;
-
- *out_err_reason = "tentative IPv6 src address not ready";
- return TRUE;
- }
-}
-
static guint
_ipv6_temporary_addr_prefixes_keep_hash(gconstpointer ptr)
{
@@ -4968,8 +4921,8 @@ nm_platform_ip_route_get_prune_list(NMPlatform *self,
* at the end of the operation. Note that if @routes contains
* the same route, then it will not be deleted. @routes overrules
* @routes_prune list.
- * @out_temporary_not_available: (allow-none) (out): routes that could
- * currently not be synced. The caller shall keep them and try later again.
+ * @out_routes_failed: (allow-none) (out): routes that could
+ * not be synced/added.
*
* Returns: %TRUE on success.
*/
@@ -4979,7 +4932,7 @@ nm_platform_ip_route_sync(NMPlatform *self,
int ifindex,
GPtrArray *routes,
GPtrArray *routes_prune,
- GPtrArray **out_temporary_not_available)
+ GPtrArray **out_routes_failed)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
const NMPlatformVTableRoute *vt;
@@ -4999,9 +4952,7 @@ nm_platform_ip_route_sync(NMPlatform *self,
for (i_type = 0; routes && i_type < 2; i_type++) {
for (i = 0; i < routes->len; i++) {
- gs_free char *extack_msg = NULL;
- gboolean gateway_route_added = FALSE;
- int r2;
+ gs_free char *extack_msg = NULL;
int r;
conf_o = routes->pdata[i];
@@ -5058,147 +5009,59 @@ nm_platform_ip_route_sync(NMPlatform *self,
}
}
-sync_route_add:
r = nm_platform_ip_route_add(self,
NMP_NLM_FLAG_APPEND
| NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
conf_o,
&extack_msg);
- if (r < 0) {
- const char *err_reason = NULL;
-
- if (r == -EEXIST) {
- /* Don't fail for EEXIST. It's not clear that the existing route
- * is identical to the one that we were about to add. However,
- * above we should have deleted conflicting (non-identical) routes. */
- if (_LOGD_ENABLED()) {
- plat_entry =
- nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o);
- if (!plat_entry) {
- _LOG3D("route-sync: adding route %s failed with EEXIST, however we "
- "cannot find such a route",
- nmp_object_to_string(conf_o,
- NMP_OBJECT_TO_STRING_PUBLIC,
- sbuf1,
- sizeof(sbuf1)));
- } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o),
- NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj),
- NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
- != 0) {
- _LOG3D("route-sync: adding route %s failed due to existing "
- "(different!) route %s",
- nmp_object_to_string(conf_o,
- NMP_OBJECT_TO_STRING_PUBLIC,
- sbuf1,
- sizeof(sbuf1)),
- nmp_object_to_string(plat_entry->obj,
- NMP_OBJECT_TO_STRING_PUBLIC,
- sbuf2,
- sizeof(sbuf2)));
- }
- }
- } else if (NMP_OBJECT_CAST_IP_ROUTE(conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) {
- _LOG3D("route-sync: ignore failure to add IPv%c route: %s: %s",
- vt->is_ip4 ? '4' : '6',
- nmp_object_to_string(conf_o,
- NMP_OBJECT_TO_STRING_PUBLIC,
- sbuf1,
- sizeof(sbuf1)),
- nm_strerror(r));
- } else if (out_temporary_not_available
- && _route_is_temporary_not_available(self, r, conf_o, &err_reason)) {
- _LOG3D("route-sync: ignore temporary failure to add route (%s, %s): %s",
- nm_strerror(r),
- err_reason,
- nmp_object_to_string(conf_o,
- NMP_OBJECT_TO_STRING_PUBLIC,
- sbuf1,
- sizeof(sbuf1)));
- if (!*out_temporary_not_available)
- *out_temporary_not_available =
- g_ptr_array_new_full(0, (GDestroyNotify) nmp_object_unref);
- g_ptr_array_add(*out_temporary_not_available,
- (gpointer) nmp_object_ref(conf_o));
- } else if (!gateway_route_added
- && ((r == -ENETUNREACH && vt->is_ip4
- && !!NMP_OBJECT_CAST_IP4_ROUTE(conf_o)->gateway)
- || (r == -EHOSTUNREACH && !vt->is_ip4
- && !IN6_IS_ADDR_UNSPECIFIED(
- &NMP_OBJECT_CAST_IP6_ROUTE(conf_o)->gateway)))) {
- NMPObject oo;
-
- if (vt->is_ip4) {
- const NMPlatformIP4Route *rt = NMP_OBJECT_CAST_IP4_ROUTE(conf_o);
-
- nmp_object_stackinit(
- &oo,
- NMP_OBJECT_TYPE_IP4_ROUTE,
- &((NMPlatformIP4Route){
- .ifindex = rt->ifindex,
- .network = rt->gateway,
- .plen = 32,
- .metric = nm_platform_ip4_route_get_effective_metric(rt),
- .rt_source = rt->rt_source,
- .table_coerced = nm_platform_ip_route_get_effective_table(
- NM_PLATFORM_IP_ROUTE_CAST(rt)),
- }));
- } else {
- const NMPlatformIP6Route *rt = NMP_OBJECT_CAST_IP6_ROUTE(conf_o);
-
- nmp_object_stackinit(
- &oo,
- NMP_OBJECT_TYPE_IP6_ROUTE,
- &((NMPlatformIP6Route){
- .ifindex = rt->ifindex,
- .network = rt->gateway,
- .plen = 128,
- .metric = nm_platform_ip6_route_get_effective_metric(rt),
- .rt_source = rt->rt_source,
- .table_coerced = nm_platform_ip_route_get_effective_table(
- NM_PLATFORM_IP_ROUTE_CAST(rt)),
- }));
- }
-
- _LOG3D("route-sync: failure to add IPv%c route: %s: %s; try adding direct "
- "route to gateway %s",
- vt->is_ip4 ? '4' : '6',
- nmp_object_to_string(conf_o,
- NMP_OBJECT_TO_STRING_PUBLIC,
- sbuf1,
- sizeof(sbuf1)),
- nm_strerror(r),
- nmp_object_to_string(&oo,
- NMP_OBJECT_TO_STRING_PUBLIC,
- sbuf2,
- sizeof(sbuf2)));
-
- r2 = nm_platform_ip_route_add(self,
- NMP_NLM_FLAG_APPEND
- | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
- &oo,
- NULL);
-
- if (r2 < 0) {
- _LOG3D("route-sync: failure to add gateway IPv%c route: %s: %s",
- vt->is_ip4 ? '4' : '6',
+ if (r == 0) {
+ /* success */
+ } else if (r == -EEXIST) {
+ /* Don't fail for EEXIST. It's not clear that the existing route
+ * is identical to the one that we were about to add. However,
+ * above we should have deleted conflicting (non-identical) routes. */
+ if (_LOGD_ENABLED()) {
+ plat_entry =
+ nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o);
+ if (!plat_entry) {
+ _LOG3D("route-sync: adding route %s failed with EEXIST, however we "
+ "cannot find such a route",
+ nmp_object_to_string(conf_o,
+ NMP_OBJECT_TO_STRING_PUBLIC,
+ sbuf1,
+ sizeof(sbuf1)));
+ } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o),
+ NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj),
+ NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
+ != 0) {
+ _LOG3D("route-sync: adding route %s failed due to existing "
+ "(different!) route %s",
nmp_object_to_string(conf_o,
NMP_OBJECT_TO_STRING_PUBLIC,
sbuf1,
sizeof(sbuf1)),
- nm_strerror(r2));
+ nmp_object_to_string(plat_entry->obj,
+ NMP_OBJECT_TO_STRING_PUBLIC,
+ sbuf2,
+ sizeof(sbuf2)));
}
+ }
+ } else {
+ _LOG3D(
+ "route-sync: failure to add IPv%c route: %s: %s%s%s%s",
+ vt->is_ip4 ? '4' : '6',
+ nmp_object_to_string(conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof(sbuf1)),
+ nm_strerror(r),
+ NM_PRINT_FMT_QUOTED(extack_msg, " (", extack_msg, ")", ""));
- gateway_route_added = TRUE;
- goto sync_route_add;
- } else {
- _LOG3W("route-sync: failure to add IPv%c route: %s: %s",
- vt->is_ip4 ? '4' : '6',
- nmp_object_to_string(conf_o,
- NMP_OBJECT_TO_STRING_PUBLIC,
- sbuf1,
- sizeof(sbuf1)),
- nm_strerror(r));
- success = FALSE;
+ success = FALSE;
+
+ if (out_routes_failed) {
+ if (!*out_routes_failed) {
+ *out_routes_failed =
+ g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+ }
+ g_ptr_array_add(*out_routes_failed, (gpointer) nmp_object_ref(conf_o));
}
}
}
@@ -5346,7 +5209,7 @@ _ip_route_add(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack, char **
char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
int ifindex;
- _CHECK_SELF(self, klass, FALSE);
+ _CHECK_SELF(self, klass, -NME_BUG);
/* The caller already ensures that this is a stack allocated copy, that
* - stays alive for the duration of the call.
@@ -7037,7 +6900,6 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route,
"%s" /* rto_min */
"%s" /* quickack */
"%s" /* mtu */
- "%s" /* r_force_commit */
"",
nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced),
str_type),
@@ -7104,8 +6966,7 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route,
" mtu %s%" G_GUINT32_FORMAT,
route->lock_mtu ? "lock " : "",
route->mtu)
- : "",
- route->r_force_commit ? " force-commit" : "");
+ : "");
if ((n_nexthops == 1 && route->ifindex > 0) || n_nexthops == 0) {
/* A plain single hop route. Nothing extra to remark. */
@@ -7225,7 +7086,6 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
"%s" /* quickack */
"%s" /* mtu */
"%s" /* pref */
- "%s" /* r_force_commit */
"",
nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced),
str_type),
@@ -7292,8 +7152,7 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
str_pref,
" pref %s",
nm_icmpv6_router_pref_to_string(route->rt_pref, str_pref2, sizeof(str_pref2)))
- : "",
- route->r_force_commit ? " force-commit" : "");
+ : "");
return buf;
}
@@ -8680,8 +8539,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
obj->lock_initcwnd,
obj->lock_initrwnd,
obj->lock_mtu,
- obj->lock_mss,
- obj->r_force_commit));
+ obj->lock_mss));
break;
}
}
@@ -8824,8 +8682,6 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
NM_CMP_FIELD(a, b, initrwnd);
NM_CMP_FIELD(a, b, mtu);
NM_CMP_FIELD(a, b, rto_min);
- if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL)
- NM_CMP_FIELD_UNSAFE(a, b, r_force_commit);
break;
}
return 0;
@@ -8925,8 +8781,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
obj->lock_initcwnd,
obj->lock_initrwnd,
obj->lock_mtu,
- obj->lock_mss,
- obj->r_force_commit),
+ obj->lock_mss),
obj->window,
obj->cwnd,
obj->initcwnd,
@@ -9016,8 +8871,6 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
NM_CMP_DIRECT(_route_pref_normalize(a->rt_pref), _route_pref_normalize(b->rt_pref));
else
NM_CMP_FIELD(a, b, rt_pref);
- if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL)
- NM_CMP_FIELD_UNSAFE(a, b, r_force_commit);
break;
}
return 0;
@@ -9339,7 +9192,6 @@ nm_platform_ip4_address_generate_device_route(const NMPlatformIP4Address *addr,
int ifindex,
guint32 route_table,
guint32 route_metric,
- gboolean force_commit,
NMPlatformIP4Route *dst)
{
in_addr_t network_4;
@@ -9369,15 +9221,14 @@ nm_platform_ip4_address_generate_device_route(const NMPlatformIP4Address *addr,
}
*dst = (NMPlatformIP4Route){
- .ifindex = ifindex,
- .rt_source = NM_IP_CONFIG_SOURCE_KERNEL,
- .network = network_4,
- .plen = addr->plen,
- .pref_src = addr->address,
- .table_coerced = nm_platform_route_table_coerce(route_table),
- .metric = route_metric,
- .scope_inv = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK),
- .r_force_commit = force_commit,
+ .ifindex = ifindex,
+ .rt_source = NM_IP_CONFIG_SOURCE_KERNEL,
+ .network = network_4,
+ .plen = addr->plen,
+ .pref_src = addr->address,
+ .table_coerced = nm_platform_route_table_coerce(route_table),
+ .metric = route_metric,
+ .scope_inv = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK),
};
nm_platform_ip_route_normalize(AF_INET, (NMPlatformIPRoute *) dst);
diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h
index 08a7716e39..86e01d63de 100644
--- a/src/libnm-platform/nm-platform.h
+++ b/src/libnm-platform/nm-platform.h
@@ -243,13 +243,6 @@ typedef enum {
guint _nm_platform_signal_id_get(NMPlatformSignalIdType signal_type);
-typedef enum {
- NM_PLATFORM_SIGNAL_NONE,
- NM_PLATFORM_SIGNAL_ADDED,
- NM_PLATFORM_SIGNAL_CHANGED,
- NM_PLATFORM_SIGNAL_REMOVED,
-} NMPlatformSignalChangeType;
-
/* Default value for adding an IPv4 route. This is also what iproute2 does.
* Note that contrary to IPv6, you can add routes with metric 0 and it is even
* the default.
@@ -2306,6 +2299,19 @@ nm_platform_ip_route_get_gateway(int addr_family, const NMPlatformIPRoute *route
return &((NMPlatformIP6Route *) route)->gateway;
}
+static inline gconstpointer
+nm_platform_ip_route_get_pref_src(int addr_family, const NMPlatformIPRoute *route)
+{
+ nm_assert_addr_family(addr_family);
+
+ if (!route)
+ return NULL;
+
+ if (NM_IS_IPv4(addr_family))
+ return &((NMPlatformIP4Route *) route)->pref_src;
+ return &((NMPlatformIP6Route *) route)->pref_src;
+}
+
int nm_platform_ip_route_add(NMPlatform *self,
NMPNlmFlags flags,
const NMPObject *route,
@@ -2326,7 +2332,7 @@ gboolean nm_platform_ip_route_sync(NMPlatform *self,
int ifindex,
GPtrArray *routes,
GPtrArray *routes_prune,
- GPtrArray **out_temporary_not_available);
+ GPtrArray **out_routes_failed);
gboolean nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex);
diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h
index 80d254b261..4802b03553 100644
--- a/src/libnm-platform/nmp-base.h
+++ b/src/libnm-platform/nmp-base.h
@@ -28,6 +28,15 @@
/*****************************************************************************/
typedef enum {
+ NM_PLATFORM_SIGNAL_NONE,
+ NM_PLATFORM_SIGNAL_ADDED,
+ NM_PLATFORM_SIGNAL_CHANGED,
+ NM_PLATFORM_SIGNAL_REMOVED,
+} NMPlatformSignalChangeType;
+
+/*****************************************************************************/
+
+typedef enum {
NM_PLATFORM_LINK_DUPLEX_UNKNOWN,
NM_PLATFORM_LINK_DUPLEX_HALF,
NM_PLATFORM_LINK_DUPLEX_FULL,
diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h
index 0d5f84b310..408f0318cb 100644
--- a/src/libnm-platform/nmp-object.h
+++ b/src/libnm-platform/nmp-object.h
@@ -1183,21 +1183,6 @@ nm_platform_lookup_object_by_addr_family(NMPlatform *platform,
/*****************************************************************************/
-static inline gboolean
-nmp_object_get_force_commit(const NMPObject *obj)
-{
- switch (NMP_OBJECT_GET_TYPE(obj)) {
- case NMP_OBJECT_TYPE_IP4_ADDRESS:
- case NMP_OBJECT_TYPE_IP6_ADDRESS:
- return NMP_OBJECT_CAST_IP_ADDRESS(obj)->a_force_commit;
- case NMP_OBJECT_TYPE_IP4_ROUTE:
- case NMP_OBJECT_TYPE_IP6_ROUTE:
- return NMP_OBJECT_CAST_IP_ROUTE(obj)->r_force_commit;
- default:
- return nm_assert_unreachable_val(FALSE);
- }
-}
-
static inline const char *
nmp_object_link_get_ifname(const NMPObject *obj)
{
diff --git a/src/libnm-platform/nmp-plobj.h b/src/libnm-platform/nmp-plobj.h
index e149150d29..833167f678 100644
--- a/src/libnm-platform/nmp-plobj.h
+++ b/src/libnm-platform/nmp-plobj.h
@@ -255,7 +255,6 @@ NMPlatformIP4Route *nm_platform_ip4_address_generate_device_route(const NMPlatfo
int ifindex,
guint32 route_table,
guint32 route_metric,
- gboolean force_commit,
NMPlatformIP4Route *dst);
typedef enum {