// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2017 Red Hat, Inc. */ #include "nm-default.h" #include "nm-netns.h" #include "nm-glib-aux/nm-dedup-multi.h" #include "NetworkManagerUtils.h" #include "nm-core-internal.h" #include "nm-l3cfg.h" #include "platform/nm-platform.h" #include "platform/nmp-netns.h" #include "platform/nmp-rules-manager.h" /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE_BASE ( PROP_PLATFORM, ); typedef struct { NMNetns *_self_signal_user_data; NMPlatform *platform; NMPNetns *platform_netns; NMPRulesManager *rules_manager; GHashTable *l3cfgs; CList l3cfg_signal_pending_lst_head; guint signal_pending_idle_id; } 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); /*****************************************************************************/ 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; } NMPRulesManager * nm_netns_get_rules_manager (NMNetns *self) { return NM_NETNS_GET_PRIVATE (self)->rules_manager; } NMDedupMultiIndex * nm_netns_get_multi_idx (NMNetns *self) { return nm_platform_get_multi_idx (NM_NETNS_GET_PRIVATE (self)->platform); } /*****************************************************************************/ typedef struct { int ifindex; guint32 signal_pending_flag; NML3Cfg *l3cfg; CList signal_pending_lst; } L3CfgData; static void _l3cfg_data_free (gpointer ptr) { L3CfgData *l3cfg_data = ptr; c_list_unlink_stale (&l3cfg_data->signal_pending_lst); nm_g_slice_free (l3cfg_data); } 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_access_l3cfg (NMNetns *self, int ifindex) { NMNetnsPrivate *priv; L3CfgData *l3cfg_data; 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_data = g_hash_table_lookup (priv->l3cfgs, &ifindex); if (l3cfg_data) { nm_log_trace (LOGD_CORE, "l3cfg["NM_HASH_OBFUSCATE_PTR_FMT",ifindex=%d] %s", NM_HASH_OBFUSCATE_PTR (l3cfg_data->l3cfg), ifindex, "referenced"); return g_object_ref (l3cfg_data->l3cfg); } l3cfg_data = g_slice_new (L3CfgData); *l3cfg_data = (L3CfgData) { .ifindex = ifindex, .l3cfg = nm_l3cfg_new (self, ifindex), .signal_pending_lst = C_LIST_INIT (l3cfg_data->signal_pending_lst), }; if (!g_hash_table_add (priv->l3cfgs, l3cfg_data)) 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_data->l3cfg), _l3cfg_weak_notify, self); /* Transfer ownership! We keep only a weak ref. */ return l3cfg_data->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); L3CfgData *l3cfg_data; while ((l3cfg_data = c_list_first_entry (&priv->l3cfg_signal_pending_lst_head, L3CfgData, signal_pending_lst))) { c_list_unlink (&l3cfg_data->signal_pending_lst); _nm_l3cfg_notify_platform_change_on_idle (l3cfg_data->l3cfg, nm_steal_int (&l3cfg_data->signal_pending_flag)); } priv->signal_pending_idle_id = 0; return G_SOURCE_REMOVE; } 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; L3CfgData *l3cfg_data; l3cfg_data = g_hash_table_lookup (priv->l3cfgs, &ifindex); if (!l3cfg_data) return; l3cfg_data->signal_pending_flag |= nmp_object_type_to_flags (obj_type); if (c_list_is_empty (&l3cfg_data->signal_pending_lst)) { c_list_link_tail (&priv->l3cfg_signal_pending_lst_head, &l3cfg_data->signal_pending_lst); if (priv->signal_pending_idle_id == 0) priv->signal_pending_idle_id = g_idle_add (_platform_signal_on_idle_cb, self); } _nm_l3cfg_notify_platform_change (l3cfg_data->l3cfg, change_type, NMP_OBJECT_UP_CAST (platform_object)); } /*****************************************************************************/ 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); } 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_equals, _l3cfg_data_free, NULL); priv->platform_netns = nm_platform_netns_get (priv->platform); priv->rules_manager = nmp_rules_manager_new (priv->platform); /* Weakly track the default rules with a dummy user-tag. These * rules are always weekly tracked... */ nmp_rules_manager_track_default (priv->rules_manager, 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_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG. */ nmp_rules_manager_track_from_platform (priv->rules_manager, NULL, AF_UNSPEC, 0, NMP_RULES_MANAGER_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_clear_g_source (&priv->signal_pending_idle_id); if (priv->platform) g_signal_handlers_disconnect_by_data (priv->platform, &priv->_self_signal_user_data); g_clear_object (&priv->platform); g_clear_pointer (&priv->l3cfgs, g_hash_table_unref); nm_clear_pointer (&priv->rules_manager, nmp_rules_manager_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); }