/* 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)) return FALSE; 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; } 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); /* 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; 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->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 { if (track_obj->l3cfg != l3cfg) { nm_l3cfg_commit_on_idle_schedule(track_obj->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); }