diff options
| author | Michael Biebl <biebl@debian.org> | 2021-02-11 18:11:46 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-02-11 18:11:46 +0100 |
| commit | 80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch) | |
| tree | e3b229aa94e8dcf0590f2317664176e7b8f7607b /src/core/nm-netns.c | |
| parent | 65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff) | |
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'src/core/nm-netns.c')
| -rw-r--r-- | src/core/nm-netns.c | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c new file mode 100644 index 00000000..88ea09b6 --- /dev/null +++ b/src/core/nm-netns.c @@ -0,0 +1,493 @@ +/* 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 "nm-glib-aux/nm-dedup-multi.h" +#include "nm-glib-aux/nm-c-list.h" + +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" +#include "nm-l3cfg.h" +#include "platform/nm-platform.h" +#include "nm-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; + GHashTable * shared_ips; + 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_obj_type_flags; + 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_get_l3cfg(NMNetns *self, int ifindex) +{ + NMNetnsPrivate *priv; + + g_return_val_if_fail(NM_IS_NETNS(self), NULL); + g_return_val_if_fail(ifindex > 0, NULL); + + priv = NM_NETNS_GET_PRIVATE(self); + + return g_hash_table_lookup(priv->l3cfgs, &ifindex); +} + +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; + CList work_list; + + priv->signal_pending_idle_id = 0; + + /* 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_data = c_list_first_entry(&work_list, L3CfgData, signal_pending_lst))) { + nm_assert(NM_IS_L3CFG(l3cfg_data->l3cfg)); + 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_obj_type_flags)); + } + + 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_obj_type_flags |= 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)); +} + +/*****************************************************************************/ + +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_UTILS_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_equals); + 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_utils_inet4_ntop(handle->addr, sbuf_addr)); + } else { + _LOGD("shared-ip4: reserved IP address range %s/24 (duplicate)", + _nm_utils_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_utils_inet4_ntop(handle->addr, sbuf_addr)); + return handle; +} + +void +nm_netns_shared_ip_release(NMNetnsSharedIPHandle *handle) +{ + NMNetns * self; + NMNetnsPrivate *priv; + char sbuf_addr[NM_UTILS_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_utils_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_utils_inet4_ntop(handle->addr, sbuf_addr)); + + handle->_self = NULL; + nm_g_slice_free(handle); +} + +/*****************************************************************************/ + +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_assert(!priv->shared_ips); + + 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); + nm_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); +} |