From 7e05a2337e445f8212614e3f5eaf97bae3fe7394 Mon Sep 17 00:00:00 2001 From: Jan Vaclav Date: Thu, 7 May 2026 14:08:18 +0200 Subject: [PATCH 01/20] libnm: add NM_VERSION_1_56_2 --- src/libnm-core-public/nm-version-macros.h.in | 1 + src/libnm-core-public/nm-version.h | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/libnm-core-public/nm-version-macros.h.in b/src/libnm-core-public/nm-version-macros.h.in index b8b89a4473..8ae92fb872 100644 --- a/src/libnm-core-public/nm-version-macros.h.in +++ b/src/libnm-core-public/nm-version-macros.h.in @@ -79,6 +79,7 @@ #define NM_VERSION_1_52 (NM_ENCODE_VERSION(1, 52, 0)) #define NM_VERSION_1_54 (NM_ENCODE_VERSION(1, 54, 0)) #define NM_VERSION_1_56 (NM_ENCODE_VERSION(1, 56, 0)) +#define NM_VERSION_1_56_2 (NM_ENCODE_VERSION(1, 56, 2)) /* For releases, NM_API_VERSION is equal to NM_VERSION. * diff --git a/src/libnm-core-public/nm-version.h b/src/libnm-core-public/nm-version.h index af45b6792d..8cf43f41b2 100644 --- a/src/libnm-core-public/nm-version.h +++ b/src/libnm-core-public/nm-version.h @@ -453,6 +453,12 @@ #define NM_AVAILABLE_IN_1_56 #endif +#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_56_2 +#define NM_AVAILABLE_IN_1_56_2 G_UNAVAILABLE(1, 56.2) +#else +#define NM_AVAILABLE_IN_1_56_2 +#endif + /* * Synchronous API for calling D-Bus in libnm is deprecated. See * https://networkmanager.dev/docs/libnm/latest/usage.html#sync-api From 08bc6652c00b808c9884840975b48abbb8816272 Mon Sep 17 00:00:00 2001 From: Jan Vaclav Date: Tue, 24 Feb 2026 11:51:50 +0100 Subject: [PATCH 02/20] platform: track onlink flag per-nexthop for IPv4 routes In kernel, the onlink flag (RTNH_F_ONLINK) is associated with each nexthop (rtnh_flags) rather than the route as a whole. NM previously stored it only per-route in NMPlatformIPRoute.r_rtm_flags, which meant that two nexthops only differing with the onlink flag were combined as one entry in the platform cache. Fix this by tracking the onlink flag per-nexthop. Resolves: https://issues.redhat.com/browse/NMT-1486 (cherry picked from commit d564a0c3f96394544f333b3d6d648315cd64aa00) --- src/core/NetworkManagerUtils.c | 9 ++-- src/core/nm-l3-config-data.c | 9 ++-- src/core/nm-netns.c | 8 +-- src/core/platform/tests/test-route.c | 74 ++++++++++++++++++++++++++ src/libnm-platform/nm-linux-platform.c | 45 ++++++++++------ src/libnm-platform/nm-platform.c | 30 +++++++---- src/libnm-platform/nm-platform.h | 21 +++++--- 7 files changed, 150 insertions(+), 46 deletions(-) diff --git a/src/core/NetworkManagerUtils.c b/src/core/NetworkManagerUtils.c index 32df3d6dfd..95ba20f167 100644 --- a/src/core/NetworkManagerUtils.c +++ b/src/core/NetworkManagerUtils.c @@ -1495,11 +1495,10 @@ nm_utils_ip_route_attribute_to_platform(int addr_family, r4->scope_inv = nm_platform_route_scope_inv(scope); } - /* Note that for IPv4 routes in kernel, the onlink flag can be set for - * each next hop separately (rtnh_flags). Not for NetworkManager. We can - * only merge routes as ECMP routes (when setting a weight) if they all - * share the same onlink flag. See NM_PLATFORM_IP_ROUTE_CMP_TYPE_ECMP_ID. - * That simplifies the code. */ + /* For IPv4 routes in kernel, the onlink flag is per-nexthop (rtnh_flags). + * Here we set the flag on r_rtm_flags which represents the first nexthop's + * flags. For ECMP routes, each nexthop carries its own onlink flag, so + * routes with different onlink settings per-nexthop can be merged. */ GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_ONLINK, onlink, BOOLEAN, boolean, FALSE); r->r_rtm_flags = ((onlink) ? (unsigned) RTNH_F_ONLINK : 0u); diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 328f59b6ce..7500337eed 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -2991,12 +2991,9 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co * the one we create here (because the "onlink" flag is part of the * identifier of a route, see nm_platform_ip4_route_cmp()). * - * Note however that for ECMP routes we currently can only merge routes - * that agree in their onlink flag. So a route without gateway cannot - * merge with an onlink route that has a gateway. That needs fixing, - * by not treating the onlink flag as for the entire route, but allowing - * to merge ECMP routes with different onlink flag. And first, we need - * to track the onlink flag for the nexthop (NMPlatformIP4RtNextHop). */ + * The onlink flag is tracked per-nexthop (in NMPlatformIP4RtNextHop.rtnh_flags + * for extra nexthops, and in r_rtm_flags for the first nexthop). ECMP routes + * can be merged regardless of per-nexthop onlink flags. */ r.r4.r_rtm_flags &= ~((unsigned) RTNH_F_ONLINK); } diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c index f55d1132d0..0e8b15a791 100644 --- a/src/core/nm-netns.c +++ b/src/core/nm-netns.c @@ -207,6 +207,7 @@ _ecmp_track_sort_lst_cmp(const CList *a, const CList *b, const void *user_data) 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)); + NM_CMP_DIRECT(route_a->r_rtm_flags & RTNH_F_ONLINK, route_b->r_rtm_flags & RTNH_F_ONLINK); return nm_assert_unreachable_val( nm_platform_ip4_route_cmp(route_a, route_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)); @@ -275,9 +276,10 @@ _ecmp_track_init_merged_obj(EcmpTrackEcmpid *track_ecmpid, const NMPObject **out NMPlatformIP4RtNextHop *nh = (gpointer) &obj_new->_ip4_route.extra_nexthops[i - 1]; *nh = (NMPlatformIP4RtNextHop) { - .ifindex = r->ifindex, - .gateway = r->gateway, - .weight = r->weight, + .ifindex = r->ifindex, + .gateway = r->gateway, + .weight = r->weight, + .rtnh_flags = r->r_rtm_flags & RTNH_F_ONLINK, }; } i++; diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c index fbad2447a9..27bb743121 100644 --- a/src/core/platform/tests/test-route.c +++ b/src/core/platform/tests/test-route.c @@ -2460,6 +2460,78 @@ done: } } +/*****************************************************************************/ +static void +test_ip4_rtnh_onlink(void) +{ + /* Extra nexthops that differ only in rtnh_flags ONLINK should + * compare as different. */ + NMPlatformIP4RtNextHop nh_a = { + .ifindex = 2, + .gateway = nmtst_inet4_from_string("10.10.10.10"), + .weight = 1, + .rtnh_flags = 0, + }; + + NMPlatformIP4RtNextHop nh_b = nh_a; + nh_b.rtnh_flags = RTNH_F_ONLINK; + + g_assert_cmpint(nm_platform_ip4_rt_nexthop_cmp(&nh_a, &nh_b, TRUE), !=, 0); + g_assert_cmpint(nm_platform_ip4_rt_nexthop_cmp(&nh_a, &nh_b, FALSE), !=, 0); + + nh_b.rtnh_flags = 0; + g_assert_cmpint(nm_platform_ip4_rt_nexthop_cmp(&nh_a, &nh_b, TRUE), ==, 0); + g_assert_cmpint(nm_platform_ip4_rt_nexthop_cmp(&nh_a, &nh_b, FALSE), ==, 0); +} + +static void +test_ip4_route_onlink_per_nexthop(void) +{ + NMPlatformIP4Route r_a = {}; + NMPlatformIP4Route r_b; + + /* Two single-hop routes that are identical, except for the onlink flag. */ + r_a.ifindex = 1; + r_a.rt_source = NM_IP_CONFIG_SOURCE_USER; + r_a.network = nmtst_inet4_from_string("10.10.10.10"); + r_a.plen = 24; + r_a.gateway = nmtst_inet4_from_string("10.10.10.1"); + r_a.metric = 100; + r_a.n_nexthops = 1; + r_a.type_coerced = nm_platform_route_type_coerce(RTN_UNICAST); + r_a.scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE); + + r_b = r_a; + r_b.r_rtm_flags = RTNH_F_ONLINK; + + /* Onlink flag should result in the same ECMP_ID but different IDs. */ + g_assert_cmpint(nm_platform_ip4_route_cmp(&r_a, &r_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ECMP_ID), + ==, + 0); + + g_assert_cmpint(nm_platform_ip4_route_cmp(&r_a, &r_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID), !=, 0); + g_assert_cmpint( + nm_platform_ip4_route_cmp(&r_a, &r_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY), + !=, + 0); + g_assert_cmpint(nm_platform_ip4_route_cmp(&r_a, &r_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL), + !=, + 0); + + r_b.r_rtm_flags = 0; + g_assert_cmpint(nm_platform_ip4_route_cmp(&r_a, &r_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID), ==, 0); + g_assert_cmpint(nm_platform_ip4_route_cmp(&r_a, &r_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ECMP_ID), + ==, + 0); + g_assert_cmpint( + nm_platform_ip4_route_cmp(&r_a, &r_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY), + ==, + 0); + g_assert_cmpint(nm_platform_ip4_route_cmp(&r_a, &r_b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL), + ==, + 0); +} + /*****************************************************************************/ NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; @@ -2480,6 +2552,8 @@ _nmtstp_setup_tests(void) nmtstp_env1_add_test_func_data(testpath, test_func, arg, 2, TRUE) add_test_func("/route/ip4", test_ip4_route); + add_test_func("/route/ip4_onlink_per_nexthop", test_ip4_route_onlink_per_nexthop); + add_test_func("/route/ip4_rtnh_onlink", test_ip4_rtnh_onlink); add_test_func("/route/ip6", test_ip6_route); add_test_func("/route/ip4_metric0", test_ip4_route_metric0); add_test_func_data("/route/ip4_options/1", test_ip4_route_options, GINT_TO_POINTER(1)); diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index cc5b99e095..3f5ed9fc1b 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -4033,6 +4033,7 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter int ifindex; NMIPAddr gateway; gboolean is_via; + unsigned rtnh_flags; } nh = { .found = FALSE, .has_more = FALSE, @@ -4142,9 +4143,10 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter v4_nh_extra_nexthops = v4_nh_extra_nexthops_heap; } nm_assert(v4_n_nexthops - 1u < v4_nh_extra_alloc); - new_nexthop = &v4_nh_extra_nexthops[v4_n_nexthops - 1u]; - new_nexthop->ifindex = rtnh->rtnh_ifindex; - new_nexthop->weight = NM_MAX(((guint) rtnh->rtnh_hops) + 1u, 1u); + new_nexthop = &v4_nh_extra_nexthops[v4_n_nexthops - 1u]; + new_nexthop->ifindex = rtnh->rtnh_ifindex; + new_nexthop->weight = NM_MAX(((guint) rtnh->rtnh_hops) + 1u, 1u); + new_nexthop->rtnh_flags = rtnh->rtnh_flags; if (rtnh->rtnh_len > sizeof(*rtnh)) { struct nlattr *ntb[RTA_MAX + 1]; @@ -4159,9 +4161,10 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter memcpy(&new_nexthop->gateway, nla_data(ntb[RTA_GATEWAY]), addr_len); } } else if (IS_IPv4 || idx == multihop_idx) { - nh.found = TRUE; - nh.ifindex = rtnh->rtnh_ifindex; - nh.weight = NM_MAX(((guint) rtnh->rtnh_hops) + 1u, 1u); + nh.found = TRUE; + nh.ifindex = rtnh->rtnh_ifindex; + nh.weight = NM_MAX(((guint) rtnh->rtnh_hops) + 1u, 1u); + nh.rtnh_flags = rtnh->rtnh_flags; if (rtnh->rtnh_len > sizeof(*rtnh)) { struct nlattr *ntb[RTA_MAX + 1]; @@ -4409,7 +4412,16 @@ rta_multipath_done: } obj->ip_route.r_rtm_flags = rtm->rtm_flags; - obj->ip_route.rt_source = nmp_utils_ip_config_source_from_rtprot(rtm->rtm_protocol); + + if (IS_IPv4 && v4_n_nexthops > 1u) { + /* For multipath routes, rtm_flags at the route level does not contain + * RTNH_F_ONLINK. Instead, each nexthop has its own rtnh_flags. Merge the + * first nexthop's RTNH_F_ONLINK into r_rtm_flags, since the first nexthop + * is embedded in the route struct. */ + obj->ip_route.r_rtm_flags |= (nh.rtnh_flags & RTNH_F_ONLINK); + } + + obj->ip_route.rt_source = nmp_utils_ip_config_source_from_rtprot(rtm->rtm_protocol); if (nh.has_more) { parse_nlmsg_iter->iter_more = TRUE; @@ -5813,15 +5825,18 @@ _nl_msg_new_route(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPObject *ob } NLA_PUT_U32(msg, RTA_GATEWAY, gw); - rtnh->rtnh_flags = 0; + if (i == 0u) { + rtnh->rtnh_flags = + (gw != 0 && NM_FLAGS_HAS(obj->ip_route.r_rtm_flags, (unsigned) RTNH_F_ONLINK)) + ? RTNH_F_ONLINK + : 0; + } else { + const NMPlatformIP4RtNextHop *n2 = &obj->_ip4_route.extra_nexthops[i - 1u]; - if (obj->ip4_route.n_nexthops > 1 - && NM_FLAGS_HAS(obj->ip_route.r_rtm_flags, (unsigned) (RTNH_F_ONLINK)) && gw != 0) { - /* Unlike kernel, we only track the onlink flag per NMPlatformIP4Address, and - * not per nexthop. That is fine for NetworkManager configuring addresses. - * It is not fine for tracking addresses from kernel in platform cache, - * because the rtnh_flags of the nexthops need to be part of nmp_object_id_cmp(). */ - rtnh->rtnh_flags |= RTNH_F_ONLINK; + rtnh->rtnh_flags = + (gw != 0 && NM_FLAGS_HAS(n2->rtnh_flags, (unsigned) RTNH_F_ONLINK)) + ? RTNH_F_ONLINK + : 0; } rtnh->rtnh_len = (char *) nlmsg_tail(nlmsg_hdr(msg)) - (char *) rtnh; diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 853157d204..68f67b5db4 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -7366,13 +7366,16 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route, "%s" /* ifindex */ "%s%s" /* gateway */ " weight %s" /* weight */ + "%s" /* onlink */ "", NM_PRINT_FMT_QUOTED2(nexthop->gateway != 0 || nexthop->ifindex <= 0, " via ", nm_inet4_ntop(nexthop->gateway, s_gateway), ""), _to_string_dev(str_dev, nexthop->ifindex), - nm_sprintf_buf(weight_str, "%u", nexthop->weight)); + nm_sprintf_buf(weight_str, "%u", nexthop->weight), + NM_FLAGS_HAS(nexthop->rtnh_flags, (unsigned) RTNH_F_ONLINK) ? " onlink" + : ""); } } } @@ -8931,7 +8934,11 @@ nm_platform_ip4_rt_nexthop_hash_update(const NMPlatformIP4RtNextHop *obj, nm_assert(obj); w = for_id ? NM_MAX(obj->weight, 1u) : obj->weight; - nm_hash_update_vals(h, obj->ifindex, obj->gateway, w); + nm_hash_update_vals(h, + obj->ifindex, + obj->gateway, + w, + for_id ? (obj->rtnh_flags & RTNH_F_ONLINK) : obj->rtnh_flags); } void @@ -8968,7 +8975,6 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj, obj->initrwnd, obj->mtu, obj->rto_min, - obj->r_rtm_flags & RTNH_F_ONLINK, NM_HASH_COMBINE_BOOLS(guint16, obj->quickack, obj->lock_window, @@ -8986,7 +8992,8 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj, obj->via.addr_family == AF_INET6 ? obj->via.addr.addr6 : in6addr_any, obj->gateway, - _ip4_route_weight_normalize(n_nexthops, obj->weight, FALSE)); + _ip4_route_weight_normalize(n_nexthops, obj->weight, FALSE), + obj->r_rtm_flags & RTNH_F_ONLINK); } } break; @@ -9079,6 +9086,11 @@ nm_platform_ip4_rt_nexthop_cmp(const NMPlatformIP4RtNextHop *a, w_b = for_id ? NM_MAX(b->weight, 1u) : b->weight; NM_CMP_DIRECT(w_a, w_b); + if (for_id) + NM_CMP_DIRECT(a->rtnh_flags & RTNH_F_ONLINK, b->rtnh_flags & RTNH_F_ONLINK); + else + NM_CMP_FIELD(a, b, rtnh_flags); + return 0; } @@ -9119,12 +9131,6 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a, NM_CMP_FIELD(a, b, mtu); NM_CMP_FIELD(a, b, rto_min); - /* Note that for NetworkManager, the onlink flag is only part of the entire route. - * For kernel, each next hop has it's own onlink flag (rtnh_flags). This means, - * we can only merge ECMP routes, if they agree with their onlink flag, and then - * all next hops are onlink (or not). */ - NM_CMP_DIRECT(a->r_rtm_flags & RTNH_F_ONLINK, b->r_rtm_flags & RTNH_F_ONLINK); - NM_CMP_FIELD_UNSAFE(a, b, quickack); NM_CMP_FIELD_UNSAFE(a, b, lock_window); NM_CMP_FIELD_UNSAFE(a, b, lock_cwnd); @@ -9143,6 +9149,10 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a, NM_CMP_DIRECT(n_nexthops, nm_platform_ip4_route_get_n_nexthops(b)); NM_CMP_DIRECT(_ip4_route_weight_normalize(n_nexthops, a->weight, FALSE), _ip4_route_weight_normalize(n_nexthops, b->weight, FALSE)); + /* The onlink flag is per-nexthop. For the first nexthop it is in + * r_rtm_flags. For extra nexthops, it's compared via + * nm_platform_ip4_rt_nexthop_cmp(). */ + NM_CMP_DIRECT(a->r_rtm_flags & RTNH_F_ONLINK, b->r_rtm_flags & RTNH_F_ONLINK); } } break; diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index 033dc51541..e33c30206e 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -287,7 +287,7 @@ guint _nm_platform_signal_id_get(NMPlatformSignalIdType signal_type); #define __NMPlatformIPRoute_COMMON \ __NMPlatformObjWithIfindex_COMMON; \ \ - /* rtnh_flags + /* rtm_flags and rtnh_flags * * Routes with rtm_flags RTM_F_CLONED are hidden by platform and * do not exist from the point-of-view of platform users. @@ -295,8 +295,15 @@ guint _nm_platform_signal_id_get(NMPlatformSignalIdType signal_type); * * NOTE: currently we ignore all flags except RTM_F_CLONED * and RTNH_F_ONLINK. - * We also may not properly consider the flags as part of the ID - * in route-cmp. */ \ + * + * For IPv4 routes, the RTNH_F_ONLINK flag here applies to the + * first nexthop (which is embedded in the route struct). Extra + * nexthops (NMPlatformIP4RtNextHop) each have their own + * rtnh_flags field. + * + * For single-hop routes, this field comes directly from + * rtm_flags. For multi-hop routes from kernel, the first + * nexthop's RTNH_F_ONLINK from rtnh_flags is merged here. */ \ unsigned r_rtm_flags; \ \ /* RTA_METRICS.RTAX_ADVMSS (iproute2: advmss) */ \ @@ -733,10 +740,10 @@ typedef struct { */ guint16 weight; - /* FIXME: each next hop in kernel also has a rtnh_flags (for example to - * set RTNH_F_ONLINK). As the next hop is part of the identifier of an - * IPv4 route, so is their flags. We must also track the flag, otherwise - * two routes that look different for kernel, get merged by platform cache. */ + /* Each next hop in kernel has its own rtnh_flags (for example to + * set RTNH_F_ONLINK). The flags are part of the identifier of a + * route. */ + unsigned rtnh_flags; } NMPlatformIP4RtNextHop; typedef struct { From 9233656d9f3783ec60d46efc6f0aea5534f6f967 Mon Sep 17 00:00:00 2001 From: Rahul Rajesh Date: Tue, 10 Feb 2026 14:19:38 -0500 Subject: [PATCH 03/20] platform: expand nmp object type flags to guint64 To allow for more than 32 NMP_OBJECT_* types. (cherry picked from commit ad78bd8570716321d6875570c1a0365c8c91537b) --- src/core/nm-ip-config.c | 8 ++++---- src/core/nm-ip-config.h | 2 +- src/core/nm-l3cfg.c | 4 ++-- src/core/nm-l3cfg.h | 6 +++--- src/core/platform/tests/test-common.c | 6 +++--- src/core/platform/tests/test-common.h | 4 ++-- src/libnm-platform/nmp-base.h | 6 +++--- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/core/nm-ip-config.c b/src/core/nm-ip-config.c index 975ae20d45..75a75b4299 100644 --- a/src/core/nm-ip-config.c +++ b/src/core/nm-ip-config.c @@ -54,7 +54,7 @@ G_DEFINE_ABSTRACT_TYPE(NMIPConfig, nm_ip_config, NM_TYPE_DBUS_OBJECT) /*****************************************************************************/ -static void _handle_platform_change(NMIPConfig *self, guint32 obj_type_flags, gboolean is_init); +static void _handle_platform_change(NMIPConfig *self, guint64 obj_type_flags, gboolean is_init); static void _handle_l3cd_changed(NMIPConfig *self, const NML3ConfigData *l3cd); /*****************************************************************************/ @@ -75,7 +75,7 @@ static void _notify_platform_handle(NMIPConfig *self, gint64 now_msec) { NMIPConfigPrivate *priv = NM_IP_CONFIG_GET_PRIVATE(self); - guint32 obj_type_flags; + guint64 obj_type_flags; nm_clear_g_source_inst(&priv->notify_platform_timeout_source); @@ -96,7 +96,7 @@ _notify_platform_cb(gpointer user_data) } static void -_notify_platform(NMIPConfig *self, guint32 obj_type_flags) +_notify_platform(NMIPConfig *self, guint64 obj_type_flags) { const int addr_family = nm_ip_config_get_addr_family(self); const int IS_IPv4 = NM_IS_IPv4(addr_family); @@ -844,7 +844,7 @@ _handle_l3cd_changed(NMIPConfig *self, const NML3ConfigData *l3cd) } static void -_handle_platform_change(NMIPConfig *self, guint32 obj_type_flags, gboolean is_init) +_handle_platform_change(NMIPConfig *self, guint64 obj_type_flags, gboolean is_init) { const int addr_family = nm_ip_config_get_addr_family(self); const int IS_IPv4 = NM_IS_IPv4(addr_family); diff --git a/src/core/nm-ip-config.h b/src/core/nm-ip-config.h index 47a40bd223..02d654c4fd 100644 --- a/src/core/nm-ip-config.h +++ b/src/core/nm-ip-config.h @@ -35,7 +35,7 @@ struct _NMIPConfigPrivate { GSource *notify_platform_timeout_source; gint64 notify_platform_rlimited_until_msec; gulong l3cfg_notify_id; - guint32 notify_platform_obj_type_flags; + guint64 notify_platform_obj_type_flags; }; struct _NMIPConfig { diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 0d93f76bc2..88a9c24181 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -624,7 +624,7 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data, case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE: nm_strbuf_append(&s, &l, - ", obj-type-flags=0x%x", + ", obj-type-flags=0x%" G_GINT64_MODIFIER "x", notify_data->platform_change_on_idle.obj_type_flags); break; case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT: @@ -1571,7 +1571,7 @@ _load_link(NML3Cfg *self, gboolean initial) /*****************************************************************************/ void -_nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint32 obj_type_flags) +_nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint64 obj_type_flags) { NML3ConfigNotifyData notify_data; diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h index 5f0721da3c..c103c9f233 100644 --- a/src/core/nm-l3cfg.h +++ b/src/core/nm-l3cfg.h @@ -178,7 +178,7 @@ typedef struct { } platform_change; struct { - guint32 obj_type_flags; + guint64 obj_type_flags; } platform_change_on_idle; struct { @@ -207,7 +207,7 @@ struct _NML3Cfg { * NML3Cfg instance. We track some per-l3cfg-data that is only * relevant to NMNetns here. */ struct { - guint32 signal_pending_obj_type_flags; + guint64 signal_pending_obj_type_flags; CList signal_pending_lst; CList ecmp_track_ifindex_lst_head; } internal_netns; @@ -223,7 +223,7 @@ NML3Cfg *nm_l3cfg_new(NMNetns *netns, int ifindex); gboolean nm_l3cfg_is_ready(NML3Cfg *self); -void _nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint32 obj_type_flags); +void _nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint64 obj_type_flags); void _nm_l3cfg_notify_platform_change(NML3Cfg *self, NMPlatformSignalChangeType change_type, diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c index 91445b30f8..3035bd491d 100644 --- a/src/core/platform/tests/test-common.c +++ b/src/core/platform/tests/test-common.c @@ -1230,7 +1230,7 @@ out: } gboolean -nmtstp_check_platform_full(NMPlatform *platform, guint32 obj_type_flags, gboolean do_assert) +nmtstp_check_platform_full(NMPlatform *platform, guint64 obj_type_flags, gboolean do_assert) { static const NMPObjectType obj_types[] = { NMP_OBJECT_TYPE_IP4_ADDRESS, @@ -1265,7 +1265,7 @@ nmtstp_check_platform_full(NMPlatform *platform, guint32 obj_type_flags, gboolea for (i_obj_types = 0; i_obj_types < (int) G_N_ELEMENTS(obj_types); i_obj_types++) { const NMPObjectType obj_type = obj_types[i_obj_types]; - const guint32 i_obj_type_flags = nmp_object_type_to_flags(obj_type); + const guint64 i_obj_type_flags = nmp_object_type_to_flags(obj_type); gs_unref_ptrarray GPtrArray *arr1 = NULL; gs_unref_ptrarray GPtrArray *arr2 = NULL; NMPLookup lookup; @@ -1408,7 +1408,7 @@ nmtstp_check_platform_full(NMPlatform *platform, guint32 obj_type_flags, gboolea } void -nmtstp_check_platform(NMPlatform *platform, guint32 obj_type_flags) +nmtstp_check_platform(NMPlatform *platform, guint64 obj_type_flags) { if (!nmtstp_check_platform_full(platform, obj_type_flags, FALSE)) { /* It's unclear why this failure sometimes happens. It happens diff --git a/src/core/platform/tests/test-common.h b/src/core/platform/tests/test-common.h index 85ed79615b..fcda583d2a 100644 --- a/src/core/platform/tests/test-common.h +++ b/src/core/platform/tests/test-common.h @@ -140,9 +140,9 @@ int nmtstp_run_command(const char *format, ...) _nm_printf(1, 2); /*****************************************************************************/ gboolean -nmtstp_check_platform_full(NMPlatform *platform, guint32 obj_type_flags, gboolean do_assert); +nmtstp_check_platform_full(NMPlatform *platform, guint64 obj_type_flags, gboolean do_assert); -void nmtstp_check_platform(NMPlatform *platform, guint32 obj_type_flags); +void nmtstp_check_platform(NMPlatform *platform, guint64 obj_type_flags); /*****************************************************************************/ diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h index f665629645..3dec106a50 100644 --- a/src/libnm-platform/nmp-base.h +++ b/src/libnm-platform/nmp-base.h @@ -194,15 +194,15 @@ typedef enum _nm_packed { NMP_OBJECT_TYPE_MAX = __NMP_OBJECT_TYPE_LAST - 1, } NMPObjectType; -static inline guint32 +static inline guint64 nmp_object_type_to_flags(NMPObjectType obj_type) { - G_STATIC_ASSERT_EXPR(NMP_OBJECT_TYPE_MAX < 32); + G_STATIC_ASSERT_EXPR(NMP_OBJECT_TYPE_MAX < 64); nm_assert(_NM_INT_NOT_NEGATIVE(obj_type)); nm_assert(obj_type < NMP_OBJECT_TYPE_MAX); - return ((guint32) 1u) << obj_type; + return ((guint64) 1u) << obj_type; } /*****************************************************************************/ From 4f3333bdea69a35bc1f60790be8ca300a112c7d1 Mon Sep 17 00:00:00 2001 From: Rahul Rajesh Date: Thu, 22 Jan 2026 17:09:48 -0500 Subject: [PATCH 04/20] platform: add support for GENEVE tunnels GENEVE (Generic Network Virtualization Encapsulation) is a network tunneling protocol that provides a flexible encapsulation format for overlay networks. It uses UDP as the transport protocol and supports variable-length metadata in the tunnel header. This patch adds GENEVE tunnel to NM's platform layer: - Add platform API functions (nm_platform_link_geneve_add, nm_platform_link_get_lnk_geneve) - Netlink message parsing for the following attributes: * IFLA_GENEVE_ID - VNI (Virtual Network Identifier) IPv4 and IPv6 remote * IFLA_GENEVE_REMOTE * IFLA_GENEVE_REMOTE6 TTL, TOS, and DF flags * IFLA_GENEVE_TTL * IFLA_GENEVE_TOS * IFLA_GENEVE_DF UDP destination port * IFLA_GENEVE_PORT - Add test cases for GENEVE tunnel creation and detection with two test modes covering IPv4 and IPv6. The implementation tries to follow the same patterns as other tunnel types (GRE, VXLAN, etc.) and integrates with the existing platform abstraction layer. (cherry picked from commit 29c8bbe21a462138a53d9925193b4613011b6cde) --- src/core/platform/tests/test-common.c | 57 +++++++++++++- src/core/platform/tests/test-common.h | 4 + src/core/platform/tests/test-link.c | 64 ++++++++++++++- src/libnm-glib-aux/nm-shared-utils.h | 1 + src/libnm-platform/nm-linux-platform.c | 86 +++++++++++++++++++++ src/libnm-platform/nm-platform.c | 79 +++++++++++++++++++ src/libnm-platform/nm-platform.h | 24 ++++++ src/libnm-platform/nmp-base.h | 1 + src/libnm-platform/nmp-object.c | 12 +++ src/libnm-platform/nmp-object.h | 8 ++ src/libnm-platform/tests/test-nm-platform.c | 2 + 11 files changed, 332 insertions(+), 6 deletions(-) diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c index 3035bd491d..f1f7caee7a 100644 --- a/src/core/platform/tests/test-common.c +++ b/src/core/platform/tests/test-common.c @@ -2393,6 +2393,61 @@ nmtstp_link_dummy_add(NMPlatform *platform, int external_command, const char *na return pllink; } +const NMPlatformLink * +nmtstp_link_geneve_add(NMPlatform *platform, + int external_command, + const char *name, + const NMPlatformLnkGeneve *lnk) +{ + const NMPlatformLink *pllink = NULL; + int success; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + g_assert(lnk->remote || !IN6_IS_ADDR_UNSPECIFIED(&lnk->remote6)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + char remote[NM_INET_ADDRSTRLEN]; + char remote6[NM_INET_ADDRSTRLEN]; + char str_ttl[30]; + + if (lnk->remote) + nm_inet4_ntop(lnk->remote, remote); + else + remote[0] = '\0'; + + if (memcmp(&lnk->remote6, &in6addr_any, sizeof(in6addr_any))) + nm_inet6_ntop(&lnk->remote6, remote6); + else + remote6[0] = '\0'; + + success = !nmtstp_run_command( + "ip link add %s type geneve id %u remote %s %s tos %02x dstport %u%s", + name, + lnk->id, + remote[0] ? remote : remote6, + lnk->ttl > 0 ? nm_sprintf_buf(str_ttl, "ttl %u", lnk->ttl & 0xff) + : lnk->ttl == 0 ? "ttl auto" + : "ttl inherit", + lnk->tos, + lnk->dst_port, + lnk->df == 1 ? " df set " + : lnk->df == 2 ? " df inherit " + : ""); + + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_GENEVE, 100); + } else + success = NMTST_NM_ERR_SUCCESS(nm_platform_link_geneve_add(platform, name, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_GENEVE); + + return pllink; +} + const NMPlatformLink * nmtstp_link_gre_add(NMPlatform *platform, int external_command, @@ -3024,8 +3079,6 @@ nmtstp_link_vxlan_add(NMPlatform *platform, return pllink; } -/*****************************************************************************/ - const NMPlatformLink * nmtstp_link_get_typed(NMPlatform *platform, int ifindex, const char *name, NMLinkType link_type) { diff --git a/src/core/platform/tests/test-common.h b/src/core/platform/tests/test-common.h index fcda583d2a..251fa51090 100644 --- a/src/core/platform/tests/test-common.h +++ b/src/core/platform/tests/test-common.h @@ -474,6 +474,10 @@ const NMPlatformLink *nmtstp_link_veth_add(NMPlatform *platform, const char *peer); const NMPlatformLink * nmtstp_link_dummy_add(NMPlatform *platform, int external_command, const char *name); +const NMPlatformLink *nmtstp_link_geneve_add(NMPlatform *platform, + int external_command, + const char *name, + const NMPlatformLnkGeneve *lnk); const NMPlatformLink *nmtstp_link_gre_add(NMPlatform *platform, int external_command, const char *name, diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c index 77e7e64121..5618308f80 100644 --- a/src/core/platform/tests/test-link.c +++ b/src/core/platform/tests/test-link.c @@ -1388,10 +1388,11 @@ test_software_detect(gconstpointer user_data) const gboolean ext = test_data->external_command; NMPlatformLnkBridge lnk_bridge = {}; NMPlatformLnkTun lnk_tun; - NMPlatformLnkGre lnk_gre = {}; - NMPlatformLnkVti lnk_vti = {}; - NMPlatformLnkVti6 lnk_vti6 = {}; - nm_auto_close int tun_fd = -1; + NMPlatformLnkGeneve lnk_geneve = {}; + NMPlatformLnkGre lnk_gre = {}; + NMPlatformLnkVti lnk_vti = {}; + NMPlatformLnkVti6 lnk_vti6 = {}; + nm_auto_close int tun_fd = -1; gboolean module_loaded; nmtstp_run_command_check("ip link add %s type dummy", PARENT_NAME); @@ -1434,6 +1435,31 @@ test_software_detect(gconstpointer user_data) g_error("Failed adding Bridge interface"); break; + case NM_LINK_TYPE_GENEVE: + { + switch (test_data->test_mode) { + case 0: + lnk_geneve.id = 42; + lnk_geneve.remote = nmtst_inet4_from_string("192.168.1.100"); + lnk_geneve.ttl = 64; + lnk_geneve.tos = 0; + lnk_geneve.dst_port = 6081; + lnk_geneve.df = 0; + break; + case 1: + lnk_geneve.id = 12345; + lnk_geneve.remote6 = nmtst_inet6_from_string("2001:db8::1"); + lnk_geneve.ttl = 128; + lnk_geneve.tos = 16; + lnk_geneve.dst_port = 6082; + lnk_geneve.df = 1; + break; + } + + g_assert(nmtstp_link_geneve_add(NULL, ext, DEVICE_NAME, &lnk_geneve)); + break; + } + case NM_LINK_TYPE_GRE: module_loaded = nmtstp_ensure_module("ip_gre"); @@ -2208,6 +2234,34 @@ test_software_detect(gconstpointer user_data) } break; } + case NM_LINK_TYPE_GENEVE: + { + const NMPlatformLnkGeneve *plnk = &lnk->lnk_geneve; + + g_assert(plnk == nm_platform_link_get_lnk_geneve(NM_PLATFORM_GET, ifindex, NULL)); + + switch (test_data->test_mode) { + case 0: + g_assert_cmpint(plnk->id, ==, 42); + nmtst_assert_ip4_address(plnk->remote, "192.168.1.100"); + nmtst_assert_ip6_address(&plnk->remote6, "::"); + g_assert_cmpint(plnk->ttl, ==, 64); + g_assert_cmpint(plnk->tos, ==, 0); + g_assert_cmpint(plnk->dst_port, ==, 6081); + g_assert_cmpint(plnk->df, ==, 0); + break; + case 1: + g_assert_cmpint(plnk->id, ==, 12345); + nmtst_assert_ip4_address(plnk->remote, "0.0.0.0"); + nmtst_assert_ip6_address(&plnk->remote6, "2001:db8::1"); + g_assert_cmpint(plnk->ttl, ==, 128); + g_assert_cmpint(plnk->tos, ==, 16); + g_assert_cmpint(plnk->dst_port, ==, 6082); + g_assert_cmpint(plnk->df, ==, 1); + break; + } + break; + } case NM_LINK_TYPE_WIREGUARD: { const NMPlatformLnkWireGuard *plnk = &lnk->lnk_wireguard; @@ -4143,6 +4197,8 @@ _nmtstp_setup_tests(void) g_test_add_func("/link/external", test_external); test_software_detect_add("/link/software/detect/bridge", NM_LINK_TYPE_BRIDGE, 0); + test_software_detect_add("/link/software/detect/geneve/0", NM_LINK_TYPE_GENEVE, 0); + test_software_detect_add("/link/software/detect/geneve/1", NM_LINK_TYPE_GENEVE, 1); test_software_detect_add("/link/software/detect/gre", NM_LINK_TYPE_GRE, 0); test_software_detect_add("/link/software/detect/gretap", NM_LINK_TYPE_GRETAP, 0); test_software_detect_add("/link/software/detect/ip6tnl/0", NM_LINK_TYPE_IP6TNL, 0); diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index 1fca6015d8..0bc6e29495 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -130,6 +130,7 @@ typedef enum { #define _NM_LINK_TYPE_SW_FIRST NM_LINK_TYPE_BNEP NM_LINK_TYPE_BNEP, /* Bluetooth Ethernet emulation */ NM_LINK_TYPE_DUMMY, + NM_LINK_TYPE_GENEVE, NM_LINK_TYPE_GRE, NM_LINK_TYPE_GRETAP, NM_LINK_TYPE_IFB, diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index 3f5ed9fc1b..b5df8b17d1 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -862,6 +862,7 @@ static const LinkDesc link_descs[] = { [NM_LINK_TYPE_BNEP] = {"bluetooth", NULL, "bluetooth"}, [NM_LINK_TYPE_DUMMY] = {"dummy", "dummy", NULL}, + [NM_LINK_TYPE_GENEVE] = {"geneve", "geneve", "geneve"}, [NM_LINK_TYPE_GRE] = {"gre", "gre", NULL}, [NM_LINK_TYPE_GRETAP] = {"gretap", "gretap", NULL}, [NM_LINK_TYPE_IFB] = {"ifb", "ifb", NULL}, @@ -909,6 +910,7 @@ _link_type_from_rtnl_type(const char *name) NM_LINK_TYPE_BOND, /* "bond" */ NM_LINK_TYPE_BRIDGE, /* "bridge" */ NM_LINK_TYPE_DUMMY, /* "dummy" */ + NM_LINK_TYPE_GENEVE, /* "geneve" */ NM_LINK_TYPE_GRE, /* "gre" */ NM_LINK_TYPE_GRETAP, /* "gretap" */ NM_LINK_TYPE_HSR, /* "hsr" */ @@ -987,6 +989,7 @@ _link_type_from_devtype(const char *name) NM_LINK_TYPE_BNEP, /* "bluetooth" */ NM_LINK_TYPE_BOND, /* "bond" */ NM_LINK_TYPE_BRIDGE, /* "bridge" */ + NM_LINK_TYPE_GENEVE, /* "geneve" */ NM_LINK_TYPE_HSR, /* "hsr" */ NM_LINK_TYPE_PPP, /* "ppp" */ NM_LINK_TYPE_VLAN, /* "vlan" */ @@ -1856,6 +1859,57 @@ _parse_lnk_gre(const char *kind, struct nlattr *info_data) /*****************************************************************************/ +static NMPObject * +_parse_lnk_geneve(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_GENEVE_ID] = {.type = NLA_U32}, + [IFLA_GENEVE_REMOTE] = {.type = NLA_U32}, + [IFLA_GENEVE_REMOTE6] = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)}, + [IFLA_GENEVE_TTL] = {.type = NLA_U8}, + [IFLA_GENEVE_TOS] = {.type = NLA_U8}, + [IFLA_GENEVE_TTL_INHERIT] = {.type = NLA_U8}, + [IFLA_GENEVE_PORT] = {.type = NLA_U16}, + [IFLA_GENEVE_DF] = {.type = NLA_U8}, + }; + + struct nlattr *tb[G_N_ELEMENTS(policy)]; + NMPObject *obj; + NMPlatformLnkGeneve *props; + + if (!info_data || !nm_streq0(kind, "geneve")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_GENEVE, NULL); + props = &obj->lnk_geneve; + + if (tb[IFLA_GENEVE_ID]) + props->id = nla_get_u32(tb[IFLA_GENEVE_ID]); + if (tb[IFLA_GENEVE_REMOTE]) + props->remote = nla_get_u32(tb[IFLA_GENEVE_REMOTE]); + if (tb[IFLA_GENEVE_REMOTE6]) + props->remote6 = *nla_data_as(struct in6_addr, tb[IFLA_GENEVE_REMOTE6]); + + if (tb[IFLA_GENEVE_TTL_INHERIT] && nla_get_u8(tb[IFLA_GENEVE_TTL_INHERIT])) + props->ttl = -1; + else if (tb[IFLA_GENEVE_TTL]) + props->ttl = nla_get_u8(tb[IFLA_GENEVE_TTL]); + + if (tb[IFLA_GENEVE_TOS]) + props->tos = nla_get_u8(tb[IFLA_GENEVE_TOS]); + if (tb[IFLA_GENEVE_PORT]) + props->dst_port = ntohs(nla_get_u16(tb[IFLA_GENEVE_PORT])); + if (tb[IFLA_GENEVE_DF]) + props->df = nla_get_u8(tb[IFLA_GENEVE_DF]); + + return obj; +} + +/*****************************************************************************/ + static NMPObject * _parse_lnk_hsr(const char *kind, struct nlattr *info_data) { @@ -3694,6 +3748,9 @@ _new_from_nl_link(NMPlatform *platform, case NM_LINK_TYPE_BOND: lnk_data = _parse_lnk_bond(nl_info_kind, nl_info_data); break; + case NM_LINK_TYPE_GENEVE: + lnk_data = _parse_lnk_geneve(nl_info_kind, nl_info_data); + break; case NM_LINK_TYPE_GRE: case NM_LINK_TYPE_GRETAP: lnk_data = _parse_lnk_gre(nl_info_kind, nl_info_data); @@ -5194,6 +5251,35 @@ _nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpo nla_nest_end(msg, info_peer); break; } + case NM_LINK_TYPE_GENEVE: + { + const NMPlatformLnkGeneve *props = extra_data; + + nm_assert(props); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U32(msg, IFLA_GENEVE_ID, props->id); + + if (props->remote) { + NLA_PUT_U32(msg, IFLA_GENEVE_REMOTE, props->remote); + } else if (!IN6_IS_ADDR_UNSPECIFIED(&props->remote6)) { + NLA_PUT(msg, IFLA_GENEVE_REMOTE6, sizeof(props->remote6), &props->remote6); + } + NLA_PUT_U16(msg, IFLA_GENEVE_PORT, htons(props->dst_port)); + if (props->ttl == -1) { + NLA_PUT_U8(msg, IFLA_GENEVE_TTL_INHERIT, 1); + } else { + /* When you want to specify a TTL value, + * don't add TTL_INHERIT to the message */ + NLA_PUT_U8(msg, IFLA_GENEVE_TTL, props->ttl & 0xff); + } + NLA_PUT_U8(msg, IFLA_GENEVE_TOS, props->tos); + NLA_PUT_U8(msg, IFLA_GENEVE_DF, props->df); + break; + } + case NM_LINK_TYPE_GRE: case NM_LINK_TYPE_GRETAP: { diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 68f67b5db4..914dcd8654 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -1388,6 +1388,12 @@ nm_platform_link_add(NMPlatform *self, case NM_LINK_TYPE_VETH: nm_sprintf_buf(buf, ", veth-peer \"%s\"", (const char *) extra_data); break; + case NM_LINK_TYPE_GENEVE: + nm_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_geneve_to_string((const NMPlatformLnkGeneve *) extra_data, + buf_p, + buf_len); + break; case NM_LINK_TYPE_GRE: case NM_LINK_TYPE_GRETAP: nm_strbuf_append_str(&buf_p, &buf_len, ", "); @@ -2565,6 +2571,12 @@ nm_platform_link_get_lnk_bridge(NMPlatform *self, int ifindex, const NMPlatformL return _link_get_lnk(self, ifindex, NM_LINK_TYPE_BRIDGE, out_link); } +const NMPlatformLnkGeneve * +nm_platform_link_get_lnk_geneve(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_GENEVE, out_link); +} + const NMPlatformLnkGre * nm_platform_link_get_lnk_gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) { @@ -6494,6 +6506,52 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le return buf; } +const char * +nm_platform_lnk_geneve_to_string(const NMPlatformLnkGeneve *lnk, char *buf, gsize len) +{ + char str_remote[NM_INET_ADDRSTRLEN]; + char str_remote1[30 + NM_INET_ADDRSTRLEN]; + char str_remote6[NM_INET_ADDRSTRLEN]; + char str_remote6_1[30 + NM_INET_ADDRSTRLEN]; + char str_ttl[30]; + char str_tos[30]; + char str_id[30]; + char str_dstport[30]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf( + buf, + len, + "geneve" + "%s" /* id */ + "%s" /* remote */ + "%s" /* remote6 */ + "%s" /* dst_port */ + "%s" /* ttl */ + "%s" /* tos */ + "%s" /* df */ + "", + lnk->id ? nm_sprintf_buf(str_id, " id %u", lnk->id) : "", + lnk->remote + ? nm_sprintf_buf(str_remote, " remote %s", nm_inet4_ntop(lnk->remote, str_remote1)) + : "", + !IN6_IS_ADDR_UNSPECIFIED(&lnk->remote6) + ? nm_sprintf_buf(str_remote6, " remote %s", nm_inet6_ntop(&lnk->remote6, str_remote6_1)) + : "", + lnk->dst_port ? nm_sprintf_buf(str_dstport, " dstport %u", lnk->dst_port) : "", + lnk->ttl > 0 ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl & 0xff) + : lnk->ttl == 0 ? "ttl auto" + : "ttl inherit", + lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf(str_tos, " tos 0x%x", lnk->tos)) + : "", + lnk->df == 1 ? " df set " + : lnk->df == 2 ? " df inherit " + : ""); + return buf; +} + const char * nm_platform_lnk_gre_to_string(const NMPlatformLnkGre *lnk, char *buf, gsize len) { @@ -8494,6 +8552,27 @@ nm_platform_lnk_gre_cmp(const NMPlatformLnkGre *a, const NMPlatformLnkGre *b) return 0; } +void +nm_platform_lnk_geneve_hash_update(const NMPlatformLnkGeneve *obj, NMHashState *h) +{ + nm_hash_update_vals(h, obj->id, obj->remote, obj->dst_port, obj->ttl, obj->tos, obj->df); + nm_hash_update_mem(h, &obj->remote6, sizeof(obj->remote6)); +} + +int +nm_platform_lnk_geneve_cmp(const NMPlatformLnkGeneve *a, const NMPlatformLnkGeneve *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, id); + NM_CMP_FIELD(a, b, remote); + NM_CMP_FIELD_MEMCMP(a, b, remote6); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD(a, b, dst_port); + NM_CMP_FIELD(a, b, df); + return 0; +} + void nm_platform_lnk_hsr_hash_update(const NMPlatformLnkHsr *obj, NMHashState *h) { diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index e33c30206e..ba37d3a270 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -842,6 +842,16 @@ typedef struct { bool use_carrier : 1; } _nm_alignas(NMPlatformObject) NMPlatformLnkBond; +typedef struct { + struct in6_addr remote6; + in_addr_t remote; + guint32 id; + gint32 ttl; + guint16 dst_port; + guint8 tos; + guint8 df; +} _nm_alignas(NMPlatformObject) NMPlatformLnkGeneve; + typedef struct { int parent_ifindex; in_addr_t local; @@ -1865,6 +1875,15 @@ nm_platform_link_vxlan_add(NMPlatform *self, return nm_platform_link_add(self, NM_LINK_TYPE_VXLAN, name, 0, NULL, 0, 0, props, out_link); } +static inline int +nm_platform_link_geneve_add(NMPlatform *self, + const char *name, + const NMPlatformLnkGeneve *props, + const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_GENEVE, name, 0, NULL, 0, 0, props, out_link); +} + static inline int nm_platform_link_6lowpan_add(NMPlatform *self, const char *name, @@ -2150,6 +2169,8 @@ const NMPlatformLnkBond * nm_platform_link_get_lnk_bond(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); const NMPlatformLnkBridge * nm_platform_link_get_lnk_bridge(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkGeneve * +nm_platform_link_get_lnk_geneve(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); const NMPlatformLnkGre * nm_platform_link_get_lnk_gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); const NMPlatformLnkGre * @@ -2489,6 +2510,7 @@ gboolean nm_platform_tc_sync(NMPlatform *self, const char *nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len); const char *nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize len); const char *nm_platform_lnk_bridge_to_string(const NMPlatformLnkBridge *lnk, char *buf, gsize len); +const char *nm_platform_lnk_geneve_to_string(const NMPlatformLnkGeneve *lnk, char *buf, gsize len); const char *nm_platform_lnk_gre_to_string(const NMPlatformLnkGre *lnk, char *buf, gsize len); const char *nm_platform_lnk_hsr_to_string(const NMPlatformLnkHsr *lnk, char *buf, gsize len); const char * @@ -2544,6 +2566,7 @@ nm_platform_mptcp_addr_to_string(const NMPlatformMptcpAddr *mptcp_addr, char *bu int nm_platform_link_cmp(const NMPlatformLink *a, const NMPlatformLink *b); int nm_platform_lnk_bond_cmp(const NMPlatformLnkBond *a, const NMPlatformLnkBond *b); int nm_platform_lnk_bridge_cmp(const NMPlatformLnkBridge *a, const NMPlatformLnkBridge *b); +int nm_platform_lnk_geneve_cmp(const NMPlatformLnkGeneve *a, const NMPlatformLnkGeneve *b); int nm_platform_lnk_gre_cmp(const NMPlatformLnkGre *a, const NMPlatformLnkGre *b); int nm_platform_lnk_hsr_cmp(const NMPlatformLnkHsr *a, const NMPlatformLnkHsr *b); int nm_platform_lnk_infiniband_cmp(const NMPlatformLnkInfiniband *a, @@ -2619,6 +2642,7 @@ void nm_platform_routing_rule_hash_update(const NMPlatformRoutingRule *obj, NMHashState *h); void nm_platform_lnk_bond_hash_update(const NMPlatformLnkBond *obj, NMHashState *h); void nm_platform_lnk_bridge_hash_update(const NMPlatformLnkBridge *obj, NMHashState *h); +void nm_platform_lnk_geneve_hash_update(const NMPlatformLnkGeneve *obj, NMHashState *h); void nm_platform_lnk_gre_hash_update(const NMPlatformLnkGre *obj, NMHashState *h); void nm_platform_lnk_hsr_hash_update(const NMPlatformLnkHsr *obj, NMHashState *h); void nm_platform_lnk_infiniband_hash_update(const NMPlatformLnkInfiniband *obj, NMHashState *h); diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h index 3dec106a50..ae538f5bad 100644 --- a/src/libnm-platform/nmp-base.h +++ b/src/libnm-platform/nmp-base.h @@ -166,6 +166,7 @@ typedef enum _nm_packed { NMP_OBJECT_TYPE_TFILTER, NMP_OBJECT_TYPE_LNK_BRIDGE, + NMP_OBJECT_TYPE_LNK_GENEVE, NMP_OBJECT_TYPE_LNK_GRE, NMP_OBJECT_TYPE_LNK_GRETAP, NMP_OBJECT_TYPE_LNK_HSR, diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c index 56533c99c5..d73eb641f1 100644 --- a/src/libnm-platform/nmp-object.c +++ b/src/libnm-platform/nmp-object.c @@ -3469,6 +3469,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = { .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_bridge_hash_update, .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_bridge_cmp, }, + [NMP_OBJECT_TYPE_LNK_GENEVE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_GENEVE, + .sizeof_data = sizeof(NMPObjectLnkGeneve), + .sizeof_public = sizeof(NMPlatformLnkGeneve), + .obj_type_name = "geneve", + .lnk_link_type = NM_LINK_TYPE_GENEVE, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_geneve_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_geneve_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_geneve_cmp, + }, [NMP_OBJECT_TYPE_LNK_GRE - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h index b526fc3bec..a960c528c7 100644 --- a/src/libnm-platform/nmp-object.h +++ b/src/libnm-platform/nmp-object.h @@ -250,6 +250,10 @@ typedef struct { NMPlatformLnkBond _public; } NMPObjectLnkBond; +typedef struct { + NMPlatformLnkGeneve _public; +} NMPObjectLnkGeneve; + typedef struct { NMPlatformLnkGre _public; } NMPObjectLnkGre; @@ -383,6 +387,9 @@ struct _NMPObject { NMPlatformLnkBond lnk_bond; NMPObjectLnkBond _lnk_bond; + NMPlatformLnkGeneve lnk_geneve; + NMPObjectLnkGeneve _lnk_geneve; + NMPlatformLnkGre lnk_gre; NMPObjectLnkGre _lnk_gre; @@ -543,6 +550,7 @@ _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX(NMPObjectType obj_type) case NMP_OBJECT_TYPE_LNK_BRIDGE: case NMP_OBJECT_TYPE_LNK_BOND: + case NMP_OBJECT_TYPE_LNK_GENEVE: case NMP_OBJECT_TYPE_LNK_GRE: case NMP_OBJECT_TYPE_LNK_GRETAP: case NMP_OBJECT_TYPE_LNK_HSR: diff --git a/src/libnm-platform/tests/test-nm-platform.c b/src/libnm-platform/tests/test-nm-platform.c index 22148f8c64..d087d66f38 100644 --- a/src/libnm-platform/tests/test-nm-platform.c +++ b/src/libnm-platform/tests/test-nm-platform.c @@ -19,6 +19,7 @@ G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPObjectIP6Route)) G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPObjectLink)); G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPObjectLnkBond)); G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPObjectLnkBridge)); +G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPObjectLnkGeneve)); G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPObjectLnkGre)); G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPObjectLnkInfiniband)); G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPObjectLnkIp6Tnl)); @@ -49,6 +50,7 @@ G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPlatformIPXRoute) G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPlatformLink)); G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPlatformLnkBond)); G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPlatformLnkBridge)); +G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPlatformLnkGeneve)); G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPlatformLnkGre)); G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPlatformLnkInfiniband)); G_STATIC_ASSERT(_nm_alignof(NMPlatformObject) == _nm_alignof(NMPlatformLnkIp6Tnl)); From 5e69724a8a891a07ea7355c2f273b8f63b1df0d8 Mon Sep 17 00:00:00 2001 From: Rahul Rajesh Date: Thu, 29 Jan 2026 17:49:02 -0500 Subject: [PATCH 05/20] geneve: add connection profile settings Added support for the following properties in connection profile: id (VNI), remote IPv4/IPv6, ttl, tos, df, destination port. See IP-LINK(8) manual page with command `man 8 ip-link` for more details on the properties. See also previous commit for nm supported attributes. id and remote are mandatory attributes: ``` $ nmcli connection add type geneve save no Error: 'id' argument is required. $ nmcli connection add type geneve id 42 save no Error: 'remote' argument is required. ``` (cherry picked from commit 2aaf88375e9d608a8ce3e54ddca420644c437f7b) --- docs/libnm/libnm-docs.xml | 1 + po/POTFILES.in | 1 + src/libnm-client-impl/libnm.ver | 21 ++ src/libnm-client-public/NetworkManager.h | 1 + src/libnm-client-public/nm-autoptr.h | 1 + ...gen-metadata-nm-settings-libnm-core.xml.in | 28 ++ src/libnm-core-impl/meson.build | 1 + src/libnm-core-impl/nm-connection.c | 16 + .../nm-meta-setting-base-impl.c | 9 + src/libnm-core-impl/nm-setting-geneve.c | 354 ++++++++++++++++++ src/libnm-core-intern/nm-core-internal.h | 1 + .../nm-meta-setting-base-impl.h | 1 + src/libnm-core-public/meson.build | 1 + src/libnm-core-public/nm-connection.h | 6 +- src/libnm-core-public/nm-core-types.h | 1 + src/libnm-core-public/nm-setting-geneve.h | 74 ++++ .../nm-meta-setting-base-impl.c | 9 + .../nm-meta-setting-base-impl.h | 1 + src/libnmc-setting/nm-meta-setting-desc.c | 60 +++ src/libnmc-setting/settings-docs.h.in | 6 + src/nmcli/connections.c | 18 +- .../gen-metadata-nm-settings-nmcli.xml.in | 32 +- vapi/NM-1.0.metadata | 1 + 23 files changed, 634 insertions(+), 10 deletions(-) create mode 100644 src/libnm-core-impl/nm-setting-geneve.c create mode 100644 src/libnm-core-public/nm-setting-geneve.h diff --git a/docs/libnm/libnm-docs.xml b/docs/libnm/libnm-docs.xml index 39f62417d1..a957f469c8 100644 --- a/docs/libnm/libnm-docs.xml +++ b/docs/libnm/libnm-docs.xml @@ -317,6 +317,7 @@ print ("NetworkManager version " + client.get_version())]]> + diff --git a/po/POTFILES.in b/po/POTFILES.in index feeaf9ebbe..44ddd4a64b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -90,6 +90,7 @@ src/libnm-core-impl/nm-setting-connection.c src/libnm-core-impl/nm-setting-dcb.c src/libnm-core-impl/nm-setting-ethtool.c src/libnm-core-impl/nm-setting-generic.c +src/libnm-core-impl/nm-setting-geneve.c src/libnm-core-impl/nm-setting-gsm.c src/libnm-core-impl/nm-setting-hsr.c src/libnm-core-impl/nm-setting-infiniband.c diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver index 6c48978a21..f1467b7fa0 100644 --- a/src/libnm-client-impl/libnm.ver +++ b/src/libnm-client-impl/libnm.ver @@ -2103,3 +2103,24 @@ global: nm_utils_copy_cert_as_user; nm_vpn_plugin_info_supports_safe_private_file_access; } libnm_1_54_0; + +libnm_1_56_2 { +global: + nm_connection_get_setting_geneve; + nm_device_geneve_get_df; + nm_device_geneve_get_dst_port; + nm_device_geneve_get_id; + nm_device_geneve_get_remote; + nm_device_geneve_get_tos; + nm_device_geneve_get_ttl; + nm_device_geneve_get_type; + nm_setting_geneve_df_get_type; + nm_setting_geneve_get_destination_port; + nm_setting_geneve_get_df; + nm_setting_geneve_get_id; + nm_setting_geneve_get_remote; + nm_setting_geneve_get_tos; + nm_setting_geneve_get_ttl; + nm_setting_geneve_get_type; + nm_setting_geneve_new; +} libnm_1_56_0; diff --git a/src/libnm-client-public/NetworkManager.h b/src/libnm-client-public/NetworkManager.h index 9e9a4eb839..2d1c56521e 100644 --- a/src/libnm-client-public/NetworkManager.h +++ b/src/libnm-client-public/NetworkManager.h @@ -37,6 +37,7 @@ #include "nm-setting-dummy.h" #include "nm-setting-ethtool.h" #include "nm-setting-generic.h" +#include "nm-setting-geneve.h" #include "nm-setting-gsm.h" #include "nm-setting-hostname.h" #include "nm-setting-hsr.h" diff --git a/src/libnm-client-public/nm-autoptr.h b/src/libnm-client-public/nm-autoptr.h index f4321ad3a6..f21f2970c8 100644 --- a/src/libnm-client-public/nm-autoptr.h +++ b/src/libnm-client-public/nm-autoptr.h @@ -80,6 +80,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingDcb, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingDummy, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingEthtool, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingGeneric, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingGeneve, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingGsm, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingHostname, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingHsr, g_object_unref) diff --git a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in index c83deea9c1..8d5d3bc98a 100644 --- a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in +++ b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in @@ -1378,6 +1378,34 @@ gprop-type="gchararray" /> + + + + + + + + diff --git a/src/libnm-core-impl/meson.build b/src/libnm-core-impl/meson.build index 610e22792e..1eee3184e5 100644 --- a/src/libnm-core-impl/meson.build +++ b/src/libnm-core-impl/meson.build @@ -17,6 +17,7 @@ libnm_core_settings_sources = files( 'nm-setting-dummy.c', 'nm-setting-ethtool.c', 'nm-setting-generic.c', + 'nm-setting-geneve.c', 'nm-setting-gsm.c', 'nm-setting-hostname.c', 'nm-setting-hsr.c', diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c index 0fbadfb85c..0e98d4348a 100644 --- a/src/libnm-core-impl/nm-connection.c +++ b/src/libnm-core-impl/nm-connection.c @@ -3526,6 +3526,22 @@ nm_connection_get_setting_generic(NMConnection *connection) return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_GENERIC); } +/** + * nm_connection_get_setting_geneve: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingGeneve the connection might contain. + * + * Returns: (transfer none): an #NMSettingGeneve if the connection contains one, otherwise NULL + * + * Since: 1.58, 1.56.2 + **/ +NMSettingGeneve * +nm_connection_get_setting_geneve(NMConnection *connection) +{ + return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_GENEVE); +} + /** * nm_connection_get_setting_gsm: * @connection: the #NMConnection diff --git a/src/libnm-core-impl/nm-meta-setting-base-impl.c b/src/libnm-core-impl/nm-meta-setting-base-impl.c index 52af9ad259..d84490d238 100644 --- a/src/libnm-core-impl/nm-meta-setting-base-impl.c +++ b/src/libnm-core-impl/nm-meta-setting-base-impl.c @@ -27,6 +27,7 @@ #include "nm-setting-dummy.h" #include "nm-setting-ethtool.h" #include "nm-setting-generic.h" +#include "nm-setting-geneve.h" #include "nm-setting-gsm.h" #include "nm-setting-hostname.h" #include "nm-setting-hsr.h" @@ -324,6 +325,13 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = { .setting_name = NM_SETTING_GENERIC_SETTING_NAME, .get_setting_gtype = nm_setting_generic_get_type, }, + [NM_META_SETTING_TYPE_GENEVE] = + { + .meta_type = NM_META_SETTING_TYPE_GENEVE, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_GENEVE_SETTING_NAME, + .get_setting_gtype = nm_setting_geneve_get_type, + }, [NM_META_SETTING_TYPE_GSM] = { .meta_type = NM_META_SETTING_TYPE_GSM, @@ -655,6 +663,7 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = { NM_META_SETTING_TYPE_CDMA, NM_META_SETTING_TYPE_DUMMY, NM_META_SETTING_TYPE_GENERIC, + NM_META_SETTING_TYPE_GENEVE, NM_META_SETTING_TYPE_GSM, NM_META_SETTING_TYPE_HSR, NM_META_SETTING_TYPE_INFINIBAND, diff --git a/src/libnm-core-impl/nm-setting-geneve.c b/src/libnm-core-impl/nm-setting-geneve.c new file mode 100644 index 0000000000..eba0375249 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-geneve.c @@ -0,0 +1,354 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-geneve.h" + +#include + +#include "nm-utils.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-geneve + * @short_description: Describes connection properties for GENEVE interfaces + * + * The #NMSettingGeneve object is a #NMSetting subclass that describes properties + * necessary for connection to GENEVE interfaces. + **/ + +#define DST_PORT_DEFAULT 6081 + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_ID, + PROP_REMOTE, + PROP_DESTINATION_PORT, + PROP_TOS, + PROP_TTL, + PROP_DF, ); + +typedef struct { + char *remote; + guint32 id; + guint32 destination_port; + guint32 tos; + gint32 ttl; + int df; +} NMSettingGenevePrivate; + +/** + * NMSettingGeneve: + * + * GENEVE Settings + */ +struct _NMSettingGeneve { + NMSetting parent; + NMSettingGenevePrivate _priv; +}; + +struct _NMSettingGeneveClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingGeneve, nm_setting_geneve, NM_TYPE_SETTING) + +#define NM_SETTING_GENEVE_GET_PRIVATE(o) \ + _NM_GET_PRIVATE(o, NMSettingGeneve, NM_IS_SETTING_GENEVE, NMSetting) + +/*****************************************************************************/ + +/** + * nm_setting_geneve_get_id: + * @setting: the #NMSettingGeneve + * + * Returns: the #NMSettingGeneve:id property of the setting + * + * Since: 1.58, 1.56.2 + **/ +guint +nm_setting_geneve_get_id(NMSettingGeneve *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GENEVE(setting), 0); + + return NM_SETTING_GENEVE_GET_PRIVATE(setting)->id; +} + +/** + * nm_setting_geneve_get_remote: + * @setting: the #NMSettingGeneve + * + * Returns: the #NMSettingGeneve:remote property of the setting + * + * Since: 1.58, 1.56.2 + **/ +const char * +nm_setting_geneve_get_remote(NMSettingGeneve *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GENEVE(setting), NULL); + + return NM_SETTING_GENEVE_GET_PRIVATE(setting)->remote; +} + +/** + * nm_setting_geneve_get_destination_port: + * @setting: the #NMSettingGeneve + * + * Returns: the #NMSettingGeneve:destination-port property of the setting + * + * Since: 1.58, 1.56.2 + **/ +guint +nm_setting_geneve_get_destination_port(NMSettingGeneve *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GENEVE(setting), DST_PORT_DEFAULT); + + return NM_SETTING_GENEVE_GET_PRIVATE(setting)->destination_port; +} + +/** + * nm_setting_geneve_get_tos: + * @setting: the #NMSettingGeneve + * + * Returns: the #NMSettingGeneve:tos property of the setting + * + * Since: 1.58, 1.56.2 + **/ +guint +nm_setting_geneve_get_tos(NMSettingGeneve *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GENEVE(setting), 0); + + return NM_SETTING_GENEVE_GET_PRIVATE(setting)->tos; +} + +/** + * nm_setting_geneve_get_ttl: + * @setting: the #NMSettingGeneve + * + * Returns: the #NMSettingGeneve:ttl property of the setting + * + * Since: 1.58, 1.56.2 + **/ +guint +nm_setting_geneve_get_ttl(NMSettingGeneve *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GENEVE(setting), 0); + + return NM_SETTING_GENEVE_GET_PRIVATE(setting)->ttl; +} + +/** + * nm_setting_geneve_get_df: + * @setting: the #NMSettingGeneve + * + * Returns: the #NMSettingGeneve:df property of the setting + * + * Since: 1.58, 1.56.2 + **/ +NMSettingGeneveDf +nm_setting_geneve_get_df(NMSettingGeneve *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GENEVE(setting), NM_SETTING_GENEVE_DF_UNSET); + + return NM_SETTING_GENEVE_GET_PRIVATE(setting)->df; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingGenevePrivate *priv = NM_SETTING_GENEVE_GET_PRIVATE(setting); + + if (priv->id == 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is required")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_GENEVE_SETTING_NAME, NM_SETTING_GENEVE_ID); + return FALSE; + } + + if (!priv->remote) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is required")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_GENEVE_SETTING_NAME, NM_SETTING_GENEVE_REMOTE); + return FALSE; + } + + if (!nm_inet_parse_bin(AF_UNSPEC, priv->remote, NULL, NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid IP address"), + priv->remote); + g_prefix_error(error, "%s.%s: ", NM_SETTING_GENEVE_SETTING_NAME, NM_SETTING_GENEVE_REMOTE); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +nm_setting_geneve_init(NMSettingGeneve *self) +{} + +/** + * nm_setting_geneve_new: + * + * Creates a new #NMSettingGeneve object with default values. + * + * Returns: (transfer full): the new empty #NMSettingGeneve object + * + * Since: 1.58, 1.56.2 + **/ +NMSetting * +nm_setting_geneve_new(void) +{ + return g_object_new(NM_TYPE_SETTING_GENEVE, NULL); +} + +static void +nm_setting_geneve_class_init(NMSettingGeneveClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray *properties_override = _nm_sett_info_property_override_create_array(); + + object_class->get_property = _nm_setting_property_get_property_direct; + object_class->set_property = _nm_setting_property_set_property_direct; + + setting_class->verify = verify; + + /** + * NMSettingGeneve:id: + * + * Specifies the GENEVE Network Identifier (or GENEVE Segment Identifier) to + * use. + * + * Since: 1.58, 1.56.2 + **/ + _nm_setting_property_define_direct_uint32(properties_override, + obj_properties, + NM_SETTING_GENEVE_ID, + PROP_ID, + 0, + (1 << 24) - 1, + 0, + NM_SETTING_PARAM_INFERRABLE, + NMSettingGenevePrivate, + id); + + /** + * NMSettingGeneve:remote: + * + * Specifies the unicast destination IP address to use in outgoing packets + * when communicating with the remote GENEVE tunnel endpoint. + * + * Since: 1.58, 1.56.2 + **/ + _nm_setting_property_define_direct_string(properties_override, + obj_properties, + NM_SETTING_GENEVE_REMOTE, + PROP_REMOTE, + NM_SETTING_PARAM_REQUIRED, + NMSettingGenevePrivate, + remote, + .direct_set_string_ip_address_addr_family = + AF_UNSPEC + 1, + .direct_string_allow_empty = TRUE); + + /** + * NMSettingGeneve:destination-port: + * + * Specifies the UDP destination port to communicate to the remote GENEVE + * tunnel endpoint. + * + * Since: 1.58, 1.56.2 + **/ + _nm_setting_property_define_direct_uint32(properties_override, + obj_properties, + NM_SETTING_GENEVE_DESTINATION_PORT, + PROP_DESTINATION_PORT, + 0, + G_MAXUINT16, + DST_PORT_DEFAULT, + NM_SETTING_PARAM_INFERRABLE, + NMSettingGenevePrivate, + destination_port); + + /** + * NMSettingGeneve:tos: + * + * Specifies the TOS value to use in outgoing packets. + * The special value "inherit" (1) means inherit from outer packet. + * + * Since: 1.58, 1.56.2 + **/ + _nm_setting_property_define_direct_uint32(properties_override, + obj_properties, + NM_SETTING_GENEVE_TOS, + PROP_TOS, + 0, + 255, + 0, + NM_SETTING_PARAM_INFERRABLE, + NMSettingGenevePrivate, + tos); + + /** + * NMSettingGeneve:ttl: + * + * Specifies the time-to-live value to use in outgoing packets. + * The special value "inherit" (-1) means inherit from outer packet, 0 means auto, 1-255 are fixed values. + * + * Since: 1.58, 1.56.2 + **/ + _nm_setting_property_define_direct_int32(properties_override, + obj_properties, + NM_SETTING_GENEVE_TTL, + PROP_TTL, + -1, + 255, + 0, + NM_SETTING_PARAM_INFERRABLE, + NMSettingGenevePrivate, + ttl); + + /** + * NMSettingGeneve:df: + * + * Specifies how the Don't Fragment (DF) flag should be handled in the outer IP + * header of GENEVE tunnel packets. + * + * %NM_SETTING_GENEVE_DF_UNSET (0): Don't set the DF flag, packets may be fragmented. + * %NM_SETTING_GENEVE_DF_SET (1): Always set the DF flag, packets will not be fragmented. + * %NM_SETTING_GENEVE_DF_INHERIT (2): Inherit the DF flag from the inner IP header. + * + * Since: 1.58, 1.56.2 + **/ + _nm_setting_property_define_direct_enum(properties_override, + obj_properties, + NM_SETTING_GENEVE_DF, + PROP_DF, + NM_TYPE_SETTING_GENEVE_DF, + NM_SETTING_GENEVE_DF_UNSET, + NM_SETTING_PARAM_INFERRABLE, + NULL, + NMSettingGenevePrivate, + df); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, + NM_META_SETTING_TYPE_GENEVE, + NULL, + properties_override, + G_STRUCT_OFFSET(NMSettingGeneve, _priv)); +} diff --git a/src/libnm-core-intern/nm-core-internal.h b/src/libnm-core-intern/nm-core-internal.h index b8df4d2e9d..f74dfc59d8 100644 --- a/src/libnm-core-intern/nm-core-internal.h +++ b/src/libnm-core-intern/nm-core-internal.h @@ -37,6 +37,7 @@ #include "nm-setting-dcb.h" #include "nm-setting-dummy.h" #include "nm-setting-generic.h" +#include "nm-setting-geneve.h" #include "nm-setting-gsm.h" #include "nm-setting-hsr.h" #include "nm-setting-hostname.h" diff --git a/src/libnm-core-intern/nm-meta-setting-base-impl.h b/src/libnm-core-intern/nm-meta-setting-base-impl.h index d1535e5e1f..8f512d09c6 100644 --- a/src/libnm-core-intern/nm-meta-setting-base-impl.h +++ b/src/libnm-core-intern/nm-meta-setting-base-impl.h @@ -121,6 +121,7 @@ typedef enum _nm_packed { NM_META_SETTING_TYPE_DUMMY, NM_META_SETTING_TYPE_ETHTOOL, NM_META_SETTING_TYPE_GENERIC, + NM_META_SETTING_TYPE_GENEVE, NM_META_SETTING_TYPE_GSM, NM_META_SETTING_TYPE_HOSTNAME, NM_META_SETTING_TYPE_HSR, diff --git a/src/libnm-core-public/meson.build b/src/libnm-core-public/meson.build index 086801d96d..2a4f143f75 100644 --- a/src/libnm-core-public/meson.build +++ b/src/libnm-core-public/meson.build @@ -22,6 +22,7 @@ libnm_core_headers = files( 'nm-setting-dummy.h', 'nm-setting-ethtool.h', 'nm-setting-generic.h', + 'nm-setting-geneve.h', 'nm-setting-gsm.h', 'nm-setting-hsr.h', 'nm-setting-hostname.h', diff --git a/src/libnm-core-public/nm-connection.h b/src/libnm-core-public/nm-connection.h index ff3e0f924a..bc1ee4922b 100644 --- a/src/libnm-core-public/nm-connection.h +++ b/src/libnm-core-public/nm-connection.h @@ -206,8 +206,10 @@ NMSettingCdma *nm_connection_get_setting_cdma(NMConnection *connection); NMSettingConnection *nm_connection_get_setting_connection(NMConnection *connection); NMSettingDcb *nm_connection_get_setting_dcb(NMConnection *connection); NM_AVAILABLE_IN_1_8 -NMSettingDummy *nm_connection_get_setting_dummy(NMConnection *connection); -NMSettingGeneric *nm_connection_get_setting_generic(NMConnection *connection); +NMSettingDummy *nm_connection_get_setting_dummy(NMConnection *connection); +NMSettingGeneric *nm_connection_get_setting_generic(NMConnection *connection); +NM_AVAILABLE_IN_1_56_2 +NMSettingGeneve *nm_connection_get_setting_geneve(NMConnection *connection); NMSettingGsm *nm_connection_get_setting_gsm(NMConnection *connection); NMSettingInfiniband *nm_connection_get_setting_infiniband(NMConnection *connection); NM_AVAILABLE_IN_1_2 diff --git a/src/libnm-core-public/nm-core-types.h b/src/libnm-core-public/nm-core-types.h index 617d6b7415..81743e31d7 100644 --- a/src/libnm-core-public/nm-core-types.h +++ b/src/libnm-core-public/nm-core-types.h @@ -27,6 +27,7 @@ typedef struct _NMSettingConnection NMSettingConnection; typedef struct _NMSettingDcb NMSettingDcb; typedef struct _NMSettingDummy NMSettingDummy; typedef struct _NMSettingEthtool NMSettingEthtool; +typedef struct _NMSettingGeneve NMSettingGeneve; typedef struct _NMSettingGeneric NMSettingGeneric; typedef struct _NMSettingGsm NMSettingGsm; typedef struct _NMSettingHostname NMSettingHostname; diff --git a/src/libnm-core-public/nm-setting-geneve.h b/src/libnm-core-public/nm-setting-geneve.h new file mode 100644 index 0000000000..8ca83f7139 --- /dev/null +++ b/src/libnm-core-public/nm-setting-geneve.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Red Hat, Inc. + */ + +#ifndef __NM_SETTING_GENEVE_H__ +#define __NM_SETTING_GENEVE_H__ + +#if !defined(__NETWORKMANAGER_H_INSIDE__) && !defined(NETWORKMANAGER_COMPILATION) +#error "Only can be included directly." +#endif + +#include "nm-setting.h" + +G_BEGIN_DECLS + +#define NM_TYPE_SETTING_GENEVE (nm_setting_geneve_get_type()) +#define NM_SETTING_GENEVE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTING_GENEVE, NMSettingGeneve)) +#define NM_SETTING_GENEVE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTING_GENEVE, NMSettingGeneveClass)) +#define NM_IS_SETTING_GENEVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTING_GENEVE)) +#define NM_IS_SETTING_GENEVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTING_GENEVE)) +#define NM_SETTING_GENEVE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTING_GENEVE, NMSettingGeneveClass)) + +#define NM_SETTING_GENEVE_SETTING_NAME "geneve" + +#define NM_SETTING_GENEVE_ID "id" +#define NM_SETTING_GENEVE_REMOTE "remote" +#define NM_SETTING_GENEVE_DESTINATION_PORT "destination-port" +#define NM_SETTING_GENEVE_TOS "tos" +#define NM_SETTING_GENEVE_TTL "ttl" +#define NM_SETTING_GENEVE_DF "df" + +/** + * NMSettingGeneveDf: + * @NM_SETTING_GENEVE_DF_UNSET: Don't set the DF flag, packets may be fragmented. + * @NM_SETTING_GENEVE_DF_SET: Always set the DF flag, packets will not be fragmented. + * @NM_SETTING_GENEVE_DF_INHERIT: Inherit the DF flag from the inner IP header. + * + * #NMSettingGeneveDf values indicate how the Don't Fragment (DF) flag should be handled + * in the outer IP header of GENEVE tunnel packets. + * + * Since: 1.58, 1.56.2 + */ +typedef enum { + NM_SETTING_GENEVE_DF_UNSET = 0, + NM_SETTING_GENEVE_DF_SET = 1, + NM_SETTING_GENEVE_DF_INHERIT = 2, +} NMSettingGeneveDf; + +typedef struct _NMSettingGeneveClass NMSettingGeneveClass; + +NM_AVAILABLE_IN_1_56_2 +GType nm_setting_geneve_get_type(void); +NM_AVAILABLE_IN_1_56_2 +NMSetting *nm_setting_geneve_new(void); +NM_AVAILABLE_IN_1_56_2 +guint nm_setting_geneve_get_id(NMSettingGeneve *setting); +NM_AVAILABLE_IN_1_56_2 +const char *nm_setting_geneve_get_remote(NMSettingGeneve *setting); +NM_AVAILABLE_IN_1_56_2 +guint nm_setting_geneve_get_destination_port(NMSettingGeneve *setting); +NM_AVAILABLE_IN_1_56_2 +guint nm_setting_geneve_get_tos(NMSettingGeneve *setting); +NM_AVAILABLE_IN_1_56_2 +guint nm_setting_geneve_get_ttl(NMSettingGeneve *setting); +NM_AVAILABLE_IN_1_56_2 +NMSettingGeneveDf nm_setting_geneve_get_df(NMSettingGeneve *setting); + +G_END_DECLS + +#endif /* __NM_SETTING_GENEVE_H__ */ diff --git a/src/libnmc-setting/nm-meta-setting-base-impl.c b/src/libnmc-setting/nm-meta-setting-base-impl.c index 52af9ad259..d84490d238 100644 --- a/src/libnmc-setting/nm-meta-setting-base-impl.c +++ b/src/libnmc-setting/nm-meta-setting-base-impl.c @@ -27,6 +27,7 @@ #include "nm-setting-dummy.h" #include "nm-setting-ethtool.h" #include "nm-setting-generic.h" +#include "nm-setting-geneve.h" #include "nm-setting-gsm.h" #include "nm-setting-hostname.h" #include "nm-setting-hsr.h" @@ -324,6 +325,13 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = { .setting_name = NM_SETTING_GENERIC_SETTING_NAME, .get_setting_gtype = nm_setting_generic_get_type, }, + [NM_META_SETTING_TYPE_GENEVE] = + { + .meta_type = NM_META_SETTING_TYPE_GENEVE, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_GENEVE_SETTING_NAME, + .get_setting_gtype = nm_setting_geneve_get_type, + }, [NM_META_SETTING_TYPE_GSM] = { .meta_type = NM_META_SETTING_TYPE_GSM, @@ -655,6 +663,7 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = { NM_META_SETTING_TYPE_CDMA, NM_META_SETTING_TYPE_DUMMY, NM_META_SETTING_TYPE_GENERIC, + NM_META_SETTING_TYPE_GENEVE, NM_META_SETTING_TYPE_GSM, NM_META_SETTING_TYPE_HSR, NM_META_SETTING_TYPE_INFINIBAND, diff --git a/src/libnmc-setting/nm-meta-setting-base-impl.h b/src/libnmc-setting/nm-meta-setting-base-impl.h index d1535e5e1f..8f512d09c6 100644 --- a/src/libnmc-setting/nm-meta-setting-base-impl.h +++ b/src/libnmc-setting/nm-meta-setting-base-impl.h @@ -121,6 +121,7 @@ typedef enum _nm_packed { NM_META_SETTING_TYPE_DUMMY, NM_META_SETTING_TYPE_ETHTOOL, NM_META_SETTING_TYPE_GENERIC, + NM_META_SETTING_TYPE_GENEVE, NM_META_SETTING_TYPE_GSM, NM_META_SETTING_TYPE_HOSTNAME, NM_META_SETTING_TYPE_HSR, diff --git a/src/libnmc-setting/nm-meta-setting-desc.c b/src/libnmc-setting/nm-meta-setting-desc.c index e2ab8dfafe..ad75782ccd 100644 --- a/src/libnmc-setting/nm-meta-setting-desc.c +++ b/src/libnmc-setting/nm-meta-setting-desc.c @@ -6111,6 +6111,57 @@ static const NMMetaPropertyInfo *const property_infos_GENERIC[] = { NULL }; +#undef _CURRENT_NM_META_SETTING_TYPE +#define _CURRENT_NM_META_SETTING_TYPE NM_META_SETTING_TYPE_GENEVE +static const NMMetaPropertyInfo *const property_infos_GENEVE[] = { + PROPERTY_INFO_WITH_DESC (NM_SETTING_GENEVE_ID, + .is_cli_option = TRUE, + .property_alias = "id", + .inf_flags = NM_META_PROPERTY_INF_FLAG_REQD, + .prompt = N_("GENEVE ID"), + .property_type = &_pt_gobject_int, + ), + PROPERTY_INFO_WITH_DESC (NM_SETTING_GENEVE_REMOTE, + .is_cli_option = TRUE, + .property_alias = "remote", + .inf_flags = NM_META_PROPERTY_INF_FLAG_REQD, + .prompt = N_("Remote"), + .property_type = &_pt_gobject_string, + ), + PROPERTY_INFO_WITH_DESC (NM_SETTING_GENEVE_DESTINATION_PORT, + .is_cli_option = TRUE, + .property_alias = "destination-port", + .prompt = N_("Destination port"), + .property_type = &_pt_gobject_int, + ), + PROPERTY_INFO_WITH_DESC (NM_SETTING_GENEVE_TOS, + .property_type = &_pt_gobject_int, + .property_typ_data = DEFINE_PROPERTY_TYP_DATA_SUBTYPE (gobject_int, + .value_infos = INT_VALUE_INFOS ( + { + .value.u64 = 1, + .nick = "inherit", + }, + ), + ), + ), + PROPERTY_INFO_WITH_DESC (NM_SETTING_GENEVE_TTL, + .property_type = &_pt_gobject_int, + .property_typ_data = DEFINE_PROPERTY_TYP_DATA_SUBTYPE (gobject_int, + .value_infos = INT_VALUE_INFOS ( + { + .value.i64 = -1, + .nick = "inherit", + }, + ), + ), + ), + PROPERTY_INFO_WITH_DESC (NM_SETTING_GENEVE_DF, + .property_type = &_pt_gobject_enum, + ), + NULL +}; + #undef _CURRENT_NM_META_SETTING_TYPE #define _CURRENT_NM_META_SETTING_TYPE NM_META_SETTING_TYPE_GSM static const NMMetaPropertyInfo *const property_infos_GSM[] = { @@ -9014,6 +9065,7 @@ _setting_init_fcn_wireless (ARGS_SETTING_INIT_FCN) #define SETTING_PRETTY_NAME_DUMMY N_("Dummy settings") #define SETTING_PRETTY_NAME_ETHTOOL N_("Ethtool settings") #define SETTING_PRETTY_NAME_GENERIC N_("Generic settings") +#define SETTING_PRETTY_NAME_GENEVE N_("Geneve settings") #define SETTING_PRETTY_NAME_GSM N_("GSM mobile broadband connection") #define SETTING_PRETTY_NAME_HOSTNAME N_("Hostname settings") #define SETTING_PRETTY_NAME_HSR N_("HSR settings") @@ -9152,6 +9204,14 @@ const NMMetaSettingInfoEditor nm_meta_setting_infos_editor[] = { NM_META_SETTING_VALID_PART_ITEM (GENERIC, TRUE), ), ), + SETTING_INFO (GENEVE, + .valid_parts = NM_META_SETTING_VALID_PARTS ( + NM_META_SETTING_VALID_PART_ITEM (CONNECTION, TRUE), + NM_META_SETTING_VALID_PART_ITEM (GENEVE, TRUE), + NM_META_SETTING_VALID_PART_ITEM (WIRED, FALSE), + NM_META_SETTING_VALID_PART_ITEM (ETHTOOL, FALSE), + ), + ), SETTING_INFO (GSM, .valid_parts = NM_META_SETTING_VALID_PARTS ( NM_META_SETTING_VALID_PART_ITEM (CONNECTION, TRUE), diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in index 89f8c6d178..17468497ed 100644 --- a/src/libnmc-setting/settings-docs.h.in +++ b/src/libnmc-setting/settings-docs.h.in @@ -498,6 +498,12 @@ #define DESCRIBE_DOC_NM_SETTING_WPAN_SHORT_ADDRESS N_("Short IEEE 802.15.4 address to be used within a restricted environment.") #define DESCRIBE_DOC_NM_SETTING_BOND_PORT_PRIO N_("The port priority for bond active port re-selection during failover. A higher number means a higher priority in selection. The primary port has the highest priority. This option is only compatible with active-backup, balance-tlb and balance-alb modes.") #define DESCRIBE_DOC_NM_SETTING_BOND_PORT_QUEUE_ID N_("The queue ID of this bond port. The maximum value of queue ID is the number of TX queues currently active in device.") +#define DESCRIBE_DOC_NM_SETTING_GENEVE_DESTINATION_PORT N_("Specifies the UDP destination port to communicate to the remote GENEVE tunnel endpoint.") +#define DESCRIBE_DOC_NM_SETTING_GENEVE_DF N_("Specifies how the Don't Fragment (DF) flag should be handled in the outer IP header of GENEVE tunnel packets. \"unset\" (0) (0): Don't set the DF flag, packets may be fragmented. \"set\" (1) (1): Always set the DF flag, packets will not be fragmented. \"inherit\" (2) (2): Inherit the DF flag from the inner IP header.") +#define DESCRIBE_DOC_NM_SETTING_GENEVE_ID N_("Specifies the GENEVE Network Identifier (or GENEVE Segment Identifier) to use.") +#define DESCRIBE_DOC_NM_SETTING_GENEVE_REMOTE N_("Specifies the unicast destination IP address to use in outgoing packets when communicating with the remote GENEVE tunnel endpoint.") +#define DESCRIBE_DOC_NM_SETTING_GENEVE_TOS N_("Specifies the TOS value to use in outgoing packets. The special value \"inherit\" (1) means inherit from outer packet.") +#define DESCRIBE_DOC_NM_SETTING_GENEVE_TTL N_("Specifies the time-to-live value to use in outgoing packets. The special value \"inherit\" (-1) means inherit from outer packet, 0 means auto, 1-255 are fixed values.") #define DESCRIBE_DOC_NM_SETTING_HOSTNAME_FROM_DHCP N_("Whether the system hostname can be determined from DHCP on this connection. When set to \"default\" (-1), the value from global configuration is used. If the property doesn't have a value in the global configuration, NetworkManager assumes the value to be \"true\" (1).") #define DESCRIBE_DOC_NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP N_("Whether the system hostname can be determined from reverse DNS lookup of addresses on this device. When set to \"default\" (-1), the value from global configuration is used. If the property doesn't have a value in the global configuration, NetworkManager assumes the value to be \"true\" (1).") #define DESCRIBE_DOC_NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT N_("If set to \"true\" (1), NetworkManager attempts to get the hostname via DHCPv4/DHCPv6 or reverse DNS lookup on this device only when the device has the default route for the given address family (IPv4/IPv6). If set to \"false\" (0), the hostname can be set from this device even if it doesn't have the default route. When set to \"default\" (-1), the value from global configuration is used. If the property doesn't have a value in the global configuration, NetworkManager assumes the value to be \"false\" (0).") diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index d93e16f03c..a3b03efe31 100644 --- a/src/nmcli/connections.c +++ b/src/nmcli/connections.c @@ -4,6 +4,7 @@ */ #include "libnm-client-aux-extern/nm-default-client.h" +#include "nmcli.h" #include "connections.h" @@ -1070,13 +1071,13 @@ const NmcMetaGenericInfo "," NM_SETTING_DCB_SETTING_NAME "," NM_SETTING_TUN_SETTING_NAME \ "," NM_SETTING_IP_TUNNEL_SETTING_NAME "," NM_SETTING_MACSEC_SETTING_NAME \ "," NM_SETTING_MACVLAN_SETTING_NAME "," NM_SETTING_VXLAN_SETTING_NAME \ - "," NM_SETTING_VRF_SETTING_NAME "," NM_SETTING_WPAN_SETTING_NAME \ - "," NM_SETTING_6LOWPAN_SETTING_NAME "," NM_SETTING_WIREGUARD_SETTING_NAME \ - "," NM_SETTING_LINK_SETTING_NAME "," NM_SETTING_PROXY_SETTING_NAME \ - "," NM_SETTING_TC_CONFIG_SETTING_NAME "," NM_SETTING_SRIOV_SETTING_NAME \ - "," NM_SETTING_ETHTOOL_SETTING_NAME "," NM_SETTING_OVS_DPDK_SETTING_NAME \ - "," NM_SETTING_HOSTNAME_SETTING_NAME "," NM_SETTING_HSR_SETTING_NAME \ - "," NM_SETTING_IPVLAN_SETTING_NAME + "," NM_SETTING_GENEVE_SETTING_NAME "," NM_SETTING_VRF_SETTING_NAME \ + "," NM_SETTING_WPAN_SETTING_NAME "," NM_SETTING_6LOWPAN_SETTING_NAME \ + "," NM_SETTING_WIREGUARD_SETTING_NAME "," NM_SETTING_LINK_SETTING_NAME \ + "," NM_SETTING_PROXY_SETTING_NAME "," NM_SETTING_TC_CONFIG_SETTING_NAME \ + "," NM_SETTING_SRIOV_SETTING_NAME "," NM_SETTING_ETHTOOL_SETTING_NAME \ + "," NM_SETTING_OVS_DPDK_SETTING_NAME "," NM_SETTING_HOSTNAME_SETTING_NAME \ + "," NM_SETTING_HSR_SETTING_NAME "," NM_SETTING_IPVLAN_SETTING_NAME /* NM_SETTING_DUMMY_SETTING_NAME NM_SETTING_WIMAX_SETTING_NAME */ const NmcMetaGenericInfo *const nmc_fields_con_active_details_groups[] = { @@ -1306,6 +1307,9 @@ usage_connection_add(void) " [source-port-min <0-65535>]\n" " [source-port-max <0-65535>]\n" " [destination-port <0-65535>]\n\n" + " geneve: id \n" + " remote \n" + " [destination-port <0-65535>]\n\n" " wpan: [short-addr <0x0000-0xffff>]\n" " [pan-id <0x0000-0xffff>]\n" " [page ]\n" diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in index 881c41cac8..7a9f4e9c2b 100644 --- a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in +++ b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in @@ -629,7 +629,7 @@ alias="type" nmcli-description="Base type of the connection. For hardware-dependent connections, should contain the setting name of the hardware-type specific setting (ie, "802-3-ethernet" or "802-11-wireless" or "bluetooth", etc), and for non-hardware dependent connections like VPN or otherwise, should contain the setting name of that setting type (ie, "vpn" or "bridge", etc)." format="string" - values="6lowpan, 802-11-olpc-mesh, 802-11-wireless, 802-3-ethernet, adsl, bluetooth, bond, bridge, cdma, dummy, generic, gsm, hsr, infiniband, ip-tunnel, ipvlan, loopback, macsec, macvlan, ovs-bridge, ovs-interface, ovs-port, pppoe, team, tun, veth, vlan, vpn, vrf, vxlan, wifi-p2p, wimax, wireguard, wpan" /> + values="6lowpan, 802-11-olpc-mesh, 802-11-wireless, 802-3-ethernet, adsl, bluetooth, bond, bridge, cdma, dummy, generic, geneve, gsm, hsr, infiniband, ip-tunnel, ipvlan, loopback, macsec, macvlan, ovs-bridge, ovs-interface, ovs-port, pppoe, team, tun, veth, vlan, vpn, vrf, vxlan, wifi-p2p, wimax, wireguard, wpan" /> + + + + + + + + Date: Mon, 2 Feb 2026 19:02:29 -0500 Subject: [PATCH 06/20] geneve: added GENEVE device support Support device type geneve in libnm and nmcli. (cherry picked from commit 0bfb8fa89d4ef5afd846f9893197ffc6a2e7b20a) --- docs/api/network-manager-docs.xml | 1 + docs/libnm/libnm-docs.xml | 1 + introspection/meson.build | 1 + ...eedesktop.NetworkManager.Device.Geneve.xml | 63 +++ po/POTFILES.in | 2 + src/core/devices/nm-device-factory.c | 1 + src/core/devices/nm-device-geneve.c | 487 ++++++++++++++++++ src/core/devices/nm-device-geneve.h | 33 ++ src/core/devices/nm-device.c | 5 +- src/core/meson.build | 1 + src/libnm-client-impl/meson.build | 1 + src/libnm-client-impl/nm-client.c | 1 + src/libnm-client-impl/nm-device-geneve.c | 344 +++++++++++++ src/libnm-client-impl/nm-device.c | 3 + src/libnm-client-impl/nm-libnm-utils.c | 1 + src/libnm-client-impl/nm-libnm-utils.h | 3 +- src/libnm-client-public/NetworkManager.h | 1 + src/libnm-client-public/meson.build | 1 + src/libnm-client-public/nm-autoptr.h | 1 + src/libnm-client-public/nm-device-geneve.h | 60 +++ src/libnm-core-impl/nm-connection.c | 1 + src/libnm-core-public/nm-dbus-interface.h | 3 + vapi/NM-1.0.metadata | 1 + 23 files changed, 1013 insertions(+), 3 deletions(-) create mode 100644 introspection/org.freedesktop.NetworkManager.Device.Geneve.xml create mode 100644 src/core/devices/nm-device-geneve.c create mode 100644 src/core/devices/nm-device-geneve.h create mode 100644 src/libnm-client-impl/nm-device-geneve.c create mode 100644 src/libnm-client-public/nm-device-geneve.h diff --git a/docs/api/network-manager-docs.xml b/docs/api/network-manager-docs.xml index caff6fa4fc..9916427399 100644 --- a/docs/api/network-manager-docs.xml +++ b/docs/api/network-manager-docs.xml @@ -183,6 +183,7 @@ + diff --git a/docs/libnm/libnm-docs.xml b/docs/libnm/libnm-docs.xml index a957f469c8..719d8998a1 100644 --- a/docs/libnm/libnm-docs.xml +++ b/docs/libnm/libnm-docs.xml @@ -378,6 +378,7 @@ print ("NetworkManager version " + client.get_version())]]> + diff --git a/introspection/meson.build b/introspection/meson.build index 16bb5ed423..7703b46983 100644 --- a/introspection/meson.build +++ b/introspection/meson.build @@ -15,6 +15,7 @@ ifaces = [ 'org.freedesktop.NetworkManager.Device.Bridge', 'org.freedesktop.NetworkManager.Device.Dummy', 'org.freedesktop.NetworkManager.Device.Generic', + 'org.freedesktop.NetworkManager.Device.Geneve', 'org.freedesktop.NetworkManager.Device.Hsr', 'org.freedesktop.NetworkManager.Device.IPTunnel', 'org.freedesktop.NetworkManager.Device.Infiniband', diff --git a/introspection/org.freedesktop.NetworkManager.Device.Geneve.xml b/introspection/org.freedesktop.NetworkManager.Device.Geneve.xml new file mode 100644 index 0000000000..7eb49a776c --- /dev/null +++ b/introspection/org.freedesktop.NetworkManager.Device.Geneve.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/po/POTFILES.in b/po/POTFILES.in index 44ddd4a64b..1cb0255415 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -11,6 +11,7 @@ src/core/devices/nm-device-bridge.c src/core/devices/nm-device-dummy.c src/core/devices/nm-device-ethernet-utils.c src/core/devices/nm-device-ethernet.c +src/core/devices/nm-device-geneve.c src/core/devices/nm-device-infiniband.c src/core/devices/nm-device-ip-tunnel.c src/core/devices/nm-device-loopback.c @@ -46,6 +47,7 @@ src/libnm-client-impl/nm-device-bt.c src/libnm-client-impl/nm-device-dummy.c src/libnm-client-impl/nm-device-ethernet.c src/libnm-client-impl/nm-device-generic.c +src/libnm-client-impl/nm-device-geneve.c src/libnm-client-impl/nm-device-hsr.c src/libnm-client-impl/nm-device-infiniband.c src/libnm-client-impl/nm-device-ip-tunnel.c diff --git a/src/core/devices/nm-device-factory.c b/src/core/devices/nm-device-factory.c index 1585836281..24755a5f00 100644 --- a/src/core/devices/nm-device-factory.c +++ b/src/core/devices/nm-device-factory.c @@ -412,6 +412,7 @@ nm_device_factory_manager_load_factories(NMDeviceFactoryManagerFactoryFunc callb _ADD_INTERNAL(nm_dummy_device_factory_get_type); _ADD_INTERNAL(nm_ethernet_device_factory_get_type); _ADD_INTERNAL(nm_generic_device_factory_get_type); + _ADD_INTERNAL(nm_geneve_device_factory_get_type); _ADD_INTERNAL(nm_hsr_device_factory_get_type); _ADD_INTERNAL(nm_infiniband_device_factory_get_type); _ADD_INTERNAL(nm_ip_tunnel_device_factory_get_type); diff --git a/src/core/devices/nm-device-geneve.c b/src/core/devices/nm-device-geneve.c new file mode 100644 index 0000000000..0968a2fb9b --- /dev/null +++ b/src/core/devices/nm-device-geneve.c @@ -0,0 +1,487 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-manager.h" +#include "nm-device-geneve.h" + +#include "libnm-core-intern/nm-core-internal.h" +#include "nm-act-request.h" +#include "nm-device-private.h" +#include "nm-setting-geneve.h" +#include "libnm-platform/nm-platform.h" +#include "nm-device-factory.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceGeneve +#include "nm-device-logging.h" + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceGeneve, + PROP_ID, + PROP_REMOTE, + PROP_TOS, + PROP_TTL, + PROP_DF, + PROP_DST_PORT, ); + +typedef struct { + NMPlatformLnkGeneve props; +} NMDeviceGenevePrivate; + +struct _NMDeviceGeneve { + NMDevice parent; + NMDeviceGenevePrivate _priv; +}; + +struct _NMDeviceGeneveClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceGeneve, nm_device_geneve, NM_TYPE_DEVICE) + +#define NM_DEVICE_GENEVE_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceGeneve, NM_IS_DEVICE_GENEVE, NMDevice) + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + return NM_DEVICE_CAP_IS_SOFTWARE; +} + +static void +update_properties(NMDevice *device) +{ + NMDeviceGeneve *self; + NMDeviceGenevePrivate *priv; + const NMPlatformLink *plink; + const NMPlatformLnkGeneve *props; + int ifindex; + + g_return_if_fail(NM_IS_DEVICE_GENEVE(device)); + self = NM_DEVICE_GENEVE(device); + priv = NM_DEVICE_GENEVE_GET_PRIVATE(self); + + ifindex = nm_device_get_ifindex(device); + g_return_if_fail(ifindex > 0); + props = nm_platform_link_get_lnk_geneve(nm_device_get_platform(device), ifindex, &plink); + + if (!props) { + _LOGW(LOGD_PLATFORM, "could not get GENEVE properties"); + return; + } + + g_object_freeze_notify((GObject *) device); + +#define CHECK_PROPERTY_CHANGED(field, prop) \ + G_STMT_START \ + { \ + if (priv->props.field != props->field) { \ + priv->props.field = props->field; \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + +#define CHECK_PROPERTY_CHANGED_IN6ADDR(field, prop) \ + G_STMT_START \ + { \ + if (memcmp(&priv->props.field, &props->field, sizeof(props->field)) != 0) { \ + priv->props.field = props->field; \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + + CHECK_PROPERTY_CHANGED(id, PROP_ID); + CHECK_PROPERTY_CHANGED(remote, PROP_REMOTE); + CHECK_PROPERTY_CHANGED_IN6ADDR(remote6, PROP_REMOTE); + CHECK_PROPERTY_CHANGED(tos, PROP_TOS); + CHECK_PROPERTY_CHANGED(ttl, PROP_TTL); + CHECK_PROPERTY_CHANGED(df, PROP_DF); + CHECK_PROPERTY_CHANGED(dst_port, PROP_DST_PORT); + + g_object_thaw_notify((GObject *) device); +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_geneve_parent_class)->link_changed(device, pllink); + update_properties(device); +} + +static void +unrealize_notify(NMDevice *device) +{ + NMDeviceGeneve *self = NM_DEVICE_GENEVE(device); + NMDeviceGenevePrivate *priv = NM_DEVICE_GENEVE_GET_PRIVATE(self); + guint i; + + NM_DEVICE_CLASS(nm_device_geneve_parent_class)->unrealize_notify(device); + + memset(&priv->props, 0, sizeof(NMPlatformLnkGeneve)); + + for (i = 1; i < _PROPERTY_ENUMS_LAST; i++) + g_object_notify_by_pspec(G_OBJECT(self), obj_properties[i]); +} + +static gboolean +create_and_realize(NMDevice *device, + NMConnection *connection, + NMDevice *parent, + const NMPlatformLink **out_plink, + GError **error) +{ + const char *iface = nm_device_get_iface(device); + NMPlatformLnkGeneve props = {}; + NMSettingGeneve *s_geneve; + const char *str; + int r; + + s_geneve = nm_connection_get_setting_geneve(connection); + g_return_val_if_fail(s_geneve, FALSE); + + props.id = nm_setting_geneve_get_id(s_geneve); + + str = nm_setting_geneve_get_remote(s_geneve); + if (!nm_inet_parse_bin(AF_INET, str, NULL, &props.remote) + && !nm_inet_parse_bin(AF_INET6, str, NULL, &props.remote6)) { + return nm_assert_unreachable_val(FALSE); + } + props.tos = nm_setting_geneve_get_tos(s_geneve); + props.ttl = nm_setting_geneve_get_ttl(s_geneve); + props.df = nm_setting_geneve_get_df(s_geneve); + props.dst_port = nm_setting_geneve_get_destination_port(s_geneve); + + r = nm_platform_link_geneve_add(nm_device_get_platform(device), iface, &props, out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create geneve interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + return TRUE; +} + +static gboolean +address_matches(const char *candidate, in_addr_t addr4, struct in6_addr *addr6) +{ + NMIPAddr candidate_addr; + int addr_family; + + if (!candidate) + return addr4 == 0u && IN6_IS_ADDR_UNSPECIFIED(addr6); + + if (!nm_inet_parse_bin(AF_UNSPEC, candidate, &addr_family, &candidate_addr)) + return FALSE; + + if (!nm_ip_addr_equal(addr_family, + &candidate_addr, + NM_IS_IPv4(addr_family) ? (gpointer) &addr4 : addr6)) + return FALSE; + + if (NM_IS_IPv4(addr_family)) + return IN6_IS_ADDR_UNSPECIFIED(addr6); + else + return addr4 == 0u; +} + +static gboolean +check_connection_compatible(NMDevice *device, + NMConnection *connection, + gboolean check_properties, + GError **error) +{ + NMDeviceGenevePrivate *priv = NM_DEVICE_GENEVE_GET_PRIVATE(device); + NMSettingGeneve *s_geneve; + + if (!NM_DEVICE_CLASS(nm_device_geneve_parent_class) + ->check_connection_compatible(device, connection, check_properties, error)) + return FALSE; + + if (check_properties && nm_device_is_real(device)) { + s_geneve = nm_connection_get_setting_geneve(connection); + + if (priv->props.id != nm_setting_geneve_get_id(s_geneve)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "geneve id mismatches"); + return FALSE; + } + + if (!address_matches(nm_setting_geneve_get_remote(s_geneve), + priv->props.remote, + &priv->props.remote6)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "geneve remote address mismatches"); + return FALSE; + } + + if (priv->props.dst_port != nm_setting_geneve_get_destination_port(s_geneve)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "geneve destination port mismatches"); + return FALSE; + } + + if (priv->props.tos != nm_setting_geneve_get_tos(s_geneve)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "geneve TOS mismatches"); + return FALSE; + } + + if (priv->props.ttl != nm_setting_geneve_get_ttl(s_geneve)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "geneve TTL mismatches"); + return FALSE; + } + + if (priv->props.df != nm_setting_geneve_get_df(s_geneve)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "geneve DF mismatches"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice *device, + NMConnection *connection, + const char *specific_object, + NMConnection *const *existing_connections, + GError **error) +{ + NMSettingGeneve *s_geneve; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_GENEVE_SETTING_NAME, + existing_connections, + NULL, + _("Geneve connection"), + NULL, + NULL); + + s_geneve = nm_connection_get_setting_geneve(connection); + if (!s_geneve) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'geneve' setting is required."); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceGenevePrivate *priv = NM_DEVICE_GENEVE_GET_PRIVATE(device); + NMSettingGeneve *s_geneve = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_GENEVE); + char sbuf[NM_INET_ADDRSTRLEN]; + + if (priv->props.id != nm_setting_geneve_get_id(s_geneve)) + g_object_set(G_OBJECT(s_geneve), NM_SETTING_GENEVE_ID, priv->props.id, NULL); + + /* Handle remote (IPv4 or IPv6) */ + if (priv->props.remote) { + g_object_set(s_geneve, + NM_SETTING_GENEVE_REMOTE, + nm_inet4_ntop(priv->props.remote, sbuf), + NULL); + } else if (memcmp(&priv->props.remote6, &in6addr_any, sizeof(in6addr_any))) { + g_object_set(s_geneve, + NM_SETTING_GENEVE_REMOTE, + nm_inet6_ntop(&priv->props.remote6, sbuf), + NULL); + } + + if (priv->props.dst_port != nm_setting_geneve_get_destination_port(s_geneve)) + g_object_set(G_OBJECT(s_geneve), + NM_SETTING_GENEVE_DESTINATION_PORT, + priv->props.dst_port, + NULL); + + if (priv->props.tos != nm_setting_geneve_get_tos(s_geneve)) + g_object_set(G_OBJECT(s_geneve), NM_SETTING_GENEVE_TOS, priv->props.tos, NULL); + + if (priv->props.ttl != nm_setting_geneve_get_ttl(s_geneve)) + g_object_set(G_OBJECT(s_geneve), NM_SETTING_GENEVE_TTL, priv->props.ttl, NULL); + + if (priv->props.df != nm_setting_geneve_get_df(s_geneve)) + g_object_set(G_OBJECT(s_geneve), NM_SETTING_GENEVE_DF, priv->props.df, NULL); +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceGenevePrivate *priv = NM_DEVICE_GENEVE_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_ID: + g_value_set_uint(value, priv->props.id); + break; + case PROP_REMOTE: + if (priv->props.remote) + g_value_take_string(value, nm_inet4_ntop_dup(priv->props.remote)); + else if (!IN6_IS_ADDR_UNSPECIFIED(&priv->props.remote6)) + g_value_take_string(value, nm_inet6_ntop_dup(&priv->props.remote6)); + break; + case PROP_TOS: + g_value_set_uchar(value, priv->props.tos); + break; + case PROP_TTL: + g_value_set_uchar(value, priv->props.ttl); + break; + case PROP_DF: + g_value_set_uint(value, priv->props.df); + break; + case PROP_DST_PORT: + g_value_set_uint(value, priv->props.dst_port); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_geneve_init(NMDeviceGeneve *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_geneve = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_GENEVE, + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Id", "u", NM_DEVICE_GENEVE_ID), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Remote", "s", NM_DEVICE_GENEVE_REMOTE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Tos", "y", NM_DEVICE_GENEVE_TOS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ttl", "y", NM_DEVICE_GENEVE_TTL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Df", "u", NM_DEVICE_GENEVE_DF), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("DstPort", + "q", + NM_DEVICE_GENEVE_DST_PORT), ), ), +}; + +static void +nm_device_geneve_class_init(NMDeviceGeneveClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass *device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_geneve); + + device_class->connection_type_supported = NM_SETTING_GENEVE_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_GENEVE_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_GENEVE); + + device_class->link_changed = link_changed; + device_class->unrealize_notify = unrealize_notify; + device_class->create_and_realize = create_and_realize; + device_class->check_connection_compatible = check_connection_compatible; + device_class->complete_connection = complete_connection; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->update_connection = update_connection; + + obj_properties[PROP_ID] = g_param_spec_uint(NM_DEVICE_GENEVE_ID, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_REMOTE] = g_param_spec_string(NM_DEVICE_GENEVE_REMOTE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_TOS] = g_param_spec_uchar(NM_DEVICE_GENEVE_TOS, + "", + "", + 0, + 255, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_TTL] = g_param_spec_uchar(NM_DEVICE_GENEVE_TTL, + "", + "", + 0, + 255, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DF] = g_param_spec_uint(NM_DEVICE_GENEVE_DF, + "", + "", + 0, + 2, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DST_PORT] = g_param_spec_uint(NM_DEVICE_GENEVE_DST_PORT, + "", + "", + 0, + 65535, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +#define NM_TYPE_GENEVE_DEVICE_FACTORY (nm_geneve_device_factory_get_type()) +#define NM_GENEVE_DEVICE_FACTORY(obj) \ + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_GENEVE_DEVICE_FACTORY, NMGeneveDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory *factory, + const char *iface, + const NMPlatformLink *plink, + NMConnection *connection, + gboolean *out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_GENEVE, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "Geneve", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_GENEVE, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_GENEVE, + NULL); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + GENEVE, + Geneve, + geneve, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_GENEVE) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_GENEVE_SETTING_NAME), + factory_class->create_device = create_device;); diff --git a/src/core/devices/nm-device-geneve.h b/src/core/devices/nm-device-geneve.h new file mode 100644 index 0000000000..d0a44e9dbb --- /dev/null +++ b/src/core/devices/nm-device-geneve.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_GENEVE_H__ +#define __NETWORKMANAGER_DEVICE_GENEVE_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_GENEVE (nm_device_geneve_get_type()) +#define NM_DEVICE_GENEVE(obj) \ + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneve)) +#define NM_DEVICE_GENEVE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneveClass)) +#define NM_IS_DEVICE_GENEVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_GENEVE)) +#define NM_IS_DEVICE_GENEVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_GENEVE)) +#define NM_DEVICE_GENEVE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneveClass)) + +#define NM_DEVICE_GENEVE_ID "id" +#define NM_DEVICE_GENEVE_REMOTE "remote" +#define NM_DEVICE_GENEVE_TOS "tos" +#define NM_DEVICE_GENEVE_TTL "ttl" +#define NM_DEVICE_GENEVE_DF "df" +#define NM_DEVICE_GENEVE_DST_PORT "dst-port" + +typedef struct _NMDeviceGeneve NMDeviceGeneve; +typedef struct _NMDeviceGeneveClass NMDeviceGeneveClass; + +GType nm_device_geneve_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_GENEVE_H__ */ diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 46fb233974..44b15e97ee 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -5900,7 +5900,6 @@ nm_device_get_route_metric_default(NMDeviceType device_type) * in some aspects a VPN. */ case NM_DEVICE_TYPE_WIREGUARD: return NM_VPN_ROUTE_METRIC_DEFAULT; - case NM_DEVICE_TYPE_ETHERNET: case NM_DEVICE_TYPE_VETH: return 100; @@ -5934,6 +5933,8 @@ nm_device_get_route_metric_default(NMDeviceType device_type) return 470; case NM_DEVICE_TYPE_VXLAN: return 500; + case NM_DEVICE_TYPE_GENEVE: + return 525; case NM_DEVICE_TYPE_DUMMY: return 550; case NM_DEVICE_TYPE_WIFI: @@ -19535,7 +19536,7 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps nm_assert(priv->type == NM_DEVICE_TYPE_UNKNOWN); priv->type = g_value_get_uint(value); nm_assert(priv->type > NM_DEVICE_TYPE_UNKNOWN); - nm_assert(priv->type <= NM_DEVICE_TYPE_IPVLAN); + nm_assert(priv->type <= NM_DEVICE_TYPE_GENEVE); break; case PROP_LINK_TYPE: /* construct-only */ diff --git a/src/core/meson.build b/src/core/meson.build index 6cf891ee7e..b1d7f2e76c 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -102,6 +102,7 @@ libNetworkManager = static_library( 'devices/nm-device-ethernet-utils.c', 'devices/nm-device-factory.c', 'devices/nm-device-generic.c', + 'devices/nm-device-geneve.c', 'devices/nm-device-hsr.c', 'devices/nm-device-infiniband.c', 'devices/nm-device-ip-tunnel.c', diff --git a/src/libnm-client-impl/meson.build b/src/libnm-client-impl/meson.build index b49366292f..3352ebfee0 100644 --- a/src/libnm-client-impl/meson.build +++ b/src/libnm-client-impl/meson.build @@ -16,6 +16,7 @@ libnm_client_impl_sources = files( 'nm-device-bt.c', 'nm-device-dummy.c', 'nm-device-ethernet.c', + 'nm-device-geneve.c', 'nm-device-generic.c', 'nm-device-hsr.c', 'nm-device-infiniband.c', diff --git a/src/libnm-client-impl/nm-client.c b/src/libnm-client-impl/nm-client.c index b81ac6e506..f13835269e 100644 --- a/src/libnm-client-impl/nm-client.c +++ b/src/libnm-client-impl/nm-client.c @@ -29,6 +29,7 @@ #include "nm-device-dummy.h" #include "nm-device-ethernet.h" #include "nm-device-generic.h" +#include "nm-device-geneve.h" #include "nm-device-hsr.h" #include "nm-device-infiniband.h" #include "nm-device-ip-tunnel.h" diff --git a/src/libnm-client-impl/nm-device-geneve.c b/src/libnm-client-impl/nm-device-geneve.c new file mode 100644 index 0000000000..93c0a5c842 --- /dev/null +++ b/src/libnm-client-impl/nm-device-geneve.c @@ -0,0 +1,344 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Red Hat, Inc. + */ + +#include "libnm-client-impl/nm-default-libnm.h" + +#include "nm-device-geneve.h" + +#include "nm-setting-connection.h" +#include "nm-setting-geneve.h" +#include "nm-utils.h" +#include "nm-object-private.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_ID, + PROP_REMOTE, + PROP_TOS, + PROP_TTL, + PROP_DST_PORT, + PROP_DF, ); + +typedef struct { + char *remote; + guint32 id; + gint32 ttl; + guint16 dst_port; + guint8 df; + guint8 tos; +} NMDeviceGenevePrivate; + +struct _NMDeviceGeneve { + NMDevice parent; + NMDeviceGenevePrivate _priv; +}; + +struct _NMDeviceGeneveClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceGeneve, nm_device_geneve, NM_TYPE_DEVICE) + +#define NM_DEVICE_GENEVE_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceGeneve, NM_IS_DEVICE_GENEVE, NMObject, NMDevice) + +/*****************************************************************************/ + +/** + * nm_device_geneve_get_id: + * @device: a #NMDeviceGeneve + * + * Returns: the device's GENEVE ID. + * + * Since: 1.58, 1.56.2 + **/ +guint +nm_device_geneve_get_id(NMDeviceGeneve *device) +{ + g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), 0); + + return NM_DEVICE_GENEVE_GET_PRIVATE(device)->id; +} + +/** + * nm_device_geneve_get_remote: + * @device: a #NMDeviceGeneve + * + * Returns: the IP address of the remote tunnel endpoint + * + * Since: 1.58, 1.56.2 + **/ +const char * +nm_device_geneve_get_remote(NMDeviceGeneve *device) +{ + g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), NULL); + + return _nml_coerce_property_str_not_empty(NM_DEVICE_GENEVE_GET_PRIVATE(device)->remote); +} + +/** + * nm_device_geneve_get_dst_port: + * @device: a #NMDeviceGeneve + * + * Returns: the UDP destination port + * + * Since: 1.58, 1.56.2 + **/ +guint +nm_device_geneve_get_dst_port(NMDeviceGeneve *device) +{ + g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), 0); + + return NM_DEVICE_GENEVE_GET_PRIVATE(device)->dst_port; +} + +/** + * nm_device_geneve_get_tos: + * @device: a #NMDeviceGeneve + * + * Returns: the TOS value to use in outgoing packets + * + * Since: 1.58, 1.56.2 + **/ +guint +nm_device_geneve_get_tos(NMDeviceGeneve *device) +{ + g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), 0); + + return NM_DEVICE_GENEVE_GET_PRIVATE(device)->tos; +} + +/** + * nm_device_geneve_get_ttl: + * @device: a #NMDeviceGeneve + * + * Returns: the time-to-live value to use in outgoing packets + * + * Since: 1.58, 1.56.2 + **/ +guint +nm_device_geneve_get_ttl(NMDeviceGeneve *device) +{ + g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), 0); + + return NM_DEVICE_GENEVE_GET_PRIVATE(device)->ttl; +} + +/** + * nm_device_geneve_get_df: + * @device: a #NMDeviceGeneve + * + * Returns: the Don't Fragment (DF) bit to set in outgoing packets + * + * Since: 1.58, 1.56.2 + **/ +guint +nm_device_geneve_get_df(NMDeviceGeneve *device) +{ + g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), 0); + + return NM_DEVICE_GENEVE_GET_PRIVATE(device)->df; +} + +static gboolean +connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMSettingGeneve *s_geneve; + + if (!NM_DEVICE_CLASS(nm_device_geneve_parent_class) + ->connection_compatible(device, connection, error)) + return FALSE; + + if (!nm_connection_is_type(connection, NM_SETTING_GENEVE_SETTING_NAME)) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + _("The connection was not a GENEVE connection.")); + return FALSE; + } + + s_geneve = nm_connection_get_setting_geneve(connection); + if (nm_setting_geneve_get_id(s_geneve) != nm_device_geneve_get_id(NM_DEVICE_GENEVE(device))) { + g_set_error_literal( + error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + _("The GENEVE identifiers of the device and the connection didn't match.")); + return FALSE; + } + + return TRUE; +} + +static GType +get_setting_type(NMDevice *device) +{ + return NM_TYPE_SETTING_GENEVE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceGeneve *device = NM_DEVICE_GENEVE(object); + + switch (prop_id) { + case PROP_ID: + g_value_set_uint(value, nm_device_geneve_get_id(device)); + break; + case PROP_REMOTE: + g_value_set_string(value, nm_device_geneve_get_remote(device)); + break; + case PROP_TOS: + g_value_set_uint(value, nm_device_geneve_get_tos(device)); + break; + case PROP_TTL: + g_value_set_int(value, nm_device_geneve_get_ttl(device)); + break; + case PROP_DST_PORT: + g_value_set_uint(value, nm_device_geneve_get_dst_port(device)); + break; + case PROP_DF: + g_value_set_uint(value, nm_device_geneve_get_df(device)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +nm_device_geneve_init(NMDeviceGeneve *device) +{} + +static void +finalize(GObject *object) +{ + NMDeviceGenevePrivate *priv = NM_DEVICE_GENEVE_GET_PRIVATE(object); + + g_free(priv->remote); + + G_OBJECT_CLASS(nm_device_geneve_parent_class)->finalize(object); +} + +const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_geneve = NML_DBUS_META_IFACE_INIT_PROP( + NM_DBUS_INTERFACE_DEVICE_GENEVE, + nm_device_geneve_get_type, + NML_DBUS_META_INTERFACE_PRIO_INSTANTIATE_30, + NML_DBUS_META_IFACE_DBUS_PROPERTIES( + NML_DBUS_META_PROPERTY_INIT_Y("Df", PROP_DF, NMDeviceGeneve, _priv.df), + NML_DBUS_META_PROPERTY_INIT_Q("DstPort", PROP_DST_PORT, NMDeviceGeneve, _priv.dst_port), + NML_DBUS_META_PROPERTY_INIT_U("Id", PROP_ID, NMDeviceGeneve, _priv.id), + NML_DBUS_META_PROPERTY_INIT_S("Remote", PROP_REMOTE, NMDeviceGeneve, _priv.remote), + NML_DBUS_META_PROPERTY_INIT_Y("Tos", PROP_TOS, NMDeviceGeneve, _priv.tos), + NML_DBUS_META_PROPERTY_INIT_I("Ttl", PROP_TTL, NMDeviceGeneve, _priv.ttl), ), ); + +static void +nm_device_geneve_class_init(NMDeviceGeneveClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMObjectClass *nm_object_class = NM_OBJECT_CLASS(klass); + NMDeviceClass *device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + object_class->finalize = finalize; + + _NM_OBJECT_CLASS_INIT_PRIV_PTR_DIRECT(nm_object_class, NMDeviceGeneve); + + device_class->connection_compatible = connection_compatible; + device_class->get_setting_type = get_setting_type; + + /** + * NMDeviceGeneve:id: + * + * The device's GENEVE ID. + * + * Since: 1.58, 1.56.2 + **/ + obj_properties[PROP_ID] = g_param_spec_uint(NM_DEVICE_GENEVE_ID, + "", + "", + 0, + (1 << 24) - 1, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMDeviceGeneve:remote: + * + * The IP address of the remote tunnel endpoint. + * + * Since: 1.58, 1.56.2 + */ + obj_properties[PROP_REMOTE] = g_param_spec_string(NM_DEVICE_GENEVE_REMOTE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMDeviceGeneve:tos: + * + * The TOS value to use in outgoing packets. + * + * Since: 1.58, 1.56.2 + */ + obj_properties[PROP_TOS] = g_param_spec_uchar(NM_DEVICE_GENEVE_TOS, + "", + "", + 0, + 255, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMDeviceGeneve:ttl: + * + * The time-to-live value to use in outgoing packets. + * + * Since: 1.58, 1.56.2 + */ + obj_properties[PROP_TTL] = g_param_spec_int(NM_DEVICE_GENEVE_TTL, + "", + "", + -1, + 255, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMDeviceGeneve:dst-port: + * + * The UDP destination port used to communicate with the remote GENEVE tunnel + * endpoint. + * + * Since: 1.58, 1.56.2 + */ + obj_properties[PROP_DST_PORT] = g_param_spec_uint(NM_DEVICE_GENEVE_DST_PORT, + "", + "", + 0, + 65535, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMDeviceGeneve:df: + * + * The Don't Fragment (DF) bit to set in outgoing packets. + * + * Since: 1.58, 1.56.2 + */ + obj_properties[PROP_DF] = g_param_spec_uchar(NM_DEVICE_GENEVE_DF, + "", + "", + 0, + 2, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + _nml_dbus_meta_class_init_with_properties(object_class, &_nml_dbus_meta_iface_nm_device_geneve); +} diff --git a/src/libnm-client-impl/nm-device.c b/src/libnm-client-impl/nm-device.c index 1712efa5bb..9203fd6f13 100644 --- a/src/libnm-client-impl/nm-device.c +++ b/src/libnm-client-impl/nm-device.c @@ -302,6 +302,7 @@ coerce_type(NMDeviceType type) case NM_DEVICE_TYPE_TUN: case NM_DEVICE_TYPE_VETH: case NM_DEVICE_TYPE_GENERIC: + case NM_DEVICE_TYPE_GENEVE: case NM_DEVICE_TYPE_UNUSED1: case NM_DEVICE_TYPE_UNUSED2: case NM_DEVICE_TYPE_UNKNOWN: @@ -1792,6 +1793,8 @@ get_type_name(NMDevice *device) return _("MACVLAN"); case NM_DEVICE_TYPE_VXLAN: return _("VXLAN"); + case NM_DEVICE_TYPE_GENEVE: + return _("GENEVE"); case NM_DEVICE_TYPE_IP_TUNNEL: return _("IPTunnel"); case NM_DEVICE_TYPE_TUN: diff --git a/src/libnm-client-impl/nm-libnm-utils.c b/src/libnm-client-impl/nm-libnm-utils.c index 1b25c92ca6..1c80c1dc4d 100644 --- a/src/libnm-client-impl/nm-libnm-utils.c +++ b/src/libnm-client-impl/nm-libnm-utils.c @@ -782,6 +782,7 @@ const NMLDBusMetaIface *const _nml_dbus_meta_ifaces[] = { &_nml_dbus_meta_iface_nm_device_bridge, &_nml_dbus_meta_iface_nm_device_dummy, &_nml_dbus_meta_iface_nm_device_generic, + &_nml_dbus_meta_iface_nm_device_geneve, &_nml_dbus_meta_iface_nm_device_hsr, &_nml_dbus_meta_iface_nm_device_iptunnel, &_nml_dbus_meta_iface_nm_device_infiniband, diff --git a/src/libnm-client-impl/nm-libnm-utils.h b/src/libnm-client-impl/nm-libnm-utils.h index 7dcf8c18dc..27b77b6009 100644 --- a/src/libnm-client-impl/nm-libnm-utils.h +++ b/src/libnm-client-impl/nm-libnm-utils.h @@ -579,7 +579,7 @@ struct _NMLDBusMetaIface { NML_DBUS_META_IFACE_OBJ_PROPERTIES(), \ ##__VA_ARGS__) -extern const NMLDBusMetaIface *const _nml_dbus_meta_ifaces[47]; +extern const NMLDBusMetaIface *const _nml_dbus_meta_ifaces[48]; extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm; extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_accesspoint; @@ -593,6 +593,7 @@ extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_bond; extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_bridge; extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_dummy; extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_generic; +extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_geneve; extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_hsr; extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_infiniband; extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_iptunnel; diff --git a/src/libnm-client-public/NetworkManager.h b/src/libnm-client-public/NetworkManager.h index 2d1c56521e..880bac6ed7 100644 --- a/src/libnm-client-public/NetworkManager.h +++ b/src/libnm-client-public/NetworkManager.h @@ -115,6 +115,7 @@ #include "nm-device-dummy.h" #include "nm-device-ethernet.h" #include "nm-device-generic.h" +#include "nm-device-geneve.h" #include "nm-device-hsr.h" #include "nm-device-infiniband.h" #include "nm-device-ip-tunnel.h" diff --git a/src/libnm-client-public/meson.build b/src/libnm-client-public/meson.build index b8ae9cce07..5aa6de2518 100644 --- a/src/libnm-client-public/meson.build +++ b/src/libnm-client-public/meson.build @@ -18,6 +18,7 @@ libnm_client_headers = files( 'nm-device-dummy.h', 'nm-device-ethernet.h', 'nm-device-generic.h', + 'nm-device-geneve.h', 'nm-device-hsr.h', 'nm-device-infiniband.h', 'nm-device-ip-tunnel.h', diff --git a/src/libnm-client-public/nm-autoptr.h b/src/libnm-client-public/nm-autoptr.h index f21f2970c8..12379fe1aa 100644 --- a/src/libnm-client-public/nm-autoptr.h +++ b/src/libnm-client-public/nm-autoptr.h @@ -41,6 +41,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceBt, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceDummy, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceEthernet, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceGeneric, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceGeneve, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceHsr, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceIPTunnel, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceInfiniband, g_object_unref) diff --git a/src/libnm-client-public/nm-device-geneve.h b/src/libnm-client-public/nm-device-geneve.h new file mode 100644 index 0000000000..f450657b7d --- /dev/null +++ b/src/libnm-client-public/nm-device-geneve.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_DEVICE_GENEVE_H__ +#define __NM_DEVICE_GENEVE_H__ + +#if !defined(__NETWORKMANAGER_H_INSIDE__) && !defined(NETWORKMANAGER_COMPILATION) +#error "Only can be included directly." +#endif + +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_GENEVE (nm_device_geneve_get_type()) +#define NM_DEVICE_GENEVE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneve)) +#define NM_DEVICE_GENEVE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneveClass)) +#define NM_IS_DEVICE_GENEVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_GENEVE)) +#define NM_IS_DEVICE_GENEVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_GENEVE)) +#define NM_DEVICE_GENEVE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneveClass)) + +#define NM_DEVICE_GENEVE_ID "id" +#define NM_DEVICE_GENEVE_REMOTE "remote" +#define NM_DEVICE_GENEVE_TOS "tos" +#define NM_DEVICE_GENEVE_TTL "ttl" +#define NM_DEVICE_GENEVE_DST_PORT "dst-port" +#define NM_DEVICE_GENEVE_DF "df" + +/** + * NMDeviceGeneve: + * + * Since: 1.58, 1.56.2 + */ +typedef struct _NMDeviceGeneve NMDeviceGeneve; +typedef struct _NMDeviceGeneveClass NMDeviceGeneveClass; + +NM_AVAILABLE_IN_1_56_2 +GType nm_device_geneve_get_type(void); + +NM_AVAILABLE_IN_1_56_2 +guint nm_device_geneve_get_id(NMDeviceGeneve *device); +NM_AVAILABLE_IN_1_56_2 +const char *nm_device_geneve_get_remote(NMDeviceGeneve *device); +NM_AVAILABLE_IN_1_56_2 +guint nm_device_geneve_get_dst_port(NMDeviceGeneve *device); +NM_AVAILABLE_IN_1_56_2 +guint nm_device_geneve_get_tos(NMDeviceGeneve *device); +NM_AVAILABLE_IN_1_56_2 +guint nm_device_geneve_get_ttl(NMDeviceGeneve *device); +NM_AVAILABLE_IN_1_56_2 +guint nm_device_geneve_get_df(NMDeviceGeneve *device); + +G_END_DECLS + +#endif /* __NM_DEVICE_GENEVE_H__ */ diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c index 0e98d4348a..bd33440b4f 100644 --- a/src/libnm-core-impl/nm-connection.c +++ b/src/libnm-core-impl/nm-connection.c @@ -3272,6 +3272,7 @@ nm_connection_is_virtual(NMConnection *connection) NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_DUMMY_SETTING_NAME, + NM_SETTING_GENEVE_SETTING_NAME, NM_SETTING_HSR_SETTING_NAME, NM_SETTING_IP_TUNNEL_SETTING_NAME, NM_SETTING_IPVLAN_SETTING_NAME, diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index 42bff04ae0..b05f530a26 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -36,6 +36,7 @@ #define NM_DBUS_INTERFACE_DEVICE_BRIDGE NM_DBUS_INTERFACE_DEVICE ".Bridge" #define NM_DBUS_INTERFACE_DEVICE_DUMMY NM_DBUS_INTERFACE_DEVICE ".Dummy" #define NM_DBUS_INTERFACE_DEVICE_GENERIC NM_DBUS_INTERFACE_DEVICE ".Generic" +#define NM_DBUS_INTERFACE_DEVICE_GENEVE NM_DBUS_INTERFACE_DEVICE ".Geneve" #define NM_DBUS_INTERFACE_DEVICE_GRE NM_DBUS_INTERFACE_DEVICE ".Gre" #define NM_DBUS_INTERFACE_DEVICE_HSR NM_DBUS_INTERFACE_DEVICE ".Hsr" #define NM_DBUS_INTERFACE_DEVICE_INFINIBAND NM_DBUS_INTERFACE_DEVICE ".Infiniband" @@ -250,6 +251,7 @@ typedef enum { * @NM_DEVICE_TYPE_LOOPBACK: a loopback interface. Since: 1.42. * @NM_DEVICE_TYPE_HSR: A HSR/PRP device. Since: 1.46. * @NM_DEVICE_TYPE_IPVLAN: A IPVLAN device. Since: 1.52. + * @NM_DEVICE_TYPE_GENEVE: A GENEVE device. Since: 1.58, 1.56.2. * * #NMDeviceType values indicate the type of hardware represented by a * device object. @@ -290,6 +292,7 @@ typedef enum { NM_DEVICE_TYPE_LOOPBACK = 32, NM_DEVICE_TYPE_HSR = 33, NM_DEVICE_TYPE_IPVLAN = 34, + NM_DEVICE_TYPE_GENEVE = 35, } NMDeviceType; /** diff --git a/vapi/NM-1.0.metadata b/vapi/NM-1.0.metadata index 6f1720ad7a..3084b7c289 100644 --- a/vapi/NM-1.0.metadata +++ b/vapi/NM-1.0.metadata @@ -115,6 +115,7 @@ DEVICE_BT_* parent="NM.DeviceBt" name="DEVICE DEVICE_DUMMY_* parent="NM.DeviceDummy" name="DEVICE_DUMMY_(.+)" DEVICE_ETHERNET_* parent="NM.DeviceEthernet" name="DEVICE_ETHERNET_(.+)" DEVICE_GENERIC_* parent="NM.DeviceGeneric" name="DEVICE_GENERIC_(.+)" +DEVICE_GENEVE_* parent="NM.DeviceGeneve" name="DEVICE_GENEVE_(.+)" DEVICE_HSR_* parent="NM.DeviceHsr" name="DEVICE_HSR_(.+)" DEVICE_INFINIBAND_* parent="NM.DeviceInfiniband" name="DEVICE_INFINIBAND_(.+)" DEVICE_IP_TUNNEL_* parent="NM.DeviceIPTunnel" name="DEVICE_IP_TUNNEL_(.+)" From 0c4640c53f6e1bd2a3fe9d9fefb90f29008ce8ba Mon Sep 17 00:00:00 2001 From: Rahul Rajesh Date: Tue, 17 Feb 2026 15:16:48 -0500 Subject: [PATCH 07/20] NEWS: add support for GENEVE interface https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2352 Resolves: https://issues.redhat.com/browse/RHEL-122042 (cherry picked from commit 2e2b4946ea4827e1a05d715d80a22828ac37f4b0) --- NEWS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS b/NEWS index 3ee8e88aca..33a229927c 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,10 @@ +============================================= +NetworkManager-1.56.2 +Overview of changes since NetworkManager-1.56 +============================================= + +* Add support for GENEVE interface. + ============================================= NetworkManager-1.56 Overview of changes since NetworkManager-1.54 From af3eff5944d26e149e329c9df127ef5faf0153f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Wed, 11 Feb 2026 09:11:46 +0100 Subject: [PATCH 08/20] dbus: device: add SetManaged method The 'Managed' property only sets the managed state in runtime, but it is not possible to persist it to disk. Add a SetManaged method that will be able to persist it to disk. In this commit, it just modify the runtime state, so it actually only does the same than setting the property. Storing to disk will be added in next commits. (cherry picked from commit 9ff530c3228196fef3a477d5fd394142be04894b) --- .../org.freedesktop.NetworkManager.Device.xml | 16 +++ src/core/devices/nm-device.c | 109 ++++++++++++++++++ src/libnm-client-impl/libnm.ver | 3 + src/libnm-client-impl/nm-device.c | 70 ++++++++++- src/libnm-client-public/nm-device.h | 9 ++ src/libnm-core-public/nm-dbus-interface.h | 15 +++ 6 files changed, 219 insertions(+), 3 deletions(-) diff --git a/introspection/org.freedesktop.NetworkManager.Device.xml b/introspection/org.freedesktop.NetworkManager.Device.xml index 085d2a1f74..34cadc2bac 100644 --- a/introspection/org.freedesktop.NetworkManager.Device.xml +++ b/introspection/org.freedesktop.NetworkManager.Device.xml @@ -175,6 +175,9 @@ property has a similar effect to configuring the device as unmanaged via the keyfile.unmanaged-devices setting in NetworkManager.conf. Changes to this value are not persistent and lost after NetworkManager restart. + + DEPRECATED: 1.58: Use the SetManaged method instead, which supports + additional features like persisting the state to disk --> @@ -391,6 +394,19 @@ --> + + + + + + diff --git a/po/POTFILES.in b/po/POTFILES.in index 1cb0255415..04de8d3f5c 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -5,6 +5,7 @@ src/core/NetworkManagerUtils.c src/core/devices/adsl/nm-device-adsl.c src/core/devices/bluetooth/nm-bluez-manager.c src/core/devices/bluetooth/nm-device-bt.c +src/core/devices/nm-device.c src/core/devices/nm-device-6lowpan.c src/core/devices/nm-device-bond.c src/core/devices/nm-device-bridge.c diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 426e4aad15..70fbde1d52 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -14847,12 +14847,71 @@ typedef struct { NMDeviceManagedFlags managed_flags; } SetManagedData; +/** + * set_managed: + * @self: the device + * @managed: the new managed state to set. + * @flags: flags to select different behaviors like storing to disk. + * @error: return location for a #GError, or %NULL + * + * Sets the managed state of the device. It can affect the runtime managed state + * if the %NM_DEVICE_MANAGED_FLAGS_RUNTIME is set, and to the value stored on disk + * (persistent across reboots) state if the %NM_DEVICE_MANAGED_FLAGS_PERMANENT is set. + * + * Returns: %TRUE if the managed state was set successfully, %FALSE otherwise. + */ static gboolean set_managed(NMDevice *self, gboolean managed, NMDeviceManagedFlags flags, GError **error) { + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMTernary old = NM_TERNARY_DEFAULT; + nm_assert((flags & ~NM_DEVICE_MANAGED_FLAGS_ALL) == 0); - g_object_set(self, NM_DEVICE_MANAGED, managed, NULL); + if (!NM_FLAGS_ANY(flags, NM_DEVICE_MANAGED_FLAGS_PERMANENT | NM_DEVICE_MANAGED_FLAGS_RUNTIME)) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + _("set managed: no permanent or runtime was selected")); + return FALSE; + } + + if (flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT) { + nm_config_get_device_managed(nm_manager_get_config(priv->manager), self, &old, error); + + if (!nm_config_set_device_managed(nm_manager_get_config(priv->manager), + self, + managed, + flags & NM_DEVICE_MANAGED_FLAGS_BY_MAC, + error)) + return FALSE; + + /* Update the unmanaged flags after the change on disk */ + nm_device_set_unmanaged_by_user_conf(self); + + if (!!nm_device_get_unmanaged_flags(self, NM_UNMANAGED_USER_CONF) != !managed) { + /* We failed to make the new state effective on disk. Maybe the new config + * collides with other config. Try to revert and return error. Otherwise, + * we would set the runtime state correctly, but get an unexpected state + * after a reboot. */ + nm_config_set_device_managed(nm_manager_get_config(priv->manager), + self, + old, + flags & NM_DEVICE_MANAGED_FLAGS_BY_MAC, + NULL); + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + _("failed to persist 'managed=%d' on disk, other configurations may be " + "overriding it"), + managed); + return FALSE; + } + } + + if (flags & NM_DEVICE_MANAGED_FLAGS_RUNTIME) { + g_object_set(self, NM_DEVICE_MANAGED, !!managed, NULL); + } return TRUE; } diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index be15a5c631..9ff2e84678 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -1251,6 +1251,10 @@ typedef enum /*< flags >*/ { /** * NMDeviceManagedFlags: * @NM_DEVICE_MANAGED_FLAGS_NONE: no flag set. + * @NM_DEVICE_MANAGED_FLAGS_RUNTIME: to set the device managed state to the runtime value. + * @NM_DEVICE_MANAGED_FLAGS_PERMANENT: to set the device managed state to the permanent (on disk) value. + * @NM_DEVICE_MANAGED_FLAGS_BY_MAC: to match the device by MAC address, not by name. + * This option only makes sense together with %NM_DEVICE_MANAGED_FLAGS_PERMANENT. * @NM_DEVICE_MANAGED_FLAGS_ALL: all flags. * * Flags for the SetManaged() D-Bus call of a device and nm_device_set_managed_async(). @@ -1258,9 +1262,12 @@ typedef enum /*< flags >*/ { * Since: 1.58, 1.56.2 */ typedef enum /*< flags >*/ { - NM_DEVICE_MANAGED_FLAGS_NONE = 0, + NM_DEVICE_MANAGED_FLAGS_NONE = 0, + NM_DEVICE_MANAGED_FLAGS_RUNTIME = 0x1, + NM_DEVICE_MANAGED_FLAGS_PERMANENT = 0x2, + NM_DEVICE_MANAGED_FLAGS_BY_MAC = 0x4, - NM_DEVICE_MANAGED_FLAGS_ALL = 0, /* */ + NM_DEVICE_MANAGED_FLAGS_ALL = 0x7, /* */ } NMDeviceManagedFlags; /** From b3b9c4b0f64cc29d391ac9ed21cf907bc9c1d22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Mon, 23 Feb 2026 16:02:49 +0100 Subject: [PATCH 12/20] core: device: allow to reset the managed property Previous commits added the capability to persist to disk the value of 'managed' received via the D-Bus API. Users might need to clear the previous content, thus reseting it to its default. Although this is specially useful for the PERMANENT flag, we need to be consistent and reset the runtime state too. (cherry picked from commit f346fcf977c22f12b509fff3c674dbfb67490a38) --- .../org.freedesktop.NetworkManager.Device.xml | 4 +- src/core/devices/nm-device.c | 56 +++++++++++++------ src/libnm-client-impl/libnm.ver | 1 + src/libnm-client-impl/nm-device.c | 4 +- src/libnm-client-public/nm-device.h | 2 +- src/libnm-core-public/nm-dbus-interface.h | 16 ++++++ 6 files changed, 61 insertions(+), 22 deletions(-) diff --git a/introspection/org.freedesktop.NetworkManager.Device.xml b/introspection/org.freedesktop.NetworkManager.Device.xml index 2390e800bd..2b84f38aa1 100644 --- a/introspection/org.freedesktop.NetworkManager.Device.xml +++ b/introspection/org.freedesktop.NetworkManager.Device.xml @@ -396,7 +396,7 @@ - + diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 70fbde1d52..a72a2196ea 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -14843,7 +14843,7 @@ impl_device_get_applied_connection(NMDBusObject *obj, /*****************************************************************************/ typedef struct { - gboolean managed_state; + NMDeviceManaged managed_state; NMDeviceManagedFlags managed_flags; } SetManagedData; @@ -14861,11 +14861,13 @@ typedef struct { * Returns: %TRUE if the managed state was set successfully, %FALSE otherwise. */ static gboolean -set_managed(NMDevice *self, gboolean managed, NMDeviceManagedFlags flags, GError **error) +set_managed(NMDevice *self, NMDeviceManaged managed, NMDeviceManagedFlags flags, GError **error) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - NMTernary old = NM_TERNARY_DEFAULT; + NMTernary managed_to_disk, old = NM_TERNARY_DEFAULT; + nm_assert( + NM_IN_SET(managed, NM_DEVICE_MANAGED_NO, NM_DEVICE_MANAGED_YES, NM_DEVICE_MANAGED_RESET)); nm_assert((flags & ~NM_DEVICE_MANAGED_FLAGS_ALL) == 0); if (!NM_FLAGS_ANY(flags, NM_DEVICE_MANAGED_FLAGS_PERMANENT | NM_DEVICE_MANAGED_FLAGS_RUNTIME)) { @@ -14877,11 +14879,12 @@ set_managed(NMDevice *self, gboolean managed, NMDeviceManagedFlags flags, GError } if (flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT) { + managed_to_disk = managed == NM_DEVICE_MANAGED_RESET ? NM_TERNARY_DEFAULT : !!managed; nm_config_get_device_managed(nm_manager_get_config(priv->manager), self, &old, error); if (!nm_config_set_device_managed(nm_manager_get_config(priv->manager), self, - managed, + managed_to_disk, flags & NM_DEVICE_MANAGED_FLAGS_BY_MAC, error)) return FALSE; @@ -14889,7 +14892,8 @@ set_managed(NMDevice *self, gboolean managed, NMDeviceManagedFlags flags, GError /* Update the unmanaged flags after the change on disk */ nm_device_set_unmanaged_by_user_conf(self); - if (!!nm_device_get_unmanaged_flags(self, NM_UNMANAGED_USER_CONF) != !managed) { + if (managed_to_disk != NM_TERNARY_DEFAULT + && managed_to_disk != !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_USER_CONF)) { /* We failed to make the new state effective on disk. Maybe the new config * collides with other config. Try to revert and return error. Otherwise, * we would set the runtime state correctly, but get an unexpected state @@ -14910,7 +14914,14 @@ set_managed(NMDevice *self, gboolean managed, NMDeviceManagedFlags flags, GError } if (flags & NM_DEVICE_MANAGED_FLAGS_RUNTIME) { - g_object_set(self, NM_DEVICE_MANAGED, !!managed, NULL); + if (managed == NM_DEVICE_MANAGED_RESET) { + nm_device_set_unmanaged_by_flags(self, + NM_UNMANAGED_USER_EXPLICIT, + NM_UNMAN_FLAG_OP_FORGET, + NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT); + } else { + g_object_set(self, NM_DEVICE_MANAGED, !!managed, NULL); + } } return TRUE; @@ -14924,7 +14935,7 @@ set_managed_cb(NMDevice *self, gpointer user_data) { SetManagedData *set_managed_data = user_data; - gboolean managed; + NMDeviceManaged managed; NMDeviceManagedFlags flags; GError *local = NULL; @@ -14932,11 +14943,20 @@ set_managed_cb(NMDevice *self, flags = set_managed_data->managed_flags; nm_g_slice_free(set_managed_data); - if (!error && (flags & ~NM_DEVICE_MANAGED_FLAGS_ALL) != 0) { - g_set_error_literal(&error, - NM_DEVICE_ERROR, - NM_DEVICE_ERROR_INVALID_ARGUMENT, - "Invalid flags"); + if (!error) { + if (!NM_IN_SET(managed, + NM_DEVICE_MANAGED_NO, + NM_DEVICE_MANAGED_YES, + NM_DEVICE_MANAGED_RESET)) + g_set_error_literal(&error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "Invalid managed value"); + else if ((flags & ~NM_DEVICE_MANAGED_FLAGS_ALL) != 0) + g_set_error_literal(&error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "Invalid flags"); } if (error) { @@ -14976,15 +14996,17 @@ impl_device_set_managed(NMDBusObject *obj, { NMDevice *self = NM_DEVICE(obj); gs_free_error GError *error = NULL; - gboolean managed; + guint32 managed_u; + NMDeviceManaged managed; guint32 flags_u; NMDeviceManagedFlags flags; SetManagedData *set_managed_data; - g_variant_get(parameters, "(bu)", &managed, &flags_u); + g_variant_get(parameters, "(uu)", &managed_u, &flags_u); - flags = flags_u; - nm_assert(flags == flags_u); + managed = managed_u; + flags = flags_u; + nm_assert(managed == managed_u && flags == flags_u); set_managed_data = g_slice_new(SetManagedData); *set_managed_data = (SetManagedData) { @@ -20030,7 +20052,7 @@ static const NMDBusInterfaceInfoExtended interface_info_device = { NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( NM_DEFINE_GDBUS_METHOD_INFO_INIT("SetManaged", .in_args = NM_DEFINE_GDBUS_ARG_INFOS( - NM_DEFINE_GDBUS_ARG_INFO("managed", "b"), + NM_DEFINE_GDBUS_ARG_INFO("managed", "u"), NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ), ), .handle = impl_device_set_managed, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Disconnect", ), diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver index 95d36d124f..bfa40be15d 100644 --- a/src/libnm-client-impl/libnm.ver +++ b/src/libnm-client-impl/libnm.ver @@ -2115,6 +2115,7 @@ global: nm_device_geneve_get_ttl; nm_device_geneve_get_type; nm_device_managed_flags_get_type; + nm_device_managed_get_type; nm_device_set_managed_async; nm_device_set_managed_finish; nm_setting_geneve_df_get_type; diff --git a/src/libnm-client-impl/nm-device.c b/src/libnm-client-impl/nm-device.c index abb51016ed..c94599eec6 100644 --- a/src/libnm-client-impl/nm-device.c +++ b/src/libnm-client-impl/nm-device.c @@ -1485,7 +1485,7 @@ nm_device_set_managed(NMDevice *device, gboolean managed) **/ void nm_device_set_managed_async(NMDevice *device, - gboolean managed, + NMDeviceManaged managed, NMDeviceManagedFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, @@ -1503,7 +1503,7 @@ nm_device_set_managed_async(NMDevice *device, _nm_object_get_path(device), NM_DBUS_INTERFACE_DEVICE, "SetManaged", - g_variant_new("(bu)", managed, flags), + g_variant_new("(uu)", managed, flags), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NONE, NM_DBUS_DEFAULT_TIMEOUT_MSEC, diff --git a/src/libnm-client-public/nm-device.h b/src/libnm-client-public/nm-device.h index e18bdcac44..bf44105566 100644 --- a/src/libnm-client-public/nm-device.h +++ b/src/libnm-client-public/nm-device.h @@ -152,7 +152,7 @@ _NM_DEPRECATED_SYNC_METHOD void nm_device_set_managed(NMDevice *device, gboolean managed); NM_AVAILABLE_IN_1_56_2 void nm_device_set_managed_async(NMDevice *device, - gboolean managed, + NMDeviceManaged managed, NMDeviceManagedFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index 9ff2e84678..827b13cb35 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -1248,6 +1248,22 @@ typedef enum /*< flags >*/ { NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP = 0x1, } NMDeviceReapplyFlags; +/** + * NMDeviceManaged: + * @NM_DEVICE_MANAGED_NO: the device is not managed. + * @NM_DEVICE_MANAGED_YES: the device is managed. + * @NM_DEVICE_MANAGED_RESET: reset the device managed state to the default value. + * + * Values for the SetManaged() D-Bus call of a device and nm_device_set_managed_async(). + * + * Since: 1.58 + */ +typedef enum { + NM_DEVICE_MANAGED_NO = 0, + NM_DEVICE_MANAGED_YES = 1, + NM_DEVICE_MANAGED_RESET = 2, +} NMDeviceManaged; + /** * NMDeviceManagedFlags: * @NM_DEVICE_MANAGED_FLAGS_NONE: no flag set. From a35bca069c76412b1bef619dd41011e323891bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Fri, 13 Feb 2026 15:41:27 +0100 Subject: [PATCH 13/20] core: device: allow to change the admin state of the device in SetManaged() Control it with a new NM_DEVICE_MANAGED_SET_ADMIN_STATE flag. This flag will make that, at the same time that the device is moved to managed/unmanaged, it's admin state is set to up/down. Many users want to have a way to have their devices in a DOWN admin state when they are not using them. Because of the complex activation process, NM wants to have its devices in UP state all the time. However, it is not a problem to have it DOWN if we are not managing it. (cherry picked from commit b9725dab739f35e941166e2a81269885c5a6672f) --- src/core/devices/nm-device.c | 10 ++++++++++ src/libnm-core-public/nm-dbus-interface.h | 15 ++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index a72a2196ea..2bb04857e3 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -14921,6 +14921,16 @@ set_managed(NMDevice *self, NMDeviceManaged managed, NMDeviceManagedFlags flags, NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT); } else { g_object_set(self, NM_DEVICE_MANAGED, !!managed, NULL); + + /* If requested, set the administrative state of the device to UP if the + * new managed state is YES, and to DOWN if it's NO. */ + if (flags & NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE) { + if (nm_device_get_ifindex(self)) + nm_platform_link_change_flags(nm_device_get_platform(self), + nm_device_get_ifindex(self), + IFF_UP, + !!managed); + } } } diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index 827b13cb35..ba96bde1ff 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -1271,6 +1271,10 @@ typedef enum { * @NM_DEVICE_MANAGED_FLAGS_PERMANENT: to set the device managed state to the permanent (on disk) value. * @NM_DEVICE_MANAGED_FLAGS_BY_MAC: to match the device by MAC address, not by name. * This option only makes sense together with %NM_DEVICE_MANAGED_FLAGS_PERMANENT. + * @NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE: to set the administrative state of the + * device to up if the managed state is %NM_DEVICE_MANAGED_YES, and down if the managed state + * is %NM_DEVICE_MANAGED_NO. If the flag is not set, the administrative state is not changed. + * The flag is ignored for %NM_DEVICE_MANAGED_RESET. * @NM_DEVICE_MANAGED_FLAGS_ALL: all flags. * * Flags for the SetManaged() D-Bus call of a device and nm_device_set_managed_async(). @@ -1278,12 +1282,13 @@ typedef enum { * Since: 1.58, 1.56.2 */ typedef enum /*< flags >*/ { - NM_DEVICE_MANAGED_FLAGS_NONE = 0, - NM_DEVICE_MANAGED_FLAGS_RUNTIME = 0x1, - NM_DEVICE_MANAGED_FLAGS_PERMANENT = 0x2, - NM_DEVICE_MANAGED_FLAGS_BY_MAC = 0x4, + NM_DEVICE_MANAGED_FLAGS_NONE = 0, + NM_DEVICE_MANAGED_FLAGS_RUNTIME = 0x1, + NM_DEVICE_MANAGED_FLAGS_PERMANENT = 0x2, + NM_DEVICE_MANAGED_FLAGS_BY_MAC = 0x4, + NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE = 0x8, - NM_DEVICE_MANAGED_FLAGS_ALL = 0x7, /* */ + NM_DEVICE_MANAGED_FLAGS_ALL = 0xF, /* */ } NMDeviceManagedFlags; /** From a27abdbdd0b004581c319801c414a0fb43e300b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Wed, 25 Feb 2026 12:30:50 +0100 Subject: [PATCH 14/20] core: device: autoselect device match criteria in SetManaged() Devices like veth without a permanent MAC address cannot be matched by MAC. If using the BY_MAC flag in SetManaged(), the changes are not effective for such kind of devices. Add a BY_NAME flag, in addition to the BY_MAC one. If the client sets one of them, it means to force this mode of matching. If none is selected, the daemon will choose how to match, preferring matching by MAC when possible, and by ifname when not possible. (cherry picked from commit 7c8f343f2c4e0937f30231426c00bd941916e7de) --- src/core/devices/nm-device.c | 47 +++++++++++++++++++++-- src/libnm-core-public/nm-dbus-interface.h | 21 ++++++---- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 2bb04857e3..dd65e44e3a 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -14847,6 +14847,43 @@ typedef struct { NMDeviceManagedFlags managed_flags; } SetManagedData; +static gboolean +get_managed_match_by_mac(NMDevice *self, NMDeviceManagedFlags flags, gboolean *out, GError **error) +{ + gboolean is_fake_hwaddr; + + nm_assert(out); + + if ((flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC) + && (flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_NAME)) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "cannot match both by 'mac' and by 'interface-name'"); + return FALSE; + } + + nm_device_get_permanent_hw_address_full(self, TRUE, &is_fake_hwaddr); + + if ((flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC) && is_fake_hwaddr) { + g_set_error_literal( + error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "cannot match by 'mac': the device doesn't have a permanent MAC address"); + return FALSE; + } + + if (flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC) + *out = TRUE; + else if (flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_NAME) + *out = FALSE; + else + *out = !is_fake_hwaddr; + + return TRUE; +} + /** * set_managed: * @self: the device @@ -14864,7 +14901,6 @@ static gboolean set_managed(NMDevice *self, NMDeviceManaged managed, NMDeviceManagedFlags flags, GError **error) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - NMTernary managed_to_disk, old = NM_TERNARY_DEFAULT; nm_assert( NM_IN_SET(managed, NM_DEVICE_MANAGED_NO, NM_DEVICE_MANAGED_YES, NM_DEVICE_MANAGED_RESET)); @@ -14879,13 +14915,18 @@ set_managed(NMDevice *self, NMDeviceManaged managed, NMDeviceManagedFlags flags, } if (flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT) { + NMTernary managed_to_disk, old = NM_TERNARY_DEFAULT; + gboolean by_mac; + managed_to_disk = managed == NM_DEVICE_MANAGED_RESET ? NM_TERNARY_DEFAULT : !!managed; nm_config_get_device_managed(nm_manager_get_config(priv->manager), self, &old, error); + if (!get_managed_match_by_mac(self, flags, &by_mac, error)) + return FALSE; if (!nm_config_set_device_managed(nm_manager_get_config(priv->manager), self, managed_to_disk, - flags & NM_DEVICE_MANAGED_FLAGS_BY_MAC, + by_mac, error)) return FALSE; @@ -14901,7 +14942,7 @@ set_managed(NMDevice *self, NMDeviceManaged managed, NMDeviceManagedFlags flags, nm_config_set_device_managed(nm_manager_get_config(priv->manager), self, old, - flags & NM_DEVICE_MANAGED_FLAGS_BY_MAC, + by_mac, NULL); g_set_error(error, NM_DEVICE_ERROR, diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index ba96bde1ff..cc80c9065d 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -1269,8 +1269,8 @@ typedef enum { * @NM_DEVICE_MANAGED_FLAGS_NONE: no flag set. * @NM_DEVICE_MANAGED_FLAGS_RUNTIME: to set the device managed state to the runtime value. * @NM_DEVICE_MANAGED_FLAGS_PERMANENT: to set the device managed state to the permanent (on disk) value. - * @NM_DEVICE_MANAGED_FLAGS_BY_MAC: to match the device by MAC address, not by name. - * This option only makes sense together with %NM_DEVICE_MANAGED_FLAGS_PERMANENT. + * @NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_NAME: to match the device by name, not by MAC address. + * @NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC: to match the device by MAC address, not by name. * @NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE: to set the administrative state of the * device to up if the managed state is %NM_DEVICE_MANAGED_YES, and down if the managed state * is %NM_DEVICE_MANAGED_NO. If the flag is not set, the administrative state is not changed. @@ -1279,16 +1279,21 @@ typedef enum { * * Flags for the SetManaged() D-Bus call of a device and nm_device_set_managed_async(). * + * %NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_NAME and %NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC + * are mutually exclusive, and they only make sense together with %NM_DEVICE_MANAGED_FLAGS_PERMANENT. + * If none is set, the matching criteria is selected automatically. + * * Since: 1.58, 1.56.2 */ typedef enum /*< flags >*/ { - NM_DEVICE_MANAGED_FLAGS_NONE = 0, - NM_DEVICE_MANAGED_FLAGS_RUNTIME = 0x1, - NM_DEVICE_MANAGED_FLAGS_PERMANENT = 0x2, - NM_DEVICE_MANAGED_FLAGS_BY_MAC = 0x4, - NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE = 0x8, + NM_DEVICE_MANAGED_FLAGS_NONE = 0, + NM_DEVICE_MANAGED_FLAGS_RUNTIME = 0x1, + NM_DEVICE_MANAGED_FLAGS_PERMANENT = 0x2, + NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_NAME = 0x4, + NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC = 0x8, + NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE = 0x10, - NM_DEVICE_MANAGED_FLAGS_ALL = 0xF, /* */ + NM_DEVICE_MANAGED_FLAGS_ALL = 0x1F, /* */ } NMDeviceManagedFlags; /** From 49ffd20a618a74ca9370ebfd95469d5649af14b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Thu, 12 Feb 2026 15:39:47 +0100 Subject: [PATCH 15/20] nmcli: add `managed --permanent yes/no/up/down/reset` Allow to manage or unmanage a device persisting across reboots. If --permanent is not specified, only the runtime managed state is changed, preserving the previous behavior. The --permanent-only option allows to edit only the persistent value, without touching the runtime value. Also add the values up/down. Up means managed=yes and set device's administrative state UP. Down means managed=no and admin state DOWN. Add the value 'reset' too. It reverts managed runtime status to default behaviour. When used with `--permanent` flag, the persisted managed settings is cleared. Co-authored-by: Rahul Rajesh (cherry picked from commit d2f98a1669df1eb447785bdf2583d30737bfa4d0) --- man/nmcli.xml | 16 +++++ src/nmcli/devices.c | 158 +++++++++++++++++++++++++++++++++----------- 2 files changed, 134 insertions(+), 40 deletions(-) diff --git a/man/nmcli.xml b/man/nmcli.xml index 7bce0de737..94f4c64cb6 100644 --- a/man/nmcli.xml +++ b/man/nmcli.xml @@ -1436,15 +1436,31 @@ + + --permanent + --permanent-only + yes no + up + down + reset Set device properties. + + The property accepts a + option to persist the managed state to disk, and not only in runtime. With + only the permanent managed state is set, and not the + runtime managed state. The special values and + can be used to set the administrative state of the device at the same time as the runtime + managed state. The value clears the explicit managed setting, and + with or it also removes + the persisted managed setting. diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c index e81e710f44..0b0ebd1ea7 100644 --- a/src/nmcli/devices.c +++ b/src/nmcli/devices.c @@ -854,7 +854,8 @@ usage(void) "delete | monitor | wifi | lldp }\n\n" " status\n\n" " show []\n\n" - " set [ifname] [autoconnect yes|no] [managed yes|no]\n\n" + " set [ifname] [autoconnect yes|no] [managed [--permanent|--permanent-only] " + "yes|no|up|down|reset]\n\n" " connect \n\n" " reapply \n\n" " modify ([+|-]. )+\n\n" @@ -972,14 +973,15 @@ usage_device_delete(void) static void usage_device_set(void) { - nmc_printerr(_("Usage: nmcli device set { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := DEVICE { PROPERTY [ PROPERTY ... ] }\n" - "DEVICE := [ifname] \n" - "PROPERTY := { autoconnect { yes | no } |\n" - " { managed { yes | no }\n" - "\n" - "Modify device properties.\n\n")); + nmc_printerr(_( + "Usage: nmcli device set { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := DEVICE { PROPERTY [ PROPERTY ... ] }\n" + "DEVICE := [ifname] \n" + "PROPERTY := { autoconnect { yes | no } |\n" + " { managed [--permanent | --permanent-only] { yes | no | up | down | reset }\n" + "\n" + "Modify device properties.\n\n")); } static void @@ -2796,17 +2798,18 @@ do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const static void do_device_set(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) { -#define DEV_SET_AUTOCONNECT 0 -#define DEV_SET_MANAGED 1 NMDevice *device = NULL; int i; struct { int idx; gboolean value; - } values[2] = { - [DEV_SET_AUTOCONNECT] = {-1}, - [DEV_SET_MANAGED] = {-1}, - }; + } autoconnect_data = {-1}; + struct { + int idx; + NMDeviceManaged value; + guint32 flags; + } managed_data = {-1}; + gs_free_error GError *error = NULL; next_arg(nmc, &argc, &argv, NULL); @@ -2828,49 +2831,120 @@ do_device_set(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *ar i = 0; do { - gboolean flag; - if (argc == 1 && nmc->complete) nmc_complete_strings(*argv, "managed", "autoconnect"); if (matches(*argv, "managed")) { + NMDeviceManaged val; + guint32 flags = 0; + gboolean val_bool; + gboolean perm = FALSE, perm_only = FALSE; + argc--; argv++; + + if (argc == 1 && nmc->complete) { + nmc_complete_strings(*argv, + "true", + "yes", + "on", + "false", + "no", + "off", + "up", + "down", + "reset", + "--permanent", + "--permanent-only"); + } + + if (managed_data.idx != -1) { + g_string_printf(nmc->return_text, _("Error: 'managed' can only be set once.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return; + } + + while (argc > 0) { + /* --perm matches with --permanent, the most common, + * but --permanent-only requires a exact match */ + if (nm_streq0(*argv, "--permanent-only")) + perm_only = TRUE; + else if (matches(*argv, "--permanent")) + perm = TRUE; + else + break; + argc--; + argv++; + } + + if (perm_only && perm) { + g_string_printf( + nmc->return_text, + _("Error: '--permanent-only' and '--permanent' cannot be used together.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return; + } else if (perm) { + flags |= NM_DEVICE_MANAGED_FLAGS_RUNTIME | NM_DEVICE_MANAGED_FLAGS_PERMANENT; + } else if (perm_only) { + flags |= NM_DEVICE_MANAGED_FLAGS_PERMANENT; + } else { + /* If --permanent/--permanent-only are missing, set only the runtime flag, as this + * is how it used to work when these options didn't exist. */ + flags |= NM_DEVICE_MANAGED_FLAGS_RUNTIME; + } + if (!argc) { - g_string_printf(nmc->return_text, - _("Error: '%s' argument is missing."), - *(argv - 1)); + g_string_printf(nmc->return_text, _("Error: 'managed' argument is missing.")); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return; } - if (argc == 1 && nmc->complete) - nmc_complete_bool(*argv); - if (!nmc_string_to_bool(*argv, &flag, &error)) { - g_string_printf(nmc->return_text, _("Error: 'managed': %s."), error->message); + + if (matches(*argv, "up") || matches(*argv, "down")) { + flags |= NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE; + val = matches(*argv, "up") ? NM_DEVICE_MANAGED_YES : NM_DEVICE_MANAGED_NO; + } else if (matches(*argv, "reset")) { + val = NM_DEVICE_MANAGED_RESET; + } else if (nmc_string_to_bool(*argv, &val_bool, NULL)) { + val = val_bool ? NM_DEVICE_MANAGED_YES : NM_DEVICE_MANAGED_NO; + } else { + g_string_printf( + nmc->return_text, + _("Error: 'managed': '%s' is not valid, use 'yes/no/up/down/reset'."), + *argv); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return; } - values[DEV_SET_MANAGED].idx = ++i; - values[DEV_SET_MANAGED].value = flag; + + managed_data.idx = i++; + managed_data.value = val; + managed_data.flags = flags; } else if (matches(*argv, "autoconnect")) { + gboolean val; + argc--; argv++; + if (!argc) { - g_string_printf(nmc->return_text, - _("Error: '%s' argument is missing."), - *(argv - 1)); + g_string_printf(nmc->return_text, _("Error: 'autoconnect' argument is missing.")); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return; } + if (autoconnect_data.idx != -1) { + g_string_printf(nmc->return_text, _("Error: 'autoconnect' can only be set once.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return; + } + if (argc == 1 && nmc->complete) nmc_complete_bool(*argv); - if (!nmc_string_to_bool(*argv, &flag, &error)) { + + if (!nmc_string_to_bool(*argv, &val, &error)) { g_string_printf(nmc->return_text, _("Error: 'autoconnect': %s."), error->message); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return; } - values[DEV_SET_AUTOCONNECT].idx = ++i; - values[DEV_SET_AUTOCONNECT].value = flag; + autoconnect_data.idx = i++; + autoconnect_data.value = val; } else { g_string_printf(nmc->return_text, _("Error: property '%s' is not known."), *argv); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; @@ -2883,15 +2957,19 @@ do_device_set(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *ar /* when multiple properties are specified, set them in the order as they * are specified on the command line. */ - if (values[DEV_SET_AUTOCONNECT].idx >= 0 && values[DEV_SET_MANAGED].idx >= 0 - && values[DEV_SET_MANAGED].idx < values[DEV_SET_AUTOCONNECT].idx) { - nm_device_set_managed(device, values[DEV_SET_MANAGED].value); - values[DEV_SET_MANAGED].idx = -1; + for (i = 0; i < 2; i++) { + if (autoconnect_data.idx == i) { + nm_device_set_autoconnect(device, autoconnect_data.value); + } + if (managed_data.idx == i) { + nm_device_set_managed_async(device, + managed_data.value, + managed_data.flags, + NULL, + NULL, + NULL); + } } - if (values[DEV_SET_AUTOCONNECT].idx >= 0) - nm_device_set_autoconnect(device, values[DEV_SET_AUTOCONNECT].value); - if (values[DEV_SET_MANAGED].idx >= 0) - nm_device_set_managed(device, values[DEV_SET_MANAGED].value); } static void From d896c3fbfa02e74d214b6c300f890f058ca94015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Mon, 2 Mar 2026 12:49:18 +0100 Subject: [PATCH 16/20] nmcli: wait for `device set` async operation to finish We need to wait for it to finish so we can show error messages, if any. Also, if we don't do it, sometimes the `d set eth0 managed ...` operation fails with the following message in the daemon's log: "Unable to determine UID of the request". This is because the client's process is terminated before the daemon can check the permissions, as it needs to check the uid and gid from the client's process. (cherry picked from commit 7ee50b687a86c6cc6ecb2dd912ff58f873db5e0c) --- src/nmcli/devices.c | 58 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c index 0b0ebd1ea7..2787439ec7 100644 --- a/src/nmcli/devices.c +++ b/src/nmcli/devices.c @@ -2795,11 +2795,52 @@ do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const } } +typedef struct { + NmCli *nmc; + GSource *timeout_source; +} DeviceSetCbInfo; + +static void +device_set_cb_info_finish(DeviceSetCbInfo *info) +{ + nm_clear_g_source_inst(&info->timeout_source); + g_slice_free(DeviceSetCbInfo, info); + quit(); +} + +static gboolean +device_set_timeout_cb(gpointer user_data) +{ + DeviceSetCbInfo *cb_info = user_data; + + timeout_cb(cb_info->nmc); + device_set_cb_info_finish(cb_info); + return G_SOURCE_REMOVE; +} + +static void +device_set_cb(GObject *object, GAsyncResult *result, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(object); + DeviceSetCbInfo *info = (DeviceSetCbInfo *) user_data; + NmCli *nmc = info->nmc; + gs_free_error GError *error = NULL; + + /* Only 'managed' is treated asynchronously, 'autoconnect' is treated synchronously */ + if (!nm_device_set_managed_finish(device, result, &error)) { + g_string_printf(nmc->return_text, _("Error: set managed failed: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + } + + device_set_cb_info_finish(info); +} + static void do_device_set(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) { - NMDevice *device = NULL; - int i; + NMDevice *device = NULL; + DeviceSetCbInfo *cb_info = NULL; + int i; struct { int idx; gboolean value; @@ -2962,12 +3003,21 @@ do_device_set(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *ar nm_device_set_autoconnect(device, autoconnect_data.value); } if (managed_data.idx == i) { + cb_info = g_slice_new0(DeviceSetCbInfo); + cb_info->nmc = nmc; + if (nmc->timeout > 0) + cb_info->timeout_source = + nm_g_timeout_add_seconds_source(nmc->timeout, device_set_timeout_cb, cb_info); + + nmc->nowait_flag = (nmc->timeout == 0); + nmc->should_wait++; + nm_device_set_managed_async(device, managed_data.value, managed_data.flags, NULL, - NULL, - NULL); + device_set_cb, + cb_info); } } } From 8999163df88eae41a7b550812d88fb3f5c6464a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Tue, 3 Mar 2026 14:29:14 +0100 Subject: [PATCH 17/20] core: config: add unit tests for the new get/set_device_managed (cherry picked from commit 1252f8dc7e27a56ea776da9ac8dcdcbb65aa5109) --- src/core/tests/config/test-config.c | 98 +++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/core/tests/config/test-config.c b/src/core/tests/config/test-config.c index 78fd10571d..26588d1a1d 100644 --- a/src/core/tests/config/test-config.c +++ b/src/core/tests/config/test-config.c @@ -311,6 +311,103 @@ test_config_override(void) g_assert_cmpstr(plugins[3], ==, "delta"); } +static void +test_config_managed(void) +{ + NMConfig *config; + const char *CONFIG_USER = BUILD_DIR "/test-config-managed.conf"; + const char *CONFIG_INTERN = BUILD_DIR "/test-config-managed-intern.conf"; + NMDevice *dev; + gs_free char *group_by_name = NULL; + const char *ifname, *group_by_mac; + NMTernary managed; + GKeyFile *kf = nm_config_create_keyfile(); + + dev = nm_test_device_new("11:11:11:11:11:11"); + ifname = nm_device_get_iface(dev); + group_by_name = + g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-%s", ifname); + group_by_mac = NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-11-11-11-11-11-11"; + + g_assert(g_file_set_contents(CONFIG_USER, "", 0, NULL)); + g_assert(g_file_set_contents(CONFIG_INTERN, "", 0, NULL)); + + config = setup_config(NULL, CONFIG_USER, CONFIG_INTERN, NULL, "/no/such/dir", "", NULL); + + g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert_cmpint(managed, ==, NM_TERNARY_DEFAULT); + + /* Matching by name */ + NMTST_EXPECT_NM_INFO("config: signal: *"); + g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_TRUE, FALSE, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert_cmpint(managed, ==, NM_TERNARY_TRUE); + g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL); + g_assert_false(g_key_file_has_key(kf, group_by_mac, "managed", NULL)); + g_assert_true(g_key_file_has_key(kf, group_by_name, "managed", NULL)); + g_assert_cmpint(g_key_file_get_integer(kf, group_by_name, "managed", NULL), ==, 1); + + NMTST_EXPECT_NM_INFO("config: signal: *"); + g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_FALSE, FALSE, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert_cmpint(managed, ==, NM_TERNARY_FALSE); + g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL); + g_assert_false(g_key_file_has_key(kf, group_by_mac, "managed", NULL)); + g_assert_true(g_key_file_has_key(kf, group_by_name, "managed", NULL)); + g_assert_cmpint(g_key_file_get_integer(kf, group_by_name, "managed", NULL), ==, 0); + + /* Matching by MAC address */ + NMTST_EXPECT_NM_INFO("config: signal: *"); + g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_TRUE, TRUE, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert_cmpint(managed, ==, NM_TERNARY_TRUE); + g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL); + g_assert_false(g_key_file_has_key(kf, group_by_name, "managed", NULL)); + g_assert_true(g_key_file_has_key(kf, group_by_mac, "managed", NULL)); + g_assert_cmpint(g_key_file_get_integer(kf, group_by_mac, "managed", NULL), ==, 1); + + NMTST_EXPECT_NM_INFO("config: signal: *"); + g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_FALSE, TRUE, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert_cmpint(managed, ==, NM_TERNARY_FALSE); + g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL); + g_assert_false(g_key_file_has_key(kf, group_by_name, "managed", NULL)); + g_assert_true(g_key_file_has_key(kf, group_by_mac, "managed", NULL)); + g_assert_cmpint(g_key_file_get_integer(kf, group_by_mac, "managed", NULL), ==, 0); + + /* Resetting the managed state */ + NMTST_EXPECT_NM_INFO("config: signal: *"); + g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_DEFAULT, FALSE, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert_cmpint(managed, ==, NM_TERNARY_DEFAULT); + g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL); + g_assert_false(g_key_file_has_key(kf, group_by_name, "managed", NULL)); + g_assert_false(g_key_file_has_key(kf, group_by_mac, "managed", NULL)); + + g_object_unref(config); + + /* Both values set in the intern config file, different values */ + g_key_file_set_string(kf, group_by_name, "managed", "1"); + g_key_file_set_string(kf, group_by_mac, "managed", "0"); + g_assert(g_key_file_save_to_file(kf, CONFIG_INTERN, NULL)); + config = setup_config(NULL, CONFIG_USER, CONFIG_INTERN, NULL, "/no/such/dir", "", NULL); + g_assert(!nm_config_get_device_managed(config, dev, &managed, NULL)); + + g_object_unref(config); + + /* Both values set in the intern config file, same values */ + g_key_file_set_string(kf, group_by_name, "managed", "1"); + g_key_file_set_string(kf, group_by_mac, "managed", "1"); + g_assert(g_key_file_save_to_file(kf, CONFIG_INTERN, NULL)); + config = setup_config(NULL, CONFIG_USER, CONFIG_INTERN, NULL, "/no/such/dir", "", NULL); + g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert_cmpint(managed, ==, NM_TERNARY_TRUE); + + g_key_file_unref(kf); + g_object_unref(dev); + g_object_unref(config); +} + static void test_config_global_dns(void) { @@ -1412,6 +1509,7 @@ main(int argc, char **argv) g_test_add_func("/config/set-values", test_config_set_values); g_test_add_func("/config/global-dns", test_config_global_dns); + g_test_add_func("/config/managed", test_config_managed); g_test_add_func("/config/connectivity-check", test_config_connectivity_check); g_test_add_func("/config/signal", test_config_signal); From 27a7e9973da1f651c341eba68e9712101bf81bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Wed, 4 Mar 2026 10:15:32 +0100 Subject: [PATCH 18/20] checkpoint: rollback devices' "permanently managed" configuration If a device's "managed" configuration is changed persistently (stored to NM-intern), it needs to be undone in a rollback. (cherry picked from commit 2fbaca1cbc7237175f91445e9d58cab775218f5c) --- src/core/devices/nm-device.c | 2 +- src/core/nm-checkpoint.c | 73 ++++++++++++++++++++++++++--- src/core/nm-config.c | 23 ++++++--- src/core/nm-config.h | 1 + src/core/tests/config/test-config.c | 21 +++++---- 5 files changed, 98 insertions(+), 22 deletions(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index dd65e44e3a..a0cf1ee6d0 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -14919,7 +14919,7 @@ set_managed(NMDevice *self, NMDeviceManaged managed, NMDeviceManagedFlags flags, gboolean by_mac; managed_to_disk = managed == NM_DEVICE_MANAGED_RESET ? NM_TERNARY_DEFAULT : !!managed; - nm_config_get_device_managed(nm_manager_get_config(priv->manager), self, &old, error); + nm_config_get_device_managed(nm_manager_get_config(priv->manager), self, &old, NULL, error); if (!get_managed_match_by_mac(self, flags, &by_mac, error)) return FALSE; diff --git a/src/core/nm-checkpoint.c b/src/core/nm-checkpoint.c index 45ea3a73b6..10db0dcbab 100644 --- a/src/core/nm-checkpoint.c +++ b/src/core/nm-checkpoint.c @@ -39,7 +39,9 @@ typedef struct { bool activation_lifetime_bound_to_profile_visibility : 1; bool settings_connection_is_unsaved : 1; bool settings_connection_is_shadowed_owned : 1; + bool permanent_managed_by_mac : 1; NMUnmanFlagOp unmanaged_explicit; + NMTernary permanent_managed; NMActivationReason activation_reason; gulong dev_exported_change_id; } DeviceCheckpoint; @@ -496,14 +498,19 @@ nm_checkpoint_rollback(NMCheckpoint *self) /* Start rolling-back each device */ g_hash_table_iter_init(&iter, priv->devices); while (g_hash_table_iter_next(&iter, (gpointer *) &device, (gpointer *) &dev_checkpoint)) { - guint32 result = NM_ROLLBACK_RESULT_OK; + guint32 result = NM_ROLLBACK_RESULT_OK; + NMTernary perm_managed = NM_TERNARY_DEFAULT; + gboolean perm_managed_by_mac = FALSE; + gboolean force_perm_managed; _LOGD("rollback: restoring device %s (state %d, realized %d, explicitly unmanaged %d, " - "connection-unsaved %d, connection-shadowed %d, connection-shadowed-owned %d)", + "permanently managed %d, connection-unsaved %d, connection-shadowed %d, " + "connection-shadowed-owned %d)", dev_checkpoint->original_dev_name, (int) dev_checkpoint->state, dev_checkpoint->realized, dev_checkpoint->unmanaged_explicit, + dev_checkpoint->permanent_managed, dev_checkpoint->settings_connection_is_unsaved, !!dev_checkpoint->settings_connection_shadowed, dev_checkpoint->settings_connection_is_shadowed_owned); @@ -541,6 +548,43 @@ nm_checkpoint_rollback(NMCheckpoint *self) NM_DEVICE_STATE_REASON_NOW_MANAGED); } + force_perm_managed = !nm_config_get_device_managed(nm_config_get(), + device, + &perm_managed, + &perm_managed_by_mac, + NULL); + + if (force_perm_managed || (perm_managed != dev_checkpoint->permanent_managed) + || (dev_checkpoint->permanent_managed != NM_TERNARY_DEFAULT + && perm_managed_by_mac != dev_checkpoint->permanent_managed_by_mac)) { + gs_free_error GError *error = NULL; + NMUnmanFlagOp set_op; + + _LOGD("rollback: restore permanent managed state"); + + if (!nm_config_set_device_managed(nm_config_get(), + device, + dev_checkpoint->permanent_managed, + dev_checkpoint->permanent_managed_by_mac, + &error)) { + _LOGE("rollback: failed to restore permanent managed state: %s", error->message); + result = NM_ROLLBACK_RESULT_ERR_FAILED; + /* even if this failed, we try to continue the rollback */ + } + + if (dev_checkpoint->permanent_managed == NM_TERNARY_TRUE) + set_op = NM_UNMAN_FLAG_OP_SET_MANAGED; + else if (dev_checkpoint->permanent_managed == NM_TERNARY_FALSE) + set_op = NM_UNMAN_FLAG_OP_SET_UNMANAGED; + else + set_op = NM_UNMAN_FLAG_OP_FORGET; + + nm_device_set_unmanaged_by_flags_queue(device, + NM_UNMANAGED_USER_CONF, + set_op, + NM_DEVICE_STATE_REASON_NOW_MANAGED); + } + if (dev_checkpoint->state == NM_DEVICE_STATE_UNMANAGED) { if (nm_device_get_state(device) != NM_DEVICE_STATE_UNMANAGED || dev_checkpoint->unmanaged_explicit == NM_UNMAN_FLAG_OP_SET_UNMANAGED) { @@ -703,6 +747,8 @@ device_checkpoint_create(NMCheckpoint *self, NMDevice *device) NMSettingsConnection *settings_connection; const char *path; NMActRequest *act_request; + gboolean perm_managed_by_mac; + gs_free_error GError *error = NULL; nm_assert(NM_IS_DEVICE(device)); nm_assert(nm_device_is_real(device)); @@ -728,12 +774,26 @@ device_checkpoint_create(NMCheckpoint *self, NMDevice *device) } else dev_checkpoint->unmanaged_explicit = NM_UNMAN_FLAG_OP_FORGET; + if (nm_config_get_device_managed(nm_config_get(), + device, + &dev_checkpoint->permanent_managed, + &perm_managed_by_mac, + NULL)) { + dev_checkpoint->permanent_managed_by_mac = perm_managed_by_mac; + } else { + dev_checkpoint->permanent_managed = NM_TERNARY_DEFAULT; + dev_checkpoint->permanent_managed_by_mac = FALSE; + _LOGW("error getting permanent managed state for %s: %s", + nm_device_get_iface(device), + error->message); + g_clear_error(&error); + } + act_request = nm_device_get_act_request(device); if (act_request) { - NMSettingsStorage *storage; - gboolean shadowed_owned = FALSE; - const char *shadowed_file; - gs_free_error GError *error = NULL; + NMSettingsStorage *storage; + gboolean shadowed_owned = FALSE; + const char *shadowed_file; settings_connection = nm_act_request_get_settings_connection(act_request); applied_connection = nm_act_request_get_applied_connection(act_request); @@ -764,6 +824,7 @@ device_checkpoint_create(NMCheckpoint *self, NMDevice *device) _LOGW("error reading shadowed connection file for %s: %s", nm_device_get_iface(device), error->message); + g_clear_error(&error); } } } diff --git a/src/core/nm-config.c b/src/core/nm-config.c index b2b8089d29..d8bf2e3ed7 100644 --- a/src/core/nm-config.c +++ b/src/core/nm-config.c @@ -2105,7 +2105,11 @@ normalize_hwaddr_for_group_name(const char *hwaddr, char *out, GError **error) * Returns: TRUE if there were no errors, FALSE otherwise. */ gboolean -nm_config_get_device_managed(NMConfig *self, NMDevice *device, NMTernary *out, GError **error) +nm_config_get_device_managed(NMConfig *self, + NMDevice *device, + NMTernary *out_managed, + gboolean *out_by_mac, + GError **error) { NMConfigPrivate *priv; const GKeyFile *keyfile = NULL; @@ -2118,14 +2122,15 @@ nm_config_get_device_managed(NMConfig *self, NMDevice *device, NMTernary *out, G g_return_val_if_fail(NM_IS_CONFIG(self), FALSE); g_return_val_if_fail(NM_CONFIG_GET_PRIVATE(self)->config_data, FALSE); - g_return_val_if_fail(out, FALSE); + g_return_val_if_fail(out_managed, FALSE); g_return_val_if_fail(ifname, FALSE); priv = NM_CONFIG_GET_PRIVATE(self); keyfile = _nm_config_data_get_keyfile_intern(priv->config_data); if (!keyfile) { - *out = NM_TERNARY_DEFAULT; + NM_SET_OUT(out_managed, NM_TERNARY_DEFAULT); + NM_SET_OUT(out_by_mac, FALSE); return TRUE; } @@ -2152,16 +2157,20 @@ nm_config_get_device_managed(NMConfig *self, NMDevice *device, NMTernary *out, G } if (val_by_name != NM_TERNARY_DEFAULT && val_by_mac == NM_TERNARY_DEFAULT) { - *out = val_by_name; + NM_SET_OUT(out_managed, val_by_name); + NM_SET_OUT(out_by_mac, FALSE); return TRUE; } else if (val_by_mac != NM_TERNARY_DEFAULT && val_by_name == NM_TERNARY_DEFAULT) { - *out = val_by_mac; + NM_SET_OUT(out_managed, val_by_mac); + NM_SET_OUT(out_by_mac, TRUE); return TRUE; } else if (val_by_name == NM_TERNARY_DEFAULT && val_by_mac == NM_TERNARY_DEFAULT) { - *out = NM_TERNARY_DEFAULT; + NM_SET_OUT(out_managed, NM_TERNARY_DEFAULT); + NM_SET_OUT(out_by_mac, FALSE); return TRUE; } else if (val_by_name == val_by_mac) { - *out = val_by_name; + NM_SET_OUT(out_managed, val_by_name); + NM_SET_OUT(out_by_mac, FALSE); return TRUE; } diff --git a/src/core/nm-config.h b/src/core/nm-config.h index a1b1463418..0cfbdfe40e 100644 --- a/src/core/nm-config.h +++ b/src/core/nm-config.h @@ -145,6 +145,7 @@ void nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled); gboolean nm_config_get_device_managed(NMConfig *self, NMDevice *device, NMTernary *out_managed, + gboolean *out_by_mac, GError **error); gboolean nm_config_set_device_managed(NMConfig *self, NMDevice *device, diff --git a/src/core/tests/config/test-config.c b/src/core/tests/config/test-config.c index 26588d1a1d..b2f29821e8 100644 --- a/src/core/tests/config/test-config.c +++ b/src/core/tests/config/test-config.c @@ -321,6 +321,7 @@ test_config_managed(void) gs_free char *group_by_name = NULL; const char *ifname, *group_by_mac; NMTernary managed; + gboolean by_mac; GKeyFile *kf = nm_config_create_keyfile(); dev = nm_test_device_new("11:11:11:11:11:11"); @@ -334,14 +335,15 @@ test_config_managed(void) config = setup_config(NULL, CONFIG_USER, CONFIG_INTERN, NULL, "/no/such/dir", "", NULL); - g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, NULL, NULL)); g_assert_cmpint(managed, ==, NM_TERNARY_DEFAULT); /* Matching by name */ NMTST_EXPECT_NM_INFO("config: signal: *"); g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_TRUE, FALSE, NULL)); - g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, &by_mac, NULL)); g_assert_cmpint(managed, ==, NM_TERNARY_TRUE); + g_assert_false(by_mac); g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL); g_assert_false(g_key_file_has_key(kf, group_by_mac, "managed", NULL)); g_assert_true(g_key_file_has_key(kf, group_by_name, "managed", NULL)); @@ -349,8 +351,9 @@ test_config_managed(void) NMTST_EXPECT_NM_INFO("config: signal: *"); g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_FALSE, FALSE, NULL)); - g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, &by_mac, NULL)); g_assert_cmpint(managed, ==, NM_TERNARY_FALSE); + g_assert_false(by_mac); g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL); g_assert_false(g_key_file_has_key(kf, group_by_mac, "managed", NULL)); g_assert_true(g_key_file_has_key(kf, group_by_name, "managed", NULL)); @@ -359,8 +362,9 @@ test_config_managed(void) /* Matching by MAC address */ NMTST_EXPECT_NM_INFO("config: signal: *"); g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_TRUE, TRUE, NULL)); - g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, &by_mac, NULL)); g_assert_cmpint(managed, ==, NM_TERNARY_TRUE); + g_assert_true(by_mac); g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL); g_assert_false(g_key_file_has_key(kf, group_by_name, "managed", NULL)); g_assert_true(g_key_file_has_key(kf, group_by_mac, "managed", NULL)); @@ -368,8 +372,9 @@ test_config_managed(void) NMTST_EXPECT_NM_INFO("config: signal: *"); g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_FALSE, TRUE, NULL)); - g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, &by_mac, NULL)); g_assert_cmpint(managed, ==, NM_TERNARY_FALSE); + g_assert_true(by_mac); g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL); g_assert_false(g_key_file_has_key(kf, group_by_name, "managed", NULL)); g_assert_true(g_key_file_has_key(kf, group_by_mac, "managed", NULL)); @@ -378,7 +383,7 @@ test_config_managed(void) /* Resetting the managed state */ NMTST_EXPECT_NM_INFO("config: signal: *"); g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_DEFAULT, FALSE, NULL)); - g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, NULL, NULL)); g_assert_cmpint(managed, ==, NM_TERNARY_DEFAULT); g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL); g_assert_false(g_key_file_has_key(kf, group_by_name, "managed", NULL)); @@ -391,7 +396,7 @@ test_config_managed(void) g_key_file_set_string(kf, group_by_mac, "managed", "0"); g_assert(g_key_file_save_to_file(kf, CONFIG_INTERN, NULL)); config = setup_config(NULL, CONFIG_USER, CONFIG_INTERN, NULL, "/no/such/dir", "", NULL); - g_assert(!nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert(!nm_config_get_device_managed(config, dev, &managed, NULL, NULL)); g_object_unref(config); @@ -400,7 +405,7 @@ test_config_managed(void) g_key_file_set_string(kf, group_by_mac, "managed", "1"); g_assert(g_key_file_save_to_file(kf, CONFIG_INTERN, NULL)); config = setup_config(NULL, CONFIG_USER, CONFIG_INTERN, NULL, "/no/such/dir", "", NULL); - g_assert(nm_config_get_device_managed(config, dev, &managed, NULL)); + g_assert(nm_config_get_device_managed(config, dev, &managed, NULL, NULL)); g_assert_cmpint(managed, ==, NM_TERNARY_TRUE); g_key_file_unref(kf); From dd5504cf2be72e41bcc9e2a86e0d86647e55dca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Tue, 3 Mar 2026 14:31:36 +0100 Subject: [PATCH 19/20] NEWS: update (cherry picked from commit b6bd9cee872ac80883ef938ecdee54c71ce43ce6) --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 33a229927c..eacc7882c8 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,9 @@ Overview of changes since NetworkManager-1.56 ============================================= * Add support for GENEVE interface. +* Allow persisting the managed state across reboots from nmcli and the D-Bus API. +* Allow changing the device's administrative state in the kernel at the same + time as a change to the managed state from nmcli and the D-Bus API. ============================================= NetworkManager-1.56 From 4ae1c67be63c139126266faec8690c2ed84b8739 Mon Sep 17 00:00:00 2001 From: Jan Vaclav Date: Thu, 7 May 2026 14:32:17 +0200 Subject: [PATCH 20/20] libnm: fix Since/DEPRECATED annotations for backported API --- introspection/org.freedesktop.NetworkManager.Device.xml | 2 +- src/libnm-core-public/nm-dbus-interface.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/introspection/org.freedesktop.NetworkManager.Device.xml b/introspection/org.freedesktop.NetworkManager.Device.xml index 2b84f38aa1..06401681de 100644 --- a/introspection/org.freedesktop.NetworkManager.Device.xml +++ b/introspection/org.freedesktop.NetworkManager.Device.xml @@ -176,7 +176,7 @@ the keyfile.unmanaged-devices setting in NetworkManager.conf. Changes to this value are not persistent and lost after NetworkManager restart. - DEPRECATED: 1.58: Use the SetManaged method instead, which supports + DEPRECATED: 1.58, 1.56.2: Use the SetManaged method instead, which supports additional features like persisting the state to disk --> diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index cc80c9065d..99f2d194fa 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -1256,7 +1256,7 @@ typedef enum /*< flags >*/ { * * Values for the SetManaged() D-Bus call of a device and nm_device_set_managed_async(). * - * Since: 1.58 + * Since: 1.58, 1.56.2 */ typedef enum { NM_DEVICE_MANAGED_NO = 0,