diff options
| author | Michael Biebl <biebl@debian.org> | 2026-05-14 19:21:28 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2026-05-14 19:21:28 +0200 |
| commit | 4312005496b2f64293dd5a34fd81e4cba5a19c5b (patch) | |
| tree | 98c36475837c1f15559d4850a9a0368923fcee04 /src/core | |
| parent | d877daadf2a7d90283a4b48a48fe854d1e9c78a6 (diff) | |
| parent | 869e9027026cdbb15d4e4a6327ff41d2697858eb (diff) | |
Update upstream source from tag 'upstream/1.56.1'
Update to upstream version '1.56.1' with Debian dir ea0a29e73fef8e57cffc45da43243a29de64bb9e
Diffstat (limited to 'src/core')
27 files changed, 1450 insertions, 75 deletions
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 @@ -2394,6 +2394,61 @@ nmtstp_link_dummy_add(NMPlatform *platform, int external_command, const char *na } 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, const char *name, @@ -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 @@ -2461,6 +2461,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 @@ -312,6 +312,108 @@ test_config_override(void) } 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) { NMConfig *config; @@ -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); |