From 869e9027026cdbb15d4e4a6327ff41d2697858eb Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Thu, 14 May 2026 19:21:22 +0200 Subject: New upstream version 1.56.1 --- src/core/NetworkManagerUtils.c | 9 +- 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 | 246 ++++++++++- src/core/dhcp/nm-dhcp-utils.c | 8 +- src/core/dns/nm-dns-manager.c | 4 +- src/core/meson.build | 1 + src/core/nm-checkpoint.c | 75 +++- src/core/nm-config-data.c | 50 ++- src/core/nm-config.c | 204 +++++++++ src/core/nm-config.h | 11 + src/core/nm-ip-config.c | 8 +- src/core/nm-ip-config.h | 2 +- src/core/nm-l3-config-data.c | 9 +- src/core/nm-l3cfg.c | 4 +- src/core/nm-l3cfg.h | 6 +- src/core/nm-netns.c | 8 +- src/core/platform/tests/test-common.c | 63 ++- src/core/platform/tests/test-common.h | 8 +- src/core/platform/tests/test-link.c | 64 ++- src/core/platform/tests/test-route.c | 74 ++++ src/core/settings/nm-agent-manager.c | 35 +- src/core/settings/nm-settings-connection.c | 3 +- .../plugins/ifcfg-rh/nms-ifcfg-rh-reader.c | 2 +- .../supplicant/nm-supplicant-settings-verify.c | 7 +- src/core/tests/config/test-config.c | 103 +++++ src/libnm-base/nm-config-base.h | 3 + src/libnm-client-impl/libnm.ver | 25 ++ 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 | 73 ++- src/libnm-client-impl/nm-libnm-utils.c | 1 + src/libnm-client-impl/nm-libnm-utils.h | 3 +- src/libnm-client-public/NetworkManager.h | 2 + src/libnm-client-public/meson.build | 1 + src/libnm-client-public/nm-autoptr.h | 2 + src/libnm-client-public/nm-device-geneve.h | 60 +++ src/libnm-client-public/nm-device.h | 9 + src/libnm-core-aux-intern/nm-libnm-core-utils.c | 6 +- .../gen-metadata-nm-settings-libnm-core.xml.in | 28 ++ src/libnm-core-impl/meson.build | 1 + src/libnm-core-impl/nm-connection.c | 17 + src/libnm-core-impl/nm-meta-setting-base-impl.c | 9 + src/libnm-core-impl/nm-setting-bridge.c | 14 +- src/libnm-core-impl/nm-setting-geneve.c | 354 +++++++++++++++ src/libnm-core-impl/nm-setting-ip-config.c | 3 + src/libnm-core-impl/nm-setting.c | 2 +- src/libnm-core-intern/nm-core-internal.h | 1 + src/libnm-core-intern/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-dbus-interface.h | 51 +++ src/libnm-core-public/nm-setting-geneve.h | 74 ++++ src/libnm-core-public/nm-version-macros.h.in | 1 + src/libnm-core-public/nm-version.h | 6 + src/libnm-glib-aux/nm-shared-utils.h | 1 + src/libnm-platform/nm-linux-platform.c | 131 +++++- src/libnm-platform/nm-platform.c | 109 ++++- src/libnm-platform/nm-platform.h | 45 +- src/libnm-platform/nmp-base.h | 7 +- src/libnm-platform/nmp-object.c | 12 + src/libnm-platform/nmp-object.h | 8 + src/libnm-platform/tests/test-nm-platform.c | 2 + src/libnm-systemd-shared/src/basic/sort-util.h | 7 +- src/libnm-systemd-shared/src/basic/string-util.c | 4 +- src/libnm-systemd-shared/src/basic/string-util.h | 18 +- .../src/fundamental/macro-fundamental.h | 9 +- src/libnm-udev-aux/nm-udev-utils.c | 15 +- src/libnmc-setting/nm-meta-setting-base-impl.c | 9 + src/libnmc-setting/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/nm-initrd-generator/nmi-cmdline-reader.c | 6 +- src/nmcli/connections.c | 18 +- src/nmcli/devices.c | 214 +++++++-- src/nmcli/gen-metadata-nm-settings-nmcli.xml.in | 32 +- 79 files changed, 3141 insertions(+), 199 deletions(-) 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 create mode 100644 src/libnm-core-impl/nm-setting-geneve.c create mode 100644 src/libnm-core-public/nm-setting-geneve.h (limited to 'src') diff --git a/src/core/NetworkManagerUtils.c b/src/core/NetworkManagerUtils.c index 32df3d6d..95ba20f1 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/devices/nm-device-factory.c b/src/core/devices/nm-device-factory.c index 15858362..24755a5f 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 00000000..0968a2fb --- /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 00000000..d0a44e9d --- /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 46fb2339..a0cf1ee6 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: @@ -14841,6 +14842,241 @@ impl_device_get_applied_connection(NMDBusObject *obj, /*****************************************************************************/ +typedef struct { + NMDeviceManaged managed_state; + 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 + * @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, NMDeviceManaged managed, NMDeviceManagedFlags flags, GError **error) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + 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)) { + 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) { + 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, NULL, 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, + by_mac, + error)) + return FALSE; + + /* Update the unmanaged flags after the change on disk */ + nm_device_set_unmanaged_by_user_conf(self); + + 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 + * after a reboot. */ + nm_config_set_device_managed(nm_manager_get_config(priv->manager), + self, + old, + 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) { + 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); + + /* 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); + } + } + } + + return TRUE; +} + +static void +set_managed_cb(NMDevice *self, + GDBusMethodInvocation *context, + NMAuthSubject *subject, + GError *error, + gpointer user_data) +{ + SetManagedData *set_managed_data = user_data; + NMDeviceManaged managed; + NMDeviceManagedFlags flags; + GError *local = NULL; + + managed = set_managed_data->managed_state; + flags = set_managed_data->managed_flags; + nm_g_slice_free(set_managed_data); + + 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) { + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_MANAGED, + self, + FALSE, + NULL, + subject, + error->message); + g_dbus_method_invocation_return_gerror(context, error); + return; + } + + if (!set_managed(self, managed, flags, &local)) { + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_MANAGED, + self, + FALSE, + NULL, + subject, + local->message); + g_dbus_method_invocation_take_error(context, g_steal_pointer(&local)); + return; + } + + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_MANAGED, self, TRUE, NULL, subject, NULL); + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +impl_device_set_managed(NMDBusObject *obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended *method_info, + GDBusConnection *connection, + const char *sender, + GDBusMethodInvocation *invocation, + GVariant *parameters) +{ + NMDevice *self = NM_DEVICE(obj); + gs_free_error GError *error = NULL; + guint32 managed_u; + NMDeviceManaged managed; + guint32 flags_u; + NMDeviceManagedFlags flags; + SetManagedData *set_managed_data; + + g_variant_get(parameters, "(uu)", &managed_u, &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) { + .managed_state = managed, + .managed_flags = flags, + }; + + nm_device_auth_request(self, + invocation, + nm_device_get_applied_connection(self), + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + NULL, + set_managed_cb, + set_managed_data); +} + +/*****************************************************************************/ + static void disconnect_cb(NMDevice *self, GDBusMethodInvocation *context, @@ -19535,7 +19771,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 */ @@ -19864,6 +20100,12 @@ static const NMDBusInterfaceInfoExtended interface_info_device = { NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), NM_DEFINE_GDBUS_ARG_INFO("version_id", "t"), ), ), .handle = impl_device_get_applied_connection, ), + 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", "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", ), .handle = impl_device_disconnect, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Delete", ), diff --git a/src/core/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c index 949d8720..8a9bd58c 100644 --- a/src/core/dhcp/nm-dhcp-utils.c +++ b/src/core/dhcp/nm-dhcp-utils.c @@ -32,11 +32,11 @@ ip4_process_dhcpcd_rfc3442_routes(const char *iface, in_addr_t address, guint32 *out_gwaddr) { - gs_free const char **routes = NULL; - const char **r; - gboolean have_routes = FALSE; + gs_free char **routes = NULL; + char **r; + gboolean have_routes = FALSE; - routes = nm_strsplit_set(str, " "); + routes = (char **) nm_strsplit_set(str, " "); if (!routes) return FALSE; diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index c746e714..ec33c464 100644 --- a/src/core/dns/nm-dns-manager.c +++ b/src/core/dns/nm-dns-manager.c @@ -1510,8 +1510,8 @@ _domain_track_is_shadowed(GHashTable *ht, const char **out_parent, int *out_parent_priority) { - char *parent; - int parent_priority; + const char *parent; + int parent_priority; if (!ht) return FALSE; diff --git a/src/core/meson.build b/src/core/meson.build index 6cf891ee..b1d7f2e7 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/core/nm-checkpoint.c b/src/core/nm-checkpoint.c index ffcf6e3a..10db0dcb 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; @@ -160,7 +162,7 @@ parse_connection_from_shadowed_file(const char *path, GError **error) { nm_auto_unref_keyfile GKeyFile *keyfile = NULL; gs_free char *base_dir = NULL; - char *sep; + const char *sep; keyfile = g_key_file_new(); if (!g_key_file_load_from_file(keyfile, path, G_KEY_FILE_NONE, error)) @@ -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-data.c b/src/core/nm-config-data.c index 461fd8ed..dd0822ad 100644 --- a/src/core/nm-config-data.c +++ b/src/core/nm-config-data.c @@ -2058,12 +2058,15 @@ _match_section_infos_construct(GKeyFile *keyfile, gboolean is_device) { char **groups; gsize i, j, ngroups; - char *connection_tag = NULL; + char *main_group = NULL; MatchSectionInfo *match_section_infos = NULL; - const char *prefix; + const char *prefix, *prefix_intern; prefix = is_device ? NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE : NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION; + prefix_intern = + is_device ? NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE + : NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION; /* get the list of existing [connection.\+]/[device.\+] sections. * @@ -2074,27 +2077,36 @@ _match_section_infos_construct(GKeyFile *keyfile, gboolean is_device) if (!groups) return NULL; - if (ngroups > 0) { - gsize l = strlen(prefix); - - for (i = 0, j = 0; i < ngroups; i++) { - if (g_str_has_prefix(groups[i], prefix)) { - if (groups[i][l] == '\0') - connection_tag = groups[i]; - else - groups[j++] = groups[i]; - } else - g_free(groups[i]); + for (i = 0, j = 0; i < ngroups; i++) { + if (nm_streq0(groups[i], prefix)) { + main_group = groups[i]; + } else if (nm_streq0(groups[i], prefix_intern)) { + /* [.intern.connection] and [.intern.device] should not exist */ + _nm_log(LOGL_WARN, + LOGD_CORE, + 0, + NULL, + NULL, + "Invalid [.intern.*] section 'connection' or 'device' found"); + g_free(groups[i]); + continue; + } else if (g_str_has_prefix(groups[i], prefix)) { + groups[j++] = groups[i]; + } else if (g_str_has_prefix(groups[i], prefix_intern)) { + /* [.intern.connection-whatever] and [.intern.device-whatever] can exist */ + groups[j++] = groups[i]; + } else { + g_free(groups[i]); } - ngroups = j; } + ngroups = j; - if (ngroups == 0 && !connection_tag) { + if (ngroups == 0 && !main_group) { g_free(groups); return NULL; } - match_section_infos = g_new0(MatchSectionInfo, ngroups + 1 + (connection_tag ? 1 : 0)); + match_section_infos = g_new0(MatchSectionInfo, ngroups + 1 + (main_group ? 1 : 0)); match_section_infos->is_device = is_device; for (i = 0; i < ngroups; i++) { /* pass ownership of @group on... */ @@ -2103,9 +2115,9 @@ _match_section_infos_construct(GKeyFile *keyfile, gboolean is_device) groups[ngroups - i - 1], is_device); } - if (connection_tag) { - /* pass ownership of @connection_tag on... */ - _match_section_info_init(&match_section_infos[i], keyfile, connection_tag, is_device); + if (main_group) { + /* pass ownership of @main_group on... */ + _match_section_info_init(&match_section_infos[i], keyfile, main_group, is_device); } g_free(groups); diff --git a/src/core/nm-config.c b/src/core/nm-config.c index d1f2bbed..d8bf2e3e 100644 --- a/src/core/nm-config.c +++ b/src/core/nm-config.c @@ -2075,6 +2075,210 @@ nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled) g_key_file_unref(keyfile); } +/*****************************************************************************/ + +static gboolean +normalize_hwaddr_for_group_name(const char *hwaddr, char *out, GError **error) +{ + guint8 hwaddr_bin[NM_UTILS_HWADDR_LEN_MAX]; + gsize hwaddr_bin_len; + + if (!_nm_utils_hwaddr_aton(hwaddr, hwaddr_bin, sizeof(hwaddr_bin), &hwaddr_bin_len)) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "Invalid MAC address: %s", + hwaddr); + return FALSE; + } + nm_utils_bin2hexstr_full(hwaddr_bin, hwaddr_bin_len, '-', TRUE, out); + return TRUE; +} + +/** + * nm_config_get_device_managed: + * @self: the NMConfig instance + * @device: the interface + * @out: (out): the managed state of the device + * @error: return location for a #GError, or %NULL + * + * Returns: TRUE if there were no errors, FALSE otherwise. + */ +gboolean +nm_config_get_device_managed(NMConfig *self, + NMDevice *device, + NMTernary *out_managed, + gboolean *out_by_mac, + GError **error) +{ + NMConfigPrivate *priv; + const GKeyFile *keyfile = NULL; + gs_free char *group_by_name = NULL; + gs_free char *group_by_mac = NULL; + const char *ifname = nm_device_get_iface(device); + const char *hwaddr = nm_device_get_permanent_hw_address(device); + char mac_group_name[NM_UTILS_HWADDR_LEN_MAX * 3 + 1]; + NMTernary val_by_name, val_by_mac = NM_TERNARY_DEFAULT; + + 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_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) { + NM_SET_OUT(out_managed, NM_TERNARY_DEFAULT); + NM_SET_OUT(out_by_mac, FALSE); + return TRUE; + } + + group_by_name = + g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-%s", ifname); + + val_by_name = (NMTernary) nm_config_keyfile_get_boolean(keyfile, + group_by_name, + NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, + NM_TERNARY_DEFAULT); + + /* Devices without a kernel link (i.e. OVS ports) don't have a MAC address */ + if (hwaddr) { + if (!normalize_hwaddr_for_group_name(hwaddr, mac_group_name, error)) + return FALSE; + + group_by_mac = g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-%s", + mac_group_name); + + val_by_mac = (NMTernary) nm_config_keyfile_get_boolean(keyfile, + group_by_mac, + NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, + NM_TERNARY_DEFAULT); + } + + if (val_by_name != NM_TERNARY_DEFAULT && val_by_mac == NM_TERNARY_DEFAULT) { + 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) { + 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) { + 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) { + NM_SET_OUT(out_managed, val_by_name); + NM_SET_OUT(out_by_mac, FALSE); + return TRUE; + } + + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "Multiple managed states found for device: %s", + nm_device_get_iface(device)); + return FALSE; +} + +/** + * nm_config_set_device_managed: + * @self: the NMConfig instance + * @device: the NMDevice instance associated with this config change + * @managed: the managed state to set + * @by_mac: if %TRUE, match by MAC address, otherwise by interface name. This is + * only used when @managed = TRUE. + * @error: return location for a #GError, or %NULL + * + * Sets the managed state of the device to the intern keyfile. Here we store the + * configuration received via the D-Bus API. Configurations from other config + * files are still in place and may have higher precedence. + * + * Prior to setting the new state, the existing configuration is removed. If + * @managed is set to %NM_TERNARY_DEFAULT, we only do the removal of the previous + * configuration. + */ +gboolean +nm_config_set_device_managed(NMConfig *self, + NMDevice *device, + NMTernary managed, + gboolean by_mac, + GError **error) +{ + NMConfigPrivate *priv; + g_autoptr(GKeyFile) keyfile = NULL; + char *group; + gs_free char *group_by_name = NULL; + gs_free char *group_by_mac = NULL; + gs_free char *match_value = NULL; + gboolean changed = FALSE; + const char *ifname = nm_device_get_iface(device); + const char *hwaddr = nm_device_get_permanent_hw_address(device); + char mac_group_name[NM_UTILS_HWADDR_LEN_MAX * 3 + 1]; + + 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(ifname, FALSE); + + if (by_mac && !hwaddr) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "the device has no MAC address, but match by MAC was requested"); + return FALSE; + } + + if (hwaddr && !normalize_hwaddr_for_group_name(hwaddr, mac_group_name, error)) + return FALSE; + + priv = NM_CONFIG_GET_PRIVATE(self); + keyfile = nm_config_data_clone_keyfile_intern(priv->config_data); + + /* Remove existing configs. Search them by group name [.intern.device-manage-*]. In + * the intern file, 'device-manage' sections are only used for this purpose, so we + * won't remove any other device's config. */ + group_by_name = + g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-%s", ifname); + + if (g_key_file_remove_group(keyfile, group_by_name, NULL)) + changed = TRUE; + + /* Devices without a kernel link (i.e. OVS ports) don't have a MAC address */ + if (hwaddr) { + group_by_mac = g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-%s", + mac_group_name); + + if (g_key_file_remove_group(keyfile, group_by_mac, NULL)) + changed = TRUE; + } + + /* If the new state is not explicitly TRUE of FALSE, we only remove the configs */ + if (managed == NM_TERNARY_DEFAULT) + goto done; + + /* Set new values */ + if (by_mac) { + group = group_by_mac; + match_value = g_strdup_printf("mac:%s", hwaddr); + } else { + group = group_by_name; + match_value = g_strdup_printf("interface-name:=%s", ifname); + } + + g_key_file_set_value(keyfile, group, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE, match_value); + g_key_file_set_value(keyfile, group, NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, managed ? "1" : "0"); + changed = TRUE; + +done: + if (changed) + nm_config_set_values(self, keyfile, TRUE, FALSE); + + return TRUE; +} + +/*****************************************************************************/ + /** * nm_config_set_values: * @self: the NMConfig instance diff --git a/src/core/nm-config.h b/src/core/nm-config.h index 5518184c..0cfbdfe4 100644 --- a/src/core/nm-config.h +++ b/src/core/nm-config.h @@ -142,6 +142,17 @@ gboolean nm_config_set_global_dns(NMConfig *self, NMGlobalDnsConfig *global_dns, 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, + NMTernary managed, + gboolean by_mac, + GError **error); + /* internal defines ... */ extern guint _nm_config_match_nm_version; extern char *_nm_config_match_env; diff --git a/src/core/nm-ip-config.c b/src/core/nm-ip-config.c index 975ae20d..75a75b42 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 47a40bd2..02d654c4 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-l3-config-data.c b/src/core/nm-l3-config-data.c index 328f59b6..7500337e 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-l3cfg.c b/src/core/nm-l3cfg.c index 0d93f76b..88a9c241 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 5f0721da..c103c9f2 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/nm-netns.c b/src/core/nm-netns.c index f55d1132..0e8b15a7 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-common.c b/src/core/platform/tests/test-common.c index 91445b30..f1f7caee 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 @@ -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 85ed7961..251fa510 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); /*****************************************************************************/ @@ -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 77e7e641..5618308f 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/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c index fbad2447..27bb7431 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/core/settings/nm-agent-manager.c b/src/core/settings/nm-agent-manager.c index 19b3cfcb..ce7dbab2 100644 --- a/src/core/settings/nm-agent-manager.c +++ b/src/core/settings/nm-agent-manager.c @@ -1083,6 +1083,39 @@ _con_get_request_start_validated(NMAuthChain *chain, _con_get_request_start_proceed(req, req->con.current_has_modify); } +static gboolean +_req_has_existing_secrets(Request *req) +{ + GVariantIter iter; + const char *setting_name; + GVariant *setting_dict; + gboolean has; + + if (!req->con.get.existing_secrets) + return FALSE; + + nm_assert(g_variant_is_of_type(req->con.get.existing_secrets, NM_VARIANT_TYPE_CONNECTION)); + + g_variant_iter_init(&iter, req->con.get.existing_secrets); + while (g_variant_iter_next(&iter, "{&s@a{sv}}", &setting_name, &setting_dict)) { + GVariantIter setting_iter; + GVariant *val; + + g_variant_iter_init(&setting_iter, setting_dict); + while (g_variant_iter_next(&setting_iter, "{&sv}", NULL, &val)) { + has = !g_variant_is_container(val) || g_variant_n_children(val) > 0; + g_variant_unref(val); + if (has) { + g_variant_unref(setting_dict); + return TRUE; + } + } + g_variant_unref(setting_dict); + } + + return FALSE; +} + static void _con_get_request_start(Request *req) { @@ -1103,7 +1136,7 @@ _con_get_request_start(Request *req) * unprivileged users. */ if ((req->con.get.flags != NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE) - && (req->con.get.existing_secrets + && (_req_has_existing_secrets(req) || _nm_connection_aggregate(req->con.connection, NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, NULL))) { diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c index d5611e76..7ed3712b 100644 --- a/src/core/settings/nm-settings-connection.c +++ b/src/core/settings/nm-settings-connection.c @@ -781,7 +781,8 @@ validate_secret_flags(NMConnection *connection, GVariant *secrets, ForEachSecret static gboolean secret_is_system_owned(NMSettingSecretFlags flags, gpointer user_data) { - return !NM_FLAGS_HAS(flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED); + return !NM_FLAGS_ANY(flags, + NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED); } static void diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c index b9e3f919..728dccac 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c @@ -77,7 +77,7 @@ get_full_file_path(const char *ifcfg_path, const char *file_path) { const char *base = file_path; gs_free char *dirname = NULL; - char *p; + const char *p; g_return_val_if_fail(ifcfg_path != NULL, NULL); g_return_val_if_fail(file_path != NULL, NULL); diff --git a/src/core/supplicant/nm-supplicant-settings-verify.c b/src/core/supplicant/nm-supplicant-settings-verify.c index 6e5e44d2..cca53d82 100644 --- a/src/core/supplicant/nm-supplicant-settings-verify.c +++ b/src/core/supplicant/nm-supplicant-settings-verify.c @@ -212,18 +212,19 @@ validate_type_utf8(const struct Opt *opt, const char *value, const guint32 len) } static gboolean -validate_type_keyword(const struct Opt *opt, const char *value, const guint32 len) +validate_type_keyword(const struct Opt *opt, const char *value_in, const guint32 len) { gs_free char *value_free = NULL; + char *value; nm_assert(opt); - nm_assert(value); + nm_assert(value_in); /* Allow everything */ if (!opt->str_allowed) return TRUE; - value = nm_strndup_a(300, value, len, &value_free); + value = nm_strndup_a(300, value_in, len, &value_free); /* validate each space-separated word in 'value' */ diff --git a/src/core/tests/config/test-config.c b/src/core/tests/config/test-config.c index 78fd1057..b2f29821 100644 --- a/src/core/tests/config/test-config.c +++ b/src/core/tests/config/test-config.c @@ -311,6 +311,108 @@ 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; + gboolean by_mac; + 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, 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, &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)); + 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, &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)); + 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, &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)); + 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, &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)); + 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, 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, 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, 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 +1514,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); diff --git a/src/libnm-base/nm-config-base.h b/src/libnm-base/nm-config-base.h index d101c95c..e11d8abf 100644 --- a/src/libnm-base/nm-config-base.h +++ b/src/libnm-base/nm-config-base.h @@ -89,4 +89,7 @@ #define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN \ NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN +#define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE \ + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE + #endif /* __NM_CONFIG_BASE_H__ */ diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver index 6c48978a..c03b8f39 100644 --- a/src/libnm-client-impl/libnm.ver +++ b/src/libnm-client-impl/libnm.ver @@ -2103,3 +2103,28 @@ global: nm_utils_copy_cert_as_user; nm_vpn_plugin_info_supports_safe_private_file_access; } libnm_1_54_0; + +libnm_1_56_1 { +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_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; + 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-impl/meson.build b/src/libnm-client-impl/meson.build index b4936629..3352ebfe 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 b81ac6e5..f1383526 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 00000000..312a99b3 --- /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.1 + **/ +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.1 + **/ +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.1 + **/ +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.1 + **/ +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.1 + **/ +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.1 + **/ +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.1 + **/ + 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.1 + */ + 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.1 + */ + 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.1 + */ + 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.1 + */ + 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.1 + */ + 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 1712efa5..63a62942 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: @@ -1445,9 +1446,7 @@ nm_device_get_managed(NMDevice *device) * * Since: 1.2 * - * Deprecated: 1.22: Use the async command nm_client_dbus_set_property() on - * nm_object_get_path(), interface %NM_DBUS_INTERFACE_DEVICE to set the - * "Managed" property to a "(b)" boolean value. + * Deprecated: 1.22: Use nm_device_set_managed_async() instead. * This function is deprecated because it calls a synchronous D-Bus method * and modifies the content of the NMClient cache client side. Also, it does * not emit a property changed signal. @@ -1469,6 +1468,72 @@ nm_device_set_managed(NMDevice *device, gboolean managed) managed); } +/** + * nm_device_set_managed_async: + * @device: a #NMDevice + * @managed: the managed state + * @flags: the flags argument. See #NMDeviceManagedFlags. + * @cancellable: a #GCancellable, or %NULL + * @callback: callback to be called when the set_managed operation completes + * @user_data: caller-specific data passed to @callback + * + * Asynchronously begins setting the managed state of the device. With the flags + * argument different behaviors can be achieved, such as persisting the state to + * disk or matching the device by MAC address. + * + * Since: 1.58, 1.56.1 + **/ +void +nm_device_set_managed_async(NMDevice *device, + NMDeviceManaged managed, + NMDeviceManagedFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail(NM_IS_DEVICE(device)); + g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable)); + + _nm_client_dbus_call(_nm_object_get_client(device), + device, + nm_device_set_managed_async, + cancellable, + callback, + user_data, + _nm_object_get_path(device), + NM_DBUS_INTERFACE_DEVICE, + "SetManaged", + g_variant_new("(uu)", managed, flags), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + NM_DBUS_DEFAULT_TIMEOUT_MSEC, + nm_dbus_connection_call_finish_void_strip_dbus_error_cb); +} + +/** + * nm_device_set_managed_finish: + * @device: a #NMDevice + * @result: the result passed to the #GAsyncReadyCallback + * @error: location for a #GError, or %NULL + * + * Gets the result of a call to nm_device_set_managed_async(). + * + * Returns: %TRUE on success, %FALSE on error, in which case @error + * will be set. + * + * Since: 1.58, 1.56.1 + **/ +gboolean +nm_device_set_managed_finish(NMDevice *device, GAsyncResult *result, GError **error) +{ + g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); + g_return_val_if_fail(nm_g_task_is_valid(result, device, nm_device_set_managed_async), FALSE); + + return g_task_propagate_boolean(G_TASK(result), error); +} + +/*****************************************************************************/ + /** * nm_device_get_autoconnect: * @device: a #NMDevice @@ -1792,6 +1857,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 1b25c92c..1c80c1dc 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 7dcf8c18..27b77b60 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 9e9a4eb8..880bac6e 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" @@ -114,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 b8ae9cce..5aa6de25 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 f4321ad3..12379fe1 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) @@ -80,6 +81,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-client-public/nm-device-geneve.h b/src/libnm-client-public/nm-device-geneve.h new file mode 100644 index 00000000..1c537fbd --- /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.1 + */ +typedef struct _NMDeviceGeneve NMDeviceGeneve; +typedef struct _NMDeviceGeneveClass NMDeviceGeneveClass; + +NM_AVAILABLE_IN_1_56_1 +GType nm_device_geneve_get_type(void); + +NM_AVAILABLE_IN_1_56_1 +guint nm_device_geneve_get_id(NMDeviceGeneve *device); +NM_AVAILABLE_IN_1_56_1 +const char *nm_device_geneve_get_remote(NMDeviceGeneve *device); +NM_AVAILABLE_IN_1_56_1 +guint nm_device_geneve_get_dst_port(NMDeviceGeneve *device); +NM_AVAILABLE_IN_1_56_1 +guint nm_device_geneve_get_tos(NMDeviceGeneve *device); +NM_AVAILABLE_IN_1_56_1 +guint nm_device_geneve_get_ttl(NMDeviceGeneve *device); +NM_AVAILABLE_IN_1_56_1 +guint nm_device_geneve_get_df(NMDeviceGeneve *device); + +G_END_DECLS + +#endif /* __NM_DEVICE_GENEVE_H__ */ diff --git a/src/libnm-client-public/nm-device.h b/src/libnm-client-public/nm-device.h index f58d3ddb..2ee3a4ae 100644 --- a/src/libnm-client-public/nm-device.h +++ b/src/libnm-client-public/nm-device.h @@ -150,6 +150,15 @@ NM_AVAILABLE_IN_1_2 NM_DEPRECATED_IN_1_22 _NM_DEPRECATED_SYNC_METHOD void nm_device_set_managed(NMDevice *device, gboolean managed); +NM_AVAILABLE_IN_1_56_1 +void nm_device_set_managed_async(NMDevice *device, + NMDeviceManaged managed, + NMDeviceManagedFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +NM_AVAILABLE_IN_1_56_1 +gboolean nm_device_set_managed_finish(NMDevice *device, GAsyncResult *result, GError **error); gboolean nm_device_get_autoconnect(NMDevice *device); diff --git a/src/libnm-core-aux-intern/nm-libnm-core-utils.c b/src/libnm-core-aux-intern/nm-libnm-core-utils.c index 2d709e20..a39f6454 100644 --- a/src/libnm-core-aux-intern/nm-libnm-core-utils.c +++ b/src/libnm-core-aux-intern/nm-libnm-core-utils.c @@ -483,8 +483,8 @@ nm_utils_validate_shared_dhcp_range(const char *shared_dhcp_range, GPtrArray *addresses, GError **error) { - char *start_address_str; - char *end_address_str; + const char *start_address_str; + const char *end_address_str; NMIPAddress *interface_address_with_prefix; NMIPAddr interface_address; NMIPAddr start_address; @@ -825,7 +825,7 @@ nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns, GError **er addr = nm_strndup_a(100, addr_port, end - addr_port, &addr_heap); /* IPv6 link-local scope-id */ - perc = strchr(addr, '%'); + perc = (char *) strchr(addr, '%'); if (perc) { *perc = '\0'; if (g_strlcpy(dns->interface, perc + 1, sizeof(dns->interface)) 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 c83deea9..8d5d3bc9 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 610e2279..1eee3184 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 0fbadfb8..180fcbbc 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, @@ -3526,6 +3527,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.1 + **/ +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 52af9ad2..d84490d2 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-bridge.c b/src/libnm-core-impl/nm-setting-bridge.c index c1618434..21457412 100644 --- a/src/libnm-core-impl/nm-setting-bridge.c +++ b/src/libnm-core-impl/nm-setting-bridge.c @@ -472,17 +472,17 @@ nm_bridge_vlan_to_str(const NMBridgeVlan *vlan, GError **error) NMBridgeVlan * nm_bridge_vlan_from_str(const char *str, GError **error) { - NMBridgeVlan *vlan = NULL; - gs_free const char **tokens = NULL; - guint i, vid_start, vid_end = 0; - gboolean pvid = FALSE; - gboolean untagged = FALSE; - char *c; + NMBridgeVlan *vlan = NULL; + gs_free char **tokens = NULL; + guint i, vid_start, vid_end = 0; + gboolean pvid = FALSE; + gboolean untagged = FALSE; + char *c; g_return_val_if_fail(str, NULL); g_return_val_if_fail(!error || !*error, NULL); - tokens = nm_utils_escaped_tokens_split(str, NM_ASCII_SPACES); + tokens = (char **) nm_utils_escaped_tokens_split(str, NM_ASCII_SPACES); if (!tokens || !tokens[0]) { g_set_error_literal(error, NM_CONNECTION_ERROR, 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 00000000..b01fcd9a --- /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.1 + **/ +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.1 + **/ +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.1 + **/ +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.1 + **/ +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.1 + **/ +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.1 + **/ +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.1 + **/ +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.1 + **/ + _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.1 + **/ + _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.1 + **/ + _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.1 + **/ + _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.1 + **/ + _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.1 + **/ + _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-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c index 1aecc20c..f4ee094c 100644 --- a/src/libnm-core-impl/nm-setting-ip-config.c +++ b/src/libnm-core-impl/nm-setting-ip-config.c @@ -5079,6 +5079,9 @@ routing_rules_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil) gboolean any = FALSE; guint i; + if (!_nm_connection_serialize_non_secret(flags)) + return NULL; + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(self); if (!priv->routing_rules || priv->routing_rules->len == 0) diff --git a/src/libnm-core-impl/nm-setting.c b/src/libnm-core-impl/nm-setting.c index 295eabe7..e87f39b2 100644 --- a/src/libnm-core-impl/nm-setting.c +++ b/src/libnm-core-impl/nm-setting.c @@ -4523,7 +4523,7 @@ nm_range_from_str(const char *str, GError **error) gs_free char *str_free = NULL; guint64 start; guint64 end = 0; - char *c; + const char *c; g_return_val_if_fail(str, NULL); g_return_val_if_fail(!error || !*error, NULL); diff --git a/src/libnm-core-intern/nm-core-internal.h b/src/libnm-core-intern/nm-core-internal.h index b8df4d2e..f74dfc59 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 d1535e5e..8f512d09 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 086801d9..2a4f143f 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 ff3e0f92..e14e23c2 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_1 +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 617d6b74..81743e31 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-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index 42bff04a..513d1fa3 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.1. * * #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; /** @@ -1245,6 +1248,54 @@ 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, 1.56.1 + */ +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. + * @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_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. + * 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(). + * + * %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.1 + */ +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_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 = 0x1F, /* */ +} NMDeviceManagedFlags; + /** * NMTernary: * @NM_TERNARY_DEFAULT: use the globally-configured default value. 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 00000000..642697dc --- /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.1 + */ +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_1 +GType nm_setting_geneve_get_type(void); +NM_AVAILABLE_IN_1_56_1 +NMSetting *nm_setting_geneve_new(void); +NM_AVAILABLE_IN_1_56_1 +guint nm_setting_geneve_get_id(NMSettingGeneve *setting); +NM_AVAILABLE_IN_1_56_1 +const char *nm_setting_geneve_get_remote(NMSettingGeneve *setting); +NM_AVAILABLE_IN_1_56_1 +guint nm_setting_geneve_get_destination_port(NMSettingGeneve *setting); +NM_AVAILABLE_IN_1_56_1 +guint nm_setting_geneve_get_tos(NMSettingGeneve *setting); +NM_AVAILABLE_IN_1_56_1 +guint nm_setting_geneve_get_ttl(NMSettingGeneve *setting); +NM_AVAILABLE_IN_1_56_1 +NMSettingGeneveDf nm_setting_geneve_get_df(NMSettingGeneve *setting); + +G_END_DECLS + +#endif /* __NM_SETTING_GENEVE_H__ */ diff --git a/src/libnm-core-public/nm-version-macros.h.in b/src/libnm-core-public/nm-version-macros.h.in index b8b89a44..fdecfa68 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_1 (NM_ENCODE_VERSION(1, 56, 1)) /* 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 af45b679..6e18acdd 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_1 +#define NM_AVAILABLE_IN_1_56_1 G_UNAVAILABLE(1, 56.1) +#else +#define NM_AVAILABLE_IN_1_56_1 +#endif + /* * Synchronous API for calling D-Bus in libnm is deprecated. See * https://networkmanager.dev/docs/libnm/latest/usage.html#sync-api diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index 1fca6015..0bc6e294 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 cc5b99e0..b5df8b17 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); @@ -4033,6 +4090,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 +4200,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 +4218,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 +4469,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; @@ -5182,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: { @@ -5813,15 +5911,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 853157d2..914dcd86 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) { @@ -7366,13 +7424,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" + : ""); } } } @@ -8491,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) { @@ -8931,7 +9013,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 +9054,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 +9071,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 +9165,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 +9210,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 +9228,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 033dc515..ba37d3a2 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 { @@ -835,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; @@ -1858,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, @@ -2143,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 * @@ -2482,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 * @@ -2537,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, @@ -2612,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 f6656296..ae538f5b 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, @@ -194,15 +195,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; } /*****************************************************************************/ diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c index 56533c99..d73eb641 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 b526fc3b..a960c528 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 22148f8c..d087d66f 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)); diff --git a/src/libnm-systemd-shared/src/basic/sort-util.h b/src/libnm-systemd-shared/src/basic/sort-util.h index 9c818bd7..a3bfb32c 100644 --- a/src/libnm-systemd-shared/src/basic/sort-util.h +++ b/src/libnm-systemd-shared/src/basic/sort-util.h @@ -25,15 +25,18 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, * Normal bsearch requires base to be nonnull. Here were require * that only if nmemb > 0. */ -static inline void* bsearch_safe(const void *key, const void *base, +static inline void* bsearch_safe_internal(const void *key, const void *base, size_t nmemb, size_t size, comparison_fn_t compar) { if (nmemb <= 0) return NULL; assert(base); - return bsearch(key, base, nmemb, size, compar); + return (void*) bsearch(key, base, nmemb, size, compar); } +#define bsearch_safe(key, base, nmemb, size, compar) \ + const_generic((base), bsearch_safe_internal(key, base, nmemb, size, compar)) + #define typesafe_bsearch(k, b, n, func) \ ({ \ const typeof((b)[0]) *_k = k; \ diff --git a/src/libnm-systemd-shared/src/basic/string-util.c b/src/libnm-systemd-shared/src/basic/string-util.c index 5efd11ea..52160fe3 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.c +++ b/src/libnm-systemd-shared/src/basic/string-util.c @@ -1470,7 +1470,7 @@ ssize_t strlevenshtein(const char *x, const char *y) { return t1[yl]; } -char* strrstr(const char *haystack, const char *needle) { +char* strrstr_internal(const char *haystack, const char *needle) { /* Like strstr() but returns the last rather than the first occurrence of "needle" in "haystack". */ if (!haystack || !needle) @@ -1479,7 +1479,7 @@ char* strrstr(const char *haystack, const char *needle) { /* Special case: for the empty string we return the very last possible occurrence, i.e. *after* the * last char, not before. */ if (*needle == 0) - return strchr(haystack, 0); + return (char*) strchr(haystack, 0); for (const char *p = strstr(haystack, needle), *q; p; p = q) { q = strstr(p + 1, needle); diff --git a/src/libnm-systemd-shared/src/basic/string-util.h b/src/libnm-systemd-shared/src/basic/string-util.h index 91e63c9b..5d2b5dfb 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.h +++ b/src/libnm-systemd-shared/src/basic/string-util.h @@ -27,24 +27,28 @@ #define URI_UNRESERVED ALPHANUMERICAL "-._~" /* [RFC3986] */ #define URI_VALID URI_RESERVED URI_UNRESERVED /* [RFC3986] */ -static inline char* strstr_ptr(const char *haystack, const char *needle) { +static inline char* strstr_ptr_internal(const char *haystack, const char *needle) { if (!haystack || !needle) return NULL; - return strstr(haystack, needle); + return (char*) strstr(haystack, needle); } -static inline char* strstrafter(const char *haystack, const char *needle) { - char *p; +#define strstr_ptr(haystack, needle) \ + const_generic(haystack, strstr_ptr_internal(haystack, needle)) +static inline char* strstrafter_internal(const char *haystack, const char *needle) { /* Returns NULL if not found, or pointer to first character after needle if found */ - p = strstr_ptr(haystack, needle); + char *p = (char*) strstr_ptr(haystack, needle); if (!p) return NULL; return p + strlen(needle); } +#define strstrafter(haystack, needle) \ + const_generic(haystack, strstrafter_internal(haystack, needle)) + static inline const char* strnull(const char *s) { return s ?: "(null)"; } @@ -300,6 +304,8 @@ bool version_is_valid_versionspec(const char *s); ssize_t strlevenshtein(const char *x, const char *y); -char* strrstr(const char *haystack, const char *needle); +char* strrstr_internal(const char *haystack, const char *needle); +#define strrstr(haystack, needle) \ + const_generic(haystack, strrstr_internal(haystack, needle)) size_t str_common_prefix(const char *a, const char *b); diff --git a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h index abdfb9b6..a89994e2 100644 --- a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h @@ -511,4 +511,11 @@ assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1); #define PROJECT_FILE (&__FILE__[STRLEN(RELATIVE_SOURCE_PATH) + 1]) #else /* NM_IGNORED */ #define PROJECT_FILE __FILE__ -#endif /* NM_IGNORED */ \ No newline at end of file +#endif /* NM_IGNORED */ + +/* This macro is used to have a const-returning and non-const returning version of a function based on + * whether its first argument is const or not (e.g. strstr()). */ +#define const_generic(ptr, call) \ + _Generic(0 ? (ptr) : (void*) 1, \ + const void*: (const typeof(*call)*) (call), \ + void*: (call)) diff --git a/src/libnm-udev-aux/nm-udev-utils.c b/src/libnm-udev-aux/nm-udev-utils.c index 744d03ce..9e43dd26 100644 --- a/src/libnm-udev-aux/nm-udev-utils.c +++ b/src/libnm-udev-aux/nm-udev-utils.c @@ -92,7 +92,8 @@ _subsystem_split(const char *subsystem_full, const char **out_devtype, char **to_free) { - char *tmp, *s; + char *tmp; + const char *s; nm_assert(subsystem_full); nm_assert(out_subsystem); @@ -101,12 +102,12 @@ _subsystem_split(const char *subsystem_full, s = strstr(subsystem_full, "/"); if (s) { - tmp = g_strdup(subsystem_full); - s = &tmp[s - subsystem_full]; - *s = '\0'; - *out_subsystem = tmp; - *out_devtype = &s[1]; - *to_free = tmp; + tmp = g_strdup(subsystem_full); + tmp[s - subsystem_full] = '\0'; + s = &tmp[s - subsystem_full]; + *out_subsystem = tmp; + *out_devtype = &s[1]; + *to_free = tmp; } else { *out_subsystem = subsystem_full; *out_devtype = NULL; diff --git a/src/libnmc-setting/nm-meta-setting-base-impl.c b/src/libnmc-setting/nm-meta-setting-base-impl.c index 52af9ad2..d84490d2 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 d1535e5e..8f512d09 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 e2ab8dfa..ad75782c 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 89f8c6d1..17468497 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/nm-initrd-generator/nmi-cmdline-reader.c b/src/nm-initrd-generator/nmi-cmdline-reader.c index 5cdb3bcd..f9b0fa16 100644 --- a/src/nm-initrd-generator/nmi-cmdline-reader.c +++ b/src/nm-initrd-generator/nmi-cmdline-reader.c @@ -1177,7 +1177,7 @@ reader_parse_rd_znet(Reader *reader, char *argument, gboolean net_ifnames) { const char *nettype; const char *subchannels[4] = {0, 0, 0, 0}; - const char *tmp; + char *tmp; gs_free char *ifname = NULL; gs_free char *str_subchannels = NULL; const char *prefix; @@ -1248,8 +1248,8 @@ reader_parse_rd_znet(Reader *reader, char *argument, gboolean net_ifnames) NULL); while ((tmp = get_word(&argument, ',')) != NULL) { - const char *key; - char *val; + char *key; + char *val; val = strchr(tmp, '='); if (!val) { diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index d93e16f0..a3b03efe 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/devices.c b/src/nmcli/devices.c index e81e710f..2787439e 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 @@ -2793,20 +2795,62 @@ 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) { -#define DEV_SET_AUTOCONNECT 0 -#define DEV_SET_MANAGED 1 - NMDevice *device = NULL; - int i; + NMDevice *device = NULL; + DeviceSetCbInfo *cb_info = 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 +2872,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 +2998,28 @@ 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; - } - 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); + for (i = 0; i < 2; i++) { + if (autoconnect_data.idx == i) { + 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, + device_set_cb, + cb_info); + } + } } static void diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in index 881c41ca..7a9f4e9c 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" /> + + + + + + + +