NetworkManager/src/core/nm-netns.c
Fernando Fernandez Mancera cbf70b4dca Revert "nm-netns: add onlink routes for ECMP routes"
ECMP IPv4 route nexthops requires an onlink route but we should trust
l3cfg when generating and managing such routes.

This reverts commit 737cb5d424.
2023-01-26 11:10:08 +01:00

957 lines
34 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2017 Red Hat, Inc.
*/
#include "src/core/nm-default-daemon.h"
#include "nm-netns.h"
#include "libnm-glib-aux/nm-dedup-multi.h"
#include "libnm-glib-aux/nm-c-list.h"
#include "NetworkManagerUtils.h"
#include "libnm-core-intern/nm-core-internal.h"
#include "nm-l3cfg.h"
#include "libnm-platform/nm-platform.h"
#include "libnm-platform/nmp-netns.h"
#include "libnm-platform/nmp-global-tracker.h"
#include "libnm-std-aux/c-list-util.h"
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PLATFORM, );
typedef struct {
NMNetns *_self_signal_user_data;
NMPlatform *platform;
NMPNetns *platform_netns;
NMPGlobalTracker *global_tracker;
GHashTable *l3cfgs;
GHashTable *shared_ips;
GHashTable *ecmp_routes;
GHashTable *ecmp_track_by_obj;
GHashTable *ecmp_track_by_ecmpid;
CList l3cfg_signal_pending_lst_head;
GSource *signal_pending_idle_source;
} NMNetnsPrivate;
struct _NMNetns {
GObject parent;
NMNetnsPrivate _priv;
};
struct _NMNetnsClass {
GObjectClass parent;
};
G_DEFINE_TYPE(NMNetns, nm_netns, G_TYPE_OBJECT);
#define NM_NETNS_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMNetns, NM_IS_NETNS)
/*****************************************************************************/
#define _NMLOG_DOMAIN LOGD_CORE
#define _NMLOG_PREFIX_NAME "netns"
#define _NMLOG(level, ...) \
G_STMT_START \
{ \
nm_log((level), \
(_NMLOG_DOMAIN), \
NULL, \
NULL, \
"netns[" NM_HASH_OBFUSCATE_PTR_FMT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
NM_HASH_OBFUSCATE_PTR(self) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
G_STMT_END
/*****************************************************************************/
NM_DEFINE_SINGLETON_GETTER(NMNetns, nm_netns_get, NM_TYPE_NETNS);
/*****************************************************************************/
void _netns_ip_route_ecmp_update_mh(NMNetns *self,
const GPtrArray *mhrts_del,
const GPtrArray *mhrts_add);
/*****************************************************************************/
#define nm_assert_l3cfg(self, l3cfg) \
G_STMT_START \
{ \
NMNetns *_self = (self); \
NML3Cfg *_l3cfg = (l3cfg); \
\
nm_assert(NM_IS_NETNS(self)); \
nm_assert(NM_IS_L3CFG(_l3cfg)); \
if (NM_MORE_ASSERTS > 5) \
nm_assert(_l3cfg == nm_netns_l3cfg_get(_self, nm_l3cfg_get_ifindex(_l3cfg))); \
} \
G_STMT_END
/*****************************************************************************/
typedef struct {
const NMPObject *representative_obj;
const NMPObject *merged_obj;
CList ecmpid_lst_head;
bool needs_update : 1;
bool already_visited : 1;
} EcmpTrackEcmpid;
typedef struct {
const NMPObject *obj;
NML3Cfg *l3cfg;
EcmpTrackEcmpid *parent_track_ecmpid;
CList ifindex_lst;
CList ecmpid_lst;
/* Calling nm_netns_ip_route_ecmp_register() will ensure that the tracked
* entry is non-dirty. This can be used to remove stale entries. */
bool dirty : 1;
} EcmpTrackObj;
static int
_ecmp_track_sort_lst_cmp(const CList *a, const CList *b, const void *user_data)
{
EcmpTrackObj *track_obj_a = c_list_entry(a, EcmpTrackObj, ecmpid_lst);
EcmpTrackObj *track_obj_b = c_list_entry(b, EcmpTrackObj, ecmpid_lst);
const NMPlatformIP4Route *route_a = NMP_OBJECT_CAST_IP4_ROUTE(track_obj_a->obj);
const NMPlatformIP4Route *route_b = NMP_OBJECT_CAST_IP4_ROUTE(track_obj_b->obj);
nm_assert(route_a->ifindex > 0);
nm_assert(route_a->n_nexthops <= 1);
nm_assert(route_b->ifindex > 0);
nm_assert(route_b->n_nexthops <= 1);
NM_CMP_FIELD(route_a, route_b, ifindex);
NM_CMP_FIELD(route_b, route_a, weight);
NM_CMP_DIRECT(htonl(route_a->gateway), htonl(route_b->gateway));
return nm_assert_unreachable_val(
nm_platform_ip4_route_cmp(route_a, route_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID));
}
static gboolean
_ecmp_track_init_merged_obj(EcmpTrackEcmpid *track_ecmpid, const NMPObject **out_obj_del)
{
EcmpTrackObj *track_obj;
nm_auto_nmpobj const NMPObject *obj_new = NULL;
gsize n_nexthops;
gsize i;
nm_assert(track_ecmpid);
nm_assert(!c_list_is_empty(&track_ecmpid->ecmpid_lst_head));
nm_assert(track_ecmpid->representative_obj
== c_list_first_entry(&track_ecmpid->ecmpid_lst_head, EcmpTrackObj, ecmpid_lst)->obj);
nm_assert(out_obj_del && !*out_obj_del);
if (!track_ecmpid->needs_update) {
/* Already up to date. Nothing to do. */
return FALSE;
}
track_ecmpid->needs_update = FALSE;
n_nexthops = c_list_length(&track_ecmpid->ecmpid_lst_head);
if (n_nexthops == 1) {
/* There is only a single entry. There is nothing to merge, just set
* the first entry. */
obj_new = nmp_object_ref(track_ecmpid->representative_obj);
goto out;
}
/* We want that the nexthop list is deterministic. We thus sort the list and update
* the representative_obj. */
c_list_sort(&track_ecmpid->ecmpid_lst_head, _ecmp_track_sort_lst_cmp, NULL);
nmp_object_ref_set(
&track_ecmpid->representative_obj,
c_list_first_entry(&track_ecmpid->ecmpid_lst_head, EcmpTrackObj, ecmpid_lst)->obj);
obj_new = nmp_object_clone(track_ecmpid->representative_obj, FALSE);
nm_assert(obj_new->ip4_route.n_nexthops <= 1);
nm_assert(!obj_new->_ip4_route.extra_nexthops);
/* Note that there actually cannot be duplicate (ifindex,gateway,weight) tuples, because
* NML3Cfg uses NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID to track the routes, and track_ecmpid
* groups them further by NM_PLATFORM_IP_ROUTE_CMP_TYPE_ECMP_ID. The comparison for
* ECMP_ID is a strict superset of ID, hence there are no dupliated.
*
* Also, kernel wouldn't care if there were duplicate nexthops anyway.
*
* This means, it's gonna be simple. We sorted the single-hop routes by next-hop,
* now just create a plain list of the nexthops (no check for duplciates, etc). */
((NMPObject *) obj_new)->ip4_route.n_nexthops = n_nexthops;
((NMPObject *) obj_new)->_ip4_route.extra_nexthops =
g_new(NMPlatformIP4RtNextHop, n_nexthops - 1u);
i = 0;
c_list_for_each_entry (track_obj, &track_ecmpid->ecmpid_lst_head, ecmpid_lst) {
if (i > 0) {
const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE(track_obj->obj);
NMPlatformIP4RtNextHop *nh = (gpointer) &obj_new->_ip4_route.extra_nexthops[i - 1];
*nh = (NMPlatformIP4RtNextHop){
.ifindex = r->ifindex,
.gateway = r->gateway,
.weight = r->weight,
};
}
i++;
}
out:
nm_assert(obj_new);
if (nmp_object_equal(track_ecmpid->merged_obj, obj_new))
/* the objects are equal but the update was needed, for example if the
* routes were removed from kernel but not from our tracking
* dictionaries and therefore we tried to register them again. */
return TRUE;
if (track_ecmpid->merged_obj)
*out_obj_del = g_steal_pointer(&track_ecmpid->merged_obj);
track_ecmpid->merged_obj = g_steal_pointer(&obj_new);
return TRUE;
}
/*****************************************************************************/
NMPNetns *
nm_netns_get_platform_netns(NMNetns *self)
{
return NM_NETNS_GET_PRIVATE(self)->platform_netns;
}
NMPlatform *
nm_netns_get_platform(NMNetns *self)
{
return NM_NETNS_GET_PRIVATE(self)->platform;
}
NMPGlobalTracker *
nm_netns_get_global_tracker(NMNetns *self)
{
return NM_NETNS_GET_PRIVATE(self)->global_tracker;
}
NMDedupMultiIndex *
nm_netns_get_multi_idx(NMNetns *self)
{
return nm_platform_get_multi_idx(NM_NETNS_GET_PRIVATE(self)->platform);
}
/*****************************************************************************/
static guint
_ecmp_routes_by_ecmpid_hash(gconstpointer ptr)
{
const NMPObject *const *p_obj = ptr;
return nm_platform_ip4_route_hash(NMP_OBJECT_CAST_IP4_ROUTE(*p_obj),
NM_PLATFORM_IP_ROUTE_CMP_TYPE_ECMP_ID);
}
static int
_ecmp_routes_by_ecmpid_equal(gconstpointer ptr_a, gconstpointer ptr_b)
{
const NMPObject *const *p_obj_a = ptr_a;
const NMPObject *const *p_obj_b = ptr_b;
return nm_platform_ip4_route_cmp(NMP_OBJECT_CAST_IP4_ROUTE(*p_obj_a),
NMP_OBJECT_CAST_IP4_ROUTE(*p_obj_b),
NM_PLATFORM_IP_ROUTE_CMP_TYPE_ECMP_ID)
== 0;
}
static void
_ecmp_routes_by_ecmpid_free(gpointer ptr)
{
EcmpTrackEcmpid *track_ecmpid = ptr;
c_list_unlink_stale(&track_ecmpid->ecmpid_lst_head);
nmp_object_unref(track_ecmpid->representative_obj);
nmp_object_unref(track_ecmpid->merged_obj);
nm_g_slice_free(track_ecmpid);
}
static void
_ecmp_routes_by_obj_free(gpointer ptr)
{
EcmpTrackObj *track_obj = ptr;
c_list_unlink_stale(&track_obj->ifindex_lst);
c_list_unlink_stale(&track_obj->ecmpid_lst);
nmp_object_unref(track_obj->obj);
nm_g_slice_free(track_obj);
}
/*****************************************************************************/
static NML3Cfg *
_l3cfg_hashed_to_l3cfg(gpointer ptr)
{
gpointer l3cfg;
l3cfg = &(((char *) ptr)[-G_STRUCT_OFFSET(NML3Cfg, priv.ifindex)]);
nm_assert(NM_IS_L3CFG(l3cfg));
return l3cfg;
}
static void
_l3cfg_hashed_free(gpointer ptr)
{
NML3Cfg *l3cfg = _l3cfg_hashed_to_l3cfg(ptr);
c_list_unlink(&l3cfg->internal_netns.signal_pending_lst);
}
static void
_l3cfg_weak_notify(gpointer data, GObject *where_the_object_was)
{
NMNetns *self = NM_NETNS(data);
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(data);
NML3Cfg *l3cfg = NM_L3CFG(where_the_object_was);
int ifindex = nm_l3cfg_get_ifindex(l3cfg);
if (!g_hash_table_remove(priv->l3cfgs, &ifindex))
nm_assert_not_reached();
if (NM_UNLIKELY(g_hash_table_size(priv->l3cfgs) == 0))
g_object_unref(self);
}
NML3Cfg *
nm_netns_l3cfg_get(NMNetns *self, int ifindex)
{
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
gpointer ptr;
nm_assert(ifindex > 0);
ptr = g_hash_table_lookup(priv->l3cfgs, &ifindex);
return ptr ? _l3cfg_hashed_to_l3cfg(ptr) : NULL;
}
NML3Cfg *
nm_netns_l3cfg_acquire(NMNetns *self, int ifindex)
{
NMNetnsPrivate *priv;
NML3Cfg *l3cfg;
g_return_val_if_fail(NM_IS_NETNS(self), NULL);
g_return_val_if_fail(ifindex > 0, NULL);
priv = NM_NETNS_GET_PRIVATE(self);
l3cfg = nm_netns_l3cfg_get(self, ifindex);
if (l3cfg) {
nm_log_trace(LOGD_CORE,
"l3cfg[" NM_HASH_OBFUSCATE_PTR_FMT ",ifindex=%d] %s",
NM_HASH_OBFUSCATE_PTR(l3cfg),
ifindex,
"referenced");
return g_object_ref(l3cfg);
}
l3cfg = nm_l3cfg_new(self, ifindex);
if (!g_hash_table_add(priv->l3cfgs, &l3cfg->priv.ifindex))
nm_assert_not_reached();
if (NM_UNLIKELY(g_hash_table_size(priv->l3cfgs) == 1))
g_object_ref(self);
g_object_weak_ref(G_OBJECT(l3cfg), _l3cfg_weak_notify, self);
/* Transfer ownership! We keep only a weak ref. */
return l3cfg;
}
/*****************************************************************************/
static gboolean
_platform_signal_on_idle_cb(gpointer user_data)
{
gs_unref_object NMNetns *self = g_object_ref(NM_NETNS(user_data));
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
NML3Cfg *l3cfg;
CList work_list;
nm_clear_g_source_inst(&priv->signal_pending_idle_source);
/* we emit all queued signals together. However, we don't want to hook the
* main loop for longer than the currently queued elements.
*
* If we catch more change events, they will be queued and processed by a future
* idle handler.
*
* Hence, move the list to a temporary list. Isn't CList great? */
c_list_init(&work_list);
c_list_splice(&work_list, &priv->l3cfg_signal_pending_lst_head);
while ((l3cfg = c_list_first_entry(&work_list, NML3Cfg, internal_netns.signal_pending_lst))) {
nm_assert(NM_IS_L3CFG(l3cfg));
c_list_unlink(&l3cfg->internal_netns.signal_pending_lst);
_nm_l3cfg_notify_platform_change_on_idle(
l3cfg,
nm_steal_int(&l3cfg->internal_netns.signal_pending_obj_type_flags));
}
return G_SOURCE_CONTINUE;
}
static void
_platform_signal_cb(NMPlatform *platform,
int obj_type_i,
int ifindex,
gconstpointer platform_object,
int change_type_i,
NMNetns **p_self)
{
NMNetns *self = NM_NETNS(*p_self);
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
const NMPObjectType obj_type = obj_type_i;
const NMPlatformSignalChangeType change_type = change_type_i;
NML3Cfg *l3cfg;
if (ifindex <= 0) {
/* platform signal callback could be triggered by nodev routes, skip them */
return;
}
l3cfg = nm_netns_l3cfg_get(self, ifindex);
if (!l3cfg)
return;
l3cfg->internal_netns.signal_pending_obj_type_flags |= nmp_object_type_to_flags(obj_type);
if (c_list_is_empty(&l3cfg->internal_netns.signal_pending_lst)) {
c_list_link_tail(&priv->l3cfg_signal_pending_lst_head,
&l3cfg->internal_netns.signal_pending_lst);
if (!priv->signal_pending_idle_source)
priv->signal_pending_idle_source =
nm_g_idle_add_source(_platform_signal_on_idle_cb, self);
}
_nm_l3cfg_notify_platform_change(l3cfg, change_type, NMP_OBJECT_UP_CAST(platform_object));
}
/*****************************************************************************/
NMNetnsSharedIPHandle *
nm_netns_shared_ip_reserve(NMNetns *self)
{
NMNetnsPrivate *priv;
NMNetnsSharedIPHandle *handle;
const in_addr_t addr_start = ntohl(0x0a2a0001u); /* 10.42.0.1 */
in_addr_t addr;
char sbuf_addr[NM_INET_ADDRSTRLEN];
/* Find an unused address in the 10.42.x.x range */
g_return_val_if_fail(NM_IS_NETNS(self), NULL);
priv = NM_NETNS_GET_PRIVATE(self);
if (!priv->shared_ips) {
addr = addr_start;
priv->shared_ips = g_hash_table_new(nm_puint32_hash, nm_puint32_equal);
g_object_ref(self);
} else {
guint32 count;
nm_assert(g_hash_table_size(priv->shared_ips) > 0);
count = 0u;
for (;;) {
addr = addr_start + htonl(count << 8u);
handle = g_hash_table_lookup(priv->shared_ips, &addr);
if (!handle)
break;
count++;
if (count > 0xFFu) {
if (handle->_ref_count == 1) {
_LOGE("shared-ip4: ran out of shared IP addresses. Reuse %s/24",
nm_inet4_ntop(handle->addr, sbuf_addr));
} else {
_LOGD("shared-ip4: reserved IP address range %s/24 (duplicate)",
nm_inet4_ntop(handle->addr, sbuf_addr));
}
handle->_ref_count++;
return handle;
}
}
}
handle = g_slice_new(NMNetnsSharedIPHandle);
*handle = (NMNetnsSharedIPHandle){
.addr = addr,
._ref_count = 1,
._self = self,
};
g_hash_table_add(priv->shared_ips, handle);
_LOGD("shared-ip4: reserved IP address range %s/24", nm_inet4_ntop(handle->addr, sbuf_addr));
return handle;
}
void
nm_netns_shared_ip_release(NMNetnsSharedIPHandle *handle)
{
NMNetns *self;
NMNetnsPrivate *priv;
char sbuf_addr[NM_INET_ADDRSTRLEN];
g_return_if_fail(handle);
self = handle->_self;
g_return_if_fail(NM_IS_NETNS(self));
priv = NM_NETNS_GET_PRIVATE(self);
nm_assert(handle->_ref_count > 0);
nm_assert(handle == nm_g_hash_table_lookup(priv->shared_ips, handle));
if (handle->_ref_count > 1) {
nm_assert(handle->addr == ntohl(0x0A2AFF01u)); /* 10.42.255.1 */
handle->_ref_count--;
_LOGD("shared-ip4: release IP address range %s/24 (%d more references held)",
nm_inet4_ntop(handle->addr, sbuf_addr),
handle->_ref_count);
return;
}
if (!g_hash_table_remove(priv->shared_ips, handle))
nm_assert_not_reached();
if (g_hash_table_size(priv->shared_ips) == 0) {
nm_clear_pointer(&priv->shared_ips, g_hash_table_unref);
g_object_unref(self);
}
_LOGD("shared-ip4: release IP address range %s/24", nm_inet4_ntop(handle->addr, sbuf_addr));
handle->_self = NULL;
nm_g_slice_free(handle);
}
/*****************************************************************************/
void
nm_netns_ip_route_ecmp_register(NMNetns *self, NML3Cfg *l3cfg, const NMPObject *obj)
{
NMNetnsPrivate *priv;
EcmpTrackObj *track_obj;
const NMPlatformIP4Route *route;
char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
nm_assert_l3cfg(self, l3cfg);
route = NMP_OBJECT_CAST_IP4_ROUTE(obj);
nm_assert(route->ifindex > 0);
nm_assert(route->ifindex == nm_l3cfg_get_ifindex(l3cfg));
nm_assert(route->n_nexthops <= 1);
priv = NM_NETNS_GET_PRIVATE(self);
track_obj = g_hash_table_lookup(priv->ecmp_track_by_obj, &obj);
if (NM_MORE_ASSERTS > 10) {
EcmpTrackObj *track_obj2;
gboolean found = FALSE;
c_list_for_each_entry (track_obj2,
&l3cfg->internal_netns.ecmp_track_ifindex_lst_head,
ifindex_lst) {
if (track_obj2->obj == obj) {
found = TRUE;
break;
}
}
nm_assert((!!track_obj) == found);
}
if (!track_obj) {
EcmpTrackEcmpid *track_ecmpid;
track_ecmpid = g_hash_table_lookup(priv->ecmp_track_by_ecmpid, &obj);
if (!track_ecmpid) {
track_ecmpid = g_slice_new(EcmpTrackEcmpid);
*track_ecmpid = (EcmpTrackEcmpid){
.representative_obj = nmp_object_ref(obj),
.merged_obj = NULL,
.ecmpid_lst_head = C_LIST_INIT(track_ecmpid->ecmpid_lst_head),
.needs_update = TRUE,
};
g_hash_table_add(priv->ecmp_track_by_ecmpid, track_ecmpid);
} else
track_ecmpid->needs_update = TRUE;
track_obj = g_slice_new(EcmpTrackObj);
*track_obj = (EcmpTrackObj){
.obj = nmp_object_ref(obj),
.l3cfg = l3cfg,
.parent_track_ecmpid = track_ecmpid,
.dirty = FALSE,
};
g_hash_table_add(priv->ecmp_track_by_obj, track_obj);
c_list_link_tail(&l3cfg->internal_netns.ecmp_track_ifindex_lst_head,
&track_obj->ifindex_lst);
c_list_link_tail(&track_ecmpid->ecmpid_lst_head, &track_obj->ecmpid_lst);
_LOGT(
"ecmp-route: track %s",
nmp_object_to_string(track_obj->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
} else {
track_obj->dirty = FALSE;
track_obj->parent_track_ecmpid->needs_update = TRUE;
}
}
void
nm_netns_ip_route_ecmp_commit(NMNetns *self, NML3Cfg *l3cfg, GPtrArray **out_singlehop_routes)
{
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
EcmpTrackObj *track_obj;
EcmpTrackObj *track_obj_safe;
EcmpTrackEcmpid *track_ecmpid;
const NMPObject *route_obj;
const NMPlatformIP4Route *route;
char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
gs_unref_ptrarray GPtrArray *mhrts_del = NULL;
gs_unref_ptrarray GPtrArray *mhrts_add = NULL;
nm_assert_l3cfg(self, l3cfg);
_LOGT("ecmp-route: committing IPv4 ECMP routes");
/* First, delete all dirty entries, and mark the survivors as dirty, so that on the
* next update they must be touched again. */
c_list_for_each_entry_safe (track_obj,
track_obj_safe,
&l3cfg->internal_netns.ecmp_track_ifindex_lst_head,
ifindex_lst) {
track_ecmpid = track_obj->parent_track_ecmpid;
track_ecmpid->already_visited = FALSE;
nm_assert(g_hash_table_lookup(priv->ecmp_track_by_ecmpid, track_ecmpid) == track_ecmpid);
nm_assert(g_hash_table_lookup(priv->ecmp_track_by_obj, track_obj) == track_obj);
nm_assert(c_list_contains(&track_ecmpid->ecmpid_lst_head, &track_obj->ecmpid_lst));
nm_assert(track_obj->l3cfg == l3cfg);
if (!track_obj->dirty) {
/* This one is still in used. Keep it, but mark dirty, so that on the
* next update cycle, it needs to be touched again or will be deleted. */
track_obj->dirty = TRUE;
continue;
}
/* This entry can be dropped. */
if (!g_hash_table_remove(priv->ecmp_track_by_obj, track_obj))
nm_assert_not_reached();
if (c_list_is_empty(&track_ecmpid->ecmpid_lst_head)) {
if (track_ecmpid->merged_obj) {
if (NMP_OBJECT_CAST_IP4_ROUTE(track_ecmpid->merged_obj)->n_nexthops > 1) {
if (!mhrts_del)
mhrts_del =
g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
g_ptr_array_add(mhrts_del,
(gpointer) g_steal_pointer(&track_ecmpid->merged_obj));
} else
nm_l3cfg_commit_on_idle_schedule(l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO);
}
g_hash_table_remove(priv->ecmp_track_by_ecmpid, track_ecmpid);
continue;
}
/* We need to update the representative obj. */
nmp_object_ref_set(
&track_ecmpid->representative_obj,
c_list_first_entry(&track_ecmpid->ecmpid_lst_head, EcmpTrackObj, ecmpid_lst)->obj);
track_ecmpid->needs_update = TRUE;
}
/* Now, we need to iterate again over all objects, and regenerate the merged_obj. */
c_list_for_each_entry (track_obj,
&l3cfg->internal_netns.ecmp_track_ifindex_lst_head,
ifindex_lst) {
nm_auto_nmpobj const NMPObject *obj_del = NULL;
gboolean changed;
track_ecmpid = track_obj->parent_track_ecmpid;
if (track_ecmpid->already_visited) {
/* We already visited this ecmpid in the same loop. We can skip, otherwise
* we might add the same route twice. */
continue;
}
track_ecmpid->already_visited = TRUE;
changed = _ecmp_track_init_merged_obj(track_obj->parent_track_ecmpid, &obj_del);
nm_assert(!obj_del || changed);
route_obj = track_obj->parent_track_ecmpid->merged_obj;
route = NMP_OBJECT_CAST_IP4_ROUTE(route_obj);
if (obj_del) {
if (NMP_OBJECT_CAST_IP4_ROUTE(obj_del)->n_nexthops > 1) {
if (!mhrts_del)
mhrts_del = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
g_ptr_array_add(mhrts_del, (gpointer) g_steal_pointer(&obj_del));
} else {
if (track_obj->l3cfg != l3cfg) {
nm_l3cfg_commit_on_idle_schedule(track_obj->l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO);
}
}
}
if (route->n_nexthops <= 1) {
/* This is a single hop route. Return it to the caller. */
if (!*out_singlehop_routes) {
/* Note that the returned array does not own a reference. This
* function has only one caller, and for that caller, it's just
* fine that the result is not additionally kept alive. */
*out_singlehop_routes =
g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
}
g_ptr_array_add(*out_singlehop_routes, (gpointer) nmp_object_ref(route_obj));
if (changed) {
_LOGT("ecmp-route: single-hop %s",
nmp_object_to_string(route_obj,
NMP_OBJECT_TO_STRING_PUBLIC,
sbuf,
sizeof(sbuf)));
}
continue;
}
if (changed) {
_LOGT("ecmp-route: multi-hop %s",
nmp_object_to_string(route_obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
if (!mhrts_add) {
/* mhrts_add doesn't own the pointers. It relies on them being alive long enough. */
mhrts_add = g_ptr_array_new();
}
g_ptr_array_add(mhrts_add, (gpointer) route_obj);
}
}
_netns_ip_route_ecmp_update_mh(self, mhrts_del, mhrts_add);
}
void
_netns_ip_route_ecmp_update_mh(NMNetns *self,
const GPtrArray *mhrts_del,
const GPtrArray *mhrts_add)
{
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
guint i;
if (mhrts_del) {
for (i = 0; i < mhrts_del->len; i++) {
const NMPObject *obj = mhrts_del->pdata[i];
if (!g_hash_table_remove(priv->ecmp_routes, obj))
nm_assert_not_reached();
nm_platform_object_delete(priv->platform, obj);
}
}
if (mhrts_add) {
for (i = 0; i < mhrts_add->len; i++) {
const NMPObject *obj = mhrts_add->pdata[i];
nm_auto_nmpobj const NMPObject *obj_old = NULL;
gpointer unused;
if (g_hash_table_steal_extended(priv->ecmp_routes,
obj,
(gpointer *) &obj_old,
&unused)) {
if (obj != obj_old)
nm_platform_object_delete(priv->platform, obj_old);
}
if (!g_hash_table_add(priv->ecmp_routes, (gpointer) nmp_object_ref(obj)))
nm_assert_not_reached();
nm_platform_ip_route_add(priv->platform, NMP_NLM_FLAG_APPEND, obj);
}
}
}
/*****************************************************************************/
static void
set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
NMNetns *self = NM_NETNS(object);
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
switch (prop_id) {
case PROP_PLATFORM:
/* construct-only */
priv->platform = g_value_get_object(value) ?: NM_PLATFORM_GET;
if (!priv->platform)
g_return_if_reached();
g_object_ref(priv->platform);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
/*****************************************************************************/
static void
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);
priv->ecmp_routes = g_hash_table_new_full((GHashFunc) nmp_object_id_hash,
(GEqualFunc) nmp_object_id_equal,
(GDestroyNotify) nmp_object_unref,
NULL);
G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(EcmpTrackObj, obj) == 0);
priv->ecmp_track_by_obj =
g_hash_table_new_full(nm_pdirect_hash, nm_pdirect_equal, _ecmp_routes_by_obj_free, NULL);
priv->ecmp_track_by_ecmpid = g_hash_table_new_full(_ecmp_routes_by_ecmpid_hash,
_ecmp_routes_by_ecmpid_equal,
_ecmp_routes_by_ecmpid_free,
NULL);
}
static void
constructed(GObject *object)
{
NMNetns *self = NM_NETNS(object);
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
if (!priv->platform)
g_return_if_reached();
priv->l3cfgs = g_hash_table_new_full(nm_pint_hash, nm_pint_equal, _l3cfg_hashed_free, NULL);
priv->platform_netns = nm_platform_netns_get(priv->platform);
priv->global_tracker = nmp_global_tracker_new(priv->platform);
/* Weakly track the default rules with a dummy user-tag. These
* rules are always weekly tracked... */
nmp_global_tracker_track_rule_default(priv->global_tracker,
AF_UNSPEC,
0,
nm_netns_parent_class /* static dummy user-tag */);
/* Also weakly track all existing rules. These were added before NetworkManager
* starts, so they are probably none of NetworkManager's business.
*
* However note that during service restart, devices may stay up and rules kept.
* That means, after restart such rules may have been added by a previous run
* of NetworkManager, we just don't know.
*
* For that reason, whenever we will touch such rules later one, we make them
* fully owned and no longer weekly tracked. See %NMP_GLOBAL_TRACKER_EXTERN_WEAKLY_TRACKED_USER_TAG. */
nmp_global_tracker_track_rule_from_platform(priv->global_tracker,
NULL,
AF_UNSPEC,
0,
NMP_GLOBAL_TRACKER_EXTERN_WEAKLY_TRACKED_USER_TAG);
G_OBJECT_CLASS(nm_netns_parent_class)->constructed(object);
g_signal_connect(priv->platform,
NM_PLATFORM_SIGNAL_LINK_CHANGED,
G_CALLBACK(_platform_signal_cb),
&priv->_self_signal_user_data);
g_signal_connect(priv->platform,
NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
G_CALLBACK(_platform_signal_cb),
&priv->_self_signal_user_data);
g_signal_connect(priv->platform,
NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,
G_CALLBACK(_platform_signal_cb),
&priv->_self_signal_user_data);
g_signal_connect(priv->platform,
NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
G_CALLBACK(_platform_signal_cb),
&priv->_self_signal_user_data);
g_signal_connect(priv->platform,
NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
G_CALLBACK(_platform_signal_cb),
&priv->_self_signal_user_data);
}
NMNetns *
nm_netns_new(NMPlatform *platform)
{
return g_object_new(NM_TYPE_NETNS, NM_NETNS_PLATFORM, platform, NULL);
}
static void
dispose(GObject *object)
{
NMNetns *self = NM_NETNS(object);
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
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_clear_pointer(&priv->ecmp_routes, g_hash_table_destroy);
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_g_source_inst(&priv->signal_pending_idle_source);
if (priv->platform)
g_signal_handlers_disconnect_by_data(priv->platform, &priv->_self_signal_user_data);
g_clear_object(&priv->platform);
nm_clear_pointer(&priv->l3cfgs, g_hash_table_unref);
nm_clear_pointer(&priv->global_tracker, nmp_global_tracker_unref);
G_OBJECT_CLASS(nm_netns_parent_class)->dispose(object);
}
static void
nm_netns_class_init(NMNetnsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->constructed = constructed;
object_class->set_property = set_property;
object_class->dispose = dispose;
obj_properties[PROP_PLATFORM] =
g_param_spec_object(NM_NETNS_PLATFORM,
"",
"",
NM_TYPE_PLATFORM,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
}