mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-21 11:00:07 +01:00
494 lines
16 KiB
C
494 lines
16 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"
|
|
|
|
/*****************************************************************************/
|
|
|
|
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;
|
|
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);
|
|
|
|
/*****************************************************************************/
|
|
|
|
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);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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_l3cfg_get(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_l3cfg_acquire(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;
|
|
|
|
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_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_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;
|
|
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_source)
|
|
priv->signal_pending_idle_source =
|
|
nm_g_idle_add_source(_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_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_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_equal, _l3cfg_data_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_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);
|
|
}
|