merge: branch 'bg/ip-reservation'

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2276
This commit is contained in:
Beniamino Galvani 2025-09-09 08:41:13 +02:00
commit d8971cceed
5 changed files with 197 additions and 72 deletions

View file

@ -266,7 +266,7 @@ typedef struct {
union { union {
struct { struct {
NMDnsMasqManager *dnsmasq_manager; NMDnsMasqManager *dnsmasq_manager;
NMNetnsSharedIPHandle *shared_ip_handle; NMNetnsIPReservation *ip_reservation;
NMFirewallConfig *firewall_config; NMFirewallConfig *firewall_config;
gulong dnsmasq_state_id; gulong dnsmasq_state_id;
const NML3ConfigData *l3cd; const NML3ConfigData *l3cd;
@ -13524,7 +13524,7 @@ _dev_ipsharedx_cleanup(NMDevice *self, int addr_family)
nm_clear_pointer(&priv->ipshared_data_4.v4.firewall_config, nm_firewall_config_free); nm_clear_pointer(&priv->ipshared_data_4.v4.firewall_config, nm_firewall_config_free);
} }
nm_clear_pointer(&priv->ipshared_data_4.v4.shared_ip_handle, nm_netns_shared_ip_release); nm_clear_pointer(&priv->ipshared_data_4.v4.ip_reservation, nm_netns_ip_reservation_release);
nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd); nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, NULL, FALSE); _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, NULL, FALSE);
@ -13558,13 +13558,14 @@ _dev_ipshared4_new_l3cd(NMDevice *self, NMConnection *connection, NMPlatformIP4A
nm_ip_address_get_address_binary(user, &a); nm_ip_address_get_address_binary(user, &a);
nm_platform_ip4_address_set_addr(&address, a, nm_ip_address_get_prefix(user)); nm_platform_ip4_address_set_addr(&address, a, nm_ip_address_get_prefix(user));
nm_clear_pointer(&priv->ipshared_data_4.v4.shared_ip_handle, nm_netns_shared_ip_release); nm_clear_pointer(&priv->ipshared_data_4.v4.ip_reservation, nm_netns_ip_reservation_release);
} else { } else {
if (!priv->ipshared_data_4.v4.shared_ip_handle) if (!priv->ipshared_data_4.v4.ip_reservation)
priv->ipshared_data_4.v4.shared_ip_handle = priv->ipshared_data_4.v4.ip_reservation =
nm_netns_shared_ip_reserve(nm_device_get_netns(self)); nm_netns_ip_reservation_get(nm_device_get_netns(self),
NM_NETNS_IP_RESERVATION_TYPE_SHARED4);
nm_platform_ip4_address_set_addr(&address, nm_platform_ip4_address_set_addr(&address,
priv->ipshared_data_4.v4.shared_ip_handle->addr, priv->ipshared_data_4.v4.ip_reservation->addr,
24); 24);
} }

View file

@ -68,7 +68,7 @@ typedef struct {
NMPNetns *platform_netns; NMPNetns *platform_netns;
NMPGlobalTracker *global_tracker; NMPGlobalTracker *global_tracker;
GHashTable *l3cfgs; GHashTable *l3cfgs;
GHashTable *shared_ips; GHashTable *ip_reservation[_NM_NETNS_IP_RESERVATION_TYPE_NUM];
GHashTable *ecmp_track_by_obj; GHashTable *ecmp_track_by_obj;
GHashTable *ecmp_track_by_ecmpid; GHashTable *ecmp_track_by_ecmpid;
@ -571,106 +571,150 @@ notify_watcher:
/*****************************************************************************/ /*****************************************************************************/
NMNetnsSharedIPHandle * typedef struct {
nm_netns_shared_ip_reserve(NMNetns *self) const char *name;
guint32 start_addr; /* host byte order */
guint prefix_len;
guint num_addrs;
gboolean allow_reuse;
} IPReservationTypeDesc;
static const IPReservationTypeDesc ip_reservation_types[_NM_NETNS_IP_RESERVATION_TYPE_NUM] = {
[NM_NETNS_IP_RESERVATION_TYPE_SHARED4] =
{
.name = "shared-ip4",
.start_addr = 0x0a2a0001, /* 10.42.0.1 */
.prefix_len = 24,
.num_addrs = 256,
.allow_reuse = TRUE,
},
};
NMNetnsIPReservation *
nm_netns_ip_reservation_get(NMNetns *self, NMNetnsIPReservationType type)
{ {
NMNetnsPrivate *priv; NMNetnsPrivate *priv;
NMNetnsSharedIPHandle *handle; const IPReservationTypeDesc *desc;
const in_addr_t addr_start = ntohl(0x0a2a0001u); /* 10.42.0.1 */ NMNetnsIPReservation *res;
GHashTable **table;
in_addr_t addr; in_addr_t addr;
char sbuf_addr[NM_INET_ADDRSTRLEN]; char buf[NM_INET_ADDRSTRLEN];
/* Find an unused address in the 10.42.x.x range */
g_return_val_if_fail(NM_IS_NETNS(self), NULL); g_return_val_if_fail(NM_IS_NETNS(self), NULL);
g_return_val_if_fail(type < _NM_NETNS_IP_RESERVATION_TYPE_NUM, NULL);
priv = NM_NETNS_GET_PRIVATE(self); priv = NM_NETNS_GET_PRIVATE(self);
desc = &ip_reservation_types[type];
table = &priv->ip_reservation[type];
if (!priv->shared_ips) { if (!*table) {
addr = addr_start; addr = htonl(desc->start_addr);
priv->shared_ips = g_hash_table_new(nm_puint32_hash, nm_puint32_equal); *table = g_hash_table_new(nm_puint32_hash, nm_puint32_equal);
g_object_ref(self); g_object_ref(self);
} else { } else {
guint32 count; guint32 count;
nm_assert(g_hash_table_size(priv->shared_ips) > 0); nm_assert(g_hash_table_size(*table) > 0);
nm_assert(desc->prefix_len > 0 && desc->prefix_len <= 32);
count = 0u; count = 0u;
for (;;) { for (;;) {
addr = addr_start + htonl(count << 8u); addr = htonl(desc->start_addr + (count << (32 - desc->prefix_len)));
handle = g_hash_table_lookup(priv->shared_ips, &addr); res = g_hash_table_lookup(*table, &addr);
if (!handle) if (!res)
break; break;
count++; count++;
if (count > 0xFFu) { if (count >= desc->num_addrs) {
if (handle->_ref_count == 1) { if (!desc->allow_reuse) {
_LOGE("shared-ip4: ran out of shared IP addresses. Reuse %s/24", _LOGE("%s: ran out of IP addresses", desc->name);
nm_inet4_ntop(handle->addr, sbuf_addr)); return NULL;
} else {
_LOGD("shared-ip4: reserved IP address range %s/24 (duplicate)",
nm_inet4_ntop(handle->addr, sbuf_addr));
} }
handle->_ref_count++;
return handle; if (res->_ref_count == 1) {
_LOGE("%s: ran out of IP addresses. Reuse %s/%u",
desc->name,
nm_inet4_ntop(res->addr, buf),
desc->prefix_len);
} else {
_LOGD("%s: reserved IP address %s/%u (duplicate)",
desc->name,
nm_inet4_ntop(res->addr, buf),
desc->prefix_len);
}
res->_ref_count++;
return res;
} }
} }
} }
handle = g_slice_new(NMNetnsSharedIPHandle); res = g_slice_new(NMNetnsIPReservation);
*handle = (NMNetnsSharedIPHandle) { *res = (NMNetnsIPReservation) {
.addr = addr, .addr = addr,
._ref_count = 1, ._ref_count = 1,
._self = self, ._self = self,
._type = type,
}; };
g_hash_table_add(priv->shared_ips, handle); g_hash_table_add(*table, res);
_LOGD("shared-ip4: reserved IP address range %s/24", nm_inet4_ntop(handle->addr, sbuf_addr)); _LOGD("%s: reserved IP address %s/%u",
return handle; desc->name,
nm_inet4_ntop(res->addr, buf),
desc->prefix_len);
return res;
} }
void void
nm_netns_shared_ip_release(NMNetnsSharedIPHandle *handle) nm_netns_ip_reservation_release(NMNetnsIPReservation *res)
{ {
NMNetns *self; NMNetns *self;
NMNetnsPrivate *priv; NMNetnsPrivate *priv;
char sbuf_addr[NM_INET_ADDRSTRLEN]; const IPReservationTypeDesc *desc;
GHashTable **table;
char buf[NM_INET_ADDRSTRLEN];
g_return_if_fail(handle); g_return_if_fail(res);
g_return_if_fail(res->_type < _NM_NETNS_IP_RESERVATION_TYPE_NUM);
self = handle->_self;
self = res->_self;
g_return_if_fail(NM_IS_NETNS(self)); g_return_if_fail(NM_IS_NETNS(self));
priv = NM_NETNS_GET_PRIVATE(self); priv = NM_NETNS_GET_PRIVATE(self);
desc = &ip_reservation_types[res->_type];
table = &priv->ip_reservation[res->_type];
nm_assert(handle->_ref_count > 0); nm_assert(res->_ref_count > 0);
nm_assert(handle == nm_g_hash_table_lookup(priv->shared_ips, handle)); nm_assert(res == nm_g_hash_table_lookup(*table, res));
if (handle->_ref_count > 1) { if (res->_ref_count > 1) {
nm_assert(handle->addr == ntohl(0x0A2AFF01u)); /* 10.42.255.1 */ nm_assert(desc->allow_reuse);
handle->_ref_count--; res->_ref_count--;
_LOGD("shared-ip4: release IP address range %s/24 (%d more references held)", _LOGD("%s: release IP address reservation %s/%u (%d more references held)",
nm_inet4_ntop(handle->addr, sbuf_addr), desc->name,
handle->_ref_count); nm_inet4_ntop(res->addr, buf),
desc->prefix_len,
res->_ref_count);
return; return;
} }
if (!g_hash_table_remove(priv->shared_ips, handle)) if (!g_hash_table_remove(*table, res))
nm_assert_not_reached(); nm_assert_not_reached();
if (g_hash_table_size(priv->shared_ips) == 0) { if (g_hash_table_size(*table) == 0) {
nm_clear_pointer(&priv->shared_ips, g_hash_table_unref); nm_clear_pointer(table, g_hash_table_unref);
g_object_unref(self); g_object_unref(self);
} }
_LOGD("shared-ip4: release IP address range %s/24", nm_inet4_ntop(handle->addr, sbuf_addr)); _LOGD("%s: release IP address reservation %s/%u",
desc->name,
nm_inet4_ntop(res->addr, buf),
desc->prefix_len);
handle->_self = NULL; res->_self = NULL;
nm_g_slice_free(handle); nm_g_slice_free(res);
} }
/*****************************************************************************/ /*****************************************************************************/
@ -1560,11 +1604,14 @@ dispose(GObject *object)
nm_assert(nm_g_hash_table_size(priv->l3cfgs) == 0); nm_assert(nm_g_hash_table_size(priv->l3cfgs) == 0);
nm_assert(c_list_is_empty(&priv->l3cfg_signal_pending_lst_head)); nm_assert(c_list_is_empty(&priv->l3cfg_signal_pending_lst_head));
nm_assert(!priv->shared_ips);
nm_assert(nm_g_hash_table_size(priv->watcher_idx) == 0); nm_assert(nm_g_hash_table_size(priv->watcher_idx) == 0);
nm_assert(nm_g_hash_table_size(priv->watcher_by_tag_idx) == 0); nm_assert(nm_g_hash_table_size(priv->watcher_by_tag_idx) == 0);
nm_assert(nm_g_hash_table_size(priv->watcher_ip_data_idx) == 0); nm_assert(nm_g_hash_table_size(priv->watcher_ip_data_idx) == 0);
for (guint i = 0; i < _NM_NETNS_IP_RESERVATION_TYPE_NUM; i++) {
nm_assert(!priv->ip_reservation[i]);
}
nm_clear_pointer(&priv->ecmp_track_by_obj, 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_pointer(&priv->ecmp_track_by_ecmpid, g_hash_table_destroy);

View file

@ -41,15 +41,22 @@ NML3Cfg *nm_netns_l3cfg_acquire(NMNetns *netns, int ifindex);
/*****************************************************************************/ /*****************************************************************************/
typedef enum {
NM_NETNS_IP_RESERVATION_TYPE_SHARED4,
_NM_NETNS_IP_RESERVATION_TYPE_NUM,
} NMNetnsIPReservationType;
typedef struct { typedef struct {
in_addr_t addr; in_addr_t addr;
int _ref_count; int _ref_count;
NMNetnsIPReservationType _type;
NMNetns *_self; NMNetns *_self;
} NMNetnsSharedIPHandle; } NMNetnsIPReservation;
NMNetnsSharedIPHandle *nm_netns_shared_ip_reserve(NMNetns *self); NMNetnsIPReservation *nm_netns_ip_reservation_get(NMNetns *self, NMNetnsIPReservationType type);
void nm_netns_shared_ip_release(NMNetnsSharedIPHandle *handle); void nm_netns_ip_reservation_release(NMNetnsIPReservation *reservation);
/*****************************************************************************/ /*****************************************************************************/

View file

@ -6,6 +6,7 @@ test_units = [
'test-core', 'test-core',
'test-core-with-expect', 'test-core-with-expect',
'test-dcb', 'test-dcb',
'test-netns',
'test-l3cfg', 'test-l3cfg',
'test-utils', 'test-utils',
'test-wired-defname', 'test-wired-defname',

View file

@ -0,0 +1,69 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "src/core/nm-default-daemon.h"
#include "nm-netns.h"
#include "nm-test-utils-core.h"
static void
test_ip_reservation_shared4(void)
{
gs_unref_object NMPlatform *platform = NULL;
gs_unref_object NMNetns *netns = NULL;
NMNetnsIPReservation *res[256];
NMNetnsIPReservation *res1;
NMNetnsIPReservation *res2;
char buf[NM_INET_ADDRSTRLEN];
guint i;
platform = g_object_ref(NM_PLATFORM_GET);
netns = nm_netns_new(platform);
/* Allocate addresses from 10.42.0.1 to 10.42.255.1 */
for (i = 0; i < 256; i++) {
res[i] = nm_netns_ip_reservation_get(netns, NM_NETNS_IP_RESERVATION_TYPE_SHARED4);
g_snprintf(buf, sizeof(buf), "10.42.%u.1", i);
nmtst_assert_ip4_address(res[i]->addr, buf);
g_assert_cmpint(res[i]->_ref_count, ==, 1);
}
/* Release an address and get it back */
nm_netns_ip_reservation_release(res[139]);
res[139] = nm_netns_ip_reservation_get(netns, NM_NETNS_IP_RESERVATION_TYPE_SHARED4);
nmtst_assert_ip4_address(res[139]->addr, "10.42.139.1");
/* Reuse 10.42.255.1 once */
NMTST_EXPECT_NM_ERROR("netns[*]: shared-ip4: ran out of IP addresses. Reuse 10.42.255.1/24");
res1 = nm_netns_ip_reservation_get(netns, NM_NETNS_IP_RESERVATION_TYPE_SHARED4);
g_test_assert_expected_messages();
nmtst_assert_ip4_address(res1->addr, "10.42.255.1");
g_assert_cmpint(res1->_ref_count, ==, 2);
/* Reuse 10.42.255.1 twice */
res2 = nm_netns_ip_reservation_get(netns, NM_NETNS_IP_RESERVATION_TYPE_SHARED4);
g_assert(res2 == res1);
nmtst_assert_ip4_address(res1->addr, "10.42.255.1");
g_assert_cmpint(res2->_ref_count, ==, 3);
/* Release all */
nm_netns_ip_reservation_release(res1);
nm_netns_ip_reservation_release(res2);
for (i = 0; i < 256; i++) {
nm_netns_ip_reservation_release(res[i]);
}
}
/*****************************************************************************/
NMTST_DEFINE();
int
main(int argc, char **argv)
{
nmtst_init_with_logging(&argc, &argv, NULL, "ALL");
nm_linux_platform_setup();
g_test_add_func("/netns/ip_reservation/shared4", test_ip_reservation_shared4);
return g_test_run();
}