diff options
| author | Michael Biebl <biebl@debian.org> | 2020-06-28 18:58:14 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2020-06-28 18:58:14 +0200 |
| commit | a54ac63bbf9b2c71026ac9028a8ffaf186cf3c82 (patch) | |
| tree | 6a32883bd916c4096357b35298beff6db8bcd0b5 /libnm-core | |
| parent | 45e8e1149027529194982212c804c0468aa01d98 (diff) | |
New upstream version 1.25.90 upstream/1.25.90
Diffstat (limited to 'libnm-core')
42 files changed, 9561 insertions, 499 deletions
diff --git a/libnm-core/meson.build b/libnm-core/meson.build index 955e1776..89acaf72 100644 --- a/libnm-core/meson.build +++ b/libnm-core/meson.build @@ -88,7 +88,11 @@ libnm_core_enum_sources = gnome.mkenums_simple( libnm_libnm_core_intern = static_library( 'nm-libnm-core-intern', - sources: nm_ethtool_utils_source + nm_libnm_core_utils_source + nm_auth_subject_source + [libnm_core_enum_sources[1]], + sources: files( + 'nm-libnm-core-intern/nm-auth-subject.c', + 'nm-libnm-core-intern/nm-ethtool-utils.c', + 'nm-libnm-core-intern/nm-libnm-core-utils.c', + ) + [libnm_core_enum_sources[1]], dependencies: libnm_core_nm_default_dep, c_args: common_c_flags, ) @@ -221,7 +225,9 @@ c_flags = [ libnm_libnm_core_aux = static_library( 'nm-libnm-core-aux', - sources: nm_libnm_core_aux_source + [libnm_core_enum_sources[1]], + sources: files( + 'nm-libnm-core-aux/nm-libnm-core-aux.c', + ) + [libnm_core_enum_sources[1]], dependencies: libnm_utils_base_dep, c_args: c_flags, link_with: libnm_libnm_core_intern, @@ -236,7 +242,10 @@ libnm_libnm_core_aux_dep = declare_dependency( libnm_keyfile = static_library( 'nm-keyfile', - sources: nm_keyfile_source + [libnm_core_enum_sources[1]], + sources: files( + 'nm-keyfile/nm-keyfile-utils.c', + 'nm-keyfile/nm-keyfile.c', + ) + [libnm_core_enum_sources[1]], dependencies: libnm_utils_base_dep, c_args: [ '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index b9abac40..6d7661b9 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -78,24 +78,82 @@ #include "nm-libnm-core-intern/nm-libnm-core-utils.h" /* IEEE 802.1D-1998 timer values */ -#define NM_BR_MIN_HELLO_TIME 1 -#define NM_BR_MAX_HELLO_TIME 10 +#define NM_BRIDGE_HELLO_TIME_MIN 1u +#define NM_BRIDGE_HELLO_TIME_DEF 2u +#define NM_BRIDGE_HELLO_TIME_MAX 10u -#define NM_BR_MIN_FORWARD_DELAY 2 -#define NM_BR_MAX_FORWARD_DELAY 30 +#define NM_BRIDGE_FORWARD_DELAY_MIN 2u +#define NM_BRIDGE_FORWARD_DELAY_DEF 15u +#define NM_BRIDGE_FORWARD_DELAY_MAX 30u -#define NM_BR_MIN_MAX_AGE 6 -#define NM_BR_MAX_MAX_AGE 40 +#define NM_BRIDGE_MAX_AGE_MIN 6u +#define NM_BRIDGE_MAX_AGE_DEF 20u +#define NM_BRIDGE_MAX_AGE_MAX 40u /* IEEE 802.1D-1998 Table 7.4 */ -#define NM_BR_MIN_AGEING_TIME 0 -#define NM_BR_MAX_AGEING_TIME 1000000 +#define NM_BRIDGE_AGEING_TIME_MIN 0u +#define NM_BRIDGE_AGEING_TIME_DEF 300u +#define NM_BRIDGE_AGEING_TIME_MAX 1000000u -#define NM_BR_PORT_MAX_PRIORITY 63 -#define NM_BR_PORT_DEF_PRIORITY 32 +#define NM_BRIDGE_PORT_PRIORITY_MIN 0u +#define NM_BRIDGE_PORT_PRIORITY_DEF 32u +#define NM_BRIDGE_PORT_PRIORITY_MAX 63u -#define NM_BR_PORT_MAX_PATH_COST 65535 -#define NM_BR_PORT_DEF_PATH_COST 100 +#define NM_BRIDGE_PORT_PATH_COST_MIN 0u +#define NM_BRIDGE_PORT_PATH_COST_DEF 100u +#define NM_BRIDGE_PORT_PATH_COST_MAX 65535u + +#define NM_BRIDGE_MULTICAST_HASH_MAX_MIN 1u +#define NM_BRIDGE_MULTICAST_HASH_MAX_DEF 4096u +#define NM_BRIDGE_MULTICAST_HASH_MAX_MAX ((guint) G_MAXUINT32) + +#define NM_BRIDGE_STP_DEF TRUE + +#define NM_BRIDGE_PRIORITY_MIN 0u +#define NM_BRIDGE_PRIORITY_DEF 0x8000u +#define NM_BRIDGE_PRIORITY_MAX ((guint) G_MAXUINT16) + +#define NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_MIN 0u +#define NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF 2u +#define NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_MAX ((guint) G_MAXUINT32) + +#define NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_MIN ((guint64) 0) +#define NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF ((guint64) 100) +#define NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_MAX G_MAXUINT64 + +#define NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_MIN ((guint64) 0) +#define NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF ((guint64) 26000) +#define NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_MAX G_MAXUINT64 + +#define NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_MIN ((guint64) 0) +#define NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF ((guint64) 25500) +#define NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_MAX G_MAXUINT64 + +#define NM_BRIDGE_MULTICAST_QUERIER_DEF FALSE + +#define NM_BRIDGE_MULTICAST_QUERY_INTERVAL_MIN ((guint64) 0) +#define NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF ((guint64) 12500) +#define NM_BRIDGE_MULTICAST_QUERY_INTERVAL_MAX G_MAXUINT64 + +#define NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_MIN ((guint64) 0) +#define NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF ((guint64) 1000) +#define NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_MAX G_MAXUINT64 + +#define NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF FALSE + +#define NM_BRIDGE_MULTICAST_SNOOPING_DEF TRUE + +#define NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_MIN 0u +#define NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF 2u +#define NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_MAX ((guint) G_MAXUINT32) + +#define NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_MIN ((guint64) 0) +#define NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF ((guint64) 3125) +#define NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_MAX G_MAXUINT64 + +#define NM_BRIDGE_VLAN_STATS_ENABLED_DEF FALSE + +#define NM_BRIDGE_VLAN_DEFAULT_PVID_DEF 1u /* NM_SETTING_COMPARE_FLAG_INFERRABLE: check whether a device-generated * connection can be replaced by a already-defined connection. This flag only @@ -252,29 +310,18 @@ gboolean _nm_setting_get_property (NMSetting *setting, const char *name, GValue /*****************************************************************************/ -GHashTable *_nm_setting_gendata_hash (NMSetting *setting, - gboolean create_if_necessary); - -void _nm_setting_gendata_notify (NMSetting *setting, - gboolean keys_changed); - -guint _nm_setting_gendata_get_all (NMSetting *setting, - const char *const**out_names, - GVariant *const**out_values); - -gboolean _nm_setting_gendata_reset_from_hash (NMSetting *setting, - GHashTable *new); - -void _nm_setting_gendata_to_gvalue (NMSetting *setting, - GValue *value); +GHashTable *_nm_setting_option_hash (NMSetting *setting, + gboolean create_if_necessary); -GVariant *nm_setting_gendata_get (NMSetting *setting, - const char *name); +void _nm_setting_option_notify (NMSetting *setting, + gboolean keys_changed); -const char *const*nm_setting_gendata_get_all_names (NMSetting *setting, - guint *out_len); +guint _nm_setting_option_get_all (NMSetting *setting, + const char *const**out_names, + GVariant *const**out_values); -GVariant *const*nm_setting_gendata_get_all_values (NMSetting *setting); +gboolean _nm_setting_option_clear (NMSetting *setting, + const char *optname); /*****************************************************************************/ diff --git a/libnm-core/nm-core-types.h b/libnm-core/nm-core-types.h index d1ce6310..ce59ec03 100644 --- a/libnm-core/nm-core-types.h +++ b/libnm-core/nm-core-types.h @@ -65,4 +65,7 @@ typedef struct _NMSettingWirelessSecurity NMSettingWirelessSecurity; typedef struct _NMSettingWpan NMSettingWpan; typedef struct _NMSimpleConnection NMSimpleConnection; +NM_AVAILABLE_IN_1_26 +typedef gboolean (*NMUtilsPredicateStr) (const char *str); + #endif /* __NM_CORE_TYPES_H__ */ diff --git a/libnm-core/nm-crypto-nss.c b/libnm-core/nm-crypto-nss.c index 60c8dfb5..5dbbef00 100644 --- a/libnm-core/nm-crypto-nss.c +++ b/libnm-core/nm-crypto-nss.c @@ -8,6 +8,7 @@ #include "nm-crypto-impl.h" +NM_PRAGMA_WARNING_DISABLE ("-Wstrict-prototypes") #include <prinit.h> #include <nss.h> #include <pk11pub.h> @@ -17,6 +18,7 @@ #include <p12.h> #include <ciferfam.h> #include <p12plcy.h> +NM_PRAGMA_WARNING_REENABLE #include "nm-glib-aux/nm-secret-utils.h" #include "nm-errors.h" diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h index 28ee89bf..4c85dae4 100644 --- a/libnm-core/nm-dbus-interface.h +++ b/libnm-core/nm-dbus-interface.h @@ -351,6 +351,8 @@ typedef enum { /*< underscore_name=nm_802_11_ap_flags, flags >*/ * supported * @NM_802_11_AP_SEC_KEY_MGMT_OWE: WPA/RSN Opportunistic Wireless Encryption is * supported + * @NM_802_11_AP_SEC_KEY_MGMT_OWE_TM: WPA/RSN Opportunistic Wireless Encryption + * transition mode is supported. Since: 1.26. * * 802.11 access point security and authentication flags. These flags describe * the current security requirements of an access point as determined from the @@ -370,6 +372,7 @@ typedef enum { /*< underscore_name=nm_802_11_ap_security_flags, flags >*/ NM_802_11_AP_SEC_KEY_MGMT_802_1X = 0x00000200, NM_802_11_AP_SEC_KEY_MGMT_SAE = 0x00000400, NM_802_11_AP_SEC_KEY_MGMT_OWE = 0x00000800, + NM_802_11_AP_SEC_KEY_MGMT_OWE_TM = 0x00001000, } NM80211ApSecurityFlags; /** @@ -857,6 +860,7 @@ typedef enum /*< flags >*/ { #undef NM_AVAILABLE_IN_1_8 #endif +#define NM_LLDP_ATTR_RAW "raw" #define NM_LLDP_ATTR_DESTINATION "destination" #define NM_LLDP_ATTR_CHASSIS_ID_TYPE "chassis-id-type" #define NM_LLDP_ATTR_CHASSIS_ID "chassis-id" @@ -876,6 +880,8 @@ typedef enum /*< flags >*/ { #define NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI "ieee-802-3-power-via-mdi" #define NM_LLDP_ATTR_IEEE_802_3_MAX_FRAME_SIZE "ieee-802-3-max-frame-size" +#define NM_LLDP_ATTR_MUD_URL "mud-url" + /* These are deprecated in favor of NM_LLDP_ATTR_IEEE_802_1_VLANS, * which can report multiple VLANs */ #define NM_LLDP_ATTR_IEEE_802_1_VID "ieee-802-1-vid" @@ -992,6 +998,8 @@ typedef enum { /*< skip >*/ * currently active but deleted on disconnect. Volatile connections are * always unsaved, but they are also no backing file on disk and are entirely * in-memory only. + * @NM_SETTINGS_CONNECTION_FLAG_EXTERNAL: the profile was generated to represent + * an external configuration of a networking device. Since: 1.26 * * Flags describing the current activation state. * @@ -1002,6 +1010,7 @@ typedef enum { /*< flags >*/ NM_SETTINGS_CONNECTION_FLAG_UNSAVED = 0x01, NM_SETTINGS_CONNECTION_FLAG_NM_GENERATED = 0x02, NM_SETTINGS_CONNECTION_FLAG_VOLATILE = 0x04, + NM_SETTINGS_CONNECTION_FLAG_EXTERNAL = 0x08, } NMSettingsConnectionFlags; /** @@ -1018,6 +1027,8 @@ typedef enum { /*< flags >*/ * of the activation is bound to the visilibity of the connection profile, * which in turn depends on "connection.permissions" and whether a session * for the user exists. Since: 1.16 + * @NM_ACTIVATION_STATE_FLAG_EXTERNAL: the active connection was generated to + * represent an external configuration of a networking device. Since: 1.26 * * Flags describing the current activation state. * @@ -1033,6 +1044,7 @@ typedef enum { /*< flags >*/ NM_ACTIVATION_STATE_FLAG_IP6_READY = 0x10, NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES = 0x20, NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY = 0x40, + NM_ACTIVATION_STATE_FLAG_EXTERNAL = 0x80, } NMActivationStateFlags; /** diff --git a/libnm-core/nm-dbus-types.xml b/libnm-core/nm-dbus-types.xml index 0a5501ae..7ff4961e 100644 --- a/libnm-core/nm-dbus-types.xml +++ b/libnm-core/nm-dbus-types.xml @@ -587,6 +587,11 @@ <entry role="enum_member_value"><para>= <literal>0x00000800</literal></para><para></para></entry> <entry role="enum_member_description"><para>WPA/RSN Opportunistic Wireless Encryption is supported</para><para></para></entry> </row> + <row role="constant"> + <entry role="enum_member_name"><para>NM_802_11_AP_SEC_KEY_MGMT_OWE_TM</para><para></para></entry> + <entry role="enum_member_value"><para>= <literal>0x00001000</literal></para><para></para></entry> + <entry role="enum_member_description"><para>WPA/RSN Opportunistic Wireless Encryption transition mode is supported. Since: 1.26.</para><para></para></entry> + </row> </tbody> </tgroup> </informaltable> @@ -1712,6 +1717,11 @@ <entry role="enum_member_value"><para>= <literal>0x04</literal></para><para></para></entry> <entry role="enum_member_description"><para>The connection will be deleted when it disconnects. That is for in-memory connections (unsaved), which are currently active but deleted on disconnect. Volatile connections are always unsaved, but they are also no backing file on disk and are entirely in-memory only.</para><para></para></entry> </row> + <row role="constant"> + <entry role="enum_member_name"><para>NM_SETTINGS_CONNECTION_FLAG_EXTERNAL</para><para></para></entry> + <entry role="enum_member_value"><para>= <literal>0x08</literal></para><para></para></entry> + <entry role="enum_member_description"><para>the profile was generated to represent an external configuration of a networking device. Since: 1.26</para><para></para></entry> + </row> </tbody> </tgroup> </informaltable> @@ -1772,6 +1782,11 @@ <entry role="enum_member_value"><para>= <literal>0x40</literal></para><para></para></entry> <entry role="enum_member_description"><para>the lifetime of the activation is bound to the visilibity of the connection profile, which in turn depends on "connection.permissions" and whether a session for the user exists. Since: 1.16</para><para></para></entry> </row> + <row role="constant"> + <entry role="enum_member_name"><para>NM_ACTIVATION_STATE_FLAG_EXTERNAL</para><para></para></entry> + <entry role="enum_member_value"><para>= <literal>0x80</literal></para><para></para></entry> + <entry role="enum_member_description"><para>the active connection was generated to represent an external configuration of a networking device. Since: 1.26</para><para></para></entry> + </row> </tbody> </tgroup> </informaltable> diff --git a/libnm-core/nm-keyfile/nm-keyfile-internal.h b/libnm-core/nm-keyfile/nm-keyfile-internal.h new file mode 100644 index 00000000..b6ac71d6 --- /dev/null +++ b/libnm-core/nm-keyfile/nm-keyfile-internal.h @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_KEYFILE_INTERNAL_H__ +#define __NM_KEYFILE_INTERNAL_H__ + +#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL) +#error Cannot use this header. +#endif + +#include <sys/types.h> + +#include "nm-connection.h" +#include "nm-setting-8021x.h" + +#include "nm-core-internal.h" +#include "nm-meta-setting.h" + +/*****************************************************************************/ + +#define NM_KEYFILE_CERT_SCHEME_PREFIX_PATH "file://" +#define NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11 "pkcs11:" +#define NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB "data:;base64," + +char *nm_keyfile_detect_unqualified_path_scheme (const char *base_dir, + gconstpointer pdata, + gsize data_len, + gboolean consider_exists, + gboolean *out_exists); + +typedef enum { /*< flags >*/ + NM_KEYFILE_HANDLER_FLAGS_NONE = 0, +} NMKeyfileHandlerFlags; + +typedef enum { + NM_KEYFILE_HANDLER_TYPE_WARN = 1, + NM_KEYFILE_HANDLER_TYPE_WRITE_CERT = 2, +} NMKeyfileHandlerType; + +typedef struct _NMKeyfileHandlerData NMKeyfileHandlerData; + +/** + * NMKeyfileReadHandler: + * + * Hook to nm_keyfile_read(). The user might fail the reading by setting + * @error. + * + * Returns: should return TRUE, if the reading was handled. Otherwise, + * a default action will be performed that depends on the @handler_type. + * For %NM_KEYFILE_HANDLER_TYPE_WARN handler_type, the default action is doing nothing. + */ +typedef gboolean (*NMKeyfileReadHandler) (GKeyFile *keyfile, + NMConnection *connection, + NMKeyfileHandlerType handler_type, + NMKeyfileHandlerData *handler_data, + void *user_data); + +typedef enum { + NM_KEYFILE_WARN_SEVERITY_DEBUG = 1000, + NM_KEYFILE_WARN_SEVERITY_INFO = 2000, + NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE = 2901, + NM_KEYFILE_WARN_SEVERITY_WARN = 3000, +} NMKeyfileWarnSeverity; + +NMConnection *nm_keyfile_read (GKeyFile *keyfile, + const char *base_dir, + NMKeyfileHandlerFlags handler_flags, + NMKeyfileReadHandler handler, + void *user_data, + GError **error); + +gboolean nm_keyfile_read_ensure_id (NMConnection *connection, + const char *fallback_id); + +gboolean nm_keyfile_read_ensure_uuid (NMConnection *connection, + const char *fallback_uuid_seed); + +/*****************************************************************************/ + +/** + * NMKeyfileWriteHandler: + * + * This is a hook to tweak the serialization. + * + * Handler for certain properties or events that are not entirely contained + * within the keyfile or that might be serialized differently. The @handler_type and + * @handler_data arguments tell which kind of argument we have at hand. + * + * Currently only the handler_type %NM_KEYFILE_HANDLER_TYPE_WRITE_CERT is supported, which provides + * @handler_data as %NMKeyfileHandlerDataWriteCert. However, this handler should be generic enough + * to support other types as well. + * + * This don't have to be only "properties". For example, nm_keyfile_read() uses + * a similar handler to push warnings to the caller. + * + * If the handler raises an error, it should set the @error value. This causes + * the an overall failure. + * + * Returns: whether the issue was handled. If the type was unhandled, + * a default action will be performed. This might be raise an error, + * do some fallback parsing, or do nothing. + */ +typedef gboolean (*NMKeyfileWriteHandler) (NMConnection *connection, + GKeyFile *keyfile, + NMKeyfileHandlerType handler_type, + NMKeyfileHandlerData *handler_data, + void *user_data); + +GKeyFile *nm_keyfile_write (NMConnection *connection, + NMKeyfileHandlerFlags handler_flags, + NMKeyfileWriteHandler handler, + void *user_data, + GError **error); + +/*****************************************************************************/ + +/** + * NMKeyfileHandlerDataWarn: + * + * this struct is passed as @handler_data for the @NMKeyfileReadHandler of + * handler_type %NM_KEYFILE_HANDLER_TYPE_WARN. + */ +typedef struct { + NMKeyfileWarnSeverity severity; + char *message; + const char *fmt; + va_list ap; +} NMKeyfileHandlerDataWarn; + +/** + * NMKeyfileHandlerDataWriteCert: + * + * this struct is passed as @handler_data for the @NMKeyfileWriteHandler of + * handler_type %NM_KEYFILE_HANDLER_TYPE_WRITE_CERT. + */ +typedef struct { + const NMSetting8021xSchemeVtable *vtable; +} NMKeyfileHandlerDataWriteCert; + +struct _NMKeyfileHandlerData { + NMKeyfileHandlerType type; + + GError **p_error; + + const char *kf_group_name; + const char *kf_key; + + NMSetting *cur_setting; + const char *cur_property; + + union { + NMKeyfileHandlerDataWarn warn; + NMKeyfileHandlerDataWriteCert write_cert; + }; +}; + +/*****************************************************************************/ + +void nm_keyfile_handler_data_fail_with_error (NMKeyfileHandlerData *handler_data, + GError *src); + +void nm_keyfile_handler_data_get_context (const NMKeyfileHandlerData *handler_data, + const char **out_kf_group_name, + const char **out_kf_key_name, + NMSetting **out_cur_setting, + const char **out_cur_property_name); + +void nm_keyfile_handler_data_warn_get (const NMKeyfileHandlerData *handler_data, + const char **out_message, + NMKeyfileWarnSeverity *out_severity); + +const char *_nm_keyfile_handler_data_warn_get_message (const NMKeyfileHandlerData *handler_data); + +/*****************************************************************************/ + +char *nm_keyfile_plugin_kf_get_string (GKeyFile *kf, const char *group, const char *key, GError **error); +void nm_keyfile_plugin_kf_set_string (GKeyFile *kf, const char *group, const char *key, const char *value); + +int nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value); + +void _nm_keyfile_copy (GKeyFile *dst, GKeyFile *src); +gboolean _nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b); +gboolean _nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order); +gboolean _nm_keyfile_has_values (GKeyFile *keyfile); + +/*****************************************************************************/ + +#define NM_KEYFILE_GROUP_NMMETA ".nmmeta" +#define NM_KEYFILE_KEY_NMMETA_NM_GENERATED "nm-generated" +#define NM_KEYFILE_KEY_NMMETA_VOLATILE "volatile" +#define NM_KEYFILE_KEY_NMMETA_EXTERNAL "external" +#define NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE "shadowed-storage" +#define NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED "shadowed-owned" + +#define NM_KEYFILE_PATH_NAME_LIB NMLIBDIR "/system-connections" +#define NM_KEYFILE_PATH_NAME_ETC_DEFAULT NMCONFDIR "/system-connections" +#define NM_KEYFILE_PATH_NAME_RUN NMRUNDIR "/system-connections" + +#define NM_KEYFILE_PATH_SUFFIX_NMCONNECTION ".nmconnection" + +#define NM_KEYFILE_PATH_SUFFIX_NMMETA ".nmmeta" + +#define NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL "/dev/null" + +gboolean nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension); + +char *nm_keyfile_utils_create_filename (const char *filename, gboolean with_extension); + +#endif /* __NM_KEYFILE_INTERNAL_H__ */ diff --git a/libnm-core/nm-keyfile/nm-keyfile-utils.c b/libnm-core/nm-keyfile/nm-keyfile-utils.c new file mode 100644 index 00000000..0ffce40b --- /dev/null +++ b/libnm-core/nm-keyfile/nm-keyfile-utils.c @@ -0,0 +1,701 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-keyfile-utils.h" + +#include <stdlib.h> + +#include "nm-glib-aux/nm-str-buf.h" + +#include "nm-keyfile-internal.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" + +/*****************************************************************************/ + +/** + * nm_key_file_get_boolean: + * @kf: the #GKeyFile + * @group: the group + * @key: the key + * @default_value: the default value if the value is set or not parsable as a boolean. + * + * Replacement for g_key_file_get_boolean() (which uses g_key_file_parse_value_as_boolean()). + * g_key_file_get_boolean() seems odd to me, because it accepts trailing ASCII whitespace, + * but not leading. + * This uses _nm_utils_ascii_str_to_bool(), which accepts trailing and leading whitespace, + * case-insensitive words, and also strings like "on" and "off". + * _nm_utils_ascii_str_to_bool() is our way to parse booleans from string, and we should + * use that one consistently. + * + * Also, it doesn't have g_key_file_get_boolean()'s odd API to require an error argument + * to detect parsing failures. + * + * Returns: either %TRUE or %FALSE if the key exists and is parsable as a boolean. + * Otherwise, @default_value. Sets errno to ENODATA, EINVAL or 0, depending on whether + * the key exists, whether the value is invalid, or success. + */ +int +nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value) +{ + int v; + gs_free char *value = NULL; + + value = g_key_file_get_value (kf, group, key, NULL); + + if (!value) { + errno = ENODATA; + return default_value; + } + v = _nm_utils_ascii_str_to_bool (value, -1); + if (v != -1) { + errno = 0; + return v; + } + errno = EINVAL; + return default_value; +} + +/*****************************************************************************/ + +typedef struct { + const char *setting; + const char *alias; +} SettingAlias; + +static const SettingAlias alias_list[] = { + { NM_SETTING_WIRED_SETTING_NAME, "ethernet" }, + { NM_SETTING_WIRELESS_SETTING_NAME, "wifi" }, + { NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-security" }, +}; + +const char * +nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name) +{ + guint i; + + g_return_val_if_fail (setting_name != NULL, NULL); + + for (i = 0; i < G_N_ELEMENTS (alias_list); i++) { + if (nm_streq (setting_name, alias_list[i].setting)) + return alias_list[i].alias; + } + return NULL; +} + +const char * +nm_keyfile_plugin_get_setting_name_for_alias (const char *alias) +{ + guint i; + + g_return_val_if_fail (alias != NULL, NULL); + + for (i = 0; i < G_N_ELEMENTS (alias_list); i++) { + if (nm_streq (alias, alias_list[i].alias)) + return alias_list[i].setting; + } + return NULL; +} + +/*****************************************************************************/ + +char ** +nm_keyfile_plugin_kf_get_string_list (GKeyFile *kf, + const char *group, + const char *key, + gsize *out_length, + GError **error) +{ + char **list; + const char *alias; + GError *local = NULL; + gsize l; + + list = g_key_file_get_string_list (kf, group, key, &l, &local); + if (nm_g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); + if (alias) { + g_clear_error (&local); + list = g_key_file_get_string_list (kf, alias, key, &l, &local); + } + } + if (local) + g_propagate_error (error, local); + if (!list) + l = 0; + NM_SET_OUT (out_length, l); + return list; +} + +guint * +nm_keyfile_plugin_kf_get_integer_list_uint (GKeyFile *key_file, + const char *group_name, + const char *key, + gsize *out_length, + GError **error) +{ + GError *key_file_error = NULL; + gs_strfreev char **values = NULL; + gs_free guint *int_values = NULL; + gsize i, num_ints; + + g_return_val_if_fail (key_file != NULL, NULL); + g_return_val_if_fail (group_name != NULL, NULL); + g_return_val_if_fail (key != NULL, NULL); + + NM_SET_OUT (out_length, 0); + + values = nm_keyfile_plugin_kf_get_string_list (key_file, group_name, key, &num_ints, &key_file_error); + + if (key_file_error) + g_propagate_error (error, key_file_error); + if (!values) + return NULL; + + int_values = g_new (guint, num_ints); + + for (i = 0; i < num_ints; i++) { + gint64 v; + + G_STATIC_ASSERT_EXPR (sizeof (v) > sizeof (guint)); + v = _nm_utils_ascii_str_to_int64 (values[i], 10, 0, G_MAXUINT, -1); + if (v == -1) { + g_set_error (error, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + _("Value cannot be interpreted as a list of numbers.")); + return NULL; + } + + int_values[i] = v; + } + + NM_SET_OUT (out_length, num_ints); + return g_steal_pointer (&int_values); +} + +void +nm_keyfile_plugin_kf_set_string_list (GKeyFile *kf, + const char *group, + const char *key, + const char *const*list, + gsize length) +{ + const char *alias; + + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); + g_key_file_set_string_list (kf, alias ?: group, key, list, length); +} + +void +nm_keyfile_plugin_kf_set_integer_list_uint (GKeyFile *kf, + const char *group, + const char *key, + const guint *data, + gsize length) +{ + nm_auto_str_buf NMStrBuf strbuf = { }; + gsize i; + + g_return_if_fail (kf); + g_return_if_fail (!length || data); + g_return_if_fail (group && group[0]); + g_return_if_fail (key && key[0]); + + nm_str_buf_init (&strbuf, length * 4u + 2u, FALSE); + for (i = 0; i < length; i++) + nm_str_buf_append_printf (&strbuf, "%u;", data[i]); + nm_keyfile_plugin_kf_set_value (kf, group, key, nm_str_buf_get_str (&strbuf)); +} + +void +nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf, + const char *group, + const char *key, + const guint8 *data, + gsize length) +{ + nm_auto_str_buf NMStrBuf strbuf = { }; + gsize i; + + g_return_if_fail (kf); + g_return_if_fail (!length || data); + g_return_if_fail (group && group[0]); + g_return_if_fail (key && key[0]); + + nm_str_buf_init (&strbuf, length * 4u + 2u, FALSE); + for (i = 0; i < length; i++) + nm_str_buf_append_printf (&strbuf, "%u;", (guint) data[i]); + nm_keyfile_plugin_kf_set_value (kf, group, key, nm_str_buf_get_str (&strbuf)); +} + +#define DEFINE_KF_WRAPPER_GET(fcn_name, get_ctype, key_file_get_fcn) \ +get_ctype \ +fcn_name (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + GError **error) \ +{ \ + get_ctype val; \ + const char *alias; \ + GError *local = NULL; \ +\ + val = key_file_get_fcn (kf, group, key, &local); \ + if (nm_g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \ + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ + if (alias) { \ + g_clear_error (&local); \ + val = key_file_get_fcn (kf, alias, key, &local); \ + } \ + } \ + if (local) \ + g_propagate_error (error, local); \ + return val; \ +} + +DEFINE_KF_WRAPPER_GET (nm_keyfile_plugin_kf_get_string, char *, g_key_file_get_string); +DEFINE_KF_WRAPPER_GET (nm_keyfile_plugin_kf_get_boolean, gboolean, g_key_file_get_boolean); +DEFINE_KF_WRAPPER_GET (nm_keyfile_plugin_kf_get_value, char *, g_key_file_get_value); + +#define DEFINE_KF_WRAPPER_SET(fcn_name, set_ctype, key_file_set_fcn) \ +void \ +fcn_name (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + set_ctype value) \ +{ \ + const char *alias; \ +\ + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ + key_file_set_fcn (kf, alias ?: group, key, value); \ +} + +DEFINE_KF_WRAPPER_SET (nm_keyfile_plugin_kf_set_string, const char *, g_key_file_set_string); +DEFINE_KF_WRAPPER_SET (nm_keyfile_plugin_kf_set_boolean, gboolean, g_key_file_set_boolean); +DEFINE_KF_WRAPPER_SET (nm_keyfile_plugin_kf_set_value, const char *, g_key_file_set_value); + +gint64 +nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf, + const char *group, + const char *key, + guint base, + gint64 min, + gint64 max, + gint64 fallback, + GError **error) +{ + gs_free char *s = NULL; + int errsv; + gint64 v; + + s = nm_keyfile_plugin_kf_get_value (kf, group, key, error); + if (!s) { + errno = ENODATA; + return fallback; + } + + v = _nm_utils_ascii_str_to_int64 (s, base, min, max, fallback); + errsv = errno; + if ( errsv != 0 + && error) { + g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value is not an integer in range [%lld, %lld]"), + (long long) min, (long long) max); + errno = errsv; + } + return v; +} + +char ** +nm_keyfile_plugin_kf_get_keys (GKeyFile *kf, + const char *group, + gsize *out_length, + GError **error) +{ + char **keys; + const char *alias; + GError *local = NULL; + gsize l; + + keys = g_key_file_get_keys (kf, group, &l, &local); + if (nm_g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); + if (alias) { + g_clear_error (&local); + keys = g_key_file_get_keys (kf, alias, &l, error ? &local : NULL); + } + } + nm_assert ((!local) != (!keys)); + if (!keys) + l = 0; + nm_assert (l == NM_PTRARRAY_LEN (keys)); + NM_SET_OUT (out_length, l); + if (local) + g_propagate_error (error, local); + return keys; +} + +gboolean +nm_keyfile_plugin_kf_has_key (GKeyFile *kf, + const char *group, + const char *key, + GError **error) +{ + gboolean has; + const char *alias; + GError *local = NULL; + + has = g_key_file_has_key (kf, group, key, &local); + if (nm_g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); + if (alias) { + g_clear_error (&local); + has = g_key_file_has_key (kf, alias, key, &local); + } + } + if (local) + g_propagate_error (error, local); + return has; +} + +/*****************************************************************************/ + +void +_nm_keyfile_copy (GKeyFile *dst, GKeyFile *src) +{ + gs_strfreev char **groups = NULL; + guint g, k; + + groups = g_key_file_get_groups (src, NULL); + for (g = 0; groups && groups[g]; g++) { + const char *group = groups[g]; + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys (src, group, NULL, NULL); + if (!keys) + continue; + + for (k = 0; keys[k]; k++) { + const char *key = keys[k]; + gs_free char *value = NULL; + + value = g_key_file_get_value (src, group, key, NULL); + if (value) + g_key_file_set_value (dst, group, key, value); + else + g_key_file_remove_key (dst, group, key, NULL); + } + } +} + +/*****************************************************************************/ + +gboolean +_nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b) +{ + gs_strfreev char **groups = NULL; + guint i, j; + + if (kf_a == kf_b) + return TRUE; + if (!kf_a || !kf_b) + return FALSE; + + groups = g_key_file_get_groups (kf_a, NULL); + for (i = 0; groups && groups[i]; i++) { + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL); + if (!keys) + continue; + + for (j = 0; keys[j]; j++) { + gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL); + gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL); + + if (g_strcmp0 (key_a, key_b) != 0) + return FALSE; + } + } + return TRUE; +} + +static gboolean +_nm_keyfile_equals_ordered (GKeyFile *kf_a, GKeyFile *kf_b) +{ + gs_strfreev char **groups = NULL; + gs_strfreev char **groups_b = NULL; + guint i, j; + + if (kf_a == kf_b) + return TRUE; + if (!kf_a || !kf_b) + return FALSE; + + groups = g_key_file_get_groups (kf_a, NULL); + groups_b = g_key_file_get_groups (kf_b, NULL); + if (!groups && !groups_b) + return TRUE; + if (!groups || !groups_b) + return FALSE; + for (i = 0; groups[i] && groups_b[i] && !strcmp (groups[i], groups_b[i]); i++) + ; + if (groups[i] || groups_b[i]) + return FALSE; + + for (i = 0; groups[i]; i++) { + gs_strfreev char **keys = NULL; + gs_strfreev char **keys_b = NULL; + + keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL); + keys_b = g_key_file_get_keys (kf_b, groups[i], NULL, NULL); + + if ((!keys) != (!keys_b)) + return FALSE; + if (!keys) + continue; + + for (j = 0; keys[j] && keys_b[j] && !strcmp (keys[j], keys_b[j]); j++) + ; + if (keys[j] || keys_b[j]) + return FALSE; + + for (j = 0; keys[j]; j++) { + gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL); + gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL); + + if (g_strcmp0 (key_a, key_b) != 0) + return FALSE; + } + } + return TRUE; +} + +gboolean +_nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order) +{ + if (!consider_order) { + return _nm_keyfile_a_contains_all_in_b (kf_a, kf_b) + && _nm_keyfile_a_contains_all_in_b (kf_b, kf_a); + } else { + return _nm_keyfile_equals_ordered (kf_a, kf_b); + } +} + +gboolean +_nm_keyfile_has_values (GKeyFile *keyfile) +{ + gs_strfreev char **groups = NULL; + + g_return_val_if_fail (keyfile, FALSE); + + groups = g_key_file_get_groups (keyfile, NULL); + return groups && groups[0]; +} + +/*****************************************************************************/ + +static const char * +_keyfile_key_encode (const char *name, + char **out_to_free) +{ + gsize len, i; + GString *str; + + nm_assert (name); + nm_assert (out_to_free && !*out_to_free); + + /* See g_key_file_is_key_name(). + * + * GKeyFile allows all UTF-8 characters (even non-well formed sequences), + * except: + * - no empty keys + * - no leading/trailing ' ' + * - no '=', '[', ']' + * + * We do something more strict here. All non-ASCII characters, all non-printable + * characters, and all invalid characters are escaped with "\\XX". + * + * We don't escape \\, unless it is followed by two hex digits. + */ + + if (!name[0]) { + /* empty keys are are backslash encoded. Note that usually + * \\00 is not a valid encode, the only exception is the empty + * word. */ + return "\\00"; + } + + /* find the first character that needs escaping. */ + i = 0; + if (name[0] != ' ') { + for (;; i++) { + const guchar ch = (guchar) name[i]; + + if (ch == '\0') + return name; + + if ( ch < 0x20 + || ch >= 127 + || NM_IN_SET (ch, '=', '[', ']') + || ( ch == '\\' + && g_ascii_isxdigit (name[i + 1]) + && g_ascii_isxdigit (name[i + 2])) + || ( ch == ' ' + && name[i + 1] == '\0')) + break; + } + } else if (name[1] == '\0') + return "\\20"; + + len = i + strlen (&name[i]); + nm_assert (len == strlen (name)); + str = g_string_sized_new (len + 15); + + if (name[0] == ' ') { + nm_assert (i == 0); + g_string_append (str, "\\20"); + i = 1; + } else + g_string_append_len (str, name, i); + + for (;; i++) { + const guchar ch = (guchar) name[i]; + + if (ch == '\0') + break; + + if ( ch < 0x20 + || ch >= 127 + || NM_IN_SET (ch, '=', '[', ']') + || ( ch == '\\' + && g_ascii_isxdigit (name[i + 1]) + && g_ascii_isxdigit (name[i + 2])) + || ( ch == ' ' + && name[i + 1] == '\0')) + g_string_append_printf (str, "\\%02X", ch); + else + g_string_append_c (str, (char) ch); + } + + return (*out_to_free = g_string_free (str, FALSE)); +} + +static const char * +_keyfile_key_decode (const char *key, + char **out_to_free) +{ + gsize i, len; + GString *str; + + nm_assert (key); + nm_assert (out_to_free && !*out_to_free); + + if (!key[0]) + return ""; + + for (i = 0; TRUE; i++) { + const char ch = key[i]; + + if (ch == '\0') + return key; + if ( ch == '\\' + && g_ascii_isxdigit (key[i + 1]) + && g_ascii_isxdigit (key[i + 2])) + break; + } + + len = i + strlen (&key[i]); + + if ( len == 3 + && nm_streq (key, "\\00")) + return ""; + + nm_assert (len == strlen (key)); + str = g_string_sized_new (len + 3); + + g_string_append_len (str, key, i); + for (;;) { + const char ch = key[i]; + char ch1, ch2; + unsigned v; + + if (ch == '\0') + break; + + if ( ch == '\\' + && g_ascii_isxdigit ((ch1 = key[i + 1])) + && g_ascii_isxdigit ((ch2 = key[i + 2]))) { + v = (g_ascii_xdigit_value (ch1) << 4) + g_ascii_xdigit_value (ch2); + if (v != 0) { + g_string_append_c (str, (char) v); + i += 3; + continue; + } + } + g_string_append_c (str, ch); + i++; + } + + return (*out_to_free = g_string_free (str, FALSE)); +} + +/*****************************************************************************/ + +const char * +nm_keyfile_key_encode (const char *name, + char **out_to_free) +{ + const char *key; + + key = _keyfile_key_encode (name, out_to_free); +#if NM_MORE_ASSERTS > 5 + nm_assert (key); + nm_assert (!*out_to_free || key == *out_to_free); + nm_assert (!*out_to_free || !nm_streq0 (name, key)); + { + gs_free char *to_free2 = NULL; + const char *name2; + + name2 = _keyfile_key_decode (key, &to_free2); + /* name2, the result of encode()+decode() is identical to name. + * That is because + * - encode() is a injective function. + * - decode() is a surjective function, however for output + * values of encode() is behaves injective too. */ + nm_assert (nm_streq0 (name2, name)); + } +#endif + return key; +} + +const char * +nm_keyfile_key_decode (const char *key, + char **out_to_free) +{ + const char *name; + + name = _keyfile_key_decode (key, out_to_free); +#if NM_MORE_ASSERTS > 5 + nm_assert (name); + nm_assert (!*out_to_free || name == *out_to_free); + { + gs_free char *to_free2 = NULL; + const char *key2; + + key2 = _keyfile_key_encode (name, &to_free2); + /* key2, the result of decode+encode may not be idential + * to the original key. That is, decode() is a surjective + * function mapping different keys to the same name. + * However, decode() behaves injective for input that + * are valid output of encode(). */ + nm_assert (key2); + } +#endif + return name; +} diff --git a/libnm-core/nm-keyfile/nm-keyfile-utils.h b/libnm-core/nm-keyfile/nm-keyfile-utils.h new file mode 100644 index 00000000..4764311b --- /dev/null +++ b/libnm-core/nm-keyfile/nm-keyfile-utils.h @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2010 - 2015 Red Hat, Inc. + */ + +#ifndef __NM_KEYFILE_UTILS_H__ +#define __NM_KEYFILE_UTILS_H__ + +#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL) +#error Cannot use this header. +#endif + +/*****************************************************************************/ + +#include "nm-glib-aux/nm-shared-utils.h" + +/*****************************************************************************/ + +#define NM_KEYFILE_GROUP_VPN_SECRETS "vpn-secrets" +#define NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER "wireguard-peer." + +#define nm_keyfile_error_is_not_found(error) \ + nm_g_error_matches (error, \ + G_KEY_FILE_ERROR, \ + G_KEY_FILE_ERROR_GROUP_NOT_FOUND, \ + G_KEY_FILE_ERROR_KEY_NOT_FOUND) + +const char *nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name); + +const char *nm_keyfile_plugin_get_setting_name_for_alias (const char *alias); + +/*****************************************************************************/ + +guint *nm_keyfile_plugin_kf_get_integer_list_uint (GKeyFile *kf, const char *group, const char *key, gsize *out_length, GError **error); +char **nm_keyfile_plugin_kf_get_string_list (GKeyFile *kf, const char *group, const char *key, gsize *out_length, GError **error); +char *nm_keyfile_plugin_kf_get_string (GKeyFile *kf, const char *group, const char *key, GError **error); +gboolean nm_keyfile_plugin_kf_get_boolean (GKeyFile *kf, const char *group, const char *key, GError **error); +char *nm_keyfile_plugin_kf_get_value (GKeyFile *kf, const char *group, const char *key, GError **error); + +void nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf, const char *group, const char *key, const guint8 *list, gsize length); +void nm_keyfile_plugin_kf_set_integer_list_uint (GKeyFile *kf, const char *group, const char *key, const guint *list, gsize length); +void nm_keyfile_plugin_kf_set_string_list (GKeyFile *kf, const char *group, const char *key, const char *const*list, gsize length); + +void nm_keyfile_plugin_kf_set_string (GKeyFile *kf, const char *group, const char *key, const char *value); +void nm_keyfile_plugin_kf_set_boolean (GKeyFile *kf, const char *group, const char *key, gboolean value); +void nm_keyfile_plugin_kf_set_value (GKeyFile *kf, const char *group, const char *key, const char *value); + +gint64 nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf, + const char *group, + const char *key, + guint base, + gint64 min, + gint64 max, + gint64 fallback, + GError **error); + +char **nm_keyfile_plugin_kf_get_keys (GKeyFile *kf, + const char *group, + gsize *out_length, + GError **error); + +gboolean nm_keyfile_plugin_kf_has_key (GKeyFile *kf, + const char *group, + const char *key, + GError **error); + +const char *nm_keyfile_key_encode (const char *name, + char **out_to_free); + +const char *nm_keyfile_key_decode (const char *key, + char **out_to_free); + +#endif /* __NM_KEYFILE_UTILS_H__ */ diff --git a/libnm-core/nm-keyfile/nm-keyfile.c b/libnm-core/nm-keyfile/nm-keyfile.c new file mode 100644 index 00000000..77af2b64 --- /dev/null +++ b/libnm-core/nm-keyfile/nm-keyfile.c @@ -0,0 +1,4445 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2008 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-keyfile-internal.h" + +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <linux/pkt_sched.h> + +#include "nm-glib-aux/nm-secret-utils.h" +#include "systemd/nm-sd-utils-shared.h" +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-core-internal.h" +#include "nm-keyfile-utils.h" + +#include "nm-setting-user.h" + +/*****************************************************************************/ + +typedef struct _ParseInfoProperty ParseInfoProperty; + +typedef struct { + NMConnection *connection; + GKeyFile *keyfile; + const char *base_dir; + NMKeyfileReadHandler read_handler; + void *user_data; + GError *error; + const char *group; + NMSetting *setting; +} KeyfileReaderInfo; + +typedef struct { + NMConnection *connection; + GKeyFile *keyfile; + GError *error; + NMKeyfileWriteHandler write_handler; + void *user_data; +} KeyfileWriterInfo; + +/*****************************************************************************/ + +static void +_key_file_handler_data_init (NMKeyfileHandlerData *handler_data, + NMKeyfileHandlerType handler_type, + const char *kf_group_name, + const char *kf_key, + NMSetting *cur_setting, + const char *cur_property, + GError **p_error) +{ + nm_assert (handler_data); + nm_assert (p_error && !*p_error); + + handler_data->type = handler_type; + handler_data->p_error = p_error; + handler_data->kf_group_name = kf_group_name; + handler_data->kf_key = kf_key; + handler_data->cur_setting = cur_setting; + handler_data->cur_property = cur_property; +} + +static void +_key_file_handler_data_init_read (NMKeyfileHandlerData *handler_data, + NMKeyfileHandlerType handler_type, + KeyfileReaderInfo *info, + const char *kf_key, + const char *cur_property) +{ + _key_file_handler_data_init (handler_data, + handler_type, + info->group, + kf_key, + info->setting, + cur_property, + &info->error); +} + +static void +_key_file_handler_data_init_write (NMKeyfileHandlerData *handler_data, + NMKeyfileHandlerType handler_type, + KeyfileWriterInfo *info, + const char *kf_group, + const char *kf_key, + NMSetting *cur_setting, + const char *cur_property) +{ + _key_file_handler_data_init (handler_data, + handler_type, + kf_group, + kf_key, + cur_setting, + cur_property, + &info->error); +} + +_nm_printf (5, 6) +static void +_handle_warn (KeyfileReaderInfo *info, + const char *kf_key, + const char *cur_property, + NMKeyfileWarnSeverity severity, + const char *fmt, + ...) +{ + NMKeyfileHandlerData handler_data; + + _key_file_handler_data_init_read (&handler_data, + NM_KEYFILE_HANDLER_TYPE_WARN, + info, + kf_key, + cur_property); + handler_data.warn = (NMKeyfileHandlerDataWarn) { + .severity = severity, + .message = NULL, + .fmt = fmt, + }; + + va_start (handler_data.warn.ap, fmt); + + info->read_handler (info->keyfile, + info->connection, + NM_KEYFILE_HANDLER_TYPE_WARN, + &handler_data, + info->user_data); + + va_end (handler_data.warn.ap); + + g_free (handler_data.warn.message); +} + +#define handle_warn(arg_info, arg_kf_key, arg_property_name, arg_severity, ...) \ + ({ \ + KeyfileReaderInfo *_info = (arg_info); \ + \ + nm_assert (!_info->error); \ + \ + if (_info->read_handler) { \ + _handle_warn (_info, \ + (arg_kf_key), \ + (arg_property_name), \ + (arg_severity), \ + __VA_ARGS__); \ + } \ + _info->error == NULL; \ + }) + +/*****************************************************************************/ + +static gboolean +_secret_flags_persist_secret (NMSettingSecretFlags flags) +{ + return flags == NM_SETTING_SECRET_FLAG_NONE; +} + +/*****************************************************************************/ +/* Some setting properties also contain setting names, such as + * NMSettingConnection's 'type' property (which specifies the base type of the + * connection, e.g. ethernet or wifi) or 'slave-type' (specifies type of slave + * connection, e.g. bond or bridge). This function handles translating those + * properties' values to the real setting name if they are an alias. + */ +static void +setting_alias_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + const char *key_setting_name; + gs_free char *s = NULL; + + s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + if (!s) + return; + + key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s); + g_object_set (G_OBJECT (setting), + key, + key_setting_name ?: s, + NULL); +} + +static void +sriov_vfs_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_unref_ptrarray GPtrArray *vfs = NULL; + gs_strfreev char **keys = NULL; + gsize n_keys = 0; + int i; + + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + vfs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_sriov_vf_unref); + + for (i = 0; i < n_keys; i++) { + gs_free char *value = NULL; + NMSriovVF *vf; + const char *rest; + + if (!g_str_has_prefix (keys[i], "vf.")) + continue; + + rest = &keys[i][3]; + + if (!NM_STRCHAR_ALL (rest, ch, g_ascii_isdigit (ch))) + continue; + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, + setting_name, + keys[i], + NULL); + + vf = _nm_utils_sriov_vf_from_strparts (rest, value, TRUE, NULL); + if (vf) + g_ptr_array_add (vfs, vf); + } + + g_object_set (G_OBJECT (setting), + key, vfs, + NULL); +} + +static void +read_array_of_uint (GKeyFile *file, + NMSetting *setting, + const char *key) +{ + gs_unref_array GArray *array = NULL; + gs_free_error GError *error = NULL; + gs_free guint *tmp = NULL; + gsize length; + + tmp = nm_keyfile_plugin_kf_get_integer_list_uint (file, nm_setting_get_name (setting), key, &length, &error); + if (error) + return; + + array = g_array_sized_new (FALSE, FALSE, sizeof (guint), length); + g_array_append_vals (array, tmp, length); + g_object_set (setting, key, array, NULL); +} + +static gboolean +get_one_int (KeyfileReaderInfo *info, + const char *kf_key, + const char *property_name, + const char *str, + guint32 max_val, + guint32 *out) +{ + gint64 tmp; + + nm_assert ((!info) == (!property_name)); + nm_assert ((!info) == (!kf_key)); + + if (!str || !str[0]) { + if (info) { + handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring missing number")); + } + return FALSE; + } + + tmp = _nm_utils_ascii_str_to_int64 (str, 10, 0, max_val, -1); + if (tmp == -1) { + if (info) { + handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid number '%s'"), + str); + } + return FALSE; + } + + *out = (guint32) tmp; + return TRUE; +} + +static gpointer +build_address (KeyfileReaderInfo *info, + const char *kf_key, + const char *property_name, + int family, + const char *address_str, + guint32 plen) +{ + NMIPAddress *addr; + GError *error = NULL; + + g_return_val_if_fail (address_str, NULL); + + addr = nm_ip_address_new (family, address_str, plen, &error); + if (!addr) { + handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid %s address: %s"), + family == AF_INET ? "IPv4" : "IPv6", + error->message); + g_error_free (error); + } + + return addr; +} + +static gpointer +build_route (KeyfileReaderInfo *info, + const char *kf_key, + const char *property_name, + int family, + const char *dest_str, + guint32 plen, + const char *gateway_str, + const char *metric_str) +{ + NMIPRoute *route; + guint32 u32; + gint64 metric = -1; + GError *error = NULL; + + g_return_val_if_fail (dest_str, NULL); + + /* Next hop */ + if (gateway_str && gateway_str[0]) { + if (!nm_utils_ipaddr_is_valid (family, gateway_str)) { + /* Try workaround for routes written by broken keyfile writer. + * Due to bug bgo#719851, an older version of writer would have + * written "a:b:c:d::/plen,metric" if the gateway was ::, instead + * of "a:b:c:d::/plen,,metric" or "a:b:c:d::/plen,::,metric" + * Try workaround by interpreting gateway_str as metric to accept such + * invalid routes. This broken syntax should not be not officially + * supported. + **/ + if ( family == AF_INET6 + && !metric_str + && get_one_int (NULL, NULL, NULL, gateway_str, G_MAXUINT32, &u32)) { + metric = u32; + gateway_str = NULL; + } else { + handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid gateway '%s' for %s route"), + gateway_str, + family == AF_INET ? "IPv4" : "IPv6"); + return NULL; + } + } + } else + gateway_str = NULL; + + /* parse metric, default to -1 */ + if (metric_str) { + if (!get_one_int (info, + kf_key, + property_name, + metric_str, + G_MAXUINT32, + &u32)) + return NULL; + metric = u32; + } + + route = nm_ip_route_new (family, + dest_str, + plen, + gateway_str, + metric, + &error); + if (!route) { + handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid %s route: %s"), + family == AF_INET ? "IPv4" : "IPv6", + error->message); + g_error_free (error); + } + + return route; +} + +/* On success, returns pointer to the zero-terminated field (original @current). + * The @current * pointer target is set to point to the rest of the input + * or %NULL if there is no more input. Sets error to %NULL for convenience. + * + * On failure, returns %NULL (unspecified). The @current pointer target is + * resets to its original value to allow skipping fields. The @error target + * is set to the character that breaks the parsing or %NULL if @current was %NULL. + * + * When @current target is %NULL, gracefully fail returning %NULL while + * leaving the @current target %NULL end setting @error to %NULL; + */ +static const char * +read_field (char **current, const char **out_err_str, const char *characters, const char *delimiters) +{ + const char *start; + + nm_assert (current); + nm_assert (out_err_str); + nm_assert (characters); + nm_assert (delimiters); + + *out_err_str = NULL; + + if (!*current) { + /* graceful failure, leave '*current' NULL */ + return NULL; + } + + /* fail on empty input */ + if (!**current) + return NULL; + + /* remember beginning of input */ + start = *current; + + while (**current && strchr (characters, **current)) + (*current)++; + if (**current) + if (strchr (delimiters, **current)) { + /* success, more data available */ + *(*current)++ = '\0'; + return start; + } else { + /* error, bad character */ + *out_err_str = *current; + *current = (char *) start; + return NULL; + } + else { + /* success, end of input */ + *current = NULL; + return start; + } +} + +/*****************************************************************************/ + +#define NM_DBUS_SERVICE_OPENCONNECT "org.freedesktop.NetworkManager.openconnect" +#define NM_OPENCONNECT_KEY_GATEWAY "gateway" +#define NM_OPENCONNECT_KEY_COOKIE "cookie" +#define NM_OPENCONNECT_KEY_GWCERT "gwcert" +#define NM_OPENCONNECT_KEY_XMLCONFIG "xmlconfig" +#define NM_OPENCONNECT_KEY_LASTHOST "lasthost" +#define NM_OPENCONNECT_KEY_AUTOCONNECT "autoconnect" +#define NM_OPENCONNECT_KEY_CERTSIGS "certsigs" + +static void +openconnect_fix_secret_flags (NMSetting *setting) +{ + NMSettingVpn *s_vpn; + NMSettingSecretFlags flags; + + /* Huge hack. There were some openconnect changes that needed to happen + * pretty late, too late to get into distros. Migration has already + * happened for many people, and their secret flags are wrong. But we + * don't want to requrie re-migration, so we have to fix it up here. Ugh. + */ + + if (!NM_IS_SETTING_VPN (setting)) + return; + + s_vpn = NM_SETTING_VPN (setting); + + if (!nm_streq0 (nm_setting_vpn_get_service_type (s_vpn), NM_DBUS_SERVICE_OPENCONNECT)) + return; + + /* These are different for every login session, and should not be stored */ + flags = NM_SETTING_SECRET_FLAG_NOT_SAVED; + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GATEWAY, flags, NULL); + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_COOKIE, flags, NULL); + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GWCERT, flags, NULL); + + /* These are purely internal data for the auth-dialog, and should be stored */ + flags = 0; + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_XMLCONFIG, flags, NULL); + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_LASTHOST, flags, NULL); + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_AUTOCONNECT, flags, NULL); + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_CERTSIGS, flags, NULL); +} + +/*****************************************************************************/ + +#define IP_ADDRESS_CHARS "0123456789abcdefABCDEF:.%" +#define DIGITS "0123456789" +#define DELIMITERS "/;," + +/* The following IPv4 and IPv6 address formats are supported: + * + * address (DEPRECATED) + * address/plen + * address/gateway (DEPRECATED) + * address/plen,gateway + * + * The following IPv4 and IPv6 route formats are supported: + * + * address/plen (NETWORK dev DEVICE) + * address/plen,gateway (NETWORK via GATEWAY dev DEVICE) + * address/plen,,metric (NETWORK dev DEVICE metric METRIC) + * address/plen,gateway,metric (NETWORK via GATEWAY dev DEVICE metric METRIC) + * + * For backward, forward and sideward compatibility, slash (/), + * semicolon (;) and comma (,) are interchangeable. The choice of + * separator in the above examples is therefore not significant. + * + * Leaving out the prefix length is discouraged and DEPRECATED. The + * default value of IPv6 prefix length was 64 and has not been + * changed. The default for IPv4 is now 24, which is the closest + * IPv4 equivalent. These defaults may just as well be changed to + * match the iproute2 defaults (32 for IPv4 and 128 for IPv6). + */ +static gpointer +read_one_ip_address_or_route (KeyfileReaderInfo *info, + const char *property_name, + const char *setting_name, + const char *kf_key, + gboolean ipv6, + gboolean route, + char **out_gateway, + NMSetting *setting) +{ + guint plen; + gpointer result; + const char *address_str; + const char *plen_str; + const char *gateway_str; + const char *metric_str; + const char *err_str = NULL; + char *current; + gs_free char *value = NULL; + gs_free char *value_orig = NULL; + +#define VALUE_ORIG() (value_orig ?: (value_orig = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, kf_key, NULL))) + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, kf_key, NULL); + if (!value) + return NULL; + + current = value; + + /* get address field */ + address_str = read_field (¤t, &err_str, IP_ADDRESS_CHARS, DELIMITERS); + if (err_str) { + handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' for address %s: '%s' (position %td)"), + *err_str, + kf_key, + VALUE_ORIG (), + err_str - current); + return NULL; + } + /* get prefix length field (skippable) */ + plen_str = read_field (¤t, &err_str, DIGITS, DELIMITERS); + /* get gateway field */ + gateway_str = read_field (¤t, &err_str, IP_ADDRESS_CHARS, DELIMITERS); + if (err_str) { + handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' for %s: '%s' (position %td)"), + *err_str, + kf_key, + VALUE_ORIG (), + err_str - current); + return NULL; + } + /* for routes, get metric */ + if (route) { + metric_str = read_field (¤t, &err_str, DIGITS, DELIMITERS); + if (err_str) { + handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"), + *err_str, + kf_key, + VALUE_ORIG (), + err_str - current); + return NULL; + } + } else + metric_str = NULL; + if (current) { + /* there is still some data */ + if (*current) { + /* another field follows */ + handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("garbage at the end of value %s: '%s'"), + kf_key, + VALUE_ORIG ()); + return NULL; + } else { + /* semicolon at the end of input */ + if (!handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_INFO, + _("deprecated semicolon at the end of value %s: '%s'"), + kf_key, + VALUE_ORIG ())) + return NULL; + } + } + +#define DEFAULT_PREFIX(for_route, for_ipv6) ( (for_route) ? ( (for_ipv6) ? 128 : 24 ) : ( (for_ipv6) ? 64 : 24 ) ) + + /* parse plen, fallback to defaults */ + if (plen_str) { + if (!get_one_int (info, + kf_key, + property_name, + plen_str, + ipv6 ? 128 : 32, + &plen)) { + plen = DEFAULT_PREFIX (route, ipv6); + if ( info->error + || !handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid prefix length for %s '%s', defaulting to %d"), + kf_key, + VALUE_ORIG (), + plen)) + return NULL; + } + } else { + plen = DEFAULT_PREFIX (route, ipv6); + if (!handle_warn (info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("missing prefix length for %s '%s', defaulting to %d"), + kf_key, + VALUE_ORIG (), + plen)) + return NULL; + } + + /* build the appropriate data structure for NetworkManager settings */ + if (route) { + result = build_route (info, + kf_key, + property_name, + ipv6 ? AF_INET6 : AF_INET, + address_str, + plen, + gateway_str, + metric_str); + } else { + result = build_address (info, + kf_key, + property_name, + ipv6 ? AF_INET6 : AF_INET, + address_str, + plen); + if (!result) + return NULL; + if (gateway_str) + NM_SET_OUT (out_gateway, g_strdup (gateway_str)); + } + +#undef VALUE_ORIG + + return result; +} + +static void +fill_route_attributes (GKeyFile *kf, NMIPRoute *route, const char *setting, const char *key, int family) +{ + gs_free char *value = NULL; + gs_unref_hashtable GHashTable *hash = NULL; + GHashTableIter iter; + char *name; + GVariant *variant; + + value = nm_keyfile_plugin_kf_get_string (kf, setting, key, NULL); + if (!value || !value[0]) + return; + + hash = nm_utils_parse_variant_attributes (value, ',', '=', TRUE, + nm_ip_route_get_variant_attribute_spec (), + NULL); + if (hash) { + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &variant)) { + if (nm_ip_route_attribute_validate (name, variant, family, NULL, NULL)) + nm_ip_route_set_attribute (route, name, g_variant_ref (variant)); + } + } +} + +typedef struct { + const char *s_key; + gint32 key_idx; + gint8 key_type; +} BuildListData; + +typedef enum { + BUILD_LIST_TYPE_ADDRESSES, + BUILD_LIST_TYPE_ROUTES, + BUILD_LIST_TYPE_ROUTING_RULES, +} BuildListType; + +static int +_build_list_data_cmp (gconstpointer p_a, gconstpointer p_b, gpointer user_data) +{ + const BuildListData *a = p_a; + const BuildListData *b = p_b; + + NM_CMP_FIELD (a, b, key_idx); + NM_CMP_FIELD (a, b, key_type); + NM_CMP_FIELD_STR (a, b, s_key); + return 0; +} + +static gboolean +_build_list_data_is_shadowed (const BuildListData *build_list, + gsize build_list_len, + gsize idx) +{ + /* the keyfile contains duplicate keys, which are both returned + * by g_key_file_get_keys() (WHY??). + * + * Skip the earlier one. */ + return idx + 1 < build_list_len + && build_list[idx].key_idx == build_list[idx + 1].key_idx + && build_list[idx].key_type == build_list[idx + 1].key_type + && nm_streq (build_list[idx].s_key, build_list[idx + 1].s_key); +} + +static gboolean +_build_list_match_key_w_name_impl (const char *key, + const char *base_name, + gsize base_name_l, + gint32 *out_key_idx) +{ + gint64 v; + + /* some very strict parsing. */ + + /* the key must start with base_name. */ + if (strncmp (key, base_name, base_name_l) != 0) + return FALSE; + + key += base_name_l; + if (key[0] == '\0') { + /* if key is identical to base_name, that's good. */ + NM_SET_OUT (out_key_idx, -1); + return TRUE; + } + + /* if base_name is followed by a zero, then it must be + * only a zero, nothing else. */ + if (key[0] == '0') { + if (key[1] != '\0') + return FALSE; + NM_SET_OUT (out_key_idx, 0); + return TRUE; + } + + /* otherwise, it can only be followed by a non-zero decimal. */ + if (!(key[0] >= '1' && key[0] <= '9')) + return FALSE; + /* and all remaining chars must be decimals too. */ + if (!NM_STRCHAR_ALL (&key[1], ch, g_ascii_isdigit (ch))) + return FALSE; + + /* and it must be convertible to a (positive) int. */ + v = _nm_utils_ascii_str_to_int64 (key, 10, 0, G_MAXINT32, -1); + if (v < 0) + return FALSE; + + /* good */ + NM_SET_OUT (out_key_idx, v); + return TRUE; +} + +#define _build_list_match_key_w_name(key, base_name, out_key_idx) \ + _build_list_match_key_w_name_impl (key, base_name, NM_STRLEN (base_name), out_key_idx) + +static BuildListData * +_build_list_create (GKeyFile *keyfile, + const char *group_name, + BuildListType build_list_type, + gsize *out_build_list_len, + char ***out_keys_strv) +{ + gs_strfreev char **keys = NULL; + gsize i_keys, n_keys; + gs_free BuildListData *build_list = NULL; + gsize build_list_len = 0; + + nm_assert (out_build_list_len && *out_build_list_len == 0); + nm_assert (out_keys_strv && !*out_keys_strv); + + keys = nm_keyfile_plugin_kf_get_keys (keyfile, group_name, &n_keys, NULL); + if (n_keys == 0) + return NULL; + + for (i_keys = 0; i_keys < n_keys; i_keys++) { + const char *s_key = keys[i_keys]; + gint32 key_idx; + gint8 key_type = 0; + + switch (build_list_type) { + case BUILD_LIST_TYPE_ROUTES: + if (_build_list_match_key_w_name (s_key, "route", &key_idx)) + key_type = 0; + else if (_build_list_match_key_w_name (s_key, "routes", &key_idx)) + key_type = 1; + else + continue; + break; + case BUILD_LIST_TYPE_ADDRESSES: + if (_build_list_match_key_w_name (s_key, "address", &key_idx)) + key_type = 0; + else if (_build_list_match_key_w_name (s_key, "addresses", &key_idx)) + key_type = 1; + else + continue; + break; + case BUILD_LIST_TYPE_ROUTING_RULES: + if (_build_list_match_key_w_name (s_key, "routing-rule", &key_idx)) + key_type = 0; + else + continue; + break; + default: + nm_assert_not_reached (); + break; + } + + if (G_UNLIKELY (!build_list)) + build_list = g_new (BuildListData, n_keys - i_keys); + + build_list[build_list_len++] = (BuildListData) { + .s_key = s_key, + .key_idx = key_idx, + .key_type = key_type, + }; + } + + if (build_list_len == 0) + return NULL; + + if (build_list_len > 1) { + g_qsort_with_data (build_list, + build_list_len, + sizeof (BuildListData), + _build_list_data_cmp, + NULL); + } + + *out_build_list_len = build_list_len; + *out_keys_strv = g_steal_pointer (&keys); + return g_steal_pointer (&build_list); +} + +static void +ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *setting_key) +{ + const char *setting_name = nm_setting_get_name (setting); + gboolean is_ipv6 = nm_streq (setting_name, "ipv6"); + gboolean is_routes = nm_streq (setting_key, "routes"); + gs_free char *gateway = NULL; + gs_unref_ptrarray GPtrArray *list = NULL; + gs_strfreev char **keys = NULL; + gs_free BuildListData *build_list = NULL; + gsize i_build_list, build_list_len = 0; + + build_list = _build_list_create (info->keyfile, + setting_name, + is_routes + ? BUILD_LIST_TYPE_ROUTES + : BUILD_LIST_TYPE_ADDRESSES, + &build_list_len, + &keys); + if (!build_list) + return; + + list = g_ptr_array_new_with_free_func (is_routes + ? (GDestroyNotify) nm_ip_route_unref + : (GDestroyNotify) nm_ip_address_unref); + + for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) { + const char *s_key; + gpointer item; + + if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list)) + continue; + + s_key = build_list[i_build_list].s_key; + item = read_one_ip_address_or_route (info, + setting_key, + setting_name, + s_key, + is_ipv6, + is_routes, + gateway ? NULL : &gateway, + setting); + if (item && is_routes) { + char options_key[128]; + + nm_sprintf_buf (options_key, "%s_options", s_key); + fill_route_attributes (info->keyfile, + item, + setting_name, + options_key, + is_ipv6 ? AF_INET6 : AF_INET); + } + + if (info->error) + return; + + if (item) + g_ptr_array_add (list, item); + } + + if (list->len >= 1) + g_object_set (setting, setting_key, list, NULL); + + if (gateway) + g_object_set (setting, "gateway", gateway, NULL); +} + +static void +ip_routing_rule_parser_full (KeyfileReaderInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting) +{ + const char *setting_name = nm_setting_get_name (setting); + gboolean is_ipv6 = nm_streq (setting_name, "ipv6"); + gs_strfreev char **keys = NULL; + gs_free BuildListData *build_list = NULL; + gsize i_build_list, build_list_len = 0; + + build_list = _build_list_create (info->keyfile, + setting_name, + BUILD_LIST_TYPE_ROUTING_RULES, + &build_list_len, + &keys); + if (!build_list) + return; + + for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) { + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL; + gs_free char *value = NULL; + gs_free_error GError *local = NULL; + + if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list)) + continue; + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, + setting_name, + build_list[i_build_list].s_key, + NULL); + if (!value) + continue; + + rule = nm_ip_routing_rule_from_string (value, + ( NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | ( is_ipv6 + ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6 + : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET)), + NULL, + &local); + if (!rule) { + if (!handle_warn (info, + build_list[i_build_list].s_key, + property_info->name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid value for \"%s\": %s"), + build_list[i_build_list].s_key, + local->message)) + return; + continue; + } + + nm_setting_ip_config_add_routing_rule (NM_SETTING_IP_CONFIG (setting), rule); + } +} + +static void +ip_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + int addr_family; + gs_strfreev char **list = NULL; + gsize i, n, length; + + nm_assert (NM_IS_SETTING_IP4_CONFIG (setting) || NM_IS_SETTING_IP6_CONFIG (setting)); + + list = nm_keyfile_plugin_kf_get_string_list (info->keyfile, + nm_setting_get_name (setting), + key, + &length, + NULL); + nm_assert (length == NM_PTRARRAY_LEN (list)); + if (length == 0) + return; + + addr_family = NM_IS_SETTING_IP4_CONFIG (setting) ? AF_INET : AF_INET6; + + n = 0; + for (i = 0; i < length; i++) { + NMIPAddr addr; + + if (inet_pton (addr_family, list[i], &addr) <= 0) { + if (!handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid DNS server IPv%c address '%s'"), + nm_utils_addr_family_to_char (addr_family), + list[i])) { + do { + nm_clear_g_free (&list[i]); + } while (++i < length); + return; + } + nm_clear_g_free (&list[i]); + continue; + } + + if (n != i) + list[n] = g_steal_pointer (&list[i]); + n++; + } + + g_object_set (setting, key, list, NULL); +} + +static void +ip6_addr_gen_mode_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + NMSettingIP6ConfigAddrGenMode addr_gen_mode; + const char *setting_name = nm_setting_get_name (setting); + gs_free char *s = NULL; + + s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + if (s) { + if (!nm_utils_enum_from_str (nm_setting_ip6_config_addr_gen_mode_get_type (), s, + (int *) &addr_gen_mode, NULL)) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid option '%s', use one of [%s]"), + s, + "eui64,stable-privacy"); + return; + } + } else + addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64; + + g_object_set (G_OBJECT (setting), key, (int) addr_gen_mode, NULL); +} + +static void +mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize addr_len, gboolean cloned_mac_addr) +{ + const char *setting_name = nm_setting_get_name (setting); + char addr_str[NM_UTILS_HWADDR_LEN_MAX * 3]; + guint8 addr_bin[NM_UTILS_HWADDR_LEN_MAX]; + gs_free char *tmp_string = NULL; + gs_free guint *int_list = NULL; + const char *mac_str; + gsize int_list_len; + gsize i; + + nm_assert (addr_len > 0); + nm_assert (addr_len <= NM_UTILS_HWADDR_LEN_MAX); + + tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + + if ( cloned_mac_addr + && NM_CLONED_MAC_IS_SPECIAL (tmp_string)) { + mac_str = tmp_string; + goto out; + } + + if ( tmp_string + && nm_utils_hwaddr_aton (tmp_string, addr_bin, addr_len)) + goto good_addr_bin; + + /* Old format; list of ints */ + int_list = nm_keyfile_plugin_kf_get_integer_list_uint (info->keyfile, setting_name, key, &int_list_len, NULL); + if (int_list_len == addr_len) { + for (i = 0; i < addr_len; i++) { + const guint val = int_list[i]; + + if (val > 255) + break; + addr_bin[i] = (guint8) val; + } + if (i == addr_len) + goto good_addr_bin; + } + + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid MAC address")); + return; + +good_addr_bin: + nm_utils_bin2hexstr_full (addr_bin, addr_len, ':', TRUE, addr_str); + mac_str = addr_str; + +out: + g_object_set (setting, key, mac_str, NULL); +} + +static void +mac_address_parser_ETHER (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser (info, setting, key, ETH_ALEN, FALSE); +} + +static void +mac_address_parser_ETHER_cloned (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser (info, setting, key, ETH_ALEN, TRUE); +} + +static void +mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser (info, setting, key, INFINIBAND_ALEN, FALSE); +} + +static void +read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key) +{ + gs_strfreev char **keys = NULL; + const char *const*iter; + const char *setting_name = nm_setting_get_name (setting); + gboolean is_vpn; + gsize n_keys; + + nm_assert ( (NM_IS_SETTING_VPN (setting) && nm_streq (key, NM_SETTING_VPN_DATA)) + || (NM_IS_SETTING_VPN (setting) && nm_streq (key, NM_SETTING_VPN_SECRETS)) + || (NM_IS_SETTING_BOND (setting) && nm_streq (key, NM_SETTING_BOND_OPTIONS)) + || (NM_IS_SETTING_USER (setting) && nm_streq (key, NM_SETTING_USER_DATA))); + + keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + if ( (is_vpn = NM_IS_SETTING_VPN (setting)) + || NM_IS_SETTING_BOND (setting)) { + for (iter = (const char *const*) keys; *iter; iter++) { + gs_free char *to_free = NULL; + gs_free char *value = NULL; + const char *name; + + value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL); + if (!value) + continue; + + name = nm_keyfile_key_decode (*iter, &to_free); + + if (is_vpn) { + /* Add any item that's not a class property to the data hash */ + if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), name)) + nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), name, value); + } else { + if (!nm_streq (name, "interface-name")) + nm_setting_bond_add_option (NM_SETTING_BOND (setting), name, value); + } + } + openconnect_fix_secret_flags (setting); + return; + } + + if (NM_IS_SETTING_USER (setting)) { + gs_unref_hashtable GHashTable *data = NULL; + + data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free); + for (iter = (const char *const*) keys; *iter; iter++) { + gs_free char *to_free = NULL; + char *value = NULL; + const char *name; + + value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL); + if (!value) + continue; + name = nm_keyfile_key_decode (*iter, &to_free); + g_hash_table_insert (data, + g_steal_pointer (&to_free) ?: g_strdup (name), + value); + } + g_object_set (setting, NM_SETTING_USER_DATA, data, NULL); + return; + } + + nm_assert_not_reached (); +} + +static gsize +unescape_semicolons (char *str) +{ + gsize i, j; + + for (i = 0, j = 0; str[i]; ) { + if (str[i] == '\\' && str[i+1] == ';') + i++; + str[j++] = str[i++];; + } + nm_explicit_bzero (&str[j], i - j); + return j; +} + +static GBytes * +get_bytes (KeyfileReaderInfo *info, + const char *setting_name, + const char *key, + gboolean zero_terminate, + gboolean unescape_semicolon) +{ + nm_auto_free_secret char *tmp_string = NULL; + gboolean may_be_int_list = TRUE; + gsize length; + GBytes *result; + + /* New format: just a string + * Old format: integer list; e.g. 11;25;38; + */ + tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + if (!tmp_string) + return NULL; + + /* if the string is empty, we return an empty GBytes array. + * Note that for NM_SETTING_802_1X_PASSWORD_RAW both %NULL and + * an empty GBytes are valid, and shall be destinguished. */ + if (!tmp_string[0]) { + /* note that even if @zero_terminate is TRUE, we return an empty + * byte-array. The reason is that zero_terminate is there to terminate + * *valid* strings. It's not there to terminated invalid (empty) strings. + */ + return g_bytes_new_static ("", 0); + } + + for (length = 0; tmp_string[length]; length++) { + const char ch = tmp_string[length]; + + if ( !g_ascii_isspace (ch) + && !g_ascii_isdigit (ch) + && ch != ';') { + may_be_int_list = FALSE; + length += strlen (&tmp_string[length]); + break; + } + } + + /* Try to parse the string as a integer list. */ + if (may_be_int_list && length > 0) { + nm_auto_free_secret_buf NMSecretBuf *bin = NULL; + const char *const s = tmp_string; + gsize i, d; + + bin = nm_secret_buf_new (length / 2 + 3); + +#define DIGIT(c) ((c) - '0') + i = 0; + d = 0; + while (TRUE) { + int n; + + /* leading whitespace */ + while (g_ascii_isspace (s[i])) + i++; + if (s[i] == '\0') + break; + /* then expect 1 to 3 digits */ + if (!g_ascii_isdigit (s[i])) { + d = 0; + break; + } + n = DIGIT (s[i]); + i++; + if (g_ascii_isdigit (s[i])) { + n = 10 * n + DIGIT (s[i]); + i++; + if (g_ascii_isdigit (s[i])) { + n = 10 * n + DIGIT (s[i]); + i++; + } + } + if (n > 255) { + d = 0; + break; + } + + nm_assert (d < bin->len); + bin->bin[d++] = n; + + /* allow whitespace after the digit. */ + while (g_ascii_isspace (s[i])) + i++; + /* need a semicolon as separator. */ + if (s[i] != ';') { + d = 0; + break; + } + i++; + } +#undef DIGIT + + /* Old format; list of ints. We already did a strict validation of the + * string format before. We expect that this conversion cannot fail. */ + if (d > 0) { + /* note that @zero_terminate does not add a terminating '\0' to + * binary data as an integer list. If the bytes are expressed as + * an integer list, all potential NUL characters are supposed to + * be included there explicitly. + * + * However, in the spirit of defensive programming, we do append a + * NUL character to the buffer, although this character is hidden + * and only a mitigation for bugs. */ + + if (d + 10 < bin->len) { + /* hm, too much unused memory. Copy the memory to a suitable + * sized buffer. */ + return nm_secret_copy_to_gbytes (bin->bin, d); + } + + nm_assert (d < bin->len); + bin->bin[d] = '\0'; + return nm_secret_buf_to_gbytes_take (g_steal_pointer (&bin), d); + } + } + + /* Handle as a simple string (ie, new format) */ + if (unescape_semicolon) + length = unescape_semicolons (tmp_string); + if (zero_terminate) + length++; + if (length == 0) + return NULL; + + result = g_bytes_new_with_free_func (tmp_string, + length, + (GDestroyNotify) nm_free_secret, + tmp_string); + tmp_string = NULL; + return result; +} + +static void +ssid_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_unref_bytes GBytes *bytes = NULL; + + bytes = get_bytes (info, setting_name, key, FALSE, TRUE); + if (!bytes) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid SSID")); + return; + } + g_object_set (setting, key, bytes, NULL); +} + +static void +password_raw_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_unref_bytes GBytes *bytes = NULL; + + bytes = get_bytes (info, setting_name, key, FALSE, TRUE); + if (!bytes) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid raw password")); + return; + } + g_object_set (setting, key, bytes, NULL); +} + +static char * +get_cert_path (const char *base_dir, const guint8 *cert_path, gsize cert_path_len) +{ + const char *base; + char *p = NULL, *path, *tmp; + + g_return_val_if_fail (base_dir != NULL, NULL); + g_return_val_if_fail (cert_path != NULL, NULL); + + path = g_strndup ((char *) cert_path, cert_path_len); + + if (path[0] == '/') + return path; + + base = path; + p = strrchr (path, '/'); + if (p) + base = p + 1; + + tmp = g_build_path ("/", base_dir, base, NULL); + g_free (path); + return tmp; +} + +static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" }; + +static gboolean +has_cert_ext (const char *path) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (certext); i++) { + if (g_str_has_suffix (path, certext[i])) + return TRUE; + } + return FALSE; +} + +char * +nm_keyfile_detect_unqualified_path_scheme (const char *base_dir, + gconstpointer pdata, + gsize data_len, + gboolean consider_exists, + gboolean *out_exists) +{ + const char *data = pdata; + gboolean exists = FALSE; + gsize validate_len; + gsize path_len, pathuri_len; + gs_free char *path = NULL; + gs_free char *pathuri = NULL; + + g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL); + + if (!pdata) + return NULL; + if (data_len == -1) + data_len = strlen (data); + if (data_len > 500 || data_len < 1) + return NULL; + + /* If there's a trailing zero tell g_utf8_validate() to validate until the zero */ + if (data[data_len - 1] == '\0') { + /* setting it to -1, would mean we accept data to contain NUL characters before the + * end. Don't accept any NUL in [0 .. data_len-1[ . */ + validate_len = data_len - 1; + } else + validate_len = data_len; + if ( validate_len == 0 + || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE) + return NULL; + + /* Might be a bare path without the file:// prefix; in that case + * if it's an absolute path, use that, otherwise treat it as a + * relative path to the current directory. + */ + + path = get_cert_path (base_dir, (const guint8 *) data, data_len); + + /* FIXME(keyfile-parse-in-memory): it is wrong that keyfile reader makes decisions based on + * the file systems content. The serialization/parsing should be entirely in-memory. */ + if ( !memchr (data, '/', data_len) + && !has_cert_ext (path)) { + if (!consider_exists) + return NULL; + exists = g_file_test (path, G_FILE_TEST_EXISTS); + if (!exists) + return NULL; + } else if (out_exists) + exists = g_file_test (path, G_FILE_TEST_EXISTS); + + /* Construct the proper value as required for the PATH scheme. + * + * When returning TRUE, we must also be sure that @data_len does not look like + * the deprecated format of list of integers. With this implementation that is the + * case, as long as @consider_exists is FALSE. */ + path_len = strlen (path); + pathuri_len = (NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + 1) + path_len; + pathuri = g_new (char, pathuri_len); + memcpy (pathuri, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)); + memcpy (&pathuri[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)], path, path_len + 1); + if (nm_setting_802_1x_check_cert_scheme (pathuri, pathuri_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH) + return NULL; + + NM_SET_OUT (out_exists, exists); + return g_steal_pointer (&pathuri); +} + +#define HAS_SCHEME_PREFIX(bin, bin_len, scheme) \ + ({ \ + const char *const _bin = (bin); \ + const gsize _bin_len = (bin_len); \ + \ + nm_assert (_bin && _bin_len > 0); \ + \ + ( _bin_len > NM_STRLEN (scheme) + 1 \ + && _bin[_bin_len - 1] == '\0' \ + && memcmp (_bin, scheme, NM_STRLEN (scheme)) == 0); \ + }) + +static void +cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_unref_bytes GBytes *bytes = NULL; + const char *bin = NULL; + gsize bin_len = 0; + char *path; + gboolean path_exists; + + bytes = get_bytes (info, setting_name, key, TRUE, FALSE); + if (bytes) + bin = g_bytes_get_data (bytes, &bin_len); + if (bin_len == 0) { + if (!info->error) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value")); + } + return; + } + + if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) { + const char *path2 = &bin[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)]; + gs_free char *path2_free = NULL; + + if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value path \"%s\""), + bin); + return; + } + + g_object_set (setting, key, bytes, NULL); + + if (path2[0] != '/') { + /* we want to read absolute paths because we use keyfile as exchange + * between different processes which might not have the same cwd. */ + path2_free = get_cert_path (info->base_dir, (const guint8 *) path2, + bin_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) - 1); + path2 = path2_free; + } + + /* FIXME(keyfile-parse-in-memory): keyfile reader must not access the file system and + * (in a first step) only operate in memory-only. If the presence of files should be checked, + * then by invoking a callback (and possibly keyfile settings plugin would + * collect the file names to be checked and check them later). */ + if (!g_file_test (path2, G_FILE_TEST_EXISTS)) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE, + _("certificate or key file '%s' does not exist"), + path2); + } + return; + } + + if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11)) { + if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PKCS11) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid PKCS#11 URI \"%s\""), + bin); + return; + } + + g_object_set (setting, key, bytes, NULL); + return; + } + + if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) { + const char *cdata = bin + NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB); + gsize cdata_len = bin_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB) - 1; + gs_free guchar *bin_decoded = NULL; + gsize bin_decoded_len = 0; + gsize i; + gboolean valid_base64; + gs_unref_bytes GBytes *val = NULL; + + /* Let's be strict here. We expect valid base64, no funny stuff!! + * We didn't write such invalid data ourselfes and refuse to read it as blob. */ + if ((valid_base64 = (cdata_len % 4 == 0))) { + for (i = 0; i < cdata_len; i++) { + char c = cdata[i]; + + if (!( (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || (c == '+' || c == '/'))) { + if (c != '=' || i < cdata_len - 2) + valid_base64 = FALSE; + else { + for (; i < cdata_len; i++) { + if (cdata[i] != '=') + valid_base64 = FALSE; + } + } + break; + } + } + } + if (valid_base64) + bin_decoded = g_base64_decode (cdata, &bin_decoded_len); + + if (bin_decoded_len == 0) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value data:;base64, is not base64")); + return; + } + + if (nm_setting_802_1x_check_cert_scheme (bin_decoded, bin_decoded_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* The blob probably starts with "file://". Setting the cert data will confuse NMSetting8021x. + * In fact this is a limitation of NMSetting8021x which does not support setting blobs that start + * with file://. Just warn and return TRUE to signal that we ~handled~ the setting. */ + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value data:;base64,file://")); + return; + } + + val = g_bytes_new_take (g_steal_pointer (&bin_decoded), bin_decoded_len); + g_object_set (setting, key, val, NULL); + return; + } + + /* If not, it might be a plain path */ + path = nm_keyfile_detect_unqualified_path_scheme (info->base_dir, bin, bin_len, TRUE, &path_exists); + if (path) { + gs_unref_bytes GBytes *val = NULL; + + /* Construct the proper value as required for the PATH scheme */ + val = g_bytes_new_take (path, strlen (path) + 1); + g_object_set (setting, key, val, NULL); + + /* Warn if the certificate didn't exist */ + if (!path_exists) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE, + _("certificate or key file '%s' does not exist"), + path); + } + return; + } + + if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* The blob probably starts with "file://" but contains invalid characters for a path. + * Setting the cert data will confuse NMSetting8021x. + * In fact, NMSetting8021x does not support setting such binary data, so just warn and + * continue. */ + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value is not a valid blob")); + return; + } + + g_object_set (setting, key, bytes, NULL); +} + +static int +_parity_from_char (int ch) +{ +#if NM_MORE_ASSERTS > 5 + { + static char check = 0; + + if (check == 0) { + nm_auto_unref_gtypeclass GEnumClass *klass = g_type_class_ref (NM_TYPE_SETTING_SERIAL_PARITY); + guint i; + + check = 1; + + /* In older versions, parity was G_TYPE_CHAR/gint8, and the character + * value was stored as integer. + * For example parity=69 equals parity=E, meaning NM_SETTING_SERIAL_PARITY_EVEN. + * + * That means, certain values are reserved. Assert that these numbers + * are not reused when we extend NMSettingSerialParity enum. + * Actually, since NM_SETTING_SERIAL_PARITY is g_param_spec_enum(), + * we anyway cannot extend the enum without breaking API... + * + * [1] commit "a91e60902e libnm-core: make NMSettingSerial:parity an enum" + * [2] https://cgit.freedesktop.org/NetworkManager/NetworkManager/commit/?id=a91e60902eabae1de93d61323dae6ac894b5d40f + */ + g_assert (G_IS_ENUM_CLASS (klass)); + for (i = 0; i < klass->n_values; i++) { + const GEnumValue *v = &klass->values[i]; + int num = v->value; + + g_assert (_parity_from_char (num) == -1); + g_assert (!NM_IN_SET (num, 'e', 'E', 'o', 'O', 'n', 'N')); + } + } + } +#endif + + switch (ch) { + case 'E': + case 'e': + return NM_SETTING_SERIAL_PARITY_EVEN; + case 'O': + case 'o': + return NM_SETTING_SERIAL_PARITY_ODD; + case 'N': + case 'n': + return NM_SETTING_SERIAL_PARITY_NONE; + } + + return -1; +} + +static void +parity_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_free_error GError *err = NULL; + int parity; + gs_free char *tmp_str = NULL; + gint64 i64; + + /* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'. + * We now accept either that or the (case-insensitive) character itself (but + * still always write it the old way, for backward compatibility). + */ + tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, &err); + if (err) + goto out_err; + + if ( tmp_str + && tmp_str[0] != '\0' + && tmp_str[1] == '\0') { + /* the ASCII characters like 'E' are taken directly... */ + parity = _parity_from_char (tmp_str[0]); + if (parity >= 0) + goto parity_good; + } + + i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64); + if ( i64 != G_MININT64 + && errno == 0) { + + if ((parity = _parity_from_char (i64)) >= 0) { + /* another oddity: the string is a valid number. However, if the numeric values + * is one of the supported ASCII codes, accept it (like 69 for 'E'). + */ + goto parity_good; + } + + /* Finally, take the numeric value as is. */ + parity = i64; + goto parity_good; + } + + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid parity value '%s'"), + tmp_str ?: ""); + return; + +parity_good: + nm_g_object_set_property_enum (G_OBJECT (setting), key, NM_TYPE_SETTING_SERIAL_PARITY, parity, &err); + +out_err: + if (!err) + return; + if (nm_keyfile_error_is_not_found (err)) { + /* ignore such errors. The key is not present. */ + return; + } + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid setting: %s"), + err->message); +} + +static void +team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_free char *conf = NULL; + gs_free_error GError *error = NULL; + + conf = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + + g_object_set (G_OBJECT (setting), key, conf, NULL); + + if ( conf + && !nm_setting_verify (setting, NULL, &error)) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid team configuration: %s"), + error->message); + g_object_set (G_OBJECT (setting), key, NULL, NULL); + } +} + +static void +bridge_vlan_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + gs_unref_ptrarray GPtrArray *vlans = NULL; + gs_free char *value = NULL; + gs_free const char **strv = NULL; + const char *const *iter; + GError *local = NULL; + NMBridgeVlan *vlan; + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, + nm_setting_get_name (setting), + key, + NULL); + if (!value || !value[0]) + return; + + vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref); + + strv = nm_utils_escaped_tokens_split (value, ","); + if (strv) { + for (iter = strv; *iter; iter++) { + vlan = nm_bridge_vlan_from_str (*iter, &local); + if (!vlan) { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + "invalid bridge VLAN: %s", + local->message); + g_clear_error (&local); + continue; + } + g_ptr_array_add (vlans, vlan); + } + } + + if (vlans->len > 0) + g_object_set (setting, key, vlans, NULL); +} + +static void +qdisc_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_unref_ptrarray GPtrArray *qdiscs = NULL; + gs_strfreev char **keys = NULL; + gsize n_keys = 0; + int i; + + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref); + + for (i = 0; i < n_keys; i++) { + NMTCQdisc *qdisc; + const char *qdisc_parent; + gs_free char *qdisc_rest = NULL; + gs_free char *qdisc_str = NULL; + gs_free_error GError *err = NULL; + + if (!g_str_has_prefix (keys[i], "qdisc.")) + continue; + + qdisc_parent = keys[i] + sizeof ("qdisc.") - 1; + qdisc_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL); + qdisc_str = g_strdup_printf ("%s%s %s", + _nm_utils_parse_tc_handle (qdisc_parent, NULL) != TC_H_UNSPEC ? "parent " : "", + qdisc_parent, + qdisc_rest); + + qdisc = nm_utils_tc_qdisc_from_str (qdisc_str, &err); + if (!qdisc) { + handle_warn (info, + keys[i], + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid qdisc: %s"), + err->message); + } else { + g_ptr_array_add (qdiscs, qdisc); + } + } + + if (qdiscs->len >= 1) + g_object_set (setting, key, qdiscs, NULL); +} + +static void +tfilter_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_unref_ptrarray GPtrArray *tfilters = NULL; + gs_strfreev char **keys = NULL; + gsize n_keys = 0; + int i; + + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref); + + for (i = 0; i < n_keys; i++) { + NMTCTfilter *tfilter; + const char *tfilter_parent; + gs_free char *tfilter_rest = NULL; + gs_free char *tfilter_str = NULL; + gs_free_error GError *err = NULL; + + if (!g_str_has_prefix (keys[i], "tfilter.")) + continue; + + tfilter_parent = keys[i] + sizeof ("tfilter.") - 1; + tfilter_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL); + tfilter_str = g_strdup_printf ("%s%s %s", + _nm_utils_parse_tc_handle (tfilter_parent, NULL) != TC_H_UNSPEC ? "parent " : "", + tfilter_parent, + tfilter_rest); + + tfilter = nm_utils_tc_tfilter_from_str (tfilter_str, &err); + if (!tfilter) { + handle_warn (info, + keys[i], + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid tfilter: %s"), + err->message); + } else { + g_ptr_array_add (tfilters, tfilter); + } + } + + if (tfilters->len >= 1) + g_object_set (setting, key, tfilters, NULL); +} + +/*****************************************************************************/ + +/* Some setting properties also contain setting names, such as + * NMSettingConnection's 'type' property (which specifies the base type of the + * connection, eg ethernet or wifi) or the 802-11-wireless setting's + * 'security' property which specifies whether or not the AP requires + * encryption. This function handles translating those properties' values + * from the real setting name to the more-readable alias. + */ +static void +setting_alias_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + const char *str, *alias; + + str = g_value_get_string (value); + alias = nm_keyfile_plugin_get_alias_for_setting_name (str); + nm_keyfile_plugin_kf_set_string (info->keyfile, + nm_setting_get_name (setting), + key, + alias ?: str); +} + +static void +sriov_vfs_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GPtrArray *vfs; + guint i; + + vfs = g_value_get_boxed (value); + if (!vfs) + return; + + for (i = 0; i < vfs->len; i++) { + const NMSriovVF *vf = vfs->pdata[i]; + gs_free char *kf_value = NULL; + char kf_key[32]; + + kf_value = nm_utils_sriov_vf_to_str (vf, TRUE, NULL); + if (!kf_value) + continue; + + nm_sprintf_buf (kf_key, "vf.%u", nm_sriov_vf_get_index (vf)); + + nm_keyfile_plugin_kf_set_string (info->keyfile, + nm_setting_get_name (setting), + kf_key, + kf_value); + } +} + +static void +write_array_of_uint (GKeyFile *file, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GArray *array; + + array = g_value_get_boxed (value); + + nm_assert (!array || g_array_get_element_size (array) == sizeof (guint)); + + if (!array || !array->len) + return; + + nm_keyfile_plugin_kf_set_integer_list_uint (file, + nm_setting_get_name (setting), + key, + (const guint *) array->data, + array->len); +} + +static void +dns_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + char **list; + + list = g_value_get_boxed (value); + if (list && list[0]) { + nm_keyfile_plugin_kf_set_string_list (info->keyfile, nm_setting_get_name (setting), key, + (const char **) list, g_strv_length (list)); + } +} + +static void +ip6_addr_gen_mode_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + NMSettingIP6ConfigAddrGenMode addr_gen_mode; + gs_free char *str = NULL; + + addr_gen_mode = (NMSettingIP6ConfigAddrGenMode) g_value_get_int (value); + str = nm_utils_enum_to_str (nm_setting_ip6_config_addr_gen_mode_get_type (), + addr_gen_mode); + nm_keyfile_plugin_kf_set_string (info->keyfile, + nm_setting_get_name (setting), + key, + str); +} + +static void +write_ip_values (GKeyFile *file, + const char *setting_name, + GPtrArray *array, + const char *gateway, + gboolean is_route) +{ + nm_auto_free_gstring GString *output = NULL; + int addr_family; + guint i; + const char *addr; + const char *gw; + guint32 plen; + char key_name[64]; + char *key_name_idx; + + if (!array->len) + return; + + addr_family = nm_streq (setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME) + ? AF_INET + : AF_INET6; + + strcpy (key_name, is_route ? "route" : "address"); + key_name_idx = key_name + strlen (key_name); + + output = g_string_sized_new (2*INET_ADDRSTRLEN + 10); + for (i = 0; i < array->len; i++) { + gint64 metric = -1; + + if (is_route) { + NMIPRoute *route = array->pdata[i]; + + addr = nm_ip_route_get_dest (route); + plen = nm_ip_route_get_prefix (route); + gw = nm_ip_route_get_next_hop (route); + metric = nm_ip_route_get_metric (route); + } else { + NMIPAddress *address = array->pdata[i]; + + addr = nm_ip_address_get_address (address); + plen = nm_ip_address_get_prefix (address); + gw = (i == 0) + ? gateway + : NULL; + } + + g_string_set_size (output, 0); + g_string_append_printf (output, "%s/%u", addr, plen); + if ( metric != -1 + || gw) { + /* Older versions of the plugin do not support the form + * "a.b.c.d/plen,,metric", so, we always have to write the + * gateway, even if there isn't one. + * The current version supports reading of the above form. + */ + if (!gw) { + if (addr_family == AF_INET) + gw = "0.0.0.0"; + else + gw = "::"; + } + + g_string_append_printf (output, ",%s", gw); + if ( is_route + && metric != -1) + g_string_append_printf (output, ",%lu", (unsigned long) metric); + } + + sprintf (key_name_idx, "%u", i + 1); + nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, output->str); + + if (is_route) { + gs_free char *attributes = NULL; + + attributes = nm_utils_format_variant_attributes (_nm_ip_route_get_attributes (array->pdata[i]), + ',', '='); + if (attributes) { + g_strlcat (key_name, "_options", sizeof (key_name)); + nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, attributes); + } + } + } +} + +static void +addr_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GPtrArray *array; + const char *setting_name = nm_setting_get_name (setting); + const char *gateway = nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (setting)); + + array = (GPtrArray *) g_value_get_boxed (value); + if (array && array->len) + write_ip_values (info->keyfile, setting_name, array, gateway, FALSE); +} + +static void +route_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GPtrArray *array; + const char *setting_name = nm_setting_get_name (setting); + + array = (GPtrArray *) g_value_get_boxed (value); + if (array && array->len) + write_ip_values (info->keyfile, setting_name, array, NULL, TRUE); +} + +static void +bridge_vlan_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + NMBridgeVlan *vlan; + GPtrArray *vlans; + GString *string; + guint i; + + vlans = (GPtrArray *) g_value_get_boxed (value); + if (!vlans || !vlans->len) + return; + + string = g_string_new (""); + for (i = 0; i < vlans->len; i++) { + gs_free char *vlan_str = NULL; + + vlan = vlans->pdata[i]; + vlan_str = nm_bridge_vlan_to_str (vlan, NULL); + if (!vlan_str) + continue; + if (string->len > 0) + g_string_append (string, ","); + nm_utils_escaped_tokens_escape_gstr_assert (vlan_str, ",", string); + } + + nm_keyfile_plugin_kf_set_string (info->keyfile, + nm_setting_get_name (setting), + "vlans", + string->str); + + g_string_free (string, TRUE); +} + + +#define ETHERNET_S390_OPTIONS_GROUP_NAME "ethernet-s390-options" + +static void +wired_s390_options_parser_full (KeyfileReaderInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting) +{ + NMSettingWired *s_wired = NM_SETTING_WIRED (setting); + gs_strfreev char **keys = NULL; + gsize n_keys; + gsize i; + + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, ETHERNET_S390_OPTIONS_GROUP_NAME, &n_keys, NULL); + for (i = 0; i < n_keys; i++) { + gs_free char *value = NULL; + gs_free char *key_to_free = NULL; + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, + ETHERNET_S390_OPTIONS_GROUP_NAME, + keys[i], + NULL); + if (!value) + continue; + + nm_setting_wired_add_s390_option (s_wired, + nm_keyfile_key_decode (keys[i], + &key_to_free), + value); + } +} + +static void +wired_s390_options_writer_full (KeyfileWriterInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting) +{ + NMSettingWired *s_wired = NM_SETTING_WIRED (setting); + guint i, n; + + n = nm_setting_wired_get_num_s390_options (s_wired); + for (i = 0; i < n; i++) { + const char *opt_key; + const char *opt_val; + gs_free char *key_to_free = NULL; + + nm_setting_wired_get_s390_option (s_wired, i, &opt_key, &opt_val); + nm_keyfile_plugin_kf_set_string (info->keyfile, + ETHERNET_S390_OPTIONS_GROUP_NAME, + nm_keyfile_key_encode (opt_key, &key_to_free), + opt_val); + } +} + +static void +ip_routing_rule_writer_full (KeyfileWriterInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting) +{ + const char *setting_name = nm_setting_get_name (setting); + NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG (setting); + guint i, j, n; + char key_name_full[100] = "routing-rule"; + char *key_name_num = &key_name_full[NM_STRLEN ("routing-rule")]; + + n = nm_setting_ip_config_get_num_routing_rules (s_ip); + j = 0; + for (i = 0; i < n; i++) { + NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule (s_ip, i); + gs_free char *str = NULL; + + str = nm_ip_routing_rule_to_string (rule, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE, + NULL, + NULL); + if (!str) + continue; + + sprintf (key_name_num, "%u", ++j); + nm_keyfile_plugin_kf_set_string (info->keyfile, + setting_name, + key_name_full, + str); + } +} + +static void +qdisc_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + gsize i; + GPtrArray *array; + + array = (GPtrArray *) g_value_get_boxed (value); + if (!array || !array->len) + return; + + for (i = 0; i < array->len; i++) { + NMTCQdisc *qdisc = array->pdata[i]; + GString *key_name = g_string_sized_new (16); + GString *value_str = g_string_sized_new (60); + + g_string_append (key_name, "qdisc."); + _nm_utils_string_append_tc_parent (key_name, NULL, + nm_tc_qdisc_get_parent (qdisc)); + _nm_utils_string_append_tc_qdisc_rest (value_str, qdisc); + + nm_keyfile_plugin_kf_set_string (info->keyfile, + NM_SETTING_TC_CONFIG_SETTING_NAME, + key_name->str, + value_str->str); + + g_string_free (key_name, TRUE); + g_string_free (value_str, TRUE); + } +} + +static void +tfilter_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + gsize i; + GPtrArray *array; + + array = (GPtrArray *) g_value_get_boxed (value); + if (!array || !array->len) + return; + + for (i = 0; i < array->len; i++) { + NMTCTfilter *tfilter = array->pdata[i]; + GString *key_name = g_string_sized_new (16); + GString *value_str = g_string_sized_new (60); + + g_string_append (key_name, "tfilter."); + _nm_utils_string_append_tc_parent (key_name, NULL, + nm_tc_tfilter_get_parent (tfilter)); + _nm_utils_string_append_tc_tfilter_rest (value_str, tfilter, NULL); + + nm_keyfile_plugin_kf_set_string (info->keyfile, + NM_SETTING_TC_CONFIG_SETTING_NAME, + key_name->str, + value_str->str); + + g_string_free (key_name, TRUE); + g_string_free (value_str, TRUE); + } +} + +static void +write_hash_of_string (GKeyFile *file, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GHashTable *hash; + const char *group_name = nm_setting_get_name (setting); + gboolean vpn_secrets = FALSE; + gs_free const char **keys = NULL; + guint i, l; + + nm_assert ( (NM_IS_SETTING_VPN (setting) && nm_streq (key, NM_SETTING_VPN_DATA)) + || (NM_IS_SETTING_VPN (setting) && nm_streq (key, NM_SETTING_VPN_SECRETS)) + || (NM_IS_SETTING_BOND (setting) && nm_streq (key, NM_SETTING_BOND_OPTIONS)) + || (NM_IS_SETTING_USER (setting) && nm_streq (key, NM_SETTING_USER_DATA))); + + /* Write VPN secrets out to a different group to keep them separate */ + if ( NM_IS_SETTING_VPN (setting) + && nm_streq (key, NM_SETTING_VPN_SECRETS)) { + group_name = NM_KEYFILE_GROUP_VPN_SECRETS; + vpn_secrets = TRUE; + } + + hash = g_value_get_boxed (value); + + keys = nm_utils_strdict_get_keys (hash, TRUE, &l); + for (i = 0; i < l; i++) { + gs_free char *to_free = NULL; + const char *property, *data; + + property = keys[i]; + + /* Handle VPN secrets specially; they are nested in the property's hash; + * we don't want to write them if the secret is not saved, not required, + * or owned by a user's secret agent. + */ + if (vpn_secrets) { + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + if (!nm_setting_get_secret_flags (setting, property, &secret_flags, NULL)) + nm_assert_not_reached (); + if (!_secret_flags_persist_secret (secret_flags)) + continue; + } + + data = g_hash_table_lookup (hash, property); + nm_keyfile_plugin_kf_set_string (file, group_name, + nm_keyfile_key_encode (property, &to_free), + data); + } +} + +static void +ssid_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GBytes *bytes; + const guint8 *ssid_data; + gsize ssid_len; + const char *setting_name = nm_setting_get_name (setting); + gboolean new_format = TRUE; + gsize semicolons = 0; + gsize i; + + g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES)); + + bytes = g_value_get_boxed (value); + if (!bytes) + return; + ssid_data = g_bytes_get_data (bytes, &ssid_len); + if (!ssid_data || !ssid_len) { + nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, ""); + return; + } + + /* Check whether each byte is printable. If not, we have to use an + * integer list, otherwise we can just use a string. + */ + for (i = 0; i < ssid_len; i++) { + const char c = ssid_data[i]; + + if (!g_ascii_isprint (c)) { + new_format = FALSE; + break; + } + if (c == ';') + semicolons++; + } + + if (new_format) { + gs_free char *ssid = NULL; + + if (semicolons == 0) + ssid = g_strndup ((char *) ssid_data, ssid_len); + else { + /* Escape semicolons with backslashes to make strings + * containing ';', such as '16;17;' unambiguous */ + gsize j = 0; + + ssid = g_malloc (ssid_len + semicolons + 1); + for (i = 0; i < ssid_len; i++) { + if (ssid_data[i] == ';') + ssid[j++] = '\\'; + ssid[j++] = ssid_data[i]; + } + ssid[j] = '\0'; + } + nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, ssid); + } else + nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, ssid_data, ssid_len); +} + +static void +password_raw_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + const char *setting_name = nm_setting_get_name (setting); + GBytes *array; + gsize len; + const guint8 *data; + + g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES)); + + array = (GBytes *) g_value_get_boxed (value); + if (!array) + return; + data = g_bytes_get_data (array, &len); + if (!data) + len = 0; + nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, data, len); +} + +/*****************************************************************************/ + +static void +cert_writer_default (NMConnection *connection, + GKeyFile *file, + NMSetting8021x *setting, + const char *setting_name, + const NMSetting8021xSchemeVtable *vtable) +{ + NMSetting8021xCKScheme scheme; + + scheme = vtable->scheme_func (setting); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + gs_free char *path_free = NULL; + gs_free char *base_dir = NULL; + gs_free char *tmp = NULL; + const char *path; + + path = vtable->path_func (setting); + g_assert (path); + + /* If the path is relative, make it an absolute path. + * Relative paths make a keyfile not easily usable in another + * context. */ + if (path[0] && path[0] != '/') { + base_dir = g_get_current_dir (); + path_free = g_strconcat (base_dir, "/", path, NULL); + path = path_free; + } else + base_dir = g_path_get_dirname (path); + + /* path cannot start with "file://" or "data:;base64,", because it is an absolute path. + * Still, make sure that a prefix-less path will be recognized. This can happen + * for example if the path is longer then 500 chars. */ + tmp = nm_keyfile_detect_unqualified_path_scheme (base_dir, path, -1, FALSE, NULL); + if (tmp) + nm_clear_g_free (&tmp); + else { + tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL); + path = tmp; + } + + /* Path contains at least a '/', hence it cannot be recognized as the old + * binary format consisting of a list of integers. */ + + nm_keyfile_plugin_kf_set_string (file, setting_name, vtable->setting_key, path); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + GBytes *blob; + const guint8 *blob_data; + gsize blob_len; + gs_free char *blob_base64 = NULL; + gs_free char *val = NULL; + + blob = vtable->blob_func (setting); + g_assert (blob); + blob_data = g_bytes_get_data (blob, &blob_len); + + blob_base64 = g_base64_encode (blob_data, blob_len); + val = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB, blob_base64, NULL); + + nm_keyfile_plugin_kf_set_string (file, setting_name, vtable->setting_key, val); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) { + nm_keyfile_plugin_kf_set_string (file, setting_name, vtable->setting_key, + vtable->uri_func (setting)); + } else { + /* scheme_func() returns UNKNOWN in all other cases. The only valid case + * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this + * case, we don't expect the writer to be called, because the default value + * will not be serialized. + * The only other reason for the scheme to be UNKNOWN is an invalid cert. + * But our connection verifies, so that cannot happen either. */ + g_return_if_reached (); + } +} + +static void +cert_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + const NMSetting8021xSchemeVtable *vtable = NULL; + const char *setting_name; + guint i; + + for (i = 0; nm_setting_8021x_scheme_vtable[i].setting_key; i++) { + if (nm_streq0 (nm_setting_8021x_scheme_vtable[i].setting_key, key)) { + vtable = &nm_setting_8021x_scheme_vtable[i]; + break; + } + } + if (!vtable) + g_return_if_reached (); + + setting_name = nm_setting_get_name (NM_SETTING (setting)); + + if (info->write_handler) { + NMKeyfileHandlerData handler_data; + + _key_file_handler_data_init_write (&handler_data, + NM_KEYFILE_HANDLER_TYPE_WRITE_CERT, + info, + setting_name, + vtable->setting_key, + setting, + key); + handler_data.write_cert = (NMKeyfileHandlerDataWriteCert) { + .vtable = vtable, + }; + + if (info->write_handler (info->connection, + info->keyfile, + NM_KEYFILE_HANDLER_TYPE_WRITE_CERT, + &handler_data, + info->user_data)) + return; + if (info->error) + return; + } + + cert_writer_default (info->connection, + info->keyfile, + NM_SETTING_802_1X (setting), + setting_name, + vtable); +} + +/*****************************************************************************/ + +struct _ParseInfoProperty { + const char *property_name; + union { + void (*parser) (KeyfileReaderInfo *info, + NMSetting *setting, + const char *key); + void (*parser_full) (KeyfileReaderInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting); + }; + union { + void (*writer) (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value); + void (*writer_full) (KeyfileWriterInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting); + }; + bool parser_skip; + bool parser_no_check_key:1; + bool writer_skip:1; + bool has_writer_full:1; + bool has_parser_full:1; + + /* usually, we skip to write values that have their + * default value. By setting this flag to TRUE, also + * default values are written. */ + bool writer_persist_default:1; +}; + +#define PARSE_INFO_PROPERTY(_property_name, ...) \ + (&((const ParseInfoProperty) { \ + .property_name = _property_name, \ + __VA_ARGS__ \ + })) + +#define PARSE_INFO_PROPERTIES(...) \ + .properties = ((const ParseInfoProperty*const[]) { \ + __VA_ARGS__ \ + NULL, \ + }) + +typedef struct { + const ParseInfoProperty*const*properties; +} ParseInfoSetting; + +#define PARSE_INFO_SETTING(setting_type, ...) \ + [setting_type] = (&((const ParseInfoSetting) { \ + __VA_ARGS__ \ + })) + +static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = { + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIRELESS, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_BSSID, + .parser = mac_address_parser_ETHER, + ), + PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, + .parser = mac_address_parser_ETHER_cloned, + ), + PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, + ), + PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_SSID, + .parser = ssid_parser, + .writer = ssid_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_802_1X, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_CA_CERT, + .parser = cert_parser, + .writer = cert_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_CLIENT_CERT, + .parser = cert_parser, + .writer = cert_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PASSWORD_RAW, + .parser = password_raw_parser, + .writer = password_raw_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_CA_CERT, + .parser = cert_parser, + .writer = cert_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + .parser = cert_parser, + .writer = cert_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + .parser = cert_parser, + .writer = cert_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PRIVATE_KEY, + .parser = cert_parser, + .writer = cert_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIRED, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_WIRED_CLONED_MAC_ADDRESS, + .parser = mac_address_parser_ETHER_cloned, + ), + PARSE_INFO_PROPERTY (NM_SETTING_WIRED_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, + ), + PARSE_INFO_PROPERTY (NM_SETTING_WIRED_S390_OPTIONS, + .parser_no_check_key = TRUE, + .parser_full = wired_s390_options_parser_full, + .writer_full = wired_s390_options_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BLUETOOTH, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_BLUETOOTH_BDADDR, + .parser = mac_address_parser_ETHER, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BOND, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_BOND_OPTIONS, + .parser_no_check_key = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, + ), + PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_VLANS, + .parser_no_check_key = TRUE, + .parser = bridge_vlan_parser, + .writer = bridge_vlan_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE_PORT, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_PORT_VLANS, + .parser_no_check_key = TRUE, + .parser = bridge_vlan_parser, + .writer = bridge_vlan_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_CONNECTION, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_CONNECTION_READ_ONLY, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_CONNECTION_TYPE, + .parser = setting_alias_parser, + .writer = setting_alias_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_INFINIBAND, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_INFINIBAND_MAC_ADDRESS, + .parser = mac_address_parser_INFINIBAND, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP4_CONFIG, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ADDRESSES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = addr_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_DNS, + .parser_no_check_key = TRUE, + .parser = ip_dns_parser, + .writer = dns_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_GATEWAY, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = route_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES, + .parser_no_check_key = TRUE, + .parser_full = ip_routing_rule_parser_full, + .writer_full = ip_routing_rule_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP6_CONFIG, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, + .parser_no_check_key = TRUE, + .parser = ip6_addr_gen_mode_parser, + .writer = ip6_addr_gen_mode_writer, + .writer_persist_default = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ADDRESSES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = addr_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_DNS, + .parser_no_check_key = TRUE, + .parser = ip_dns_parser, + .writer = dns_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_GATEWAY, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = route_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES, + .parser_no_check_key = TRUE, + .parser_full = ip_routing_rule_parser_full, + .writer_full = ip_routing_rule_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SERIAL, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_SERIAL_PARITY, + .parser = parity_parser, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SRIOV, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_SRIOV_VFS, + .parser_no_check_key = TRUE, + .parser = sriov_vfs_parser, + .writer = sriov_vfs_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TC_CONFIG, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_TC_CONFIG_QDISCS, + .parser_no_check_key = TRUE, + .parser = qdisc_parser, + .writer = qdisc_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TC_CONFIG_TFILTERS, + .parser_no_check_key = TRUE, + .parser = tfilter_parser, + .writer = tfilter_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TEAM, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_CONFIG, + .parser = team_config_parser, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_LINK_WATCHERS, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_MCAST_REJOIN_COUNT, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_ACTIVE, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_FAST_RATE, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_HWADDR_POLICY, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_MIN_PORTS, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_SYS_PRIO, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_BALANCER, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_HASH, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TEAM_PORT, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_CONFIG, + .parser = team_config_parser, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LACP_KEY, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LACP_PRIO, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LINK_WATCHERS, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_PRIO, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_QUEUE_ID, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_STICKY, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_USER, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_USER_DATA, + .parser_no_check_key = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_VLAN, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_VLAN_FLAGS, + .writer_persist_default = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_VPN, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_VPN_DATA, + .parser_no_check_key = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_VPN_PERSISTENT, + .parser_no_check_key = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_VPN_SECRETS, + .parser_no_check_key = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_VPN_SERVICE_TYPE, + .parser_no_check_key = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_VPN_TIMEOUT, + .parser_no_check_key = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_VPN_USER_NAME, + .parser_no_check_key = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIMAX, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_WIMAX_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, + ), + ), + ), +}; + +static void +_parse_info_find (NMSetting *setting, + const char *property_name, + const NMMetaSettingInfo **out_setting_info, + const ParseInfoSetting **out_parse_info_setting, + const ParseInfoProperty **out_parse_info_property) +{ + const NMMetaSettingInfo *setting_info; + const ParseInfoSetting *pis; + const ParseInfoProperty *pip; + +#if NM_MORE_ASSERTS > 10 + { + guint i, j; + static int asserted = FALSE; + + if (!asserted) { + for (i = 0; i < G_N_ELEMENTS (parse_infos); i++) { + pis = parse_infos[i]; + + if (!pis) + continue; + if (!pis->properties) + continue; + + g_assert (pis->properties[0]); + for (j = 0; pis->properties[j]; j++) { + const ParseInfoProperty *pip0; + const ParseInfoProperty *pipj = pis->properties[j]; + + g_assert (pipj->property_name); + if ( j > 0 + && (pip0 = pis->properties[j - 1]) + && strcmp (pip0->property_name, pipj->property_name) >= 0) { + g_error ("Wrong order at index #%d.%d: \"%s.%s\" before \"%s.%s\"", + i, j - 1, + nm_meta_setting_infos[i].setting_name, pip0->property_name, + nm_meta_setting_infos[i].setting_name, pipj->property_name); + } + } + } + asserted = TRUE; + } + } +#endif + + if ( !NM_IS_SETTING (setting) + || !(setting_info = NM_SETTING_GET_CLASS (setting)->setting_info)) { + /* handle invalid setting objects gracefully. */ + NM_SET_OUT (out_setting_info, NULL); + NM_SET_OUT (out_parse_info_setting, NULL); + NM_SET_OUT (out_parse_info_property, NULL); + return; + } + + nm_assert (setting_info->setting_name); + nm_assert (_NM_INT_NOT_NEGATIVE (setting_info->meta_type)); + nm_assert (setting_info->meta_type < G_N_ELEMENTS (parse_infos)); + + pis = parse_infos[setting_info->meta_type]; + + pip = NULL; + if ( pis + && property_name) { + gssize idx; + + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (ParseInfoProperty, property_name) == 0); + + idx = nm_utils_ptrarray_find_binary_search ((gconstpointer *) pis->properties, + NM_PTRARRAY_LEN (pis->properties), + &property_name, + nm_strcmp_p_with_data, + NULL, + NULL, + NULL); + if (idx >= 0) + pip = pis->properties[idx]; + } + + NM_SET_OUT (out_setting_info, setting_info); + NM_SET_OUT (out_parse_info_setting, pis); + NM_SET_OUT (out_parse_info_property, pip); +} + +/*****************************************************************************/ + +static void +read_one_setting_value (KeyfileReaderInfo *info, + NMSetting *setting, + const NMSettInfoProperty *property_info) +{ + GKeyFile *keyfile = info->keyfile; + gs_free_error GError *err = NULL; + const NMMetaSettingInfo *setting_info; + const ParseInfoProperty *pip; + gs_free char *tmp_str = NULL; + const char *key; + GType type; + guint64 u64; + gint64 i64; + + nm_assert (!info->error); + nm_assert ( !property_info->param_spec + || nm_streq (property_info->param_spec->name, property_info->name)); + + key = property_info->name; + + _parse_info_find (setting, key, &setting_info, NULL, &pip); + + nm_assert (setting_info); + + if (!pip) { + if (nm_streq (key, NM_SETTING_NAME)) + return; + if (!property_info->param_spec) + return; + if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE) + return; + } else { + if (pip->parser_skip) + return; + if (pip->has_parser_full) { + pip->parser_full (info, setting_info, property_info, pip, setting); + return; + } + } + + nm_assert (property_info->param_spec); + nm_assert ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) == G_PARAM_WRITABLE); + + /* Check for the exact key in the GKeyFile if required. Most setting + * properties map 1:1 to a key in the GKeyFile, but for those properties + * like IP addresses and routes where more than one value is actually + * encoded by the setting property, this won't be true. + */ + if ( (!pip || !pip->parser_no_check_key) + && !nm_keyfile_plugin_kf_has_key (keyfile, setting_info->setting_name, key, &err)) { + /* Key doesn't exist or an error occurred, thus nothing to do. */ + if (err) { + if (!handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("error loading setting value: %s"), + err->message)) + return; + } + return; + } + + if ( pip + && pip->parser) { + pip->parser (info, setting, key); + return; + } + + type = G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec); + + if (type == G_TYPE_STRING) { + gs_free char *str_val = NULL; + + str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_info->setting_name, key, &err); + if (!err) + nm_g_object_set_property_string_take (G_OBJECT (setting), key, g_steal_pointer (&str_val), &err); + } else if (type == G_TYPE_UINT) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64); + if ( u64 == G_MAXUINT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_uint (G_OBJECT (setting), key, u64, &err); + } + } else if (type == G_TYPE_INT) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64); + if ( i64 == G_MININT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_int (G_OBJECT (setting), key, i64, &err); + } + } else if (type == G_TYPE_BOOLEAN) { + gboolean bool_val; + + bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_info->setting_name, key, &err); + if (!err) + nm_g_object_set_property_boolean (G_OBJECT (setting), key, bool_val, &err); + } else if (type == G_TYPE_CHAR) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + /* As documented by glib, G_TYPE_CHAR is really a (signed!) gint8. */ + i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT8, G_MAXINT8, G_MININT64); + if ( i64 == G_MININT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_char (G_OBJECT (setting), key, i64, &err); + } + } else if (type == G_TYPE_UINT64) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT64, G_MAXUINT64); + if ( u64 == G_MAXUINT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_uint64 (G_OBJECT (setting), key, u64, &err); + } + } else if (type == G_TYPE_INT64) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT64, G_MAXINT64, G_MAXINT64); + if ( i64 == G_MAXINT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_int64 (G_OBJECT (setting), key, i64, &err); + } + } else if (type == G_TYPE_BYTES) { + nm_auto_unref_bytearray GByteArray *array = NULL; + gs_unref_bytes GBytes *bytes= NULL; + gs_free guint *tmp = NULL; + gsize length; + int i; + gboolean already_warned = FALSE; + + tmp = nm_keyfile_plugin_kf_get_integer_list_uint (keyfile, setting_info->setting_name, key, &length, NULL); + + array = g_byte_array_sized_new (length); + for (i = 0; i < length; i++) { + const guint val = tmp[i]; + unsigned char v = (unsigned char) (val & 0xFF); + + if (val > 255u) { + if ( !already_warned + && !handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid byte element '%u' (not between 0 and 255 inclusive)"), + val)) + return; + already_warned = TRUE; + } else + g_byte_array_append (array, (const unsigned char *) &v, sizeof (v)); + } + + bytes = g_byte_array_free_to_bytes (g_steal_pointer (&array)); + g_object_set (setting, key, bytes, NULL); + } else if (type == G_TYPE_STRV) { + gs_strfreev char **sa = NULL; + gsize length; + + sa = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_info->setting_name, key, &length, NULL); + g_object_set (setting, key, sa, NULL); + } else if (type == G_TYPE_HASH_TABLE) { + read_hash_of_string (keyfile, setting, key); + } else if (type == G_TYPE_ARRAY) { + read_array_of_uint (keyfile, setting, key); + } else if (G_TYPE_IS_FLAGS (type)) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64); + if ( u64 == G_MAXUINT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_flags (G_OBJECT (setting), key, type, u64, &err); + } + } else if (G_TYPE_IS_ENUM (type)) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MAXINT64); + if ( i64 == G_MAXINT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_enum (G_OBJECT (setting), key, type, i64, &err); + } + } else + g_return_if_reached (); + + if (err) { + if (nm_keyfile_error_is_not_found (err)) { + /* ignore such errors. The key is not present. */ + } else { + handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid setting: %s"), + err->message); + } + } +} + +static void +_read_setting (KeyfileReaderInfo *info) +{ + const NMSettInfoSetting *sett_info; + gs_unref_object NMSetting *setting = NULL; + const char *alias; + GType type; + guint i; + + alias = nm_keyfile_plugin_get_setting_name_for_alias (info->group); + if (!alias) + alias = info->group; + + type = nm_setting_lookup_type (alias); + if (!type) { + handle_warn (info, + NULL, + NULL, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid setting name '%s'"), + info->group); + return; + } + + setting = g_object_new (type, NULL); + + info->setting = setting; + + sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting)); + + if (sett_info->detail.gendata_info) { + gs_free char **keys = NULL; + gsize k, n_keys; + + keys = g_key_file_get_keys (info->keyfile, info->group, &n_keys, NULL); + if (!keys) + n_keys = 0; + if (n_keys > 0) { + GHashTable *h = _nm_setting_option_hash (setting, TRUE); + + nm_utils_strv_sort (keys, n_keys); + for (k = 0; k < n_keys; k++) { + gs_free char *key = keys[k]; + gs_free_error GError *local = NULL; + const GVariantType *variant_type; + GVariant *variant; + + /* a GKeyFile can return duplicate keys, there is just no API to make sense + * of them. Skip them. */ + if ( k + 1 < n_keys + && nm_streq (key, keys[k + 1])) + continue; + + /* currently, the API is very simple. The setting class just returns + * the desired variant type, and keyfile reader will try to parse + * it accordingly. Note, that this does currently not allow, that + * a particular key can contain different variant types, nor is it + * very flexible in general. + * + * We add flexibility when we need it. Keep it simple for now. */ + variant_type = sett_info->detail.gendata_info->get_variant_type (sett_info, + key, + &local); + if (!variant_type) { + if (!handle_warn (info, + key, + NULL, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key '%s.%s'"), + info->group, + key)) + break; + continue; + } + + if (g_variant_type_equal (variant_type, G_VARIANT_TYPE_BOOLEAN)) { + gboolean v; + + v = g_key_file_get_boolean (info->keyfile, + info->group, + key, + &local); + if (local) { + if (!handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not boolean"), + info->group, + key)) + break; + continue; + } + variant = g_variant_new_boolean (v); + } else if (g_variant_type_equal (variant_type, G_VARIANT_TYPE_UINT32)) { + guint64 v; + + v = g_key_file_get_uint64 (info->keyfile, + info->group, + key, + &local); + + if (local) { + if (!handle_warn (info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a uint32"), + info->group, + key)) + break; + continue; + } + variant = g_variant_new_uint32 ((guint32) v); + } else { + nm_assert_not_reached (); + continue; + } + + g_hash_table_insert (h, + g_steal_pointer (&key), + g_variant_take_ref (variant)); + } + for (; k < n_keys; k++) + g_free (keys[k]); + } + } + + for (i = 0; i < sett_info->property_infos_len; i++) { + read_one_setting_value (info, + setting, + &sett_info->property_infos[i]); + if (info->error) + goto out; + } + +out: + info->setting = NULL; + if (!info->error) + nm_connection_add_setting (info->connection, g_steal_pointer (&setting)); +} + +static void +_read_setting_wireguard_peer (KeyfileReaderInfo *info) +{ + gs_unref_object NMSettingWireGuard *s_wg_new = NULL; + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + gs_free_error GError *error = NULL; + NMSettingWireGuard *s_wg; + gs_free char *str = NULL; + const char *cstr = NULL; + const char *key; + gint64 i64; + gs_strfreev char **sa = NULL; + gsize n_sa; + + peer = nm_wireguard_peer_new (); + + nm_assert (g_str_has_prefix (info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)); + cstr = &info->group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)]; + if ( !nm_utils_base64secret_normalize (cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str) + || !nm_streq0 (str, cstr)) { + /* the group name must be identical to the normalized(!) key, so that it + * is uniquely identified. */ + handle_warn (info, + NULL, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid peer public key in section '%s'"), + info->group); + return; + } + nm_wireguard_peer_set_public_key (peer, cstr, TRUE); + nm_clear_g_free (&str); + + key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY; + str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL); + if (str) { + if (!nm_wireguard_peer_set_preshared_key (peer, str, FALSE)) { + if (!handle_warn (info, + key, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a valid 256 bit key in base64 encoding"), + info->group, + key)) + return; + } + nm_clear_g_free (&str); + } + + key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS; + i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, NM_SETTING_SECRET_FLAG_ALL, -1, NULL); + if (errno != ENODATA) { + if ( i64 == -1 + || !_nm_setting_secret_flags_valid (i64)) { + if (!handle_warn (info, + key, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a valid secret flag"), + info->group, + key)) + return; + } else + nm_wireguard_peer_set_preshared_key_flags (peer, i64); + } + + key = NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE; + i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, G_MAXUINT32, -1, NULL); + if (errno != ENODATA) { + if (i64 == -1) { + if (!handle_warn (info, + key, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a integer in range 0 to 2^32"), + info->group, + key)) + return; + } else + nm_wireguard_peer_set_persistent_keepalive (peer, i64); + } + + key = NM_WIREGUARD_PEER_ATTR_ENDPOINT; + str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL); + if (str && str[0]) { + if (!nm_wireguard_peer_set_endpoint (peer, str, FALSE)) { + if (!handle_warn (info, + key, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a valid endpoint"), + info->group, + key)) + return; + } + } + nm_clear_g_free (&str); + + key = NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS; + sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, info->group, key, &n_sa, NULL); + if (n_sa > 0) { + gboolean has_error = FALSE; + gsize i; + + for (i = 0; i < n_sa; i++) { + if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC, sa[i], NULL, NULL, NULL)) { + has_error = TRUE; + continue; + } + nm_wireguard_peer_append_allowed_ip (peer, sa[i], TRUE); + } + if (has_error) { + if (!handle_warn (info, + key, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' has invalid allowed-ips"), + info->group, + key)) + return; + } + } + + if (info->error) + return; + + if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &error)) { + handle_warn (info, + NULL, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("peer '%s' is invalid: %s"), + info->group, + error->message); + return; + } + + s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (info->connection, NM_TYPE_SETTING_WIREGUARD)); + if (!s_wg) { + s_wg_new = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ()); + s_wg = s_wg_new; + } + + nm_setting_wireguard_append_peer (s_wg, peer); + + if (s_wg_new) { + nm_connection_add_setting (info->connection, + NM_SETTING (g_steal_pointer (&s_wg_new))); + } +} + +static void +_read_setting_vpn_secrets (KeyfileReaderInfo *info) +{ + gs_strfreev char **keys = NULL; + gsize i, n_keys; + NMSettingVpn *s_vpn; + + s_vpn = nm_connection_get_setting_vpn (info->connection); + if (!s_vpn) { + /* if we don't also have a [vpn] section (which must be parsed earlier), + * we don't do anything. */ + nm_assert (!g_key_file_has_group (info->keyfile, "vpn")); + return; + } + + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, &n_keys, NULL); + for (i = 0; i < n_keys; i++) { + gs_free char *secret = NULL; + + secret = nm_keyfile_plugin_kf_get_string (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, keys[i], NULL); + if (secret) + nm_setting_vpn_add_secret (s_vpn, keys[i], secret); + } +} + +gboolean +nm_keyfile_read_ensure_id (NMConnection *connection, + const char *fallback_id) +{ + NMSettingConnection *s_con; + + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + g_return_val_if_fail (fallback_id, FALSE); + + s_con = nm_connection_get_setting_connection (connection); + g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE); + + if (nm_setting_connection_get_id (s_con)) + return FALSE; + + g_object_set (s_con, NM_SETTING_CONNECTION_ID, fallback_id, NULL); + return TRUE; +} + +gboolean +nm_keyfile_read_ensure_uuid (NMConnection *connection, + const char *fallback_uuid_seed) +{ + NMSettingConnection *s_con; + gs_free char *hashed_uuid = NULL; + + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + g_return_val_if_fail (fallback_uuid_seed, FALSE); + + s_con = nm_connection_get_setting_connection (connection); + g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE); + + if (nm_setting_connection_get_uuid (s_con)) + return FALSE; + + hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", fallback_uuid_seed, NULL); + g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL); + return TRUE; +} + +/** + * nm_keyfile_read: + * @keyfile: the keyfile from which to create the connection + * @base_dir: when reading certificates from files with relative name, + * the relative path is made absolute using @base_dir. This must + * be an absolute path. + * @handler_flags: the #NMKeyfileHandlerFlags. + * @handler: read handler + * @user_data: user data for read handler + * @error: error + * + * Tries to create a NMConnection from a keyfile. The resulting keyfile is + * not normalized and might not even verify. + * + * Returns: (transfer full): on success, returns the created connection. + */ +NMConnection * +nm_keyfile_read (GKeyFile *keyfile, + const char *base_dir, + NMKeyfileHandlerFlags handler_flags, + NMKeyfileReadHandler handler, + void *user_data, + GError **error) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection *s_con; + gs_strfreev char **groups = NULL; + gsize n_groups; + gsize i; + gboolean vpn_secrets = FALSE; + KeyfileReaderInfo info; + + g_return_val_if_fail (keyfile, NULL); + g_return_val_if_fail (!error || !*error, NULL); + g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL); + g_return_val_if_fail (handler_flags == NM_KEYFILE_HANDLER_FLAGS_NONE, NULL); + + connection = nm_simple_connection_new (); + + info = (KeyfileReaderInfo) { + .connection = connection, + .keyfile = keyfile, + .base_dir = base_dir, + .read_handler = handler, + .user_data = user_data, + }; + + groups = g_key_file_get_groups (keyfile, &n_groups); + if (!groups) + n_groups = 0; + + for (i = 0; i < n_groups; i++) { + + info.group = groups[i]; + + if (nm_streq (groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) { + /* Only read out secrets when needed */ + vpn_secrets = TRUE; + } else if (NM_STR_HAS_PREFIX (groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)) + _read_setting_wireguard_peer (&info); + else if (NM_IN_STRSET (groups[i], NM_KEYFILE_GROUP_NMMETA, + ETHERNET_S390_OPTIONS_GROUP_NAME)) { + /* pass */ + } else + _read_setting (&info); + + info.group = NULL; + + if (info.error) + goto out_with_info_error; + } + + s_con = nm_connection_get_setting_connection (connection); + if (!s_con) { + s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ()); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + } + + /* Make sure that we have 'interface-name' even if it was specified in the + * "wrong" (ie, deprecated) group. + */ + if ( !nm_setting_connection_get_interface_name (s_con) + && nm_setting_connection_get_connection_type (s_con)) { + gs_free char *interface_name = NULL; + + interface_name = g_key_file_get_string (keyfile, + nm_setting_connection_get_connection_type (s_con), + "interface-name", + NULL); + if (interface_name) + g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL); + } + + if (vpn_secrets) { + info.group = NM_KEYFILE_GROUP_VPN_SECRETS; + _read_setting_vpn_secrets (&info); + info.group = NULL;; + if (info.error) + goto out_with_info_error; + } + + return g_steal_pointer (&connection); + +out_with_info_error: + g_propagate_error (error, info.error); + return NULL; +} + +/*****************************************************************************/ + +static void +write_setting_value (KeyfileWriterInfo *info, + NMSetting *setting, + const NMSettInfoProperty *property_info) +{ + const NMMetaSettingInfo *setting_info; + const ParseInfoProperty *pip; + const char *key; + char numstr[64]; + GValue value; + GType type; + + nm_assert (!info->error); + nm_assert ( !property_info->param_spec + || nm_streq (property_info->param_spec->name, property_info->name)); + + key = property_info->name; + + _parse_info_find (setting, key, &setting_info, NULL, &pip); + + if (!pip) { + if (!setting_info) { + /* the setting type is unknown. That is highly unexpected + * (and as this is currently only called from NetworkManager + * daemon, not possible). + * + * Still, handle it gracefully, because later keyfile writer will become + * public API of libnm, where @setting is (untrusted) user input. + * + * Gracefully here just means: ignore the setting. */ + return; + } + if (!property_info->param_spec) + return; + if (nm_streq (key, NM_SETTING_NAME)) + return; + } else { + if (pip->has_writer_full) { + pip->writer_full (info, setting_info, property_info, pip, setting); + return; + } + if (pip->writer_skip) + return; + } + + nm_assert (property_info->param_spec); + + /* Don't write secrets that are owned by user secret agents or aren't + * supposed to be saved. VPN secrets are handled specially though since + * the secret flags there are in a third-level hash in the 'secrets' + * property. + */ + if ( (property_info->param_spec->flags & NM_SETTING_PARAM_SECRET) + && !NM_IS_SETTING_VPN (setting)) { + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL)) + g_return_if_reached (); + if (!_secret_flags_persist_secret (secret_flags)) + return; + } + + value = (GValue) { 0 }; + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec)); + g_object_get_property (G_OBJECT (setting), property_info->param_spec->name, &value); + + if ( (!pip || !pip->writer_persist_default) + && g_param_value_defaults (property_info->param_spec, &value)) { + nm_assert (!g_key_file_has_key (info->keyfile, setting_info->setting_name, key, NULL)); + goto out_unset_value; + } + + if ( pip + && pip->writer) { + pip->writer (info, setting, key, &value); + goto out_unset_value; + } + + type = G_VALUE_TYPE (&value); + if (type == G_TYPE_STRING) { + const char *str; + + str = g_value_get_string (&value); + if (str) + nm_keyfile_plugin_kf_set_string (info->keyfile, setting_info->setting_name, key, str); + } else if (type == G_TYPE_UINT) { + nm_sprintf_buf (numstr, "%u", g_value_get_uint (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_INT) { + nm_sprintf_buf (numstr, "%d", g_value_get_int (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_UINT64) { + nm_sprintf_buf (numstr, "%" G_GUINT64_FORMAT, g_value_get_uint64 (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_INT64) { + nm_sprintf_buf (numstr, "%" G_GINT64_FORMAT, g_value_get_int64 (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_BOOLEAN) { + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, + g_value_get_boolean (&value) + ? "true" + : "false"); + } else if (type == G_TYPE_CHAR) { + nm_sprintf_buf (numstr, "%d", (int) g_value_get_schar (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_BYTES) { + GBytes *bytes; + const guint8 *data; + gsize len = 0; + + bytes = g_value_get_boxed (&value); + data = bytes ? g_bytes_get_data (bytes, &len) : NULL; + + if (data != NULL && len > 0) + nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_info->setting_name, key, data, len); + } else if (type == G_TYPE_STRV) { + char **array; + + array = (char **) g_value_get_boxed (&value); + nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_info->setting_name, key, (const char **const) array, g_strv_length (array)); + } else if (type == G_TYPE_HASH_TABLE) { + write_hash_of_string (info->keyfile, setting, key, &value); + } else if (type == G_TYPE_ARRAY) { + write_array_of_uint (info->keyfile, setting, key, &value); + } else if (G_VALUE_HOLDS_FLAGS (&value)) { + nm_sprintf_buf (numstr, "%u", g_value_get_flags (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (G_VALUE_HOLDS_ENUM (&value)) { + nm_sprintf_buf (numstr, "%d", g_value_get_enum (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else + g_return_if_reached (); + +out_unset_value: + g_value_unset (&value); +} + +static void +_write_setting_wireguard (NMSetting *setting, KeyfileWriterInfo *info) +{ + NMSettingWireGuard *s_wg; + guint i_peer, n_peers; + + s_wg = NM_SETTING_WIREGUARD (setting); + + n_peers = nm_setting_wireguard_get_peers_len (s_wg); + for (i_peer = 0; i_peer < n_peers; i_peer++) { + NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i_peer); + const char *public_key; + char group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER) + 200]; + NMSettingSecretFlags secret_flags; + gboolean any_key = FALSE; + guint i_aip, n_aip; + const char *cstr; + guint32 u32; + + public_key = nm_wireguard_peer_get_public_key (peer); + if ( !public_key + || !public_key[0] + || !NM_STRCHAR_ALL (public_key, ch, nm_sd_utils_unbase64char (ch, TRUE) >= 0)) { + /* invalid peer. Skip it */ + continue; + } + + if (g_snprintf (group, + sizeof (group), + "%s%s", + NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER, + nm_wireguard_peer_get_public_key (peer)) >= sizeof (group)) { + /* Too long. Not a valid public key. Skip the peer. */ + continue; + } + + cstr = nm_wireguard_peer_get_endpoint (peer); + if (cstr) { + g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, cstr); + any_key = TRUE; + } + + secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer); + if (_secret_flags_persist_secret (secret_flags)) { + cstr = nm_wireguard_peer_get_preshared_key (peer); + if (cstr) { + g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, cstr); + any_key = TRUE; + } + } + + /* usually, we don't persist the secret-flags 0 (because they are the default). + * For WireGuard peers, the default secret-flags for preshared-key are 4 (not-required). + * So, in this case behave differently: a missing preshared-key-flag setting means + * "not-required". */ + if (secret_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) { + g_key_file_set_int64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, secret_flags); + any_key = TRUE; + } + + u32 = nm_wireguard_peer_get_persistent_keepalive (peer); + if (u32) { + g_key_file_set_uint64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, u32); + any_key = TRUE; + } + + n_aip = nm_wireguard_peer_get_allowed_ips_len (peer); + if (n_aip > 0) { + gs_free const char **strv = NULL; + + strv = g_new (const char *, ((gsize) n_aip) + 1); + for (i_aip = 0; i_aip < n_aip; i_aip++) + strv[i_aip] = nm_wireguard_peer_get_allowed_ip (peer, i_aip, NULL); + strv[n_aip] = NULL; + g_key_file_set_string_list (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, + strv, n_aip); + any_key = TRUE; + } + + if (!any_key) { + /* we cannot omit all keys. At an empty endpoint. */ + g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, ""); + } + } +} + +GKeyFile * +nm_keyfile_write (NMConnection *connection, + NMKeyfileHandlerFlags handler_flags, + NMKeyfileWriteHandler handler, + void *user_data, + GError **error) +{ + gs_unref_keyfile GKeyFile *keyfile = NULL; + KeyfileWriterInfo info; + gs_free NMSetting **settings = NULL; + guint i, j, n_settings = 0; + + g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (!error || !*error, NULL); + g_return_val_if_fail (handler_flags == NM_KEYFILE_HANDLER_FLAGS_NONE, NULL); + + if (!nm_connection_verify (connection, error)) + return NULL; + + keyfile = g_key_file_new (); + + info = (KeyfileWriterInfo) { + .connection = connection, + .keyfile = keyfile, + .error = NULL, + .write_handler = handler, + .user_data = user_data, + }; + + settings = nm_connection_get_settings (connection, &n_settings); + for (i = 0; i < n_settings; i++) { + const NMSettInfoSetting *sett_info; + NMSetting *setting = settings[i]; + const char *setting_name; + const char *setting_alias; + + sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting)); + + setting_name = sett_info->setting_class->setting_info->setting_name; + + if (sett_info->detail.gendata_info) { + guint k, n_keys; + const char *const*keys; + + nm_assert (!nm_keyfile_plugin_get_alias_for_setting_name (sett_info->setting_class->setting_info->setting_name)); + + n_keys = _nm_setting_option_get_all (setting, &keys, NULL); + + if (n_keys > 0) { + GHashTable *h = _nm_setting_option_hash (setting, FALSE); + + for (k = 0; k < n_keys; k++) { + const char *key = keys[k]; + GVariant *v; + + v = g_hash_table_lookup (h, key); + + if (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) { + g_key_file_set_boolean (info.keyfile, + setting_name, + key, + g_variant_get_boolean (v)); + } else if (g_variant_is_of_type (v, G_VARIANT_TYPE_UINT32)) { + g_key_file_set_uint64 (info.keyfile, + setting_name, + key, + (guint64) g_variant_get_uint32 (v)); + } else { + /* BUG: The variant type is not implemented. Since the connection + * verifies, this can only mean we either wrongly didn't reject + * the connection as invalid, or we didn't properly implement the + * variant type. */ + nm_assert_not_reached (); + continue; + } + } + } + } + + for (j = 0; j < sett_info->property_infos_len; j++) { + const NMSettInfoProperty *property_info = _nm_sett_info_property_info_get_sorted (sett_info, j); + + write_setting_value (&info, setting, property_info); + if (info.error) + goto out_with_info_error; + } + + setting_alias = nm_keyfile_plugin_get_alias_for_setting_name (setting_name); + if ( ( setting_alias + && g_key_file_has_group (info.keyfile, setting_alias)) + || g_key_file_has_group (info.keyfile, setting_name)) { + /* we have a section for the setting. Nothing to do. */ + } else { + /* ensure the group is present. There is no API for that, so add and remove + * a dummy key. */ + g_key_file_set_value (info.keyfile, setting_alias ?: setting_name, ".X", "1"); + g_key_file_remove_key (info.keyfile, setting_alias ?: setting_name, ".X", NULL); + } + + if (NM_IS_SETTING_WIREGUARD (setting)) { + _write_setting_wireguard (setting, &info); + if (info.error) + goto out_with_info_error; + } + + nm_assert (!info.error); + } + + nm_assert (!info.error); + + return g_steal_pointer (&keyfile); + +out_with_info_error: + g_propagate_error (error, info.error); + return NULL; +} + +/*****************************************************************************/ + +static const char temp_letters[] = +"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/* + * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()). + */ +static gboolean +check_mkstemp_suffix (const char *path) +{ + const char *ptr; + + nm_assert (path); + + /* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */ + ptr = strrchr (path, '.'); + if ( ptr + && strspn (&ptr[1], temp_letters) == 6 + && ptr[7] == '\0') + return TRUE; + return FALSE; +} + +static gboolean +_check_suffix_impl (const char *base, const char *tag, gsize tag_len) +{ + gsize len; + + nm_assert (base); + nm_assert (tag); + nm_assert (strlen (tag) == tag_len); + + len = strlen (base); + if ( len > tag_len + && !g_ascii_strcasecmp (base + len - tag_len, tag)) + return TRUE; + return FALSE; +} +#define check_suffix(base, tag) _check_suffix_impl ((base), ""tag"", NM_STRLEN (tag)) + +#define SWP_TAG ".swp" +#define SWPX_TAG ".swpx" +#define PEM_TAG ".pem" +#define DER_TAG ".der" + +gboolean +nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension) +{ + const char *base; + gsize l; + + /* ignore_filename() must mirror nm_keyfile_utils_create_filename() */ + + g_return_val_if_fail (filename, TRUE); + + base = strrchr (filename, '/'); + if (base) + base++; + else + base = filename; + + if (!base[0]) { + /* this check above with strrchr() also rejects "/some/path/with/trailing/slash/", + * but that is fine, because such a path would name a directory, and we are not + * interested in directories. */ + return TRUE; + } + + if (base[0] == '.') { + /* don't allow hidden files */ + return TRUE; + } + + l = strlen (base); + + if (require_extension) { + if ( l <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION) + || !NM_STR_HAS_SUFFIX (base, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) + return TRUE; + return FALSE; + } + + /* Ignore backup files */ + if (base[l - 1] == '~') + return TRUE; + + /* Ignore temporary files + * + * This check is also important to ignore .nmload files (see + * %NM_KEYFILE_PATH_SUFFIX_NMMETA). */ + if (check_mkstemp_suffix (base)) + return TRUE; + + /* Ignore 802.1x certificates and keys */ + if ( check_suffix (base, PEM_TAG) + || check_suffix (base, DER_TAG)) + return TRUE; + + return FALSE; +} + +char * +nm_keyfile_utils_create_filename (const char *name, + gboolean with_extension) +{ + GString *str; + const char *f = name; + /* keyfile used to escape with '*', do not change that behavior. + * + * But for newly added escapings, use '_' instead. + * Also, @with_extension is new-style. */ + const char ESCAPE_CHAR = with_extension ? '_' : '*'; + const char ESCAPE_CHAR2 = '_'; + + g_return_val_if_fail (name && name[0], NULL); + + str = g_string_sized_new (60); + + /* Convert '/' to ESCAPE_CHAR */ + for (f = name; f[0]; f++) { + if (f[0] == '/') + g_string_append_c (str, ESCAPE_CHAR); + else + g_string_append_c (str, f[0]); + } + + /* nm_keyfile_utils_create_filename() must avoid anything that ignore_filename() would reject. + * We can escape here more aggressivly then what we would read back. */ + if (str->str[0] == '.') + str->str[0] = ESCAPE_CHAR2; + if (str->str[str->len - 1] == '~') + str->str[str->len - 1] = ESCAPE_CHAR2; + if ( check_mkstemp_suffix (str->str) + || check_suffix (str->str, PEM_TAG) + || check_suffix (str->str, DER_TAG)) + g_string_append_c (str, ESCAPE_CHAR2); + + if (with_extension) + g_string_append (str, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION); + + /* nm_keyfile_utils_create_filename() must mirror ignore_filename() */ + nm_assert (!strchr (str->str, '/')); + nm_assert (!nm_keyfile_utils_ignore_filename (str->str, with_extension)); + + return g_string_free (str, FALSE);; +} + +/*****************************************************************************/ + +/** + * nm_keyfile_handler_data_fail_with_error: + * @handler_data: the #NMKeyfileHandlerData + * @src: (transfer full): error to move into the return location + * + * Set the error for the handler. This lets the operation fail + * with the provided error. You may only set the error once. + * + * @src must be non-%NULL. + * + * Note that @src is no longer valid after this call. If you want + * to keep using the same GError*, you need to set it to %NULL + * after calling this function on it. + */ +void +nm_keyfile_handler_data_fail_with_error (NMKeyfileHandlerData *handler_data, + GError *src) +{ + g_return_if_fail (handler_data); + g_return_if_fail (handler_data->p_error && !*handler_data->p_error); + g_return_if_fail (src); + + *handler_data->p_error = src; +} + +/** + * nm_keyfile_handler_data_get_context: + * @handler_data: the #NMKeyfileHandlerData for any event. + * @out_kf_group_name: (out) (allow-none) (transfer none): if the event is in the + * context of a keyfile group, the group name. + * @out_kf_key_name: (out) (allow-none) (transfer none): if the event is in the + * context of a keyfile value, the key name. + * @out_cur_setting: (out) (allow-none) (transfer none): if the event happens while + * handling a particular #NMSetting instance. + * @out_cur_property_name: (out) (allow-none) (transfer none): the property name if applicable. + * + * Get context information of the current event. This function can be called + * on all events, but the context information may be unset. + */ +void +nm_keyfile_handler_data_get_context (const NMKeyfileHandlerData *handler_data, + const char **out_kf_group_name, + const char **out_kf_key_name, + NMSetting **out_cur_setting, + const char **out_cur_property_name) +{ + g_return_if_fail (handler_data); + + NM_SET_OUT (out_kf_group_name, handler_data->kf_group_name); + NM_SET_OUT (out_kf_key_name, handler_data->kf_key); + NM_SET_OUT (out_cur_setting, handler_data->cur_setting); + NM_SET_OUT (out_cur_property_name, handler_data->cur_property); +} + +const char * +_nm_keyfile_handler_data_warn_get_message (const NMKeyfileHandlerData *handler_data) +{ + nm_assert (handler_data); + nm_assert (handler_data->type == NM_KEYFILE_HANDLER_TYPE_WARN); + + if (!handler_data->warn.message) { + /* we cast the const away. @handler_data is const w.r.t. visible mutations + * from POV of the user. Internally, we construct the message in + * a lazy manner. It's like a mutable field in C++. */ + NM_PRAGMA_WARNING_DISABLE ("-Wformat-nonliteral") + ((NMKeyfileHandlerData *) handler_data)->warn.message = g_strdup_vprintf (handler_data->warn.fmt, + ((NMKeyfileHandlerData *) handler_data)->warn.ap); + NM_PRAGMA_WARNING_REENABLE + } + return handler_data->warn.message; +} + +/** + * nm_keyfile_handler_data_warn_get: + * @handler_data: the #NMKeyfileHandlerData for a %NM_KEYFILE_HANDLER_TYPE_WARN + * event. + * @out_message: (out) (allow-none) (transfer none): the warning message. + * @out_severity: (out) (allow-none): the #NMKeyfileWarnSeverity warning severity. + */ +void +nm_keyfile_handler_data_warn_get (const NMKeyfileHandlerData *handler_data, + const char **out_message, + NMKeyfileWarnSeverity *out_severity) +{ + g_return_if_fail (handler_data); + g_return_if_fail (handler_data->type == NM_KEYFILE_HANDLER_TYPE_WARN); + + NM_SET_OUT (out_message, _nm_keyfile_handler_data_warn_get_message (handler_data)); + NM_SET_OUT (out_severity, handler_data->warn.severity); +} diff --git a/libnm-core/nm-libnm-core-aux/README.md b/libnm-core/nm-libnm-core-aux/README.md new file mode 100644 index 00000000..01f361b4 --- /dev/null +++ b/libnm-core/nm-libnm-core-aux/README.md @@ -0,0 +1,21 @@ +nm-libnm-core-aux is a static library that: + + - uses parts of "libnm-core", that are public API of "libnm" + - can be statically linked into users of libnm-core (like libnm + and NetworkManager). + - that can also be statically linked into other users of libnm. + +Basically, it is a static library with utility functions that extends +libnm-core (the part that is public API of libnm), but can also be +used without full libnm. + +That means: + + - you can use it everywhere where you either statically link + with libnm-core, or dynamically link with libnm. + - you cannot use it inside libnm-core itself. This is the difference + between nm-libnm-core-intern and nm-libnm-core-aux. + +Also, since nm-libnm-core-aux itself only uses public (stable) +API of libnm, you theoretically can copy the sources into your +own source tree. diff --git a/libnm-core/nm-libnm-core-aux/nm-dispatcher-api.h b/libnm-core/nm-libnm-core-aux/nm-dispatcher-api.h new file mode 100644 index 00000000..96f512e0 --- /dev/null +++ b/libnm-core/nm-libnm-core-aux/nm-dispatcher-api.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2008 - 2012 Red Hat, Inc. + */ + +#ifndef __NM_DISPACHER_API_H__ +#define __NM_DISPACHER_API_H__ + +#define NM_DISPATCHER_DBUS_SERVICE "org.freedesktop.nm_dispatcher" +#define NM_DISPATCHER_DBUS_INTERFACE "org.freedesktop.nm_dispatcher" +#define NM_DISPATCHER_DBUS_PATH "/org/freedesktop/nm_dispatcher" + +#define NMD_CONNECTION_PROPS_PATH "path" +#define NMD_CONNECTION_PROPS_FILENAME "filename" +#define NMD_CONNECTION_PROPS_EXTERNAL "external" + +#define NMD_DEVICE_PROPS_INTERFACE "interface" +#define NMD_DEVICE_PROPS_IP_INTERFACE "ip-interface" +#define NMD_DEVICE_PROPS_TYPE "type" +#define NMD_DEVICE_PROPS_STATE "state" +#define NMD_DEVICE_PROPS_PATH "path" + +/* Actions */ +#define NMD_ACTION_HOSTNAME "hostname" +#define NMD_ACTION_PRE_UP "pre-up" +#define NMD_ACTION_UP "up" +#define NMD_ACTION_PRE_DOWN "pre-down" +#define NMD_ACTION_DOWN "down" +#define NMD_ACTION_VPN_PRE_UP "vpn-pre-up" +#define NMD_ACTION_VPN_UP "vpn-up" +#define NMD_ACTION_VPN_PRE_DOWN "vpn-pre-down" +#define NMD_ACTION_VPN_DOWN "vpn-down" +#define NMD_ACTION_DHCP4_CHANGE "dhcp4-change" +#define NMD_ACTION_DHCP6_CHANGE "dhcp6-change" +#define NMD_ACTION_CONNECTIVITY_CHANGE "connectivity-change" + +typedef enum { + DISPATCH_RESULT_UNKNOWN = 0, + DISPATCH_RESULT_SUCCESS = 1, + DISPATCH_RESULT_EXEC_FAILED = 2, + DISPATCH_RESULT_FAILED = 3, + DISPATCH_RESULT_TIMEOUT = 4, +} DispatchResult; + +#endif /* __NM_DISPACHER_API_H__ */ diff --git a/libnm-core/nm-libnm-core-aux/nm-libnm-core-aux.c b/libnm-core/nm-libnm-core-aux/nm-libnm-core-aux.c new file mode 100644 index 00000000..f827726f --- /dev/null +++ b/libnm-core/nm-libnm-core-aux/nm-libnm-core-aux.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-libnm-core-aux.h" + +#include "nm-libnm-core-intern/nm-libnm-core-utils.h" + +/*****************************************************************************/ + +typedef enum { + KEY_TYPE_STRING, + KEY_TYPE_INT, + KEY_TYPE_BOOL, +} KeyType; + +typedef struct { + const char *str_val; + union { + int vint; + bool vbool; + } typ_val; +} ParseData; + +typedef struct { + const char *name; + NMTeamLinkWatcherType watcher_type; + KeyType key_type; + union { + int (*fint) (const NMTeamLinkWatcher *watcher); + gboolean (*fbool) (const NMTeamLinkWatcher *watcher); + const char *(*fstring) (const NMTeamLinkWatcher *watcher); + } get_fcn; + union { + int vint; + bool vbool; + } def_val; +} TeamLinkWatcherKeyInfo; + +static gboolean +_team_link_watcher_validate_active (const NMTeamLinkWatcher *watcher) +{ + return NM_FLAGS_HAS (nm_team_link_watcher_get_flags (watcher), NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE); +} + +static gboolean +_team_link_watcher_validate_inactive (const NMTeamLinkWatcher *watcher) +{ + return NM_FLAGS_HAS (nm_team_link_watcher_get_flags (watcher), NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE); +} + +static gboolean +_team_link_watcher_send_always (const NMTeamLinkWatcher *watcher) +{ + return NM_FLAGS_HAS (nm_team_link_watcher_get_flags (watcher), NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS); +} + +static const TeamLinkWatcherKeyInfo _team_link_watcher_key_infos[_NM_TEAM_LINK_WATCHER_KEY_NUM] = { + +#define _KEY_INFO(key_id, _name, _watcher_type, _key_type, ...) \ + [key_id] = { .name = ""_name"", .watcher_type = (_watcher_type), .key_type = _key_type, ##__VA_ARGS__ } + + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_NAME, "name", NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL | NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_STRING, .get_fcn.fstring = nm_team_link_watcher_get_name, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_DELAY_UP, "delay-up", NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_delay_up, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN, "delay-down", NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_delay_down, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT, "init-wait", NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_init_wait, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_INTERVAL, "interval", NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_interval, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX, "missed-max", NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_missed_max, .def_val.vint = 3, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST, "target-host", NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_STRING, .get_fcn.fstring = nm_team_link_watcher_get_target_host, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_VLANID, "vlanid", NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_vlanid, .def_val.vint = -1, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST, "source-host", NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_STRING, .get_fcn.fstring = nm_team_link_watcher_get_source_host, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE, "validate-active", NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_BOOL, .get_fcn.fbool = _team_link_watcher_validate_active, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE, "validate-inactive", NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_BOOL, .get_fcn.fbool = _team_link_watcher_validate_inactive, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS, "send-always", NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_BOOL, .get_fcn.fbool = _team_link_watcher_send_always, ), + +}; + +static NMTeamLinkWatcherType +_team_link_watcher_get_watcher_type_from_name (const char *name) +{ + if (name) { + if (nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL)) + return NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL; + if (nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING)) + return NM_TEAM_LINK_WATCHER_TYPE_NSNAPING; + if (nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING)) + return NM_TEAM_LINK_WATCHER_TYPE_ARPING; + } + return NM_TEAM_LINK_WATCHER_TYPE_NONE; +} + +static const char * +_parse_data_get_str (const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM], + NMTeamLinkWatcherKeyId key_id) +{ + nm_assert (_NM_INT_NOT_NEGATIVE (key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM); + nm_assert (_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_STRING); + + return parse_data[key_id].str_val; +} + +static int +_parse_data_get_int (const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM], + NMTeamLinkWatcherKeyId key_id) +{ + nm_assert (_NM_INT_NOT_NEGATIVE (key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM); + nm_assert (_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_INT); + + if (parse_data[key_id].str_val) + return parse_data[key_id].typ_val.vint; + return _team_link_watcher_key_infos[key_id].def_val.vint; +} + +static int +_parse_data_get_bool (const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM], + NMTeamLinkWatcherKeyId key_id) +{ + nm_assert (_NM_INT_NOT_NEGATIVE (key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM); + nm_assert (_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_BOOL); + + if (parse_data[key_id].str_val) + return parse_data[key_id].typ_val.vbool; + return _team_link_watcher_key_infos[key_id].def_val.vbool; +} + +char * +nm_utils_team_link_watcher_to_string (const NMTeamLinkWatcher *watcher) +{ + nm_auto_free_gstring GString *str = NULL; + const char *name; + NMTeamLinkWatcherType watcher_type; + NMTeamLinkWatcherKeyId key_id; + + if (!watcher) + return NULL; + + str = g_string_new (NULL); + + name = nm_team_link_watcher_get_name (watcher); + g_string_append_printf (str, "name=%s", name ?: ""); + + watcher_type = _team_link_watcher_get_watcher_type_from_name (name); + + for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) { + const TeamLinkWatcherKeyInfo *info = &_team_link_watcher_key_infos[key_id]; + const char *vstr; + int vint; + bool vbool; + + nm_assert (info->name && info->name && NM_STRCHAR_ALL (info->name, ch,((ch >= 'a' && ch <= 'z') || NM_IN_SET (ch, '-')))); + nm_assert (NM_IN_SET (info->key_type, KEY_TYPE_STRING, + KEY_TYPE_INT, + KEY_TYPE_BOOL)); + + if (key_id == NM_TEAM_LINK_WATCHER_KEY_NAME) + continue; + + if (!NM_FLAGS_ALL (info->watcher_type, watcher_type)) + continue; + + switch (info->key_type) { + case KEY_TYPE_STRING: + vstr = info->get_fcn.fstring (watcher); + if (vstr) { + g_string_append_printf (nm_gstring_add_space_delimiter (str), + "%s=%s", info->name, vstr); + } + break; + case KEY_TYPE_INT: + vint = info->get_fcn.fint (watcher); + if (vint != info->def_val.vint) { + g_string_append_printf (nm_gstring_add_space_delimiter (str), + "%s=%d", info->name, vint); + } + break; + case KEY_TYPE_BOOL: + vbool = info->get_fcn.fbool (watcher); + if (vbool != info->def_val.vbool) { + g_string_append_printf (nm_gstring_add_space_delimiter (str), + "%s=%s", info->name, vbool ? "true" : "false"); + } + break; + } + } + + return g_string_free (g_steal_pointer (&str), FALSE); +} + +NMTeamLinkWatcher * +nm_utils_team_link_watcher_from_string (const char *str, + GError **error) +{ + gs_free const char **tokens = NULL; + ParseData parse_data[_NM_TEAM_LINK_WATCHER_KEY_NUM] = { }; + NMTeamLinkWatcherType watcher_type; + NMTeamLinkWatcherKeyId key_id; + gsize i_token; + NMTeamLinkWatcher *watcher; + int errsv; + + 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); + if (!tokens) { + g_set_error (error, 1, 0, "'%s' is not valid", str); + return NULL; + } + + for (i_token = 0; tokens[i_token]; i_token++) { + const TeamLinkWatcherKeyInfo *info; + const char *key = tokens[i_token]; + const char *val; + + val = strchr (key, '='); + if (!val) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("'%s' is not valid: properties should be specified as 'key=value'"), + key); + return NULL; + } + ((char *) val)[0] = '\0'; + val++; + + for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) { + info = &_team_link_watcher_key_infos[key_id]; + if (nm_streq (key, info->name)) + break; + } + + if (key_id == _NM_TEAM_LINK_WATCHER_KEY_NUM) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("'%s' is not a valid key"), key); + return NULL; + } + + if (parse_data[key_id].str_val) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("duplicate key '%s'"), key); + return NULL; + } + + parse_data[key_id].str_val = val; + + if (info->key_type == KEY_TYPE_INT) { + gint64 v64; + + v64 = _nm_utils_ascii_str_to_int64 (val, 10, G_MININT, G_MAXINT, G_MAXINT64); + if ( v64 == G_MAXINT64 + && ((errsv = errno) != 0)) { + if (errsv == ERANGE) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("number for '%s' is out of range"), key); + } else { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("value for '%s' must be a number"), key); + } + return NULL; + } + parse_data[key_id].typ_val.vint = v64; + } else if (info->key_type == KEY_TYPE_BOOL) { + int vbool; + + vbool = _nm_utils_ascii_str_to_bool (val, -1); + if (vbool == -1) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("value for '%s' must be a boolean"), key); + return NULL; + } + parse_data[key_id].typ_val.vbool = vbool; + } + } + + if (!parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("missing 'name' attribute")); + return NULL; + } + + watcher_type = _team_link_watcher_get_watcher_type_from_name (parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val); + if (watcher_type == NM_TEAM_LINK_WATCHER_TYPE_NONE) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("invalid 'name' \"%s\""), + parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val); + return NULL; + } + + for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) { + const TeamLinkWatcherKeyInfo *info = &_team_link_watcher_key_infos[key_id]; + + if (!parse_data[key_id].str_val) + continue; + if (!NM_FLAGS_ALL (info->watcher_type, watcher_type)) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("attribute '%s' is invalid for \"%s\""), + info->name, + parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val); + return NULL; + } + } + + switch (watcher_type) { + case NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL: + watcher = nm_team_link_watcher_new_ethtool (_parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_DELAY_UP), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN), + error); + break; + case NM_TEAM_LINK_WATCHER_TYPE_NSNAPING: + watcher = nm_team_link_watcher_new_nsna_ping (_parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INTERVAL), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX), + _parse_data_get_str (parse_data, NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST), + error); + break; + default: + nm_assert (watcher_type == NM_TEAM_LINK_WATCHER_TYPE_ARPING); + watcher = nm_team_link_watcher_new_arp_ping2 (_parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INTERVAL), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_VLANID), + _parse_data_get_str (parse_data, NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST), + _parse_data_get_str (parse_data, NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST), + ( NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE + | (_parse_data_get_bool (parse_data, NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE) ? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE : NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE) + | (_parse_data_get_bool (parse_data, NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE) ? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE : NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE) + | (_parse_data_get_bool (parse_data, NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS) ? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS : NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE) + ), + error); + break; + } + +#if NM_MORE_ASSERTS > 5 + if (watcher) { + gs_free char *str2 = NULL; + nm_auto_unref_team_link_watcher NMTeamLinkWatcher *watcher2 = NULL; + static _nm_thread_local int recursive; + + nm_assert (!error || !*error); + if (recursive == 0) { + recursive = 1; + str2 = nm_utils_team_link_watcher_to_string (watcher); + nm_assert (str2); + watcher2 = nm_utils_team_link_watcher_from_string (str2, NULL); + nm_assert (watcher2); + nm_assert (nm_team_link_watcher_equal (watcher, watcher2)); + nm_assert (nm_team_link_watcher_equal (watcher2, watcher)); + nm_assert (recursive == 1); + recursive = 0; + } + } else + nm_assert (!error || *error); +#endif + + return watcher; +} diff --git a/libnm-core/nm-libnm-core-aux/nm-libnm-core-aux.h b/libnm-core/nm-libnm-core-aux/nm-libnm-core-aux.h new file mode 100644 index 00000000..8a835205 --- /dev/null +++ b/libnm-core/nm-libnm-core-aux/nm-libnm-core-aux.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NM_LIBNM_CORE_AUX_H__ +#define __NM_LIBNM_CORE_AUX_H__ + +#include "nm-setting-team.h" + +typedef enum { + NM_TEAM_LINK_WATCHER_TYPE_NONE = 0, + NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL = (1u << 0), + NM_TEAM_LINK_WATCHER_TYPE_NSNAPING = (1u << 1), + NM_TEAM_LINK_WATCHER_TYPE_ARPING = (1u << 2), +} NMTeamLinkWatcherType; + +typedef enum { + NM_TEAM_LINK_WATCHER_KEY_NAME, + NM_TEAM_LINK_WATCHER_KEY_DELAY_UP, + NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN, + NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT, + NM_TEAM_LINK_WATCHER_KEY_INTERVAL, + NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX, + NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST, + NM_TEAM_LINK_WATCHER_KEY_VLANID, + NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST, + NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE, + NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE, + NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS, + _NM_TEAM_LINK_WATCHER_KEY_NUM, +} NMTeamLinkWatcherKeyId; + +char *nm_utils_team_link_watcher_to_string (const NMTeamLinkWatcher *watcher); + +NMTeamLinkWatcher *nm_utils_team_link_watcher_from_string (const char *str, + GError **error); + +#endif /* __NM_LIBNM_CORE_AUX_H__ */ diff --git a/libnm-core/nm-libnm-core-intern/README.md b/libnm-core/nm-libnm-core-intern/README.md new file mode 100644 index 00000000..2dcc3c80 --- /dev/null +++ b/libnm-core/nm-libnm-core-intern/README.md @@ -0,0 +1,21 @@ +nm-libnm-core-intern is a static library that: + + - uses parts of "libnm-core", that are public API of "libnm" + - that is statically linked into libnm-core (and thus libnm + and NetworkManager). + - that can also be statically linked into other users of libnm. + +Basically, it is a static library with utility functions that extends +libnm-core (the part that is public API of libnm), but it is used +by libnm-core. + +That means: + + - you can use it everywhere where you either statically link + with libnm-core, or dynamically link with libnm. + - you can even use it inside of libnm-core itself. This is the difference + between nm-libnm-core-intern and nm-libnm-core-aux. + +Also, since nm-libnm-core-intern itself only uses public (stable) +API of libnm, you theoretically can copy the sources into your +own source tree. diff --git a/libnm-core/nm-libnm-core-intern/nm-auth-subject.c b/libnm-core/nm-libnm-core-intern/nm-auth-subject.c new file mode 100644 index 00000000..15c2d652 --- /dev/null +++ b/libnm-core/nm-libnm-core-intern/nm-auth-subject.c @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2013 - 2014 Red Hat, Inc. + */ + +/** + * SECTION:nm-auth-subject + * @short_description: Encapsulates authentication information about a requestor + * + * #NMAuthSubject encpasulates identifying information about an entity that + * makes requests, like process identifier and user UID. + */ + +#include "nm-default.h" + +#include "nm-auth-subject.h" + +#include <stdlib.h> + +enum { + PROP_0, + PROP_SUBJECT_TYPE, + PROP_UNIX_PROCESS_DBUS_SENDER, + PROP_UNIX_PROCESS_PID, + PROP_UNIX_PROCESS_UID, + PROP_UNIX_SESSION_ID, + + PROP_LAST, +}; + +typedef struct { + NMAuthSubjectType subject_type; + struct { + gulong pid; + gulong uid; + guint64 start_time; + char *dbus_sender; + } unix_process; + + struct { + char *id; + } unix_session; +} NMAuthSubjectPrivate; + +struct _NMAuthSubject { + GObject parent; + NMAuthSubjectPrivate _priv; +}; + +struct _NMAuthSubjectClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE (NMAuthSubject, nm_auth_subject, G_TYPE_OBJECT) + +#define NM_AUTH_SUBJECT_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMAuthSubject, NM_IS_AUTH_SUBJECT) + +/*****************************************************************************/ + +#define CHECK_SUBJECT(self, error_value) \ + NMAuthSubjectPrivate *priv; \ + g_return_val_if_fail (NM_IS_AUTH_SUBJECT (self), error_value); \ + priv = NM_AUTH_SUBJECT_GET_PRIVATE (self); \ + +#define CHECK_SUBJECT_TYPED(self, expected_subject_type, error_value) \ + CHECK_SUBJECT (self, error_value); \ + g_return_val_if_fail (priv->subject_type == (expected_subject_type), error_value); + +const char * +nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len) +{ + CHECK_SUBJECT (self, NULL); + + switch (priv->subject_type) { + case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS: + g_snprintf (buf, buf_len, "unix-process[pid=%lu, uid=%lu, start=%llu]", + (unsigned long) priv->unix_process.pid, + (unsigned long) priv->unix_process.uid, + (unsigned long long) priv->unix_process.start_time); + break; + case NM_AUTH_SUBJECT_TYPE_INTERNAL: + g_strlcpy (buf, "internal", buf_len); + break; + case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION: + g_snprintf (buf, buf_len, "unix-session[id=%s]", + priv->unix_session.id); + break; + default: + g_strlcpy (buf, "invalid", buf_len); + break; + } + return buf; +} + +/* returns a floating variant */ +GVariant * +nm_auth_subject_unix_to_polkit_gvariant (NMAuthSubject *self) +{ + GVariantBuilder builder; + CHECK_SUBJECT (self, NULL); + + switch (priv->subject_type) { + + case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION: + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&builder, "{sv}", "session-id", + g_variant_new_string (priv->unix_session.id)); + return g_variant_new ("(sa{sv})", "unix-session", &builder); + + case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS: + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&builder, "{sv}", "pid", + g_variant_new_uint32 (priv->unix_process.pid)); + g_variant_builder_add (&builder, "{sv}", "start-time", + g_variant_new_uint64 (priv->unix_process.start_time)); + g_variant_builder_add (&builder, "{sv}", "uid", + g_variant_new_int32 (priv->unix_process.uid)); + return g_variant_new ("(sa{sv})", "unix-process", &builder); + + default: + g_return_val_if_reached (NULL); + } +} + +NMAuthSubjectType +nm_auth_subject_get_subject_type (NMAuthSubject *subject) +{ + CHECK_SUBJECT (subject, NM_AUTH_SUBJECT_TYPE_INVALID); + + return priv->subject_type; +} + +gulong +nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject) +{ + CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG); + + return priv->unix_process.pid; +} + +gulong +nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject) +{ + CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG); + + return priv->unix_process.uid; +} + +const char * +nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject) +{ + CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, NULL); + + return priv->unix_process.dbus_sender; +} + +const char * +nm_auth_subject_get_unix_session_id (NMAuthSubject *subject) +{ + CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_SESSION, NULL); + + return priv->unix_session.id; +} + +/*****************************************************************************/ + +/** + * nm_auth_subject_new_internal(): + * + * Creates a new auth subject representing the NetworkManager process itself. + * + * Returns: the new #NMAuthSubject + */ +NMAuthSubject * +nm_auth_subject_new_internal (void) +{ + return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT, + NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_INTERNAL, + NULL)); +} + +/** + * nm_auth_subject_new_unix_session(): + * + * Creates a new auth subject representing a given unix session. + * + * Returns: the new #NMAuthSubject + */ +NMAuthSubject * +nm_auth_subject_new_unix_session (const char *session_id) +{ + return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT, + NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_SESSION, + NM_AUTH_SUBJECT_UNIX_SESSION_ID, session_id, + NULL)); +} + +/** + * nm_auth_subject_new_unix_process(): + * + * Creates a new auth subject representing a given unix process. + * + * Returns: the new #NMAuthSubject + */ +NMAuthSubject * +nm_auth_subject_new_unix_process (const char *dbus_sender, gulong pid, gulong uid) +{ + return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT, + NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, dbus_sender, + NM_AUTH_SUBJECT_UNIX_PROCESS_PID, pid, + NM_AUTH_SUBJECT_UNIX_PROCESS_UID, uid, + NULL)); +} + +/** + * nm_auth_subject_new_unix_process_self(): + * + * Creates a new auth subject representing the current executing process. + * + * Returns: the new #NMAuthSubject + */ +NMAuthSubject * +nm_auth_subject_new_unix_process_self (void) +{ + return nm_auth_subject_new_unix_process (NULL, getpid(), getuid()); +} + +/*****************************************************************************/ + +static void +get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_SUBJECT_TYPE: + g_value_set_int (value, priv->subject_type); + break; + case PROP_UNIX_PROCESS_DBUS_SENDER: + g_value_set_string (value, priv->unix_process.dbus_sender); + break; + case PROP_UNIX_PROCESS_PID: + g_value_set_ulong (value, priv->unix_process.pid); + break; + case PROP_UNIX_PROCESS_UID: + g_value_set_ulong (value, priv->unix_process.uid); + break; + case PROP_UNIX_SESSION_ID: + g_value_set_string (value, priv->unix_session.id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (object); + NMAuthSubjectType subject_type; + int i; + const char *str; + gulong id; + + switch (prop_id) { + case PROP_SUBJECT_TYPE: + /* construct-only */ + i = g_value_get_int (value); + g_return_if_fail (NM_IN_SET (i, + (int) NM_AUTH_SUBJECT_TYPE_INTERNAL, + (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + (int) NM_AUTH_SUBJECT_TYPE_UNIX_SESSION)); + subject_type = i; + priv->subject_type |= subject_type; + g_return_if_fail (priv->subject_type == subject_type); + break; + case PROP_UNIX_PROCESS_DBUS_SENDER: + /* construct-only */ + if ((str = g_value_get_string (value))) { + priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS; + g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS); + priv->unix_process.dbus_sender = g_strdup (str); + } + break; + case PROP_UNIX_PROCESS_PID: + /* construct-only */ + if ((id = g_value_get_ulong (value)) != G_MAXULONG) { + priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS; + g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS); + priv->unix_process.pid = id; + } + break; + case PROP_UNIX_PROCESS_UID: + /* construct-only */ + if ((id = g_value_get_ulong (value)) != G_MAXULONG) { + priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS; + g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS); + priv->unix_process.uid = id; + } + break; + case PROP_UNIX_SESSION_ID: + /* construct-only */ + if ((str = g_value_get_string (value))) { + priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_SESSION; + g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_SESSION); + priv->unix_session.id = g_strdup (str); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_clear_private (NMAuthSubject *self) +{ + NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self); + + priv->subject_type = NM_AUTH_SUBJECT_TYPE_INVALID; + priv->unix_process.pid = G_MAXULONG; + priv->unix_process.uid = G_MAXULONG; + nm_clear_g_free (&priv->unix_process.dbus_sender); + + nm_clear_g_free (&priv->unix_session.id); +} + +static void +nm_auth_subject_init (NMAuthSubject *self) +{ + _clear_private (self); +} + +static void +constructed (GObject *object) +{ + NMAuthSubject *self = NM_AUTH_SUBJECT (object); + NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self); + + /* validate that the created instance. */ + + switch (priv->subject_type) { + case NM_AUTH_SUBJECT_TYPE_INTERNAL: + priv->unix_process.pid = G_MAXULONG; + priv->unix_process.uid = 0; /* internal uses 'root' user */ + return; + case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS: + /* Ensure pid and uid to be representable as int32. + * DBUS treats them as uint32, polkit library as int. */ + if (priv->unix_process.pid > MIN (G_MAXINT, G_MAXINT32)) + break; + if (priv->unix_process.uid > MIN (G_MAXINT, G_MAXINT32)) { + /* for uid==-1, libpolkit-gobject-1 detects the user based on the process id. + * Don't bother and require the user id as parameter. */ + break; + } + + priv->unix_process.start_time = nm_utils_get_start_time_for_pid (priv->unix_process.pid, NULL, NULL); + + if (!priv->unix_process.start_time) { + /* Is the process already gone? Then fail creation of the auth subject + * by clearing the type. */ + if (kill (priv->unix_process.pid, 0) != 0) + _clear_private (self); + + /* Otherwise, although we didn't detect a start_time, the process is still around. + * That could be due to procfs mounted with hidepid. So just accept the request. + * + * Polkit on the other side, will accept 0 and try to lookup /proc/$PID/stat + * itself (and if it fails to do so, assume a start-time of 0 and proceed). + * The only combination that would fail here, is when NM is able to read the + * start-time, but polkit is not. */ + } + return; + case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION: + return; + default: + break; + } + + _clear_private (self); + g_return_if_reached (); +} + +static void +finalize (GObject *object) +{ + _clear_private ((NMAuthSubject *) object); + + G_OBJECT_CLASS (nm_auth_subject_parent_class)->finalize (object); +} + +static void +nm_auth_subject_class_init (NMAuthSubjectClass *config_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (config_class); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->finalize = finalize; + + g_object_class_install_property + (object_class, PROP_SUBJECT_TYPE, + g_param_spec_int (NM_AUTH_SUBJECT_SUBJECT_TYPE, "", "", + NM_AUTH_SUBJECT_TYPE_INVALID, + NM_AUTH_SUBJECT_TYPE_UNIX_SESSION, + NM_AUTH_SUBJECT_TYPE_INVALID, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_UNIX_PROCESS_DBUS_SENDER, + g_param_spec_string (NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, "", "", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_UNIX_PROCESS_PID, + g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_PID, "", "", + 0, G_MAXULONG, G_MAXULONG, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_UNIX_PROCESS_UID, + g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_UID, "", "", + 0, G_MAXULONG, G_MAXULONG, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_UNIX_SESSION_ID, + g_param_spec_string (NM_AUTH_SUBJECT_UNIX_SESSION_ID, "", "", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} diff --git a/libnm-core/nm-libnm-core-intern/nm-auth-subject.h b/libnm-core/nm-libnm-core-intern/nm-auth-subject.h new file mode 100644 index 00000000..fc37b8e2 --- /dev/null +++ b/libnm-core/nm-libnm-core-intern/nm-auth-subject.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_AUTH_SUBJECT_H__ +#define __NETWORKMANAGER_AUTH_SUBJECT_H__ + +#define NM_TYPE_AUTH_SUBJECT (nm_auth_subject_get_type ()) +#define NM_AUTH_SUBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_AUTH_SUBJECT, NMAuthSubject)) +#define NM_AUTH_SUBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_AUTH_SUBJECT, NMAuthSubjectClass)) +#define NM_IS_AUTH_SUBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_AUTH_SUBJECT)) +#define NM_IS_AUTH_SUBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_AUTH_SUBJECT)) +#define NM_AUTH_SUBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_AUTH_SUBJECT, NMAuthSubjectClass)) + +typedef enum { + NM_AUTH_SUBJECT_TYPE_INVALID = 0, + NM_AUTH_SUBJECT_TYPE_INTERNAL = 1, + NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS = 2, + NM_AUTH_SUBJECT_TYPE_UNIX_SESSION = 4, +} NMAuthSubjectType; + +#define NM_AUTH_SUBJECT_SUBJECT_TYPE "subject-type" +#define NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER "unix-process-dbus-sender" +#define NM_AUTH_SUBJECT_UNIX_PROCESS_PID "unix-process-pid" +#define NM_AUTH_SUBJECT_UNIX_PROCESS_UID "unix-process-uid" +#define NM_AUTH_SUBJECT_UNIX_SESSION_ID "unix-session-id" + +typedef struct _NMAuthSubjectClass NMAuthSubjectClass; +typedef struct _NMAuthSubject NMAuthSubject; + +GType nm_auth_subject_get_type (void); + +NMAuthSubject *nm_auth_subject_new_internal (void); + +NMAuthSubject *nm_auth_subject_new_unix_session (const char *session_id); + +NMAuthSubject *nm_auth_subject_new_unix_process (const char *dbus_sender, gulong pid, gulong uid); + +NMAuthSubject *nm_auth_subject_new_unix_process_self (void); + +NMAuthSubjectType nm_auth_subject_get_subject_type (NMAuthSubject *subject); + +gulong nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject); + +const char *nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject); + +gulong nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject); + +const char *nm_auth_subject_get_unix_session_id (NMAuthSubject *subject); + +const char *nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len); + +GVariant *nm_auth_subject_unix_to_polkit_gvariant (NMAuthSubject *self); + +#endif /* __NETWORKMANAGER_AUTH_SUBJECT_H__ */ diff --git a/libnm-core/nm-libnm-core-intern/nm-common-macros.h b/libnm-core/nm-libnm-core-intern/nm-common-macros.h new file mode 100644 index 00000000..fcfe6919 --- /dev/null +++ b/libnm-core/nm-libnm-core-intern/nm-common-macros.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NM_COMMON_MACROS_H__ +#define __NM_COMMON_MACROS_H__ + +/*****************************************************************************/ + +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK "org.freedesktop.NetworkManager.enable-disable-network" +#define NM_AUTH_PERMISSION_SLEEP_WAKE "org.freedesktop.NetworkManager.sleep-wake" +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI "org.freedesktop.NetworkManager.enable-disable-wifi" +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN "org.freedesktop.NetworkManager.enable-disable-wwan" +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX "org.freedesktop.NetworkManager.enable-disable-wimax" +#define NM_AUTH_PERMISSION_NETWORK_CONTROL "org.freedesktop.NetworkManager.network-control" +#define NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED "org.freedesktop.NetworkManager.wifi.share.protected" +#define NM_AUTH_PERMISSION_WIFI_SHARE_OPEN "org.freedesktop.NetworkManager.wifi.share.open" +#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM "org.freedesktop.NetworkManager.settings.modify.system" +#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN "org.freedesktop.NetworkManager.settings.modify.own" +#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME "org.freedesktop.NetworkManager.settings.modify.hostname" +#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS "org.freedesktop.NetworkManager.settings.modify.global-dns" +#define NM_AUTH_PERMISSION_RELOAD "org.freedesktop.NetworkManager.reload" +#define NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK "org.freedesktop.NetworkManager.checkpoint-rollback" +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS "org.freedesktop.NetworkManager.enable-disable-statistics" +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK "org.freedesktop.NetworkManager.enable-disable-connectivity-check" +#define NM_AUTH_PERMISSION_WIFI_SCAN "org.freedesktop.NetworkManager.wifi.scan" + +#define NM_CLONED_MAC_PRESERVE "preserve" +#define NM_CLONED_MAC_PERMANENT "permanent" +#define NM_CLONED_MAC_RANDOM "random" +#define NM_CLONED_MAC_STABLE "stable" + +static inline gboolean +NM_CLONED_MAC_IS_SPECIAL (const char *str) +{ + return NM_IN_STRSET (str, + NM_CLONED_MAC_PRESERVE, + NM_CLONED_MAC_PERMANENT, + NM_CLONED_MAC_RANDOM, + NM_CLONED_MAC_STABLE); +} + +#define NM_IAID_MAC "mac" +#define NM_IAID_PERM_MAC "perm-mac" +#define NM_IAID_IFNAME "ifname" +#define NM_IAID_STABLE "stable" + +#define NM_CONNECTION_MUD_URL_NONE "none" + +static inline gboolean +NM_IAID_IS_SPECIAL (const char *str) +{ + return NM_IN_STRSET (str, + NM_IAID_MAC, + NM_IAID_PERM_MAC, + NM_IAID_IFNAME, + NM_IAID_STABLE); +} + +#endif /* __NM_COMMON_MACROS_H__ */ diff --git a/libnm-core/nm-libnm-core-intern/nm-ethtool-utils.c b/libnm-core/nm-libnm-core-intern/nm-ethtool-utils.c new file mode 100644 index 00000000..3e3c28d3 --- /dev/null +++ b/libnm-core/nm-libnm-core-intern/nm-ethtool-utils.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-ethtool-utils.h" + +#include "nm-setting-ethtool.h" + +/*****************************************************************************/ + +#define ETHT_DATA(xname) \ + [NM_ETHTOOL_ID_##xname] = (&((const NMEthtoolData) { \ + .optname = NM_ETHTOOL_OPTNAME_##xname, \ + .id = NM_ETHTOOL_ID_##xname, \ + })) + +const NMEthtoolData *const nm_ethtool_data[_NM_ETHTOOL_ID_NUM + 1] = { + /* indexed by NMEthtoolID */ + ETHT_DATA (COALESCE_ADAPTIVE_RX), + ETHT_DATA (COALESCE_ADAPTIVE_TX), + ETHT_DATA (COALESCE_PKT_RATE_HIGH), + ETHT_DATA (COALESCE_PKT_RATE_LOW), + ETHT_DATA (COALESCE_RX_FRAMES), + ETHT_DATA (COALESCE_RX_FRAMES_HIGH), + ETHT_DATA (COALESCE_RX_FRAMES_IRQ), + ETHT_DATA (COALESCE_RX_FRAMES_LOW), + ETHT_DATA (COALESCE_RX_USECS), + ETHT_DATA (COALESCE_RX_USECS_HIGH), + ETHT_DATA (COALESCE_RX_USECS_IRQ), + ETHT_DATA (COALESCE_RX_USECS_LOW), + ETHT_DATA (COALESCE_SAMPLE_INTERVAL), + ETHT_DATA (COALESCE_STATS_BLOCK_USECS), + ETHT_DATA (COALESCE_TX_FRAMES), + ETHT_DATA (COALESCE_TX_FRAMES_HIGH), + ETHT_DATA (COALESCE_TX_FRAMES_IRQ), + ETHT_DATA (COALESCE_TX_FRAMES_LOW), + ETHT_DATA (COALESCE_TX_USECS), + ETHT_DATA (COALESCE_TX_USECS_HIGH), + ETHT_DATA (COALESCE_TX_USECS_IRQ), + ETHT_DATA (COALESCE_TX_USECS_LOW), + ETHT_DATA (FEATURE_ESP_HW_OFFLOAD), + ETHT_DATA (FEATURE_ESP_TX_CSUM_HW_OFFLOAD), + ETHT_DATA (FEATURE_FCOE_MTU), + ETHT_DATA (FEATURE_GRO), + ETHT_DATA (FEATURE_GSO), + ETHT_DATA (FEATURE_HIGHDMA), + ETHT_DATA (FEATURE_HW_TC_OFFLOAD), + ETHT_DATA (FEATURE_L2_FWD_OFFLOAD), + ETHT_DATA (FEATURE_LOOPBACK), + ETHT_DATA (FEATURE_LRO), + ETHT_DATA (FEATURE_NTUPLE), + ETHT_DATA (FEATURE_RX), + ETHT_DATA (FEATURE_RXHASH), + ETHT_DATA (FEATURE_RXVLAN), + ETHT_DATA (FEATURE_RX_ALL), + ETHT_DATA (FEATURE_RX_FCS), + ETHT_DATA (FEATURE_RX_GRO_HW), + ETHT_DATA (FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD), + ETHT_DATA (FEATURE_RX_VLAN_FILTER), + ETHT_DATA (FEATURE_RX_VLAN_STAG_FILTER), + ETHT_DATA (FEATURE_RX_VLAN_STAG_HW_PARSE), + ETHT_DATA (FEATURE_SG), + ETHT_DATA (FEATURE_TLS_HW_RECORD), + ETHT_DATA (FEATURE_TLS_HW_TX_OFFLOAD), + ETHT_DATA (FEATURE_TSO), + ETHT_DATA (FEATURE_TX), + ETHT_DATA (FEATURE_TXVLAN), + ETHT_DATA (FEATURE_TX_CHECKSUM_FCOE_CRC), + ETHT_DATA (FEATURE_TX_CHECKSUM_IPV4), + ETHT_DATA (FEATURE_TX_CHECKSUM_IPV6), + ETHT_DATA (FEATURE_TX_CHECKSUM_IP_GENERIC), + ETHT_DATA (FEATURE_TX_CHECKSUM_SCTP), + ETHT_DATA (FEATURE_TX_ESP_SEGMENTATION), + ETHT_DATA (FEATURE_TX_FCOE_SEGMENTATION), + ETHT_DATA (FEATURE_TX_GRE_CSUM_SEGMENTATION), + ETHT_DATA (FEATURE_TX_GRE_SEGMENTATION), + ETHT_DATA (FEATURE_TX_GSO_PARTIAL), + ETHT_DATA (FEATURE_TX_GSO_ROBUST), + ETHT_DATA (FEATURE_TX_IPXIP4_SEGMENTATION), + ETHT_DATA (FEATURE_TX_IPXIP6_SEGMENTATION), + ETHT_DATA (FEATURE_TX_NOCACHE_COPY), + ETHT_DATA (FEATURE_TX_SCATTER_GATHER), + ETHT_DATA (FEATURE_TX_SCATTER_GATHER_FRAGLIST), + ETHT_DATA (FEATURE_TX_SCTP_SEGMENTATION), + ETHT_DATA (FEATURE_TX_TCP6_SEGMENTATION), + ETHT_DATA (FEATURE_TX_TCP_ECN_SEGMENTATION), + ETHT_DATA (FEATURE_TX_TCP_MANGLEID_SEGMENTATION), + ETHT_DATA (FEATURE_TX_TCP_SEGMENTATION), + ETHT_DATA (FEATURE_TX_UDP_SEGMENTATION), + ETHT_DATA (FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION), + ETHT_DATA (FEATURE_TX_UDP_TNL_SEGMENTATION), + ETHT_DATA (FEATURE_TX_VLAN_STAG_HW_INSERT), + ETHT_DATA (RING_RX), + ETHT_DATA (RING_RX_JUMBO), + ETHT_DATA (RING_RX_MINI), + ETHT_DATA (RING_TX), + [_NM_ETHTOOL_ID_NUM] = NULL, +}; + +static const guint8 _by_name[_NM_ETHTOOL_ID_NUM] = { + /* sorted by optname. */ + NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX, + NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX, + NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH, + NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW, + NM_ETHTOOL_ID_COALESCE_RX_FRAMES, + NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH, + NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ, + NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW, + NM_ETHTOOL_ID_COALESCE_RX_USECS, + NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH, + NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ, + NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW, + NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL, + NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS, + NM_ETHTOOL_ID_COALESCE_TX_FRAMES, + NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH, + NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ, + NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW, + NM_ETHTOOL_ID_COALESCE_TX_USECS, + NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH, + NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ, + NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW, + NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_FCOE_MTU, + NM_ETHTOOL_ID_FEATURE_GRO, + NM_ETHTOOL_ID_FEATURE_GSO, + NM_ETHTOOL_ID_FEATURE_HIGHDMA, + NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_LOOPBACK, + NM_ETHTOOL_ID_FEATURE_LRO, + NM_ETHTOOL_ID_FEATURE_NTUPLE, + NM_ETHTOOL_ID_FEATURE_RX, + NM_ETHTOOL_ID_FEATURE_RX_ALL, + NM_ETHTOOL_ID_FEATURE_RX_FCS, + NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, + NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, + NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, + NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, + NM_ETHTOOL_ID_FEATURE_RXHASH, + NM_ETHTOOL_ID_FEATURE_RXVLAN, + NM_ETHTOOL_ID_FEATURE_SG, + NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, + NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_TSO, + NM_ETHTOOL_ID_FEATURE_TX, + NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC, + NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC, + NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4, + NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6, + NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP, + NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, + NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, + NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY, + NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER, + NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST, + NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, + NM_ETHTOOL_ID_FEATURE_TXVLAN, + NM_ETHTOOL_ID_RING_RX, + NM_ETHTOOL_ID_RING_RX_JUMBO, + NM_ETHTOOL_ID_RING_RX_MINI, + NM_ETHTOOL_ID_RING_TX, +}; + +/*****************************************************************************/ + +static void +_ASSERT_data (void) +{ + int i; + + if (!NM_MORE_ASSERT_ONCE (10)) + return; + + G_STATIC_ASSERT_EXPR (_NM_ETHTOOL_ID_FIRST == 0); + G_STATIC_ASSERT_EXPR (_NM_ETHTOOL_ID_LAST == _NM_ETHTOOL_ID_NUM - 1); + G_STATIC_ASSERT_EXPR (_NM_ETHTOOL_ID_NUM > 0); + + nm_assert (NM_PTRARRAY_LEN (nm_ethtool_data) == _NM_ETHTOOL_ID_NUM); + nm_assert (G_N_ELEMENTS (_by_name) == _NM_ETHTOOL_ID_NUM); + nm_assert (G_N_ELEMENTS (nm_ethtool_data) == _NM_ETHTOOL_ID_NUM + 1); + + for (i = 0; i < _NM_ETHTOOL_ID_NUM; i++) { + const NMEthtoolData *d = nm_ethtool_data[i]; + + nm_assert (d); + nm_assert (d->id == (NMEthtoolID) i); + nm_assert (d->optname && d->optname[0]); + } + + for (i = 0; i < _NM_ETHTOOL_ID_NUM; i++) { + NMEthtoolID id = _by_name[i]; + const NMEthtoolData *d; + + nm_assert (id >= 0); + nm_assert (id < _NM_ETHTOOL_ID_NUM); + + d = nm_ethtool_data[id]; + if (i > 0) { + /* since we assert that all optnames are sorted strictly monotonically increasing, + * it also follows that there are no duplicates in the _by_name. + * It also follows, that all names in nm_ethtool_data are unique. */ + if (strcmp (nm_ethtool_data[_by_name[i - 1]]->optname, d->optname) >= 0) { + g_error ("nm_ethtool_data is not sorted asciibetically: %u/%s should be after %u/%s", + i - 1, nm_ethtool_data[_by_name[i - 1]]->optname, + i, d->optname); + } + } + } +} + +static int +_by_name_cmp (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + const guint8 *p_id = a; + const char *optname = b; + + nm_assert (p_id && p_id >= _by_name && p_id <= &_by_name[_NM_ETHTOOL_ID_NUM]); + nm_assert (*p_id < _NM_ETHTOOL_ID_NUM); + + return strcmp (nm_ethtool_data[*p_id]->optname, optname); +} + +const NMEthtoolData * +nm_ethtool_data_get_by_optname (const char *optname) +{ + gssize idx; + + if (!optname) + return NULL; + + _ASSERT_data (); + + idx = nm_utils_array_find_binary_search ((gconstpointer *) _by_name, + sizeof (_by_name[0]), + _NM_ETHTOOL_ID_NUM, + optname, + _by_name_cmp, + NULL); + return (idx < 0) ? NULL : nm_ethtool_data[_by_name[idx]]; +} + +NMEthtoolType +nm_ethtool_id_to_type (NMEthtoolID id) +{ + if (nm_ethtool_id_is_coalesce (id)) + return NM_ETHTOOL_TYPE_COALESCE; + if (nm_ethtool_id_is_feature (id)) + return NM_ETHTOOL_TYPE_FEATURE; + if (nm_ethtool_id_is_ring (id)) + return NM_ETHTOOL_TYPE_RING; + + return NM_ETHTOOL_TYPE_UNKNOWN; +} diff --git a/libnm-core/nm-libnm-core-intern/nm-ethtool-utils.h b/libnm-core/nm-libnm-core-intern/nm-ethtool-utils.h new file mode 100644 index 00000000..cf971edb --- /dev/null +++ b/libnm-core/nm-libnm-core-intern/nm-ethtool-utils.h @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_ETHTOOL_UTILS_H__ +#define __NM_ETHTOOL_UTILS_H__ + +/*****************************************************************************/ + +typedef enum { + NM_ETHTOOL_ID_UNKNOWN = -1, + + _NM_ETHTOOL_ID_FIRST = 0, + + _NM_ETHTOOL_ID_COALESCE_FIRST = _NM_ETHTOOL_ID_FIRST, + NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX = _NM_ETHTOOL_ID_COALESCE_FIRST, + NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX, + NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH, + NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW, + NM_ETHTOOL_ID_COALESCE_RX_FRAMES, + NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH, + NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ, + NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW, + NM_ETHTOOL_ID_COALESCE_RX_USECS, + NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH, + NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ, + NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW, + NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL, + NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS, + NM_ETHTOOL_ID_COALESCE_TX_FRAMES, + NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH, + NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ, + NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW, + NM_ETHTOOL_ID_COALESCE_TX_USECS, + NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH, + NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ, + NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW, + _NM_ETHTOOL_ID_COALESCE_LAST = NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW, + + _NM_ETHTOOL_ID_FEATURE_FIRST = _NM_ETHTOOL_ID_COALESCE_LAST + 1, + NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD = _NM_ETHTOOL_ID_FEATURE_FIRST, + NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_FCOE_MTU, + NM_ETHTOOL_ID_FEATURE_GRO, + NM_ETHTOOL_ID_FEATURE_GSO, + NM_ETHTOOL_ID_FEATURE_HIGHDMA, + NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_LOOPBACK, + NM_ETHTOOL_ID_FEATURE_LRO, + NM_ETHTOOL_ID_FEATURE_NTUPLE, + NM_ETHTOOL_ID_FEATURE_RX, + NM_ETHTOOL_ID_FEATURE_RXHASH, + NM_ETHTOOL_ID_FEATURE_RXVLAN, + NM_ETHTOOL_ID_FEATURE_RX_ALL, + NM_ETHTOOL_ID_FEATURE_RX_FCS, + NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, + NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, + NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, + NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, + NM_ETHTOOL_ID_FEATURE_SG, + NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, + NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, + NM_ETHTOOL_ID_FEATURE_TSO, + NM_ETHTOOL_ID_FEATURE_TX, + NM_ETHTOOL_ID_FEATURE_TXVLAN, + NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC, + NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4, + NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6, + NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC, + NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP, + NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, + NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, + NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY, + NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER, + NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST, + NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, + NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, + _NM_ETHTOOL_ID_FEATURE_LAST = NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, + + _NM_ETHTOOL_ID_RING_FIRST = _NM_ETHTOOL_ID_FEATURE_LAST + 1, + NM_ETHTOOL_ID_RING_RX = _NM_ETHTOOL_ID_RING_FIRST, + NM_ETHTOOL_ID_RING_RX_JUMBO, + NM_ETHTOOL_ID_RING_RX_MINI, + NM_ETHTOOL_ID_RING_TX, + _NM_ETHTOOL_ID_RING_LAST = NM_ETHTOOL_ID_RING_TX, + + _NM_ETHTOOL_ID_LAST = _NM_ETHTOOL_ID_RING_LAST, + + _NM_ETHTOOL_ID_COALESCE_NUM = (_NM_ETHTOOL_ID_COALESCE_LAST - _NM_ETHTOOL_ID_COALESCE_FIRST + 1), + _NM_ETHTOOL_ID_FEATURE_NUM = (_NM_ETHTOOL_ID_FEATURE_LAST - _NM_ETHTOOL_ID_FEATURE_FIRST + 1), + _NM_ETHTOOL_ID_RING_NUM = (_NM_ETHTOOL_ID_RING_LAST - _NM_ETHTOOL_ID_RING_FIRST + 1), + _NM_ETHTOOL_ID_NUM = (_NM_ETHTOOL_ID_LAST - _NM_ETHTOOL_ID_FIRST + 1), +} NMEthtoolID; + +#define _NM_ETHTOOL_ID_FEATURE_AS_IDX(ethtool_id) ((ethtool_id) - _NM_ETHTOOL_ID_FEATURE_FIRST) +#define _NM_ETHTOOL_ID_COALESCE_AS_IDX(ethtool_id) ((ethtool_id) - _NM_ETHTOOL_ID_COALESCE_FIRST) + +typedef enum { + NM_ETHTOOL_TYPE_UNKNOWN, + NM_ETHTOOL_TYPE_COALESCE, + NM_ETHTOOL_TYPE_FEATURE, + NM_ETHTOOL_TYPE_RING, +} NMEthtoolType; + +typedef struct { + const char *optname; + NMEthtoolID id; +} NMEthtoolData; + +extern const NMEthtoolData *const nm_ethtool_data[_NM_ETHTOOL_ID_NUM + 1]; + +const NMEthtoolData *nm_ethtool_data_get_by_optname (const char *optname); + +NMEthtoolType nm_ethtool_id_to_type (NMEthtoolID id); + +/****************************************************************************/ + +static inline NMEthtoolID +nm_ethtool_id_get_by_name (const char *optname) +{ + const NMEthtoolData *d; + + d = nm_ethtool_data_get_by_optname (optname); + return d ? d->id : NM_ETHTOOL_ID_UNKNOWN; +} + +static inline gboolean +nm_ethtool_id_is_feature (NMEthtoolID id) +{ + return id >= _NM_ETHTOOL_ID_FEATURE_FIRST && id <= _NM_ETHTOOL_ID_FEATURE_LAST; +} + +static inline gboolean +nm_ethtool_id_is_coalesce (NMEthtoolID id) +{ + return id >= _NM_ETHTOOL_ID_COALESCE_FIRST && id <= _NM_ETHTOOL_ID_COALESCE_LAST; +} + +static inline gboolean +nm_ethtool_id_is_ring (NMEthtoolID id) +{ + return id >= _NM_ETHTOOL_ID_RING_FIRST && id <= _NM_ETHTOOL_ID_RING_LAST; +} + +/****************************************************************************/ + +#endif /* __NM_ETHTOOL_UTILS_H__ */ diff --git a/libnm-core/nm-libnm-core-intern/nm-libnm-core-utils.c b/libnm-core/nm-libnm-core-intern/nm-libnm-core-utils.c new file mode 100644 index 00000000..df2f2e77 --- /dev/null +++ b/libnm-core/nm-libnm-core-intern/nm-libnm-core-utils.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#include "nm-default.h" + +#include "nm-libnm-core-utils.h" + +#include "nm-common-macros.h" + +/*****************************************************************************/ + +gboolean +nm_utils_vlan_priority_map_parse_str (NMVlanPriorityMap map_type, + const char *str, + gboolean allow_wildcard_to, + guint32 *out_from, + guint32 *out_to, + gboolean *out_has_wildcard_to) +{ + const char *s2; + gint64 v1, v2; + + nm_assert (str); + + s2 = strchr (str, ':'); + + if (!s2) { + if (!allow_wildcard_to) + return FALSE; + v1 = _nm_utils_ascii_str_to_int64 (str, 10, 0, G_MAXUINT32, -1); + v2 = -1; + } else { + gs_free char *s1_free = NULL; + gsize s1_len = (s2 - str); + + s2 = nm_str_skip_leading_spaces (&s2[1]); + if ( s2[0] == '\0' + || ( s2[0] == '*' + && NM_STRCHAR_ALL (&s2[1], ch, g_ascii_isspace (ch)))) { + if (!allow_wildcard_to) + return FALSE; + v2 = -1; + } else { + v2 = _nm_utils_ascii_str_to_int64 (s2, 10, 0, G_MAXUINT32, -1); + if ( v2 < 0 + || (guint32) v2 > nm_utils_vlan_priority_map_get_max_prio (map_type, FALSE)) + return FALSE; + } + + v1 = _nm_utils_ascii_str_to_int64 (nm_strndup_a (100, str, s1_len, &s1_free), + 10, 0, G_MAXUINT32, -1); + } + + if ( v1 < 0 + || (guint32) v1 > nm_utils_vlan_priority_map_get_max_prio (map_type, TRUE)) + return FALSE; + + NM_SET_OUT (out_from, v1); + NM_SET_OUT (out_to, v2 < 0 + ? 0u + : (guint) v2); + NM_SET_OUT (out_has_wildcard_to, v2 < 0); + return TRUE; +} + +/*****************************************************************************/ + +const char *const nm_auth_permission_names_by_idx[NM_CLIENT_PERMISSION_LAST] = { + [NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK - 1] = NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN, + [NM_CLIENT_PERMISSION_NETWORK_CONTROL - 1] = NM_AUTH_PERMISSION_NETWORK_CONTROL, + [NM_CLIENT_PERMISSION_RELOAD - 1] = NM_AUTH_PERMISSION_RELOAD, + [NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS - 1] = NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS, + [NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME - 1] = NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, + [NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN - 1] = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN, + [NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM - 1] = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM, + [NM_CLIENT_PERMISSION_SLEEP_WAKE - 1] = NM_AUTH_PERMISSION_SLEEP_WAKE, + [NM_CLIENT_PERMISSION_WIFI_SCAN - 1] = NM_AUTH_PERMISSION_WIFI_SCAN, + [NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN - 1] = NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, + [NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED - 1] = NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, +}; + +const NMClientPermission nm_auth_permission_sorted[NM_CLIENT_PERMISSION_LAST] = { + NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN, + NM_CLIENT_PERMISSION_NETWORK_CONTROL, + NM_CLIENT_PERMISSION_RELOAD, + NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS, + NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME, + NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN, + NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM, + NM_CLIENT_PERMISSION_SLEEP_WAKE, + NM_CLIENT_PERMISSION_WIFI_SCAN, + NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN, + NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED, +}; + +const char * +nm_auth_permission_to_string (NMClientPermission permission) +{ + if (permission < 1) + return NULL; + if (permission > NM_CLIENT_PERMISSION_LAST) + return NULL; + return nm_auth_permission_names_by_idx[permission - 1]; +} + +#define AUTH_PERMISSION_PREFIX "org.freedesktop.NetworkManager." + +static int +_nm_auth_permission_from_string_cmp (gconstpointer a, gconstpointer b, gpointer user_data) +{ + const NMClientPermission *const p = a; + const char *const needle = b; + const char *ss = nm_auth_permission_names_by_idx[*p - 1]; + + nm_assert (NM_STR_HAS_PREFIX (ss, AUTH_PERMISSION_PREFIX)); + nm_assert (ss[NM_STRLEN (AUTH_PERMISSION_PREFIX)] != '\0'); + + return strcmp (&ss[NM_STRLEN (AUTH_PERMISSION_PREFIX)], needle); +} + +NMClientPermission +nm_auth_permission_from_string (const char *str) +{ + gssize idx; + + if (!str) + return NM_CLIENT_PERMISSION_NONE; + + if (!NM_STR_HAS_PREFIX (str, AUTH_PERMISSION_PREFIX)) + return NM_CLIENT_PERMISSION_NONE; + idx = nm_utils_array_find_binary_search (nm_auth_permission_sorted, + sizeof (nm_auth_permission_sorted[0]), + G_N_ELEMENTS (nm_auth_permission_sorted), + &str[NM_STRLEN (AUTH_PERMISSION_PREFIX)], + _nm_auth_permission_from_string_cmp, + NULL); + if (idx < 0) + return NM_CLIENT_PERMISSION_NONE; + return nm_auth_permission_sorted[idx]; +} + +/*****************************************************************************/ + +NMClientPermissionResult +nm_client_permission_result_from_string (const char *nm) +{ + if (!nm) + return NM_CLIENT_PERMISSION_RESULT_UNKNOWN; + if (nm_streq (nm, "yes")) + return NM_CLIENT_PERMISSION_RESULT_YES; + if (nm_streq (nm, "no")) + return NM_CLIENT_PERMISSION_RESULT_NO; + if (nm_streq (nm, "auth")) + return NM_CLIENT_PERMISSION_RESULT_AUTH; + return NM_CLIENT_PERMISSION_RESULT_UNKNOWN; +} + +const char * +nm_client_permission_result_to_string (NMClientPermissionResult permission) +{ + switch (permission) { + case NM_CLIENT_PERMISSION_RESULT_YES: return "yes"; + case NM_CLIENT_PERMISSION_RESULT_NO: return "no"; + case NM_CLIENT_PERMISSION_RESULT_AUTH: return "auth"; + case NM_CLIENT_PERMISSION_RESULT_UNKNOWN: return "unknown"; + } + nm_assert_not_reached (); + return NULL; +} diff --git a/libnm-core/nm-libnm-core-intern/nm-libnm-core-utils.h b/libnm-core/nm-libnm-core-intern/nm-libnm-core-utils.h new file mode 100644 index 00000000..a35a5e15 --- /dev/null +++ b/libnm-core/nm-libnm-core-intern/nm-libnm-core-utils.h @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#ifndef __NM_LIBNM_SHARED_UTILS_H__ +#define __NM_LIBNM_SHARED_UTILS_H__ + +/****************************************************************************/ + +#include "nm-setting-bridge.h" +#include "nm-setting-connection.h" +#include "nm-setting-ip-config.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-sriov.h" +#include "nm-setting-team.h" +#include "nm-setting-vlan.h" +#include "nm-setting-wireguard.h" + +/****************************************************************************/ + +#define nm_auto_unref_ip_address nm_auto (_nm_ip_address_unref) +NM_AUTO_DEFINE_FCN0 (NMIPAddress *, _nm_ip_address_unref, nm_ip_address_unref) + +#define nm_auto_unref_ip_route nm_auto (_nm_auto_unref_ip_route) +NM_AUTO_DEFINE_FCN0 (NMIPRoute *, _nm_auto_unref_ip_route, nm_ip_route_unref) + +#define nm_auto_unref_ip_routing_rule nm_auto(_nm_auto_unref_ip_routing_rule) +NM_AUTO_DEFINE_FCN0 (NMIPRoutingRule *, _nm_auto_unref_ip_routing_rule, nm_ip_routing_rule_unref) + +#define nm_auto_unref_sriov_vf nm_auto (_nm_auto_unref_sriov_vf) +NM_AUTO_DEFINE_FCN0 (NMSriovVF *, _nm_auto_unref_sriov_vf, nm_sriov_vf_unref) + +#define nm_auto_unref_tc_qdisc nm_auto (_nm_auto_unref_tc_qdisc) +NM_AUTO_DEFINE_FCN0 (NMTCQdisc *, _nm_auto_unref_tc_qdisc, nm_tc_qdisc_unref) + +#define nm_auto_unref_tc_tfilter nm_auto (_nm_auto_unref_tc_tfilter) +NM_AUTO_DEFINE_FCN0 (NMTCTfilter *, _nm_auto_unref_tc_tfilter, nm_tc_tfilter_unref) + +#define nm_auto_unref_bridge_vlan nm_auto (_nm_auto_unref_bridge_vlan) +NM_AUTO_DEFINE_FCN0 (NMBridgeVlan *, _nm_auto_unref_bridge_vlan, nm_bridge_vlan_unref) + +#define nm_auto_unref_team_link_watcher nm_auto (_nm_auto_unref_team_link_watcher) +NM_AUTO_DEFINE_FCN0 (NMTeamLinkWatcher *, _nm_auto_unref_team_link_watcher, nm_team_link_watcher_unref) + +#define nm_auto_unref_wgpeer nm_auto (_nm_auto_unref_wgpeer) +NM_AUTO_DEFINE_FCN0 (NMWireGuardPeer *, _nm_auto_unref_wgpeer, nm_wireguard_peer_unref) + +/****************************************************************************/ + +static inline guint32 +nm_utils_vlan_priority_map_get_max_prio (NMVlanPriorityMap map, gboolean from) +{ + if (map == NM_VLAN_INGRESS_MAP) { + return from + ? 7u /* MAX_8021P_PRIO */ + : (guint32) G_MAXUINT32 /* MAX_SKB_PRIO */; + } + nm_assert (map == NM_VLAN_EGRESS_MAP); + return from + ? (guint32) G_MAXUINT32 /* MAX_SKB_PRIO */ + : 7u /* MAX_8021P_PRIO */; +} + +gboolean nm_utils_vlan_priority_map_parse_str (NMVlanPriorityMap map_type, + const char *str, + gboolean allow_wildcard_to, + guint32 *out_from, + guint32 *out_to, + gboolean *out_has_wildcard_to); + +/*****************************************************************************/ + +static inline int +nm_setting_ip_config_get_addr_family (NMSettingIPConfig *s_ip) +{ + if (NM_IS_SETTING_IP4_CONFIG (s_ip)) + return AF_INET; + if (NM_IS_SETTING_IP6_CONFIG (s_ip)) + return AF_INET6; + g_return_val_if_reached (AF_UNSPEC); +} + +/*****************************************************************************/ + +/* The maximum MTU for infiniband. + * + * This is both in transport-mode "datagram" and "connected" + * and they both have the same maximum define. + * + * Note that in the past, MTU in "datagram" mode was restricted + * to 2044 bytes. That is no longer the case and we accept large + * MTUs. + * + * This define is the maxiumum for the MTU in a connection profile (the + * setting). Whether large MTUs can be configured later (at activation time) + * depends on other factors. */ +#define NM_INFINIBAND_MAX_MTU ((guint) 65520) + +/*****************************************************************************/ + +#define _NM_CAPABILITY_MAX NM_CAPABILITY_OVS + +/*****************************************************************************/ + +extern const char *const nm_auth_permission_names_by_idx[NM_CLIENT_PERMISSION_LAST]; +extern const NMClientPermission nm_auth_permission_sorted[NM_CLIENT_PERMISSION_LAST]; + +const char *nm_auth_permission_to_string (NMClientPermission permission); +NMClientPermission nm_auth_permission_from_string (const char *str); + +/*****************************************************************************/ + +NMClientPermissionResult nm_client_permission_result_from_string (const char *nm); +const char *nm_client_permission_result_to_string (NMClientPermissionResult permission); + +#endif /* __NM_LIBNM_SHARED_UTILS_H__ */ diff --git a/libnm-core/nm-setting-bridge-port.c b/libnm-core/nm-setting-bridge-port.c index 5fcf1e60..92ac28ec 100644 --- a/libnm-core/nm-setting-bridge-port.c +++ b/libnm-core/nm-setting-bridge-port.c @@ -236,18 +236,20 @@ nm_setting_bridge_port_remove_vlan_by_vid (NMSettingBridgePort *setting, guint16 vid_end) { NMSettingBridgePortPrivate *priv; - guint16 v_start, v_end; - NMBridgeVlan *vlan; guint i; if (vid_end == 0) vid_end = vid_start; g_return_val_if_fail (NM_IS_SETTING_BRIDGE_PORT (setting), FALSE); + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting); for (i = 0; i < priv->vlans->len; i++) { - vlan = (NMBridgeVlan *) priv->vlans->pdata[i]; + NMBridgeVlan *vlan = priv->vlans->pdata[i]; + guint16 v_start = 0; + guint16 v_end = 0; + nm_bridge_vlan_get_vid_range (vlan, &v_start, &v_end); if (v_start == vid_start && v_end == vid_end) { g_ptr_array_remove_index (priv->vlans, i); @@ -440,8 +442,8 @@ nm_setting_bridge_port_init (NMSettingBridgePort *setting) priv->vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref); - priv->priority = NM_BR_PORT_DEF_PRIORITY; - priv->path_cost = NM_BR_PORT_DEF_PATH_COST; + priv->priority = NM_BRIDGE_PORT_PRIORITY_DEF; + priv->path_cost = NM_BRIDGE_PORT_PATH_COST_DEF; } /** @@ -498,7 +500,7 @@ nm_setting_bridge_port_class_init (NMSettingBridgePortClass *klass) */ obj_properties[PROP_PRIORITY] = g_param_spec_uint (NM_SETTING_BRIDGE_PORT_PRIORITY, "", "", - 0, NM_BR_PORT_MAX_PRIORITY, NM_BR_PORT_DEF_PRIORITY, + NM_BRIDGE_PORT_PRIORITY_MIN, NM_BRIDGE_PORT_PRIORITY_MAX, NM_BRIDGE_PORT_PRIORITY_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -519,7 +521,7 @@ nm_setting_bridge_port_class_init (NMSettingBridgePortClass *klass) */ obj_properties[PROP_PATH_COST] = g_param_spec_uint (NM_SETTING_BRIDGE_PORT_PATH_COST, "", "", - 0, NM_BR_PORT_MAX_PATH_COST, NM_BR_PORT_DEF_PATH_COST, + NM_BRIDGE_PORT_PATH_COST_MIN, NM_BRIDGE_PORT_PATH_COST_MAX, NM_BRIDGE_PORT_PATH_COST_DEF, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); diff --git a/libnm-core/nm-setting-bridge.c b/libnm-core/nm-setting-bridge.c index 957520f4..2ead2739 100644 --- a/libnm-core/nm-setting-bridge.c +++ b/libnm-core/nm-setting-bridge.c @@ -22,18 +22,6 @@ * necessary for bridging connections. **/ -#define BRIDGE_AGEING_TIME_DEFAULT 300 -#define BRIDGE_FORWARD_DELAY_DEFAULT 15 -#define BRIDGE_HELLO_TIME_DEFAULT 2 -#define BRIDGE_MAX_AGE_DEFAULT 20 -#define BRIDGE_MULTICAST_QUERIER_DEFAULT FALSE -#define BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEFAULT FALSE -#define BRIDGE_MULTICAST_SNOOPING_DEFAULT TRUE -#define BRIDGE_PRIORITY_DEFAULT 0x8000 -#define BRIDGE_STP_DEFAULT TRUE -#define BRIDGE_VLAN_DEFAULT_PVID_DEFAULT 1 -#define BRIDGE_VLAN_STATS_ENABLED_DEFAULT FALSE - /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingBridge, @@ -46,10 +34,19 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingBridge, PROP_AGEING_TIME, PROP_GROUP_ADDRESS, PROP_GROUP_FORWARD_MASK, + PROP_MULTICAST_HASH_MAX, + PROP_MULTICAST_LAST_MEMBER_COUNT, + PROP_MULTICAST_LAST_MEMBER_INTERVAL, + PROP_MULTICAST_MEMBERSHIP_INTERVAL, PROP_MULTICAST_ROUTER, PROP_MULTICAST_QUERIER, + PROP_MULTICAST_QUERIER_INTERVAL, + PROP_MULTICAST_QUERY_INTERVAL, + PROP_MULTICAST_QUERY_RESPONSE_INTERVAL, PROP_MULTICAST_QUERY_USE_IFADDR, PROP_MULTICAST_SNOOPING, + PROP_MULTICAST_STARTUP_QUERY_COUNT, + PROP_MULTICAST_STARTUP_QUERY_INTERVAL, PROP_VLAN_FILTERING, PROP_VLAN_DEFAULT_PVID, PROP_VLAN_PROTOCOL, @@ -63,7 +60,16 @@ typedef struct { char * multicast_router; char * group_address; char * vlan_protocol; + guint64 multicast_last_member_interval; + guint64 multicast_membership_interval; + guint64 multicast_querier_interval; + guint64 multicast_query_interval; + guint64 multicast_query_response_interval; + guint64 multicast_startup_query_interval; guint32 ageing_time; + guint32 multicast_hash_max; + guint32 multicast_last_member_count; + guint32 multicast_startup_query_count; guint16 priority; guint16 forward_delay; guint16 hello_time; @@ -1010,6 +1016,150 @@ nm_setting_bridge_get_multicast_querier (const NMSettingBridge *setting) return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_querier; } +/** + * nm_setting_bridge_get_multicast_hash_max: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-hash-max property of the setting + * + * Since 1.26 + **/ +guint32 +nm_setting_bridge_get_multicast_hash_max (const NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_hash_max; +} + +/** + * nm_setting_bridge_get_multicast_last_member_count: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-last-member-count property of the setting + * + * Since 1.26 + **/ +guint32 +nm_setting_bridge_get_multicast_last_member_count (const NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_last_member_count; +} + +/** + * nm_setting_bridge_get_multicast_last_member_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-last-member-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_last_member_interval (const NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_last_member_interval; +} + +/** + * nm_setting_bridge_get_multicast_membership_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-membership-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_membership_interval (const NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_membership_interval; +} + +/** + * nm_setting_bridge_get_multicast_querier_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-querier-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_querier_interval (const NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_querier_interval; +} + +/** + * nm_setting_bridge_get_multicast_query_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-query-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_query_interval (const NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_query_interval; +} + +/** + * nm_setting_bridge_get_multicast_query_response_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-query-response-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_query_response_interval (const NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_query_response_interval; +} + +/** + * nm_setting_bridge_get_multicast_startup_query_count: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-query-response-interval property of the setting + * + * Since 1.26 + **/ +guint32 +nm_setting_bridge_get_multicast_startup_query_count (const NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_startup_query_count; +} + +/** + * nm_setting_bridge_get_multicast_startup_query_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-startup-query-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_startup_query_interval (const NMSettingBridge *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_startup_query_interval; +} + /*****************************************************************************/ static gboolean @@ -1050,32 +1200,32 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) } if (!check_range (priv->forward_delay, - NM_BR_MIN_FORWARD_DELAY, - NM_BR_MAX_FORWARD_DELAY, + NM_BRIDGE_FORWARD_DELAY_MIN, + NM_BRIDGE_FORWARD_DELAY_MAX, !priv->stp, NM_SETTING_BRIDGE_FORWARD_DELAY, error)) return FALSE; if (!check_range (priv->hello_time, - NM_BR_MIN_HELLO_TIME, - NM_BR_MAX_HELLO_TIME, + NM_BRIDGE_HELLO_TIME_MIN, + NM_BRIDGE_HELLO_TIME_MAX, !priv->stp, NM_SETTING_BRIDGE_HELLO_TIME, error)) return FALSE; if (!check_range (priv->max_age, - NM_BR_MIN_MAX_AGE, - NM_BR_MAX_MAX_AGE, + NM_BRIDGE_MAX_AGE_MIN, + NM_BRIDGE_MAX_AGE_MAX, !priv->stp, NM_SETTING_BRIDGE_MAX_AGE, error)) return FALSE; if (!check_range (priv->ageing_time, - NM_BR_MIN_AGEING_TIME, - NM_BR_MAX_AGEING_TIME, + NM_BRIDGE_AGEING_TIME_MIN, + NM_BRIDGE_AGEING_TIME_MAX, !priv->stp, NM_SETTING_BRIDGE_AGEING_TIME, error)) @@ -1122,31 +1272,27 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) return FALSE; } - if (priv->multicast_router) { - if (!NM_IN_STRSET (priv->multicast_router, - "auto", - "enabled", - "disabled")) { - g_set_error_literal (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_INVALID_PROPERTY, - _("is not a valid option")); - g_prefix_error (error, "%s.%s: ", NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_BRIDGE_MULTICAST_ROUTER); - return FALSE; - } + if (!NM_IN_STRSET (priv->multicast_router, + NULL, + "auto", + "enabled", + "disabled")) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is not a valid option")); + g_prefix_error (error, "%s.%s: ", NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_BRIDGE_MULTICAST_ROUTER); + return FALSE; + } - if ( NM_IN_STRSET (priv->multicast_router, - "auto", - "enabled") - && !priv->multicast_snooping) { - g_set_error (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_INVALID_PROPERTY, - _("'%s' option requires '%s' option to be enabled"), - NM_SETTING_BRIDGE_MULTICAST_ROUTER, NM_SETTING_BRIDGE_MULTICAST_SNOOPING); - g_prefix_error (error, "%s.%s: ", NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_BRIDGE_MULTICAST_ROUTER); - return FALSE; - } + if (!nm_utils_is_power_of_two (priv->multicast_hash_max)) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option must be a power of 2"), + NM_SETTING_BRIDGE_MULTICAST_HASH_MAX); + g_prefix_error (error, "%s.%s: ", NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_BRIDGE_MULTICAST_HASH_MAX); + return FALSE; } /* Failures from here on are NORMALIZABLE... */ @@ -1235,6 +1381,18 @@ get_property (GObject *object, guint prop_id, case PROP_GROUP_FORWARD_MASK: g_value_set_uint (value, priv->group_forward_mask); break; + case PROP_MULTICAST_HASH_MAX: + g_value_set_uint (value, priv->multicast_hash_max); + break; + case PROP_MULTICAST_LAST_MEMBER_COUNT: + g_value_set_uint (value, priv->multicast_last_member_count); + break; + case PROP_MULTICAST_LAST_MEMBER_INTERVAL: + g_value_set_uint64 (value, priv->multicast_last_member_interval); + break; + case PROP_MULTICAST_MEMBERSHIP_INTERVAL: + g_value_set_uint64 (value, priv->multicast_membership_interval); + break; case PROP_MULTICAST_SNOOPING: g_value_set_boolean (value, priv->multicast_snooping); break; @@ -1244,9 +1402,24 @@ get_property (GObject *object, guint prop_id, case PROP_MULTICAST_QUERIER: g_value_set_boolean (value, priv->multicast_querier); break; + case PROP_MULTICAST_QUERIER_INTERVAL: + g_value_set_uint64 (value, priv->multicast_querier_interval); + break; + case PROP_MULTICAST_QUERY_INTERVAL: + g_value_set_uint64 (value, priv->multicast_query_interval); + break; + case PROP_MULTICAST_QUERY_RESPONSE_INTERVAL: + g_value_set_uint64 (value, priv->multicast_query_response_interval); + break; case PROP_MULTICAST_QUERY_USE_IFADDR: g_value_set_boolean (value, priv->multicast_query_use_ifaddr); break; + case PROP_MULTICAST_STARTUP_QUERY_COUNT: + g_value_set_uint (value, priv->multicast_startup_query_count); + break; + case PROP_MULTICAST_STARTUP_QUERY_INTERVAL: + g_value_set_uint64 (value, priv->multicast_startup_query_interval); + break; case PROP_VLAN_FILTERING: g_value_set_boolean (value, priv->vlan_filtering); break; @@ -1308,6 +1481,18 @@ set_property (GObject *object, guint prop_id, case PROP_GROUP_FORWARD_MASK: priv->group_forward_mask = (guint16) g_value_get_uint (value); break; + case PROP_MULTICAST_HASH_MAX: + priv->multicast_hash_max = g_value_get_uint (value); + break; + case PROP_MULTICAST_LAST_MEMBER_COUNT: + priv->multicast_last_member_count = g_value_get_uint (value); + break; + case PROP_MULTICAST_LAST_MEMBER_INTERVAL: + priv->multicast_last_member_interval = g_value_get_uint64 (value); + break; + case PROP_MULTICAST_MEMBERSHIP_INTERVAL: + priv->multicast_membership_interval = g_value_get_uint64 (value); + break; case PROP_MULTICAST_SNOOPING: priv->multicast_snooping = g_value_get_boolean (value); break; @@ -1318,9 +1503,24 @@ set_property (GObject *object, guint prop_id, case PROP_MULTICAST_QUERIER: priv->multicast_querier = g_value_get_boolean (value); break; + case PROP_MULTICAST_QUERIER_INTERVAL: + priv->multicast_querier_interval = g_value_get_uint64 (value); + break; + case PROP_MULTICAST_QUERY_INTERVAL: + priv->multicast_query_interval = g_value_get_uint64 (value); + break; + case PROP_MULTICAST_QUERY_RESPONSE_INTERVAL: + priv->multicast_query_response_interval = g_value_get_uint64 (value); + break; case PROP_MULTICAST_QUERY_USE_IFADDR: priv->multicast_query_use_ifaddr = g_value_get_boolean (value); break; + case PROP_MULTICAST_STARTUP_QUERY_COUNT: + priv->multicast_startup_query_count = g_value_get_uint (value); + break; + case PROP_MULTICAST_STARTUP_QUERY_INTERVAL: + priv->multicast_startup_query_interval = g_value_get_uint64 (value); + break; case PROP_VLAN_FILTERING: priv->vlan_filtering = g_value_get_boolean (value); break; @@ -1355,17 +1555,27 @@ nm_setting_bridge_init (NMSettingBridge *setting) priv->vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref); - priv->ageing_time = BRIDGE_AGEING_TIME_DEFAULT; - priv->forward_delay = BRIDGE_FORWARD_DELAY_DEFAULT; - priv->hello_time = BRIDGE_HELLO_TIME_DEFAULT; - priv->max_age = BRIDGE_MAX_AGE_DEFAULT; - priv->multicast_snooping = BRIDGE_MULTICAST_SNOOPING_DEFAULT; - priv->priority = BRIDGE_PRIORITY_DEFAULT; - priv->stp = BRIDGE_STP_DEFAULT; - priv->vlan_default_pvid = BRIDGE_VLAN_DEFAULT_PVID_DEFAULT; - priv->vlan_stats_enabled = BRIDGE_VLAN_STATS_ENABLED_DEFAULT; - priv->multicast_query_use_ifaddr = BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEFAULT; - priv->multicast_querier = BRIDGE_MULTICAST_QUERIER_DEFAULT; + priv->ageing_time = NM_BRIDGE_AGEING_TIME_DEF; + priv->forward_delay = NM_BRIDGE_FORWARD_DELAY_DEF; + priv->hello_time = NM_BRIDGE_HELLO_TIME_DEF; + priv->max_age = NM_BRIDGE_MAX_AGE_DEF; + priv->multicast_last_member_count = NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF; + priv->multicast_last_member_interval = NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF; + priv->multicast_membership_interval = NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF; + priv->multicast_hash_max = NM_BRIDGE_MULTICAST_HASH_MAX_DEF; + priv->multicast_snooping = NM_BRIDGE_MULTICAST_SNOOPING_DEF; + priv->priority = NM_BRIDGE_PRIORITY_DEF; + priv->stp = NM_BRIDGE_STP_DEF; + priv->vlan_default_pvid = NM_BRIDGE_VLAN_DEFAULT_PVID_DEF; + priv->multicast_query_interval = NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF; + priv->multicast_query_response_interval = NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF; + priv->multicast_querier_interval = NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF; + priv->multicast_startup_query_count = NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF; + priv->multicast_startup_query_interval = NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF; + + nm_assert (priv->multicast_querier == NM_BRIDGE_MULTICAST_QUERIER_DEF); + nm_assert (priv->multicast_query_use_ifaddr == NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF); + nm_assert (priv->vlan_stats_enabled == NM_BRIDGE_VLAN_STATS_ENABLED_DEF); } /** @@ -1461,7 +1671,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_STP] = g_param_spec_boolean (NM_SETTING_BRIDGE_STP, "", "", - BRIDGE_STP_DEFAULT, + NM_BRIDGE_STP_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -1483,7 +1693,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_PRIORITY] = g_param_spec_uint (NM_SETTING_BRIDGE_PRIORITY, "", "", - 0, G_MAXUINT16, BRIDGE_PRIORITY_DEFAULT, + NM_BRIDGE_PRIORITY_MIN, NM_BRIDGE_PRIORITY_MAX, NM_BRIDGE_PRIORITY_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -1503,7 +1713,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_FORWARD_DELAY] = g_param_spec_uint (NM_SETTING_BRIDGE_FORWARD_DELAY, "", "", - 0, NM_BR_MAX_FORWARD_DELAY, BRIDGE_FORWARD_DELAY_DEFAULT, + 0, NM_BRIDGE_FORWARD_DELAY_MAX, NM_BRIDGE_FORWARD_DELAY_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -1523,7 +1733,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_HELLO_TIME] = g_param_spec_uint (NM_SETTING_BRIDGE_HELLO_TIME, "", "", - 0, NM_BR_MAX_HELLO_TIME, BRIDGE_HELLO_TIME_DEFAULT, + 0, NM_BRIDGE_HELLO_TIME_MAX, NM_BRIDGE_HELLO_TIME_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -1543,7 +1753,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_MAX_AGE] = g_param_spec_uint (NM_SETTING_BRIDGE_MAX_AGE, "", "", - 0, NM_BR_MAX_MAX_AGE, BRIDGE_MAX_AGE_DEFAULT, + 0, NM_BRIDGE_MAX_AGE_MAX, NM_BRIDGE_MAX_AGE_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -1563,7 +1773,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_AGEING_TIME] = g_param_spec_uint (NM_SETTING_BRIDGE_AGEING_TIME, "", "", - NM_BR_MIN_AGEING_TIME, NM_BR_MAX_AGEING_TIME, BRIDGE_AGEING_TIME_DEFAULT, + NM_BRIDGE_AGEING_TIME_MIN, NM_BRIDGE_AGEING_TIME_MAX, NM_BRIDGE_AGEING_TIME_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -1607,7 +1817,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_MULTICAST_SNOOPING] = g_param_spec_boolean (NM_SETTING_BRIDGE_MULTICAST_SNOOPING, "", "", - BRIDGE_MULTICAST_SNOOPING_DEFAULT, + NM_BRIDGE_MULTICAST_SNOOPING_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -1652,7 +1862,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_VLAN_DEFAULT_PVID] = g_param_spec_uint (NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID, "", "", - 0, NM_BRIDGE_VLAN_VID_MAX, BRIDGE_VLAN_DEFAULT_PVID_DEFAULT, + 0, NM_BRIDGE_VLAN_VID_MAX, NM_BRIDGE_VLAN_DEFAULT_PVID_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -1767,7 +1977,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_VLAN_STATS_ENABLED] = g_param_spec_boolean (NM_SETTING_BRIDGE_VLAN_STATS_ENABLED, "", "", - BRIDGE_VLAN_STATS_ENABLED_DEFAULT, + NM_BRIDGE_VLAN_STATS_ENABLED_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -1775,8 +1985,8 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) /** * NMSettingBridge:multicast-router: * - * Sets bridge's multicast router. - * multicast-snooping must be enabled for this option to work. + * Sets bridge's multicast router. Multicast-snooping must be enabled + * for this option to work. * * Supported values are: 'auto', 'disabled', 'enabled'. * If not specified the default value is 'auto'. @@ -1816,7 +2026,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_MULTICAST_QUERY_USE_IFADDR] = g_param_spec_boolean (NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR, "", "", - BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEFAULT, + NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); @@ -1838,11 +2048,209 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass) */ obj_properties[PROP_MULTICAST_QUERIER] = g_param_spec_boolean (NM_SETTING_BRIDGE_MULTICAST_QUERIER, "", "", - BRIDGE_MULTICAST_QUERIER_DEFAULT, + NM_BRIDGE_MULTICAST_QUERIER_DEF, G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + /** + * NMSettingBridge:multicast-hash-max: + * + * Set maximum size of multicast hash table (value must be a power of 2). + **/ + /* ---ifcfg-rh--- + * property: multicast-hash-max + * variable: BRIDGING_OPTS: multicast_hash_max= + * default: 4096 + * example: BRIDGING_OPTS="multicast_hash_max=8192" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_HASH_MAX] = + g_param_spec_uint (NM_SETTING_BRIDGE_MULTICAST_HASH_MAX, "", "", + NM_BRIDGE_MULTICAST_HASH_MAX_MIN, NM_BRIDGE_MULTICAST_HASH_MAX_MAX, NM_BRIDGE_MULTICAST_HASH_MAX_DEF, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-last-member-count: + * + * Set the number of queries the bridge will send before + * stopping forwarding a multicast group after a "leave" + * message has been received. + **/ + /* ---ifcfg-rh--- + * property: multicast-last-member-count + * variable: BRIDGING_OPTS: multicast_last_member_count= + * default: 2 + * example: BRIDGING_OPTS="multicast_last_member_count=4" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_LAST_MEMBER_COUNT] = + g_param_spec_uint (NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT, "", "", + NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_MIN, NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_MAX, NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-last-member-interval: + * + * Set interval (in deciseconds) between queries to find remaining + * members of a group, after a "leave" message is received. + **/ + /* ---ifcfg-rh--- + * property: multicast-last-member-interval + * variable: BRIDGING_OPTS: multicast_last_member_interval= + * default: 100 + * example: BRIDGING_OPTS="multicast_last_member_interval=200" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_LAST_MEMBER_INTERVAL] = + g_param_spec_uint64 (NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL, "", "", + NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_MIN, NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_MAX, NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-membership-interval: + * + * Set delay (in deciseconds) after which the bridge will + * leave a group, if no membership reports for this + * group are received. + **/ + /* ---ifcfg-rh--- + * property: multicast-membership-interval + * variable: BRIDGING_OPTS: multicast_membership_interval= + * default: 26000 + * example: BRIDGING_OPTS="multicast_membership_interval=16000" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_MEMBERSHIP_INTERVAL] = + g_param_spec_uint64 (NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL, "", "", + NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_MIN, NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_MAX, NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-querier-interval: + * + * If no queries are seen after this delay (in deciseconds) has passed, + * the bridge will start to send its own queries. + **/ + /* ---ifcfg-rh--- + * property: multicast-querier-interval + * variable: BRIDGING_OPTS: multicast_querier_interval= + * default: 25500 + * example: BRIDGING_OPTS="multicast_querier_interval=20000" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_QUERIER_INTERVAL] = + g_param_spec_uint64 (NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL, "", "", + NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_MIN, NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_MAX, NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-query-interval: + * + * Interval (in deciseconds) between queries sent + * by the bridge after the end of the startup phase. + **/ + /* ---ifcfg-rh--- + * property: multicast-query-interval + * variable: BRIDGING_OPTS: multicast_query_interval= + * default: 12500 + * example: BRIDGING_OPTS="multicast_query_interval=22500" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_QUERY_INTERVAL] = + g_param_spec_uint64 (NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL, "", "", + NM_BRIDGE_MULTICAST_QUERY_INTERVAL_MIN, NM_BRIDGE_MULTICAST_QUERY_INTERVAL_MAX, NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-query-response-interval: + * + * Set the Max Response Time/Max Response Delay + * (in deciseconds) for IGMP/MLD queries sent by the bridge. + **/ + /* ---ifcfg-rh--- + * property: multicast-query-response-interval + * variable: BRIDGING_OPTS: multicast_query_response_interval= + * default: 1000 + * example: BRIDGING_OPTS="multicast_query_response_interval=2000" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_QUERY_RESPONSE_INTERVAL] = + g_param_spec_uint64 (NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL, "", "", + NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_MIN, NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_MAX, NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-startup-query-count: + * + * Set the number of IGMP queries to send during startup phase. + **/ + /* ---ifcfg-rh--- + * property: multicast-startup-query-count + * variable: BRIDGING_OPTS: multicast_startup_query_count= + * default: 2 + * example: BRIDGING_OPTS="multicast_startup_query_count=4" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_STARTUP_QUERY_COUNT] = + g_param_spec_uint (NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT, "", "", + NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_MIN, NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_MAX, NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-startup-query-interval: + * + * Sets the time (in deciseconds) between queries sent out + * at startup to determine membership information. + **/ + /* ---ifcfg-rh--- + * property: multicast-startup-query-interval + * variable: BRIDGING_OPTS: multicast_startup_query_interval= + * default: 3125 + * example: BRIDGING_OPTS="multicast_startup_query_interval=4000" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_STARTUP_QUERY_INTERVAL] = + g_param_spec_uint64 (NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL, "", "", + NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_MIN, NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_MAX, NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF, + G_PARAM_READWRITE | + NM_SETTING_PARAM_INFERRABLE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); _nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_BRIDGE, diff --git a/libnm-core/nm-setting-bridge.h b/libnm-core/nm-setting-bridge.h index 6bd5100f..5f0ef0b3 100644 --- a/libnm-core/nm-setting-bridge.h +++ b/libnm-core/nm-setting-bridge.h @@ -23,24 +23,33 @@ G_BEGIN_DECLS #define NM_SETTING_BRIDGE_SETTING_NAME "bridge" -#define NM_SETTING_BRIDGE_MAC_ADDRESS "mac-address" -#define NM_SETTING_BRIDGE_STP "stp" -#define NM_SETTING_BRIDGE_PRIORITY "priority" -#define NM_SETTING_BRIDGE_FORWARD_DELAY "forward-delay" -#define NM_SETTING_BRIDGE_HELLO_TIME "hello-time" -#define NM_SETTING_BRIDGE_MAX_AGE "max-age" -#define NM_SETTING_BRIDGE_AGEING_TIME "ageing-time" -#define NM_SETTING_BRIDGE_GROUP_FORWARD_MASK "group-forward-mask" -#define NM_SETTING_BRIDGE_MULTICAST_SNOOPING "multicast-snooping" -#define NM_SETTING_BRIDGE_MULTICAST_ROUTER "multicast-router" -#define NM_SETTING_BRIDGE_MULTICAST_QUERIER "multicast-querier" -#define NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR "multicast-query-use-ifaddr" -#define NM_SETTING_BRIDGE_VLAN_FILTERING "vlan-filtering" -#define NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID "vlan-default-pvid" -#define NM_SETTING_BRIDGE_VLANS "vlans" -#define NM_SETTING_BRIDGE_GROUP_ADDRESS "group-address" -#define NM_SETTING_BRIDGE_VLAN_PROTOCOL "vlan-protocol" -#define NM_SETTING_BRIDGE_VLAN_STATS_ENABLED "vlan-stats-enabled" +#define NM_SETTING_BRIDGE_MAC_ADDRESS "mac-address" +#define NM_SETTING_BRIDGE_STP "stp" +#define NM_SETTING_BRIDGE_PRIORITY "priority" +#define NM_SETTING_BRIDGE_FORWARD_DELAY "forward-delay" +#define NM_SETTING_BRIDGE_HELLO_TIME "hello-time" +#define NM_SETTING_BRIDGE_MAX_AGE "max-age" +#define NM_SETTING_BRIDGE_AGEING_TIME "ageing-time" +#define NM_SETTING_BRIDGE_GROUP_FORWARD_MASK "group-forward-mask" +#define NM_SETTING_BRIDGE_MULTICAST_HASH_MAX "multicast-hash-max" +#define NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT "multicast-last-member-count" +#define NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL "multicast-last-member-interval" +#define NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL "multicast-membership-interval" +#define NM_SETTING_BRIDGE_MULTICAST_SNOOPING "multicast-snooping" +#define NM_SETTING_BRIDGE_MULTICAST_ROUTER "multicast-router" +#define NM_SETTING_BRIDGE_MULTICAST_QUERIER "multicast-querier" +#define NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL "multicast-querier-interval" +#define NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL "multicast-query-interval" +#define NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL "multicast-query-response-interval" +#define NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR "multicast-query-use-ifaddr" +#define NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT "multicast-startup-query-count" +#define NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL "multicast-startup-query-interval" +#define NM_SETTING_BRIDGE_VLAN_FILTERING "vlan-filtering" +#define NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID "vlan-default-pvid" +#define NM_SETTING_BRIDGE_VLANS "vlans" +#define NM_SETTING_BRIDGE_GROUP_ADDRESS "group-address" +#define NM_SETTING_BRIDGE_VLAN_PROTOCOL "vlan-protocol" +#define NM_SETTING_BRIDGE_VLAN_STATS_ENABLED "vlan-stats-enabled" #define NM_BRIDGE_VLAN_VID_MIN 1 #define NM_BRIDGE_VLAN_VID_MAX 4094 @@ -140,6 +149,33 @@ gboolean nm_setting_bridge_get_multicast_query_use_ifaddr (const NMSettingBridge NM_AVAILABLE_IN_1_24 gboolean nm_setting_bridge_get_multicast_querier (const NMSettingBridge *setting); +NM_AVAILABLE_IN_1_26 +guint32 nm_setting_bridge_get_multicast_hash_max (const NMSettingBridge *setting); + +NM_AVAILABLE_IN_1_26 +guint32 nm_setting_bridge_get_multicast_last_member_count (const NMSettingBridge *setting); + +NM_AVAILABLE_IN_1_26 +guint64 nm_setting_bridge_get_multicast_last_member_interval (const NMSettingBridge *setting); + +NM_AVAILABLE_IN_1_26 +guint64 nm_setting_bridge_get_multicast_membership_interval (const NMSettingBridge *setting); + +NM_AVAILABLE_IN_1_26 +guint64 nm_setting_bridge_get_multicast_querier_interval (const NMSettingBridge *setting); + +NM_AVAILABLE_IN_1_26 +guint64 nm_setting_bridge_get_multicast_query_interval (const NMSettingBridge *setting); + +NM_AVAILABLE_IN_1_26 +guint64 nm_setting_bridge_get_multicast_query_response_interval (const NMSettingBridge *setting); + +NM_AVAILABLE_IN_1_26 +guint32 nm_setting_bridge_get_multicast_startup_query_count (const NMSettingBridge *setting); + +NM_AVAILABLE_IN_1_26 +guint64 nm_setting_bridge_get_multicast_startup_query_interval (const NMSettingBridge *setting); + G_END_DECLS #endif /* __NM_SETTING_BRIDGE_H__ */ diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c index 2e8fa37a..018129be 100644 --- a/libnm-core/nm-setting-connection.c +++ b/libnm-core/nm-setting-connection.c @@ -8,6 +8,7 @@ #include "nm-setting-connection.h" +#include "nm-libnm-core-intern/nm-common-macros.h" #include "nm-utils.h" #include "nm-utils-private.h" #include "nm-core-enum-types.h" @@ -16,6 +17,7 @@ #include "nm-setting-bridge.h" #include "nm-setting-team.h" #include "nm-setting-vlan.h" +#include "systemd/nm-sd-utils-shared.h" /** * SECTION:nm-setting-connection @@ -63,6 +65,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingConnection, PROP_STABLE_ID, PROP_AUTH_RETRIES, PROP_WAIT_DEVICE_TIMEOUT, + PROP_MUD_URL, ); typedef struct { @@ -76,6 +79,7 @@ typedef struct { char *master; char *slave_type; char *zone; + char *mud_url; guint64 timestamp; int autoconnect_priority; int autoconnect_retries; @@ -751,6 +755,23 @@ nm_setting_connection_get_secondary (NMSettingConnection *setting, guint32 idx) } /** + * nm_setting_connection_get_mud_url: + * @setting: the #NMSettingConnection + * + * Returns the value contained in the #NMSettingConnection:mud-url + * property. + * + * Since: 1.26 + **/ +const char * +nm_setting_connection_get_mud_url (NMSettingConnection *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_CONNECTION (setting), NULL); + + return NM_SETTING_CONNECTION_GET_PRIVATE (setting)->mud_url; +} + +/** * nm_setting_connection_add_secondary: * @setting: the #NMSettingConnection * @sec_uuid: the secondary connection UUID to add @@ -1211,6 +1232,31 @@ after_interface_name: return FALSE; } + if (priv->mud_url) { + if (!priv->mud_url[0]) { + g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), NM_SETTING_CONNECTION_MUD_URL); + return FALSE; + } + if (nm_streq (priv->mud_url, NM_CONNECTION_MUD_URL_NONE)) { + /* pass */ + } else { + if (strlen (priv->mud_url) > 255) { + g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("DHCP option cannot be longer than 255 characters")); + g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), NM_SETTING_CONNECTION_MUD_URL); + return FALSE; + } + if (!nm_sd_http_url_is_valid_https (priv->mud_url)) { + g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("MUD URL is not a valid URL")); + g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), NM_SETTING_CONNECTION_MUD_URL); + return FALSE; + } + } + } + /* *** errors above here should be always fatal, below NORMALIZABLE_ERROR *** */ if (!priv->uuid) { @@ -1467,6 +1513,9 @@ get_property (GObject *object, guint prop_id, case PROP_WAIT_DEVICE_TIMEOUT: g_value_set_int (value, priv->wait_device_timeout); break; + case PROP_MUD_URL: + g_value_set_string (value, priv->mud_url); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1562,6 +1611,10 @@ set_property (GObject *object, guint prop_id, case PROP_WAIT_DEVICE_TIMEOUT: priv->wait_device_timeout = g_value_get_int (value); break; + case PROP_MUD_URL: + g_free (priv->mud_url); + priv->mud_url = g_value_dup_string (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1611,6 +1664,7 @@ finalize (GObject *object) g_free (priv->zone); g_free (priv->master); g_free (priv->slave_type); + g_free (priv->mud_url); g_slist_free_full (priv->permissions, (GDestroyNotify) permission_free); g_slist_free_full (priv->secondaries, g_free); @@ -2286,6 +2340,35 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:mud-url: + * + * If configured, set to a Manufacturer Usage Description (MUD) URL that points + * to manufacturer-recommended network policies for IoT devices. It is transmitted + * as a DHCPv4 or DHCPv6 option. The value must be a valid URL starting with "https://". + * + * The special value "none" is allowed to indicate that no MUD URL is used. + * + * If the per-profile value is unspecified (the default), a global connection default gets + * consulted. If still unspecified, the ultimate default is "none". + * + * Since: 1.26 + **/ + /* ---ifcfg-rh--- + * property: mud-url + * variable: MUD_URL + * values: a valid URL that points to recommended policy for this device + * description: MUD_URL to be sent by device (See RFC 8520). + * example: https://yourdevice.example.com/model.json + * ---end--- + */ + obj_properties[PROP_MUD_URL] = + g_param_spec_string (NM_SETTING_CONNECTION_MUD_URL, "", "", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); _nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_CONNECTION, diff --git a/libnm-core/nm-setting-connection.h b/libnm-core/nm-setting-connection.h index b93e9b4d..9a6f09a8 100644 --- a/libnm-core/nm-setting-connection.h +++ b/libnm-core/nm-setting-connection.h @@ -52,6 +52,7 @@ G_BEGIN_DECLS #define NM_SETTING_CONNECTION_MDNS "mdns" #define NM_SETTING_CONNECTION_LLMNR "llmnr" #define NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT "wait-device-timeout" +#define NM_SETTING_CONNECTION_MUD_URL "mud-url" /* Types for property values */ /** @@ -204,6 +205,9 @@ NMSettingConnectionLlmnr nm_setting_connection_get_llmnr (NMSettingConnection * NM_AVAILABLE_IN_1_20 gint32 nm_setting_connection_get_wait_device_timeout (NMSettingConnection *setting); +NM_AVAILABLE_IN_1_26 +const char *nm_setting_connection_get_mud_url (NMSettingConnection *setting); + G_END_DECLS #endif /* __NM_SETTING_CONNECTION_H__ */ diff --git a/libnm-core/nm-setting-ethtool.c b/libnm-core/nm-setting-ethtool.c index a8976b69..17fbdc62 100644 --- a/libnm-core/nm-setting-ethtool.c +++ b/libnm-core/nm-setting-ethtool.c @@ -22,6 +22,21 @@ /*****************************************************************************/ +static const GVariantType * +get_variant_type_from_ethtool_id (NMEthtoolID ethtool_id) +{ + if (nm_ethtool_id_is_feature (ethtool_id)) + return G_VARIANT_TYPE_BOOLEAN; + + if ( nm_ethtool_id_is_coalesce (ethtool_id) + || nm_ethtool_id_is_ring (ethtool_id)) + return G_VARIANT_TYPE_UINT32; + + return NULL; +} + +/*****************************************************************************/ + /** * nm_ethtool_optname_is_feature: * @optname: (allow-none): the option name to check @@ -42,6 +57,38 @@ nm_ethtool_optname_is_feature (const char *optname) return optname && nm_ethtool_id_is_feature (nm_ethtool_id_get_by_name (optname)); } +/** + * nm_ethtool_optname_is_coalesce: + * @optname: (allow-none): the option name to check + * + * Checks whether @optname is a valid option name for a coalesce setting. + * + * %Returns: %TRUE, if @optname is valid + * + * Since: 1.26 + */ +gboolean +nm_ethtool_optname_is_coalesce (const char *optname) +{ + return optname && nm_ethtool_id_is_coalesce (nm_ethtool_id_get_by_name (optname)); +} + +/** + * nm_ethtool_optname_is_ring: + * @optname: (allow-none): the option name to check + * + * Checks whether @optname is a valid option name for a ring setting. + * + * %Returns: %TRUE, if @optname is valid + * + * Since: 1.26 + */ +gboolean +nm_ethtool_optname_is_ring (const char *optname) +{ + return optname && nm_ethtool_id_is_ring (nm_ethtool_id_get_by_name (optname)); +} + /*****************************************************************************/ /** @@ -65,14 +112,6 @@ G_DEFINE_TYPE (NMSettingEthtool, nm_setting_ethtool, NM_TYPE_SETTING) /*****************************************************************************/ -static void -_notify_attributes (NMSettingEthtool *self) -{ - _nm_setting_gendata_notify (NM_SETTING (self), TRUE); -} - -/*****************************************************************************/ - /** * nm_setting_ethtool_get_feature: * @setting: the #NMSettingEthtool @@ -88,24 +127,23 @@ _notify_attributes (NMSettingEthtool *self) * is enabled, disabled, or left untouched. * * Since: 1.14 + * + * Deprecated: 1.26: use nm_setting_option_get_boolean() instead. */ NMTernary nm_setting_ethtool_get_feature (NMSettingEthtool *setting, const char *optname) { - GVariant *v; + gboolean v; g_return_val_if_fail (NM_IS_SETTING_ETHTOOL (setting), NM_TERNARY_DEFAULT); g_return_val_if_fail (optname && nm_ethtool_optname_is_feature (optname), NM_TERNARY_DEFAULT); - v = nm_setting_gendata_get (NM_SETTING (setting), optname); - if ( v - && g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) { - return g_variant_get_boolean (v) - ? NM_TERNARY_TRUE - : NM_TERNARY_FALSE; - } - return NM_TERNARY_DEFAULT; + if (!nm_setting_option_get_boolean (NM_SETTING (setting), optname, &v)) + return NM_TERNARY_DEFAULT; + return v + ? NM_TERNARY_TRUE + : NM_TERNARY_FALSE; } /** @@ -121,49 +159,24 @@ nm_setting_ethtool_get_feature (NMSettingEthtool *setting, * nm_ethtool_optname_is_feature(). * * Since: 1.14 + * + * Deprecated: 1.26: use nm_setting_option_set() or nm_setting_option_set_boolean() instead. */ void nm_setting_ethtool_set_feature (NMSettingEthtool *setting, const char *optname, NMTernary value) { - GHashTable *hash; - GVariant *v; - g_return_if_fail (NM_IS_SETTING_ETHTOOL (setting)); g_return_if_fail (optname && nm_ethtool_optname_is_feature (optname)); g_return_if_fail (NM_IN_SET (value, NM_TERNARY_DEFAULT, NM_TERNARY_FALSE, NM_TERNARY_TRUE)); - hash = _nm_setting_gendata_hash (NM_SETTING (setting), - value != NM_TERNARY_DEFAULT); - - if (value == NM_TERNARY_DEFAULT) { - if (hash) { - if (g_hash_table_remove (hash, optname)) - _notify_attributes (setting); - } - return; - } - - v = g_hash_table_lookup (hash, optname); - if ( v - && g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) { - if (g_variant_get_boolean (v)) { - if (value == NM_TERNARY_TRUE) - return; - } else { - if (value == NM_TERNARY_FALSE) - return; - } - } - - v = g_variant_ref_sink (g_variant_new_boolean (value != NM_TERNARY_FALSE)); - g_hash_table_insert (hash, - g_strdup (optname), - v); - _notify_attributes (setting); + if (value == NM_TERNARY_DEFAULT) + nm_setting_option_set (NM_SETTING (setting), optname, NULL); + else + nm_setting_option_set_boolean (NM_SETTING (setting), optname, (value != NM_TERNARY_FALSE)); } /** @@ -173,33 +186,20 @@ nm_setting_ethtool_set_feature (NMSettingEthtool *setting, * Clears all offload features settings * * Since: 1.14 + * + * Deprecated: 1.26: use nm_setting_option_clear_by_name() with nm_ethtool_optname_is_feature() predicate instead. */ void nm_setting_ethtool_clear_features (NMSettingEthtool *setting) { - GHashTable *hash; - GHashTableIter iter; - const char *name; - gboolean changed = FALSE; - g_return_if_fail (NM_IS_SETTING_ETHTOOL (setting)); - hash = _nm_setting_gendata_hash (NM_SETTING (setting), FALSE); - if (!hash) - return; - - g_hash_table_iter_init (&iter, hash); - while (g_hash_table_iter_next (&iter, (gpointer *) &name, NULL)) { - if (nm_ethtool_optname_is_feature (name)) { - g_hash_table_iter_remove (&iter); - changed = TRUE; - } - } - - if (changed) - _notify_attributes (setting); + nm_setting_option_clear_by_name (NM_SETTING (setting), + nm_ethtool_optname_is_feature); } +/*****************************************************************************/ + guint nm_setting_ethtool_init_features (NMSettingEthtool *setting, NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */) @@ -217,7 +217,7 @@ nm_setting_ethtool_init_features (NMSettingEthtool *setting, for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) requested[i] = NM_TERNARY_DEFAULT; - hash = _nm_setting_gendata_hash (NM_SETTING (setting), FALSE); + hash = _nm_setting_option_hash (NM_SETTING (setting), FALSE); if (!hash) return 0; @@ -230,9 +230,9 @@ nm_setting_ethtool_init_features (NMSettingEthtool *setting, if (!g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN)) continue; - requested[ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = g_variant_get_boolean (variant) - ? NM_TERNARY_TRUE - : NM_TERNARY_FALSE; + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX (ethtool_id)] = g_variant_get_boolean (variant) + ? NM_TERNARY_TRUE + : NM_TERNARY_FALSE; n_req++; } @@ -255,16 +255,23 @@ nm_setting_ethtool_init_features (NMSettingEthtool *setting, * @setting and may get invalidated when @setting gets modified. * * Since: 1.20 + * + * Deprecated: 1.26: use nm_setting_option_get_all_names() instead. */ const char ** nm_setting_ethtool_get_optnames (NMSettingEthtool *setting, guint *out_length) { + const char *const*names; + guint len; + g_return_val_if_fail (NM_IS_SETTING_ETHTOOL (setting), NULL); - return nm_utils_strdict_get_keys (_nm_setting_gendata_hash (NM_SETTING (setting), FALSE), - TRUE, - out_length); + names = nm_setting_option_get_all_names (NM_SETTING (setting), &len); + NM_SET_OUT (out_length, len); + return len > 0 + ? nm_memdup (names, sizeof (names[0]) * (((gsize) len) + 1u)) + : NULL; } /*****************************************************************************/ @@ -272,37 +279,54 @@ nm_setting_ethtool_get_optnames (NMSettingEthtool *setting, static gboolean verify (NMSetting *setting, NMConnection *connection, GError **error) { - GHashTable *hash; - GHashTableIter iter; - const char *optname; - GVariant *variant; + const char *const*optnames; + GVariant *const*variants; + guint len; + guint i; - hash = _nm_setting_gendata_hash (setting, FALSE); + len = _nm_setting_option_get_all (setting, &optnames, &variants); - if (!hash) - goto out; + for (i = 0; i < len; i++) { + const char *optname = optnames[i]; + GVariant *variant = variants[i]; + const GVariantType *variant_type; + NMEthtoolID ethtool_id; - g_hash_table_iter_init (&iter, hash); - while (g_hash_table_iter_next (&iter, (gpointer *) &optname, (gpointer *) &variant)) { - if (!nm_ethtool_optname_is_feature (optname)) { + ethtool_id = nm_ethtool_id_get_by_name (optname); + variant_type = get_variant_type_from_ethtool_id (ethtool_id); + + if (!variant_type) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, - _("unsupported offload feature")); + _("unsupported ethtool setting")); g_prefix_error (error, "%s.%s: ", NM_SETTING_ETHTOOL_SETTING_NAME, optname); return FALSE; } - if (!g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN)) { + + if (!g_variant_is_of_type (variant, variant_type)) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, - _("offload feature has invalid variant type")); + _("setting has invalid variant type")); g_prefix_error (error, "%s.%s: ", NM_SETTING_ETHTOOL_SETTING_NAME, optname); return FALSE; } + + if (NM_IN_SET (ethtool_id, + NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX, + NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)) { + if (!NM_IN_SET (g_variant_get_uint32 (variant), 0, 1)) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("coalesce option must be either 0 or 1")); + g_prefix_error (error, "%s.%s: ", NM_SETTING_ETHTOOL_SETTING_NAME, optname); + return FALSE; + } + } } -out: return TRUE; } @@ -313,15 +337,20 @@ get_variant_type (const NMSettInfoSetting *sett_info, const char *name, GError **error) { - if (nm_ethtool_optname_is_feature (name)) - return G_VARIANT_TYPE_BOOLEAN; + const GVariantType *variant_type; - g_set_error (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_INVALID_PROPERTY, - _("unknown ethtool option '%s'"), - name); - return NULL; + variant_type = get_variant_type_from_ethtool_id (nm_ethtool_id_get_by_name (name)); + + if (!variant_type) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("unknown ethtool option '%s'"), + name); + return NULL; + } + + return variant_type; } /*****************************************************************************/ diff --git a/libnm-core/nm-setting-ethtool.h b/libnm-core/nm-setting-ethtool.h index ba8c6a6e..27cb9658 100644 --- a/libnm-core/nm-setting-ethtool.h +++ b/libnm-core/nm-setting-ethtool.h @@ -69,9 +69,43 @@ G_BEGIN_DECLS #define NM_ETHTOOL_OPTNAME_FEATURE_TX_UDP_TNL_SEGMENTATION "feature-tx-udp_tnl-segmentation" #define NM_ETHTOOL_OPTNAME_FEATURE_TX_VLAN_STAG_HW_INSERT "feature-tx-vlan-stag-hw-insert" +#define NM_ETHTOOL_OPTNAME_COALESCE_ADAPTIVE_RX "coalesce-adaptive-rx" +#define NM_ETHTOOL_OPTNAME_COALESCE_ADAPTIVE_TX "coalesce-adaptive-tx" +#define NM_ETHTOOL_OPTNAME_COALESCE_PKT_RATE_HIGH "coalesce-pkt-rate-high" +#define NM_ETHTOOL_OPTNAME_COALESCE_PKT_RATE_LOW "coalesce-pkt-rate-low" +#define NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES "coalesce-rx-frames" +#define NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES_HIGH "coalesce-rx-frames-high" +#define NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES_IRQ "coalesce-rx-frames-irq" +#define NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES_LOW "coalesce-rx-frames-low" +#define NM_ETHTOOL_OPTNAME_COALESCE_RX_USECS "coalesce-rx-usecs" +#define NM_ETHTOOL_OPTNAME_COALESCE_RX_USECS_HIGH "coalesce-rx-usecs-high" +#define NM_ETHTOOL_OPTNAME_COALESCE_RX_USECS_IRQ "coalesce-rx-usecs-irq" +#define NM_ETHTOOL_OPTNAME_COALESCE_RX_USECS_LOW "coalesce-rx-usecs-low" +#define NM_ETHTOOL_OPTNAME_COALESCE_SAMPLE_INTERVAL "coalesce-sample-interval" +#define NM_ETHTOOL_OPTNAME_COALESCE_STATS_BLOCK_USECS "coalesce-stats-block-usecs" +#define NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES "coalesce-tx-frames" +#define NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES_HIGH "coalesce-tx-frames-high" +#define NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES_IRQ "coalesce-tx-frames-irq" +#define NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES_LOW "coalesce-tx-frames-low" +#define NM_ETHTOOL_OPTNAME_COALESCE_TX_USECS "coalesce-tx-usecs" +#define NM_ETHTOOL_OPTNAME_COALESCE_TX_USECS_HIGH "coalesce-tx-usecs-high" +#define NM_ETHTOOL_OPTNAME_COALESCE_TX_USECS_IRQ "coalesce-tx-usecs-irq" +#define NM_ETHTOOL_OPTNAME_COALESCE_TX_USECS_LOW "coalesce-tx-usecs-low" + +#define NM_ETHTOOL_OPTNAME_RING_RX "ring-rx" +#define NM_ETHTOOL_OPTNAME_RING_RX_JUMBO "ring-rx-jumbo" +#define NM_ETHTOOL_OPTNAME_RING_RX_MINI "ring-rx-mini" +#define NM_ETHTOOL_OPTNAME_RING_TX "ring-tx" + NM_AVAILABLE_IN_1_20 gboolean nm_ethtool_optname_is_feature (const char *optname); +NM_AVAILABLE_IN_1_26 +gboolean nm_ethtool_optname_is_coalesce (const char *optname); + +NM_AVAILABLE_IN_1_26 +gboolean nm_ethtool_optname_is_ring (const char *optname); + /*****************************************************************************/ #define NM_TYPE_SETTING_ETHTOOL (nm_setting_ethtool_get_type ()) @@ -95,20 +129,24 @@ NMSetting *nm_setting_ethtool_new (void); /*****************************************************************************/ +NM_AVAILABLE_IN_1_20 +NM_DEPRECATED_IN_1_26 +const char ** nm_setting_ethtool_get_optnames (NMSettingEthtool *setting, + guint *out_length); + NM_AVAILABLE_IN_1_14 +NM_DEPRECATED_IN_1_26 NMTernary nm_setting_ethtool_get_feature (NMSettingEthtool *setting, const char *optname); NM_AVAILABLE_IN_1_14 +NM_DEPRECATED_IN_1_26 void nm_setting_ethtool_set_feature (NMSettingEthtool *setting, const char *optname, NMTernary value); NM_AVAILABLE_IN_1_14 +NM_DEPRECATED_IN_1_26 void nm_setting_ethtool_clear_features (NMSettingEthtool *setting); -NM_AVAILABLE_IN_1_20 -const char ** nm_setting_ethtool_get_optnames (NMSettingEthtool *setting, - guint *out_length); - G_END_DECLS #endif /* __NM_SETTING_ETHTOOL_H__ */ diff --git a/libnm-core/nm-setting-ip-config.c b/libnm-core/nm-setting-ip-config.c index 36f09b69..09a34283 100644 --- a/libnm-core/nm-setting-ip-config.c +++ b/libnm-core/nm-setting-ip-config.c @@ -2186,7 +2186,7 @@ nm_ip_routing_rule_get_xifname_bin (const NMIPRoutingRule *self, if (!xifname) return FALSE; - bin = nm_utils_buf_utf8safe_unescape (xifname, &len, &bin_to_free); + bin = nm_utils_buf_utf8safe_unescape (xifname, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &len, &bin_to_free); strncpy (out_xifname, bin, 16 /* IFNAMSIZ */); out_xifname[15] = '\0'; diff --git a/libnm-core/nm-setting-match.c b/libnm-core/nm-setting-match.c index 3ad58daf..8fb513e5 100644 --- a/libnm-core/nm-setting-match.c +++ b/libnm-core/nm-setting-match.c @@ -20,6 +20,9 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingMatch, PROP_INTERFACE_NAME, + PROP_KERNEL_COMMAND_LINE, + PROP_DRIVER, + PROP_PATH, ); /** @@ -31,7 +34,10 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingMatch, */ struct _NMSettingMatch { NMSetting parent; - GPtrArray *interface_name; + GArray *interface_name; + GArray *kernel_command_line; + GArray *driver; + GArray *path; }; struct _NMSettingMatchClass { @@ -55,7 +61,7 @@ nm_setting_match_get_num_interface_names (NMSettingMatch *setting) { g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), 0); - return setting->interface_name->len; + return nm_g_array_len (setting->interface_name); } /** @@ -72,9 +78,9 @@ nm_setting_match_get_interface_name (NMSettingMatch *setting, int idx) { g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), NULL); - g_return_val_if_fail (idx >= 0 && idx < setting->interface_name->len, NULL); + g_return_val_if_fail (setting->interface_name && idx >= 0 && idx < setting->interface_name->len, NULL); - return setting->interface_name->pdata[idx]; + return g_array_index (setting->interface_name, const char *, idx); } /** @@ -94,7 +100,7 @@ nm_setting_match_add_interface_name (NMSettingMatch *setting, g_return_if_fail (interface_name != NULL); g_return_if_fail (interface_name[0] != '\0'); - g_ptr_array_add (setting->interface_name, g_strdup (interface_name)); + nm_strvarray_add (nm_strvarray_ensure (&setting->interface_name), interface_name); _notify (setting, PROP_INTERFACE_NAME); } @@ -112,9 +118,9 @@ nm_setting_match_remove_interface_name (NMSettingMatch *setting, int idx) { g_return_if_fail (NM_IS_SETTING_MATCH (setting)); - g_return_if_fail (idx >= 0 && idx < setting->interface_name->len); + g_return_if_fail (setting->interface_name && idx >= 0 && idx < setting->interface_name->len); - g_ptr_array_remove_index (setting->interface_name, idx); + g_array_remove_index (setting->interface_name, idx); _notify (setting, PROP_INTERFACE_NAME); } @@ -139,9 +145,12 @@ nm_setting_match_remove_interface_name_by_value (NMSettingMatch *setting, g_return_val_if_fail (interface_name != NULL, FALSE); g_return_val_if_fail (interface_name[0] != '\0', FALSE); + if (!setting->interface_name) + return FALSE; + for (i = 0; i < setting->interface_name->len; i++) { - if (nm_streq (interface_name, setting->interface_name->pdata[i])) { - g_ptr_array_remove_index (setting->interface_name, i); + if (nm_streq (interface_name, g_array_index (setting->interface_name, const char *, i))) { + g_array_remove_index (setting->interface_name, i); _notify (setting, PROP_INTERFACE_NAME); return TRUE; } @@ -162,8 +171,8 @@ nm_setting_match_clear_interface_names (NMSettingMatch *setting) { g_return_if_fail (NM_IS_SETTING_MATCH (setting)); - if (setting->interface_name->len != 0) { - g_ptr_array_set_size (setting->interface_name, 0); + if (nm_g_array_len (setting->interface_name) != 0) { + nm_clear_pointer (&setting->interface_name, g_array_unref); _notify (setting, PROP_INTERFACE_NAME); } } @@ -171,11 +180,14 @@ nm_setting_match_clear_interface_names (NMSettingMatch *setting) /** * nm_setting_match_get_interface_names: * @setting: the #NMSettingMatch - * @length: (out): the length of the returned interface names array. + * @length: (out) (allow-none): the length of the returned interface names array. * * Returns all the interface names. * - * Returns: (transfer none) (array length=length): the configured interface names. + * Returns: (transfer none) (array length=length): the NULL terminated list of + * configured interface names. + * + * Before 1.26, the returned array was not %NULL terminated and you MUST provide a length. * * Since: 1.14 **/ @@ -183,10 +195,458 @@ const char *const * nm_setting_match_get_interface_names (NMSettingMatch *setting, guint *length) { g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), NULL); - g_return_val_if_fail (length, NULL); - NM_SET_OUT (length, setting->interface_name->len); - return (const char *const *) setting->interface_name->pdata; + return nm_strvarray_get_strv (&setting->interface_name, length); +} + +/*****************************************************************************/ + +/** + * nm_setting_match_get_num_kernel_command_lines: + * @setting: the #NMSettingMatch + * + * Returns: the number of configured kernel command line arguments + * + * Since: 1.26 + **/ +guint +nm_setting_match_get_num_kernel_command_lines (NMSettingMatch *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), 0); + + return nm_g_array_len (setting->kernel_command_line); +} + +/** + * nm_setting_match_get_kernel_command_line: + * @setting: the #NMSettingMatch + * @idx: index number of the kernel command line argument to return + * + * Returns: the kernel command line argument at index @idx + * + * Since: 1.26 + **/ +const char * +nm_setting_match_get_kernel_command_line (NMSettingMatch *setting, guint idx) +{ + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), NULL); + + g_return_val_if_fail (setting->kernel_command_line && idx < setting->kernel_command_line->len, NULL); + + return g_array_index (setting->kernel_command_line, const char *, idx); +} + +/** + * nm_setting_match_add_kernel_command_line: + * @setting: the #NMSettingMatch + * @kernel_command_line: the kernel command line argument to add + * + * Adds a new kernel command line argument to the setting. + * + * Since: 1.26 + **/ +void +nm_setting_match_add_kernel_command_line (NMSettingMatch *setting, + const char *kernel_command_line) +{ + g_return_if_fail (NM_IS_SETTING_MATCH (setting)); + g_return_if_fail (kernel_command_line != NULL); + g_return_if_fail (kernel_command_line[0] != '\0'); + + nm_strvarray_add (nm_strvarray_ensure (&setting->kernel_command_line), kernel_command_line); + _notify (setting, PROP_KERNEL_COMMAND_LINE); +} + +/** + * nm_setting_match_remove_kernel_command_line: + * @setting: the #NMSettingMatch + * @idx: index number of the kernel command line argument + * + * Removes the kernel command line argument at index @idx. + * + * Since: 1.26 + **/ +void +nm_setting_match_remove_kernel_command_line (NMSettingMatch *setting, guint idx) +{ + g_return_if_fail (NM_IS_SETTING_MATCH (setting)); + + g_return_if_fail (setting->kernel_command_line && idx < setting->kernel_command_line->len); + + g_array_remove_index (setting->kernel_command_line, idx); + _notify (setting, PROP_KERNEL_COMMAND_LINE); +} + +/** + * nm_setting_match_remove_kernel_command_line_by_value: + * @setting: the #NMSettingMatch + * @kernel_command_line: the kernel command line argument name to remove + * + * Removes @kernel_command_line. + * + * Returns: %TRUE if the kernel command line argument was found and removed; %FALSE if it was not. + * + * Since: 1.26 + **/ +gboolean +nm_setting_match_remove_kernel_command_line_by_value (NMSettingMatch *setting, + const char *kernel_command_line) +{ + guint i; + + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), FALSE); + g_return_val_if_fail (kernel_command_line != NULL, FALSE); + g_return_val_if_fail (kernel_command_line[0] != '\0', FALSE); + + if (!setting->kernel_command_line) + return FALSE; + + for (i = 0; i < setting->kernel_command_line->len; i++) { + if (nm_streq (kernel_command_line, g_array_index (setting->kernel_command_line, const char *, i))) { + g_array_remove_index (setting->kernel_command_line, i); + _notify (setting, PROP_KERNEL_COMMAND_LINE); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_match_clear_kernel_command_lines: + * @setting: the #NMSettingMatch + * + * Removes all configured kernel command line arguments. + * + * Since: 1.26 + **/ +void +nm_setting_match_clear_kernel_command_lines (NMSettingMatch *setting) +{ + g_return_if_fail (NM_IS_SETTING_MATCH (setting)); + + if (nm_g_array_len (setting->kernel_command_line) != 0) { + nm_clear_pointer (&setting->kernel_command_line, g_array_unref); + _notify (setting, PROP_KERNEL_COMMAND_LINE); + } +} + +/** + * nm_setting_match_get_kernel_command_lines: + * @setting: the #NMSettingMatch + * @length: (out) (allow-none): the length of the returned interface names array. + * + * Returns all the interface names. + * + * Returns: (transfer none) (array length=length): the configured interface names. + * + * Since: 1.26 + **/ +const char *const * +nm_setting_match_get_kernel_command_lines (NMSettingMatch *setting, guint *length) +{ + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), NULL); + + return nm_strvarray_get_strv (&setting->kernel_command_line, length); +} + +/*****************************************************************************/ + +/** + * nm_setting_match_get_num_drivers: + * @setting: the #NMSettingMatch + * + * Returns: the number of configured drivers + * + * Since: 1.26 + **/ +guint +nm_setting_match_get_num_drivers (NMSettingMatch *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), 0); + + return nm_g_array_len (setting->driver); +} + +/** + * nm_setting_match_get_driver: + * @setting: the #NMSettingMatch + * @idx: index number of the DNS search domain to return + * + * Returns: the driver at index @idx + * + * Since: 1.26 + **/ +const char * +nm_setting_match_get_driver (NMSettingMatch *setting, guint idx) +{ + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), NULL); + + g_return_val_if_fail (setting->driver && idx < setting->driver->len, NULL); + + return g_array_index (setting->driver, const char *, idx); +} + +/** + * nm_setting_match_add_driver: + * @setting: the #NMSettingMatch + * @driver: the driver to add + * + * Adds a new driver to the setting. + * + * Since: 1.26 + **/ +void +nm_setting_match_add_driver (NMSettingMatch *setting, + const char *driver) +{ + g_return_if_fail (NM_IS_SETTING_MATCH (setting)); + g_return_if_fail (driver != NULL); + g_return_if_fail (driver[0] != '\0'); + + nm_strvarray_add (nm_strvarray_ensure (&setting->driver), driver); + _notify (setting, PROP_DRIVER); +} + +/** + * nm_setting_match_remove_driver: + * @setting: the #NMSettingMatch + * @idx: index number of the driver + * + * Removes the driver at index @idx. + * + * Since: 1.26 + **/ +void +nm_setting_match_remove_driver (NMSettingMatch *setting, guint idx) +{ + g_return_if_fail (NM_IS_SETTING_MATCH (setting)); + + g_return_if_fail (setting->driver && idx < setting->driver->len); + + g_array_remove_index (setting->driver, idx); + _notify (setting, PROP_DRIVER); +} + +/** + * nm_setting_match_remove_driver_by_value: + * @setting: the #NMSettingMatch + * @driver: the driver to remove + * + * Removes @driver. + * + * Returns: %TRUE if the driver was found and removed; %FALSE if it was not. + * + * Since: 1.26 + **/ +gboolean +nm_setting_match_remove_driver_by_value (NMSettingMatch *setting, + const char *driver) +{ + guint i; + + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), FALSE); + g_return_val_if_fail (driver != NULL, FALSE); + g_return_val_if_fail (driver[0] != '\0', FALSE); + + if (!setting->driver) + return FALSE; + + for (i = 0; i < setting->driver->len; i++) { + if (nm_streq (driver, g_array_index (setting->driver, const char *, i))) { + g_array_remove_index (setting->driver, i); + _notify (setting, PROP_DRIVER); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_match_clear_drivers: + * @setting: the #NMSettingMatch + * + * Removes all configured drivers. + * + * Since: 1.26 + **/ +void +nm_setting_match_clear_drivers (NMSettingMatch *setting) +{ + g_return_if_fail (NM_IS_SETTING_MATCH (setting)); + + if (nm_g_array_len (setting->driver) != 0) { + nm_clear_pointer (&setting->driver, g_array_unref); + _notify (setting, PROP_DRIVER); + } +} + +/** + * nm_setting_match_get_drivers: + * @setting: the #NMSettingMatch + * @length: (out) (allow-none): the length of the returned interface names array. + * + * Returns all the drivers. + * + * Returns: (transfer none) (array length=length): the configured drivers. + * + * Since: 1.26 + **/ +const char *const * +nm_setting_match_get_drivers (NMSettingMatch *setting, guint *length) +{ + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), NULL); + + return nm_strvarray_get_strv (&setting->driver, length); +} + +/*****************************************************************************/ + +/** + * nm_setting_match_get_num_paths: + * @setting: the #NMSettingMatch + * + * Returns: the number of configured paths + * + * Since: 1.26 + **/ +guint +nm_setting_match_get_num_paths (NMSettingMatch *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), 0); + + return nm_g_array_len (setting->path); +} + +/** + * nm_setting_match_get_path: + * @setting: the #NMSettingMatch + * @idx: index number of the path to return + * + * Returns: the path at index @idx + * + * Since: 1.26 + **/ +const char * +nm_setting_match_get_path (NMSettingMatch *setting, guint idx) +{ + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), NULL); + + g_return_val_if_fail (setting->path && idx < setting->path->len, NULL); + + return g_array_index (setting->path, const char *, idx); +} + +/** + * nm_setting_match_add_path: + * @setting: the #NMSettingMatch + * @path: the path to add + * + * Adds a new path to the setting. + * + * Since: 1.26 + **/ +void +nm_setting_match_add_path (NMSettingMatch *setting, + const char *path) +{ + g_return_if_fail (NM_IS_SETTING_MATCH (setting)); + g_return_if_fail (path != NULL); + g_return_if_fail (path[0] != '\0'); + + nm_strvarray_add (nm_strvarray_ensure (&setting->path), path); + _notify (setting, PROP_PATH); +} + +/** + * nm_setting_match_remove_path: + * @setting: the #NMSettingMatch + * @idx: index number of the path + * + * Removes the path at index @idx. + * + * Since: 1.26 + **/ +void +nm_setting_match_remove_path (NMSettingMatch *setting, guint idx) +{ + g_return_if_fail (NM_IS_SETTING_MATCH (setting)); + + g_return_if_fail (setting->path && idx < setting->path->len); + + g_array_remove_index (setting->path, idx); + _notify (setting, PROP_PATH); +} + +/** + * nm_setting_match_remove_path_by_value: + * @setting: the #NMSettingMatch + * @path: the path to remove + * + * Removes @path. + * + * Returns: %TRUE if the path was found and removed; %FALSE if it was not. + * + * Since: 1.26 + **/ +gboolean +nm_setting_match_remove_path_by_value (NMSettingMatch *setting, + const char *path) +{ + guint i; + + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), FALSE); + g_return_val_if_fail (path != NULL, FALSE); + g_return_val_if_fail (path[0] != '\0', FALSE); + + if (!setting->path) + return FALSE; + + for (i = 0; i < setting->path->len; i++) { + if (nm_streq (path, g_array_index (setting->path, const char *, i))) { + g_array_remove_index (setting->path, i); + _notify (setting, PROP_PATH); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_match_clear_paths: + * @setting: the #NMSettingMatch + * + * Removes all configured paths. + * + * Since: 1.26 + **/ +void +nm_setting_match_clear_paths (NMSettingMatch *setting) +{ + g_return_if_fail (NM_IS_SETTING_MATCH (setting)); + + if (nm_g_array_len (setting->path) != 0) { + nm_clear_pointer (&setting->path, g_array_unref); + _notify (setting, PROP_PATH); + } +} + +/** + * nm_setting_match_get_paths: + * @setting: the #NMSettingMatch + * @length: (out) (allow-none): the length of the returned paths array. + * + * Returns all the paths. + * + * Returns: (transfer none) (array length=length): the configured paths. + * + * Since: 1.26 + **/ +const char *const * +nm_setting_match_get_paths (NMSettingMatch *setting, guint *length) +{ + g_return_val_if_fail (NM_IS_SETTING_MATCH (setting), NULL); + + return nm_strvarray_get_strv (&setting->path, length); } /*****************************************************************************/ @@ -199,7 +659,16 @@ get_property (GObject *object, guint prop_id, switch (prop_id) { case PROP_INTERFACE_NAME: - g_value_take_boxed (value, _nm_utils_ptrarray_to_strv (self->interface_name)); + g_value_set_boxed (value, nm_strvarray_get_strv (&self->interface_name, NULL)); + break; + case PROP_KERNEL_COMMAND_LINE: + g_value_set_boxed (value, nm_strvarray_get_strv (&self->kernel_command_line, NULL)); + break; + case PROP_DRIVER: + g_value_set_boxed (value, nm_strvarray_get_strv (&self->driver, NULL)); + break; + case PROP_PATH: + g_value_set_boxed (value, nm_strvarray_get_strv (&self->path, NULL)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -215,8 +684,16 @@ set_property (GObject *object, guint prop_id, switch (prop_id) { case PROP_INTERFACE_NAME: - g_ptr_array_unref (self->interface_name); - self->interface_name = _nm_utils_strv_to_ptrarray (g_value_get_boxed (value)); + nm_strvarray_set_strv (&self->interface_name, g_value_get_boxed (value)); + break; + case PROP_KERNEL_COMMAND_LINE: + nm_strvarray_set_strv (&self->kernel_command_line, g_value_get_boxed (value)); + break; + case PROP_DRIVER: + nm_strvarray_set_strv (&self->driver, g_value_get_boxed (value)); + break; + case PROP_PATH: + nm_strvarray_set_strv (&self->path, g_value_get_boxed (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -229,7 +706,6 @@ set_property (GObject *object, guint prop_id, static void nm_setting_match_init (NMSettingMatch *setting) { - setting->interface_name = g_ptr_array_new_with_free_func (g_free); } /** @@ -247,12 +723,79 @@ nm_setting_match_new (void) return (NMSetting *) g_object_new (NM_TYPE_SETTING_MATCH, NULL); } +static gboolean +verify (NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingMatch *self = NM_SETTING_MATCH (setting); + guint i; + + if (self->interface_name) { + for (i = 0; i < self->interface_name->len; i++) { + if (nm_str_is_empty (g_array_index (self->interface_name, const char *, i))) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is empty")); + g_prefix_error (error, "%s.%s: ", NM_SETTING_MATCH_SETTING_NAME, + NM_SETTING_MATCH_INTERFACE_NAME); + return FALSE; + } + } + } + + if (self->kernel_command_line) { + for (i = 0; i < self->kernel_command_line->len; i++) { + if (nm_str_is_empty (g_array_index (self->kernel_command_line, const char *, i))) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is empty")); + g_prefix_error (error, "%s.%s: ", NM_SETTING_MATCH_SETTING_NAME, + NM_SETTING_MATCH_KERNEL_COMMAND_LINE); + return FALSE; + } + } + } + + if (self->driver) { + for (i = 0; i < self->driver->len; i++) { + if (nm_str_is_empty (g_array_index (self->driver, const char *, i))) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is empty")); + g_prefix_error (error, "%s.%s: ", NM_SETTING_MATCH_SETTING_NAME, + NM_SETTING_MATCH_DRIVER); + return FALSE; + } + } + } + + if (self->path) { + for (i = 0; i < self->path->len; i++) { + if (nm_str_is_empty (g_array_index (self->path, const char *, i))) { + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is empty")); + g_prefix_error (error, "%s.%s: ", NM_SETTING_MATCH_SETTING_NAME, + NM_SETTING_MATCH_PATH); + return FALSE; + } + } + } + + return TRUE; +} + static void finalize (GObject *object) { NMSettingMatch *self = NM_SETTING_MATCH (object); - g_ptr_array_unref (self->interface_name); + nm_clear_pointer (&self->interface_name, g_array_unref); + nm_clear_pointer (&self->kernel_command_line, g_array_unref); + nm_clear_pointer (&self->driver, g_array_unref); G_OBJECT_CLASS (nm_setting_match_parent_class)->finalize (object); } @@ -267,6 +810,8 @@ nm_setting_match_class_init (NMSettingMatchClass *klass) object_class->set_property = set_property; object_class->finalize = finalize; + setting_class->verify = verify; + /** * NMSettingMatch:interface-name * @@ -288,6 +833,89 @@ nm_setting_match_class_init (NMSettingMatchClass *klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + /** + * NMSettingMatch:kernel-command-line + * + * A list of kernel command line arguments to match. This may be used to check + * whether a specific kernel command line option is set (or if prefixed with + * the exclamation mark unset). The argument must either be a single word, or + * an assignment (i.e. two words, separated "="). In the former case the kernel + * command line is searched for the word appearing as is, or as left hand side + * of an assignment. In the latter case, the exact assignment is looked for + * with right and left hand side matching. + * + * Since: 1.26 + **/ + obj_properties[PROP_KERNEL_COMMAND_LINE] = + g_param_spec_boxed (NM_SETTING_MATCH_KERNEL_COMMAND_LINE, "", "", + G_TYPE_STRV, + NM_SETTING_PARAM_FUZZY_IGNORE | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMatch:driver + * + * A list of driver names to match. Each element is a shell wildcard pattern. + * When an element is prefixed with exclamation mark (!) the condition is + * inverted. A candidate driver name is considered matching when both these + * conditions are satisfied: (a) any of the elements not prefixed with '!' + * matches or there aren't such elements; (b) none of the elements prefixed + * with '!' match. + * + * Since: 1.26 + **/ + obj_properties[PROP_DRIVER] = + g_param_spec_boxed (NM_SETTING_MATCH_DRIVER, "", "", + G_TYPE_STRV, + NM_SETTING_PARAM_FUZZY_IGNORE | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + + /** + * NMSettingMatch:path + * + * A list of paths to match against the ID_PATH udev property of + * devices. ID_PATH represents the topological persistent path of a + * device. It typically contains a subsystem string (pci, usb, platform, + * etc.) and a subsystem-specific identifier. + * + * For PCI devices the path has the form + * "pci-$domain:$bus:$device.$function", where each variable is an + * hexadecimal value; for example "pci-0000:0a:00.0". + * + * The path of a device can be obtained with "udevadm info + * /sys/class/net/$dev | grep ID_PATH=" or by looking at the "path" + * property exported by NetworkManager ("nmcli -f general.path device + * show $dev"). + * + * Each element of the list is a shell wildcard pattern. When an + * element is prefixed with exclamation mark (!) the condition is + * inverted. + * + * A candidate path is considered matching when both these + * conditions are satisfied: (a) any of the elements not prefixed with '!' + * matches or there aren't such elements; (b) none of the elements + * prefixed with '!' match. + * + * Since: 1.26 + **/ + /* ---ifcfg-rh--- + * property: path + * variable: MATCH_PATH + * description: space-separated list of paths to match against the udev + * property ID_PATHS of devices + * example: MATCH_PATH="pci-0000:01:00.0 pci-0000:0c:00.0" + * ---end--- + */ + obj_properties[PROP_PATH] = + g_param_spec_boxed (NM_SETTING_MATCH_PATH, "", "", + G_TYPE_STRV, + NM_SETTING_PARAM_FUZZY_IGNORE | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); _nm_setting_class_commit (setting_class, NM_META_SETTING_TYPE_MATCH); diff --git a/libnm-core/nm-setting-match.h b/libnm-core/nm-setting-match.h index e15911ad..63bd4847 100644 --- a/libnm-core/nm-setting-match.h +++ b/libnm-core/nm-setting-match.h @@ -23,7 +23,10 @@ G_BEGIN_DECLS #define NM_SETTING_MATCH_SETTING_NAME "match" -#define NM_SETTING_MATCH_INTERFACE_NAME "interface-name" +#define NM_SETTING_MATCH_INTERFACE_NAME "interface-name" +#define NM_SETTING_MATCH_KERNEL_COMMAND_LINE "kernel-command-line" +#define NM_SETTING_MATCH_DRIVER "driver" +#define NM_SETTING_MATCH_PATH "path" typedef struct _NMSettingMatchClass NMSettingMatchClass; @@ -49,6 +52,61 @@ NM_AVAILABLE_IN_1_14 void nm_setting_match_clear_interface_names (NMSettingMatch *setting); NM_AVAILABLE_IN_1_14 const char *const *nm_setting_match_get_interface_names (NMSettingMatch *setting, guint *length); + + +NM_AVAILABLE_IN_1_26 +guint nm_setting_match_get_num_kernel_command_lines (NMSettingMatch *setting); +NM_AVAILABLE_IN_1_26 +const char *nm_setting_match_get_kernel_command_line (NMSettingMatch *setting, guint idx); +NM_AVAILABLE_IN_1_26 +void nm_setting_match_remove_kernel_command_line (NMSettingMatch *setting, guint idx); +NM_AVAILABLE_IN_1_26 +gboolean nm_setting_match_remove_kernel_command_line_by_value (NMSettingMatch *setting, + const char *kernel_command_line); +NM_AVAILABLE_IN_1_26 +void nm_setting_match_add_kernel_command_line (NMSettingMatch *setting, + const char *kernel_command_line); +NM_AVAILABLE_IN_1_26 +void nm_setting_match_clear_kernel_command_lines (NMSettingMatch *setting); +NM_AVAILABLE_IN_1_26 +const char *const *nm_setting_match_get_kernel_command_lines (NMSettingMatch *setting, guint *length); + + +NM_AVAILABLE_IN_1_26 +guint nm_setting_match_get_num_drivers (NMSettingMatch *setting); +NM_AVAILABLE_IN_1_26 +const char *nm_setting_match_get_driver (NMSettingMatch *setting, guint idx); +NM_AVAILABLE_IN_1_26 +void nm_setting_match_remove_driver (NMSettingMatch *setting, guint idx); +NM_AVAILABLE_IN_1_26 +gboolean nm_setting_match_remove_driver_by_value (NMSettingMatch *setting, + const char *driver); +NM_AVAILABLE_IN_1_26 +void nm_setting_match_add_driver (NMSettingMatch *setting, + const char *driver); +NM_AVAILABLE_IN_1_26 +void nm_setting_match_clear_drivers (NMSettingMatch *setting); +NM_AVAILABLE_IN_1_26 +const char *const *nm_setting_match_get_drivers (NMSettingMatch *setting, guint *length); + + +NM_AVAILABLE_IN_1_26 +guint nm_setting_match_get_num_paths (NMSettingMatch *setting); +NM_AVAILABLE_IN_1_26 +const char *nm_setting_match_get_path (NMSettingMatch *setting, guint idx); +NM_AVAILABLE_IN_1_26 +void nm_setting_match_remove_path (NMSettingMatch *setting, guint idx); +NM_AVAILABLE_IN_1_26 +gboolean nm_setting_match_remove_path_by_value (NMSettingMatch *setting, + const char *path); +NM_AVAILABLE_IN_1_26 +void nm_setting_match_add_path (NMSettingMatch *setting, + const char *path); +NM_AVAILABLE_IN_1_26 +void nm_setting_match_clear_paths (NMSettingMatch *setting); +NM_AVAILABLE_IN_1_26 +const char *const *nm_setting_match_get_paths (NMSettingMatch *setting, guint *length); + G_END_DECLS #endif /* NM_SETTING_MATCH_H */ diff --git a/libnm-core/nm-setting-ovs-patch.c b/libnm-core/nm-setting-ovs-patch.c index 6a9c9f56..5135e6ae 100644 --- a/libnm-core/nm-setting-ovs-patch.c +++ b/libnm-core/nm-setting-ovs-patch.c @@ -81,13 +81,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) return FALSE; } - if ( !nm_utils_ipaddr_is_valid (AF_INET, self->peer) - && !nm_utils_ipaddr_is_valid (AF_INET6, self->peer)) { - g_set_error (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_INVALID_PROPERTY, - _("'%s' is not a valid IP address"), - self->peer); + if (!nm_utils_ifname_valid (self->peer, NMU_IFACE_OVS, error)) { g_prefix_error (error, "%s.%s: ", NM_SETTING_OVS_PATCH_SETTING_NAME, NM_SETTING_OVS_PATCH_PEER); @@ -179,8 +173,8 @@ nm_setting_ovs_patch_class_init (NMSettingOvsPatchClass *klass) /** * NMSettingOvsPatch:peer: * - * Specifies the unicast destination IP address of a remote Open vSwitch - * bridge port to connect to. + * Specifies the name of the interface for the other side of the patch. + * The patch on the other side must also set this interface as peer. * * Since: 1.10 **/ diff --git a/libnm-core/nm-setting-wireless.c b/libnm-core/nm-setting-wireless.c index 2ef51088..124f9c45 100644 --- a/libnm-core/nm-setting-wireless.c +++ b/libnm-core/nm-setting-wireless.c @@ -222,8 +222,8 @@ nm_setting_wireless_ap_security_compatible (NMSettingWireless *s_wireless, && !(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_SAE)) return FALSE; } else if (!strcmp (key_mgmt, "owe")) { - if ( !(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_OWE) - && !(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_OWE)) + if ( !NM_FLAGS_ANY (ap_wpa, NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM) + && !NM_FLAGS_ANY (ap_rsn, NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) return FALSE; } diff --git a/libnm-core/nm-setting.c b/libnm-core/nm-setting.c index a75302f0..dfb2bed9 100644 --- a/libnm-core/nm-setting.c +++ b/libnm-core/nm-setting.c @@ -715,7 +715,7 @@ _nm_setting_to_dbus (NMSetting *setting, g_variant_builder_init (&builder, NM_VARIANT_TYPE_SETTING); - n_properties = _nm_setting_gendata_get_all (setting, &gendata_keys, NULL); + n_properties = _nm_setting_option_get_all (setting, &gendata_keys, NULL); for (i = 0; i < n_properties; i++) { g_variant_builder_add (&builder, "{sv}", @@ -873,7 +873,18 @@ init_from_dbus (NMSetting *setting, g_hash_table_remove (keys, key); } - _nm_setting_gendata_notify (setting, TRUE); + _nm_setting_option_notify (setting, TRUE); + + /* Currently, only NMSettingEthtool supports gendata based options, and + * that one has no other properties (except "name"). That means, we + * consumed all options above. + * + * In the future it may be interesting to have settings that are both + * based on gendata and regular properties. In that case, we would need + * to handle this case differently. */ + nm_assert (nm_streq (G_OBJECT_TYPE_NAME (setting), "NMSettingEthtool")); + nm_assert (sett_info->property_infos_len == 1); + return TRUE; } @@ -1781,7 +1792,7 @@ nm_setting_enumerate_values (NMSetting *setting, /* the properties of this setting are not real GObject properties. * Hence, this API makes little sense (or does it?). Still, call * @func with each value. */ - n_properties = _nm_setting_gendata_get_all (setting, &names, NULL); + n_properties = _nm_setting_option_get_all (setting, &names, NULL); if (n_properties > 0) { gs_strfreev char **keys = g_strdupv ((char **) names); GHashTable *h = _gendata_hash (setting, FALSE)->hash; @@ -2378,7 +2389,7 @@ _gendata_hash (NMSetting *setting, gboolean create_if_necessary) } GHashTable * -_nm_setting_gendata_hash (NMSetting *setting, gboolean create_if_necessary) +_nm_setting_option_hash (NMSetting *setting, gboolean create_if_necessary) { GenData *gendata; @@ -2387,8 +2398,8 @@ _nm_setting_gendata_hash (NMSetting *setting, gboolean create_if_necessary) } void -_nm_setting_gendata_notify (NMSetting *setting, - gboolean names_changed) +_nm_setting_option_notify (NMSetting *setting, + gboolean names_changed) { GenData *gendata; @@ -2422,23 +2433,10 @@ out: _nm_setting_emit_property_changed (setting); } -GVariant * -nm_setting_gendata_get (NMSetting *setting, - const char *name) -{ - GenData *gendata; - - g_return_val_if_fail (NM_IS_SETTING (setting), NULL); - g_return_val_if_fail (name, NULL); - - gendata = _gendata_hash (setting, FALSE); - return gendata ? g_hash_table_lookup (gendata->hash, name) : NULL; -} - guint -_nm_setting_gendata_get_all (NMSetting *setting, - const char *const**out_names, - GVariant *const**out_values) +_nm_setting_option_get_all (NMSetting *setting, + const char *const**out_names, + GVariant *const**out_values) { GenData *gendata; GHashTable *hash; @@ -2484,125 +2482,322 @@ out_zero: } /** - * nm_setting_gendata_get_all_names: + * nm_setting_option_get_all_names: * @setting: the #NMSetting * @out_len: (allow-none) (out): * - * Gives the number of generic data elements and optionally returns all their - * key names and values. This API is low level access and unless you know what you - * are doing, it might not be what you want. + * Gives the name of all set options. * * Returns: (array length=out_len zero-terminated=1) (transfer none): * A %NULL terminated array of key names. If no names are present, this returns * %NULL. The returned array and the names are owned by %NMSetting and might be invalidated - * soon. + * by the next operation. * - * Since: 1.14 + * Since: 1.26 **/ const char *const* -nm_setting_gendata_get_all_names (NMSetting *setting, - guint *out_len) +nm_setting_option_get_all_names (NMSetting *setting, + guint *out_len) { const char *const*names; guint len; g_return_val_if_fail (NM_IS_SETTING (setting), NULL); - len = _nm_setting_gendata_get_all (setting, &names, NULL); + len = _nm_setting_option_get_all (setting, &names, NULL); NM_SET_OUT (out_len, len); return names; } -/** - * nm_setting_gendata_get_all_values: - * @setting: the #NMSetting - * - * Gives the number of generic data elements and optionally returns all their - * key names and values. This API is low level access and unless you know what you - * are doing, it might not be what you want. - * - * Returns: (array zero-terminated=1) (transfer none): - * A %NULL terminated array of #GVariant. If no data is present, this returns - * %NULL. The returned array and the variants are owned by %NMSetting and might be invalidated - * soon. The sort order of nm_setting_gendata_get_all_names() and nm_setting_gendata_get_all_values() - * is consistent. That means, the nth value has the nth name returned by nm_setting_gendata_get_all_names(). - * - * Since: 1.14 - **/ -GVariant *const* -nm_setting_gendata_get_all_values (NMSetting *setting) +gboolean +_nm_setting_option_clear (NMSetting *setting, + const char *optname) { - GVariant *const*values; + GHashTable *ht; - g_return_val_if_fail (NM_IS_SETTING (setting), NULL); + nm_assert (NM_IS_SETTING (setting)); + nm_assert (nm_str_not_empty (optname)); + + ht = _nm_setting_option_hash (setting, FALSE); + if (!ht) + return FALSE; - _nm_setting_gendata_get_all (setting, NULL, &values); - return values; + return g_hash_table_remove (ht, optname); } +/** + * nm_setting_option_clear_by_name: + * @setting: the #NMSetting + * @predicate: (allow-none) (scope call): the predicate for which names + * should be clear. + * If the predicate returns %TRUE for an option name, the option + * gets removed. If %NULL, all options will be removed. + * + * Since: 1.26 + */ void -_nm_setting_gendata_to_gvalue (NMSetting *setting, - GValue *value) +nm_setting_option_clear_by_name (NMSetting *setting, + NMUtilsPredicateStr predicate) { - GenData *gendata; - GHashTable *new; - const char *key; - GVariant *val; + GHashTable *hash; GHashTableIter iter; + const char *name; + gboolean changed = FALSE; - nm_assert (NM_IS_SETTING (setting)); - nm_assert (value); - nm_assert (G_TYPE_CHECK_VALUE_TYPE ((value), G_TYPE_HASH_TABLE)); + g_return_if_fail (NM_IS_SETTING (setting)); - new = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); + hash = _nm_setting_option_hash (NM_SETTING (setting), FALSE); + if (!hash) + return; - gendata = _gendata_hash (setting, FALSE); - if (gendata) { - g_hash_table_iter_init (&iter, gendata->hash); - while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) - g_hash_table_insert (new, g_strdup (key), g_variant_ref (val)); + if (!predicate) { + changed = (g_hash_table_size (hash) > 0); + if (changed) + g_hash_table_remove_all (hash); + } else { + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, NULL)) { + if (predicate (name)) { + g_hash_table_iter_remove (&iter); + changed = TRUE; + } + } } - g_value_take_boxed (value, new); + if (changed) + _nm_setting_option_notify (setting, TRUE); } -gboolean -_nm_setting_gendata_reset_from_hash (NMSetting *setting, - GHashTable *new) +/*****************************************************************************/ + +/** + * nm_setting_option_get: + * @setting: the #NMSetting + * @opt_name: the option name to request. + * + * Returns: (transfer none): the #GVariant or %NULL if the option + * is not set. + * + * Since: 1.26. + */ +GVariant * +nm_setting_option_get (NMSetting *setting, + const char *opt_name) { GenData *gendata; - GHashTableIter iter; - const char *key; - GVariant *val; - guint num; - nm_assert (NM_IS_SETTING (setting)); - nm_assert (new); + g_return_val_if_fail (NM_IS_SETTING (setting), FALSE); + g_return_val_if_fail (opt_name, FALSE); - num = new ? g_hash_table_size (new) : 0; + gendata = _gendata_hash (setting, FALSE); + return gendata ? g_hash_table_lookup (gendata->hash, opt_name) : NULL; +} + +/** + * nm_setting_option_get_boolean: + * @setting: the #NMSetting + * @opt_name: the option to get + * @out_value: (allow-none) (out): the optional output value. + * If the option is unset, %FALSE will be returned. + * + * Returns: %TRUE if @opt_name is set to a boolean variant. + * + * Since: 1.26 + */ +gboolean +nm_setting_option_get_boolean (NMSetting *setting, + const char *opt_name, + gboolean *out_value) +{ + GVariant *v; - gendata = _gendata_hash (setting, num > 0); + v = nm_setting_option_get (NM_SETTING (setting), opt_name); + if ( v + && g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) { + NM_SET_OUT (out_value, g_variant_get_boolean (v)); + return TRUE; + } + NM_SET_OUT (out_value, FALSE); + return FALSE; +} - if (num == 0) { - if ( !gendata - || g_hash_table_size (gendata->hash) == 0) - return FALSE; +/** + * nm_setting_option_get_uint32: + * @setting: the #NMSetting + * @opt_name: the option to get + * @out_value: (allow-none) (out): the optional output value. + * If the option is unset, 0 will be returned. + * + * Returns: %TRUE if @opt_name is set to a uint32 variant. + * + * Since: 1.26 + */ +gboolean +nm_setting_option_get_uint32 (NMSetting *setting, + const char *opt_name, + guint32 *out_value) +{ + GVariant *v; - g_hash_table_remove_all (gendata->hash); - _nm_setting_gendata_notify (setting, TRUE); + v = nm_setting_option_get (NM_SETTING (setting), opt_name); + if ( v + && g_variant_is_of_type (v, G_VARIANT_TYPE_UINT32)) { + NM_SET_OUT (out_value, g_variant_get_uint32 (v)); return TRUE; } + NM_SET_OUT (out_value, 0); + return FALSE; +} + +/** + * nm_setting_option_set: + * @setting: the #NMSetting + * @opt_name: the option name to set + * @variant: (allow-none): the variant to set. + * + * If @variant is %NULL, this clears the option if it is set. + * Otherwise, @variant is set as the option. If @variant is + * a floating reference, it will be consumed. + * + * Note that not all setting types support options. It is a bug + * setting a variant to a setting that doesn't support it. + * Currently only #NMSettingEthtool supports it. + * + * Since: 1.26 + */ +void +nm_setting_option_set (NMSetting *setting, + const char *opt_name, + GVariant *variant) +{ + GVariant *old_variant; + gboolean changed_name; + gboolean changed_value; + GHashTable *hash; - /* let's not bother to find out whether the new hash has any different - * content the current gendata. Just replace it. */ - g_hash_table_remove_all (gendata->hash); - if (num > 0) { - g_hash_table_iter_init (&iter, new); - while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) - g_hash_table_insert (gendata->hash, g_strdup (key), g_variant_ref (val)); + g_return_if_fail (NM_IS_SETTING (setting)); + g_return_if_fail (opt_name); + + hash = _nm_setting_option_hash (setting, variant != NULL); + + if (!variant) { + if (hash) { + if (g_hash_table_remove (hash, opt_name)) + _nm_setting_option_notify (setting, TRUE); + } + return; } - _nm_setting_gendata_notify (setting, TRUE); - return TRUE; + + /* Currently, it is a bug setting any option, unless the setting type supports it. + * And currently, only NMSettingEthtool supports it. + * + * In the future, more setting types may support it. Or we may relax this so + * that options can be attached to all setting types (to indicate "unsupported" + * settings for forward compatibility). + * + * As it is today, internal code will only add gendata options to NMSettingEthtool, + * and there exists not public API to add such options. Still, it is permissible + * to call get(), clear() and set(variant=NULL) also on settings that don't support + * it, as these operations don't add options. + */ + g_return_if_fail (_nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting))->detail.gendata_info); + + old_variant = g_hash_table_lookup (hash, opt_name); + + changed_name = (old_variant == NULL); + changed_value = changed_name + || !g_variant_equal (old_variant, variant); + + /* We always want to replace the variant, even if it has + * the same value according to g_variant_equal(). The reason + * is that we want to take a reference on @variant, because + * that is what the user might expect. */ + g_hash_table_insert (hash, + g_strdup (opt_name), + g_variant_ref_sink (variant)); + + if (changed_value) + _nm_setting_option_notify (setting, !changed_name); +} + +/** + * nm_setting_option_set_boolean: + * @setting: the #NMSetting + * @value: the value to set. + * + * Like nm_setting_option_set() to set a boolean GVariant. + * + * Since: 1.26 + */ +void +nm_setting_option_set_boolean (NMSetting *setting, + const char *opt_name, + gboolean value) +{ + GVariant *old_variant; + gboolean changed_name; + gboolean changed_value; + GHashTable *hash; + + g_return_if_fail (NM_IS_SETTING (setting)); + g_return_if_fail (opt_name); + + value = (!!value); + + hash = _nm_setting_option_hash (setting, TRUE); + + old_variant = g_hash_table_lookup (hash, opt_name); + + changed_name = (old_variant == NULL); + changed_value = changed_name + || ( !g_variant_is_of_type (old_variant, G_VARIANT_TYPE_BOOLEAN) + || g_variant_get_boolean (old_variant) != value); + + g_hash_table_insert (hash, + g_strdup (opt_name), + g_variant_ref_sink (g_variant_new_boolean (value))); + + if (changed_value) + _nm_setting_option_notify (setting, !changed_name); +} + +/** + * nm_setting_option_set_uint32: + * @setting: the #NMSetting + * @value: the value to set. + * + * Like nm_setting_option_set() to set a uint32 GVariant. + * + * Since: 1.26 + */ +void +nm_setting_option_set_uint32 (NMSetting *setting, + const char *opt_name, + guint32 value) +{ + GVariant *old_variant; + gboolean changed_name; + gboolean changed_value; + GHashTable *hash; + + g_return_if_fail (NM_IS_SETTING (setting)); + g_return_if_fail (opt_name); + + hash = _nm_setting_option_hash (setting, TRUE); + + old_variant = g_hash_table_lookup (hash, opt_name); + + changed_name = (old_variant == NULL); + changed_value = changed_name + || ( !g_variant_is_of_type (old_variant, G_VARIANT_TYPE_UINT32) + || g_variant_get_uint32 (old_variant) != value); + + g_hash_table_insert (hash, + g_strdup (opt_name), + g_variant_ref_sink (g_variant_new_uint32 (value))); + + if (changed_value) + _nm_setting_option_notify (setting, !changed_name); } /*****************************************************************************/ diff --git a/libnm-core/nm-setting.h b/libnm-core/nm-setting.h index 26c1b1ee..01cd1503 100644 --- a/libnm-core/nm-setting.h +++ b/libnm-core/nm-setting.h @@ -333,6 +333,46 @@ gboolean nm_setting_set_secret_flags (NMSetting *setting, /*****************************************************************************/ +NM_AVAILABLE_IN_1_26 +GVariant *nm_setting_option_get (NMSetting *setting, + const char *opt_name); + +NM_AVAILABLE_IN_1_26 +gboolean nm_setting_option_get_boolean (NMSetting *setting, + const char *opt_name, + gboolean *out_value); + +NM_AVAILABLE_IN_1_26 +gboolean nm_setting_option_get_uint32 (NMSetting *setting, + const char *opt_name, + guint32 *out_value); + +NM_AVAILABLE_IN_1_26 +void nm_setting_option_set (NMSetting *setting, + const char *opt_name, + GVariant *variant); + +NM_AVAILABLE_IN_1_26 +void nm_setting_option_set_uint32 (NMSetting *setting, + const char *opt_name, + guint32 value); + +NM_AVAILABLE_IN_1_26 +void nm_setting_option_set_boolean (NMSetting *setting, + const char *opt_name, + gboolean value); + +NM_AVAILABLE_IN_1_26 +const char *const*nm_setting_option_get_all_names (NMSetting *setting, + guint *out_len); + + +NM_AVAILABLE_IN_1_26 +void nm_setting_option_clear_by_name (NMSetting *setting, + NMUtilsPredicateStr predicate); + +/*****************************************************************************/ + const GVariantType *nm_setting_get_dbus_property_type (NMSetting *setting, const char *property_name); diff --git a/libnm-core/nm-utils-private.h b/libnm-core/nm-utils-private.h index f3e9d3f1..dd3785be 100644 --- a/libnm-core/nm-utils-private.h +++ b/libnm-core/nm-utils-private.h @@ -67,11 +67,6 @@ char * _nm_utils_hwaddr_canonical_or_invalid (const char *mac, gssize lengt gboolean _nm_utils_hwaddr_link_local_valid (const char *mac); -void _nm_utils_format_variant_attributes_full (GString *str, - const NMUtilsNamedValue *values, - guint num_values, - char attr_separator, - char key_value_separator); gboolean _nm_sriov_vf_parse_vlans (NMSriovVF *vf, const char *str, GError **error); gboolean _nm_utils_bridge_vlan_verify_list (GPtrArray *vlans, diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index fed3f110..c88afb32 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -48,6 +48,20 @@ /*****************************************************************************/ +/** + * NMUtilsPredicateStr: + * @str: the name to check. + * + * This function takes a string argument and returns either %TRUE or %FALSE. + * It is a general purpose predicate, for example used by nm_setting_option_clear_by_name(). + * + * Returns: %TRUE if the predicate function matches. + * + * Since: 1.26 + */ + +/*****************************************************************************/ + struct _NMSockAddrEndpoint { const char *host; guint16 port; @@ -1252,7 +1266,7 @@ nm_utils_security_valid (NMUtilsSecurityType type, return FALSE; if (!have_ap) return TRUE; - if (!(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_OWE)) + if (!NM_FLAGS_ANY (ap_rsn, NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) return FALSE; return TRUE; case NMU_SEC_INVALID: @@ -2286,11 +2300,29 @@ fail: } static const NMVariantAttributeSpec *const tc_object_attribute_spec[] = { - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("root", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("parent", G_VARIANT_TYPE_STRING, .str_type = 'a', ), - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("handle", G_VARIANT_TYPE_STRING, .str_type = 'a', ), - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("kind", G_VARIANT_TYPE_STRING, .no_value = TRUE, .str_type = 'a', ), - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("", G_VARIANT_TYPE_STRING, .no_value = TRUE, .consumes_rest = TRUE, .str_type = 'a', ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("root", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("parent", G_VARIANT_TYPE_STRING, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("handle", G_VARIANT_TYPE_STRING, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("kind", G_VARIANT_TYPE_STRING, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("", G_VARIANT_TYPE_STRING, .no_value = TRUE, .consumes_rest = TRUE, ), + NULL, +}; + +static const NMVariantAttributeSpec *const tc_qdisc_sfq_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("quantum", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("perturb", G_VARIANT_TYPE_INT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("limit", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("divisor", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("flows", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("depth", G_VARIANT_TYPE_UINT32, ), + NULL, +}; + +static const NMVariantAttributeSpec *const tc_qdisc_tbf_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("rate", G_VARIANT_TYPE_UINT64, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("burst", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("limit", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("latency", G_VARIANT_TYPE_UINT32, ), NULL, }; @@ -2321,6 +2353,8 @@ typedef struct { static const NMQdiscAttributeSpec *const tc_qdisc_attribute_spec[] = { &(const NMQdiscAttributeSpec) { "fq_codel", tc_qdisc_fq_codel_spec }, + &(const NMQdiscAttributeSpec) { "sfq", tc_qdisc_sfq_spec }, + &(const NMQdiscAttributeSpec) { "tbf", tc_qdisc_tbf_spec }, NULL, }; @@ -2500,7 +2534,7 @@ nm_utils_tc_qdisc_from_str (const char *str, GError **error) break; } } - nm_clear_pointer (&rest, g_free); + nm_clear_g_free (&rest); if (options) { value = g_hash_table_lookup (options, ""); @@ -2536,17 +2570,17 @@ static const NMVariantAttributeSpec *const tc_action_simple_attribute_spec[] = { }; static const NMVariantAttributeSpec *const tc_action_mirred_attribute_spec[] = { - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("egress", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("ingress", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("mirror", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("redirect", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("dev", G_VARIANT_TYPE_STRING, .no_value = TRUE, .str_type = 'a', ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("egress", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("ingress", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("mirror", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("redirect", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("dev", G_VARIANT_TYPE_STRING, ), NULL, }; static const NMVariantAttributeSpec *const tc_action_attribute_spec[] = { - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("kind", G_VARIANT_TYPE_STRING, .no_value = TRUE, .str_type = 'a', ), - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("", G_VARIANT_TYPE_STRING, .no_value = TRUE, .consumes_rest = TRUE, .str_type = 'a', ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("kind", G_VARIANT_TYPE_STRING, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("", G_VARIANT_TYPE_STRING, .no_value = TRUE, .consumes_rest = TRUE, ), NULL, }; @@ -2744,8 +2778,8 @@ nm_utils_tc_tfilter_to_str (NMTCTfilter *tfilter, GError **error) } static const NMVariantAttributeSpec *const tc_tfilter_attribute_spec[] = { - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("action", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), - NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("", G_VARIANT_TYPE_STRING, .no_value = TRUE, .consumes_rest = TRUE, .str_type = 'a', ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("action", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("", G_VARIANT_TYPE_STRING, .no_value = TRUE, .consumes_rest = TRUE, ), NULL, }; @@ -4682,7 +4716,7 @@ nm_utils_is_valid_iface_name_utf8safe (const char *utf8safe_name) g_return_val_if_fail (utf8safe_name, FALSE); - bin = nm_utils_buf_utf8safe_unescape (utf8safe_name, &len, &bin_to_free); + bin = nm_utils_buf_utf8safe_unescape (utf8safe_name, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &len, &bin_to_free); if (bin_to_free) { /* some unescaping happened... */ @@ -5449,23 +5483,6 @@ nm_utils_is_json_object (const char *str, GError **error) } static char * -attribute_escape (const char *src, char c1, char c2) -{ - char *ret, *dest; - - dest = ret = g_malloc (strlen (src) * 2 + 1); - - while (*src) { - if (*src == c1 || *src == c2 || *src == '\\') - *dest++ = '\\'; - *dest++ = *src++; - } - *dest++ = '\0'; - - return ret; -} - -static char * attribute_unescape (const char *start, const char *end) { char *ret, *dest; @@ -5657,6 +5674,24 @@ nm_utils_parse_variant_attributes (const char *string, return NULL; } variant = g_variant_new_uint32 (num); + } else if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_INT32)) { + gint64 num = _nm_utils_ascii_str_to_int64 (value, 10, G_MININT32, G_MAXINT32, G_MAXINT64); + + if (num == G_MAXINT64) { + g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, + _("invalid int32 value '%s' for attribute '%s'"), value, (*s)->name); + return NULL; + } + variant = g_variant_new_int32 (num); + } else if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_UINT64)) { + guint64 num = _nm_utils_ascii_str_to_uint64 (value, 10, 0, G_MAXUINT64, G_MAXUINT64); + + if (num == G_MAXUINT64 && errno != 0) { + g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, + _("invalid uint64 value '%s' for attribute '%s'"), value, (*s)->name); + return NULL; + } + variant = g_variant_new_uint64 (num); } else if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_BYTE)) { gint64 num = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT8, -1); @@ -5699,55 +5734,6 @@ next: return g_steal_pointer (&ht); } -void -_nm_utils_format_variant_attributes_full (GString *str, - const NMUtilsNamedValue *values, - guint num_values, - char attr_separator, - char key_value_separator) -{ - const char *name, *value; - GVariant *variant; - char *escaped; - char buf[64]; - char sep = 0; - guint i; - - for (i = 0; i < num_values; i++) { - name = values[i].name; - variant = (GVariant *) values[i].value_ptr; - value = NULL; - - if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32)) - value = nm_sprintf_buf (buf, "%u", g_variant_get_uint32 (variant)); - else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTE)) - value = nm_sprintf_buf (buf, "%hhu", g_variant_get_byte (variant)); - else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN)) - value = g_variant_get_boolean (variant) ? "true" : "false"; - else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) - value = g_variant_get_string (variant, NULL); - else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTESTRING)) - value = g_variant_get_bytestring (variant); - else - continue; - - if (sep) - g_string_append_c (str, sep); - - escaped = attribute_escape (name, attr_separator, key_value_separator); - g_string_append (str, escaped); - g_free (escaped); - - g_string_append_c (str, key_value_separator); - - escaped = attribute_escape (value, attr_separator, key_value_separator); - g_string_append (str, escaped); - g_free (escaped); - - sep = attr_separator; - } -} - /* * nm_utils_format_variant_attributes: * @attributes: (element-type utf8 GVariant): a #GHashTable mapping attribute names to #GVariant values @@ -5766,25 +5752,9 @@ nm_utils_format_variant_attributes (GHashTable *attributes, char attr_separator, char key_value_separator) { - GString *str = NULL; - gs_free NMUtilsNamedValue *values = NULL; - guint len; - - g_return_val_if_fail (attr_separator, NULL); - g_return_val_if_fail (key_value_separator, NULL); - - if (!attributes || !g_hash_table_size (attributes)) - return NULL; - - values = nm_utils_named_values_from_str_dict (attributes, &len); - - str = g_string_new (""); - _nm_utils_format_variant_attributes_full (str, - values, - len, - attr_separator, - key_value_separator); - return g_string_free (str, FALSE); + return _nm_utils_format_variant_attributes (attributes, + attr_separator, + key_value_separator); } /*****************************************************************************/ @@ -5865,7 +5835,7 @@ nm_utils_base64secret_decode (const char *base64_key, gsize required_key_len, guint8 *out_key) { - gs_free guint8 *bin_arr = NULL; + nm_auto_free guint8 *bin_arr = NULL; gsize base64_key_len; gsize bin_len; int r; diff --git a/libnm-core/nm-version.h b/libnm-core/nm-version.h index 195fc196..20384037 100644 --- a/libnm-core/nm-version.h +++ b/libnm-core/nm-version.h @@ -229,6 +229,20 @@ # define NM_AVAILABLE_IN_1_24 #endif +#if NM_VERSION_MIN_REQUIRED >= NM_VERSION_1_26 +# define NM_DEPRECATED_IN_1_26 G_DEPRECATED +# define NM_DEPRECATED_IN_1_26_FOR(f) G_DEPRECATED_FOR(f) +#else +# define NM_DEPRECATED_IN_1_26 +# define NM_DEPRECATED_IN_1_26_FOR(f) +#endif + +#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_26 +# define NM_AVAILABLE_IN_1_26 G_UNAVAILABLE(1,26) +#else +# define NM_AVAILABLE_IN_1_26 +#endif + /* * Synchronous API for calling D-Bus in libnm is deprecated. See * https://developer.gnome.org/libnm/stable/usage.html#sync-api diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index 2b1f1e3d..a6fb7007 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -3334,6 +3334,7 @@ test_connection_diff_a_only (void) { NM_SETTING_CONNECTION_AUTH_RETRIES, NM_SETTING_DIFF_RESULT_IN_A }, { NM_SETTING_CONNECTION_MDNS, NM_SETTING_DIFF_RESULT_IN_A }, { NM_SETTING_CONNECTION_LLMNR, NM_SETTING_DIFF_RESULT_IN_A }, + { NM_SETTING_CONNECTION_MUD_URL, NM_SETTING_DIFF_RESULT_IN_A }, { NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A }, { NULL, NM_SETTING_DIFF_RESULT_UNKNOWN } } }, @@ -7814,7 +7815,7 @@ _do_test_utils_str_utf8safe_unescape (const char *str, const char *expected, gsi gs_free gpointer buf_free_1 = NULL; gs_free char *str_free_1 = NULL; - s = nm_utils_buf_utf8safe_unescape (str, &l, &buf_free_1); + s = nm_utils_buf_utf8safe_unescape (str, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &l, &buf_free_1); g_assert_cmpint (expected_len, ==, l); g_assert_cmpstr (s, ==, expected); @@ -7838,7 +7839,7 @@ _do_test_utils_str_utf8safe_unescape (const char *str, const char *expected, gsi if ( expected && l == strlen (expected)) { /* there are no embeeded NULs. Check that nm_utils_str_utf8safe_unescape() yields the same result. */ - s = nm_utils_str_utf8safe_unescape (str, &str_free_1); + s = nm_utils_str_utf8safe_unescape (str, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &str_free_1); g_assert_cmpstr (s, ==, expected); if (strchr (str, '\\')) { g_assert (str_free_1 != str); @@ -7907,10 +7908,10 @@ _do_test_utils_str_utf8safe (const char *str, gsize str_len, const char *expecte g_assert (g_utf8_validate (str, -1, NULL)); } - g_assert (str == nm_utils_str_utf8safe_unescape (str_safe, &str_free_4)); + g_assert (str == nm_utils_str_utf8safe_unescape (str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &str_free_4)); g_assert (!str_free_4); - str_free_5 = nm_utils_str_utf8safe_unescape_cp (str_safe); + str_free_5 = nm_utils_str_utf8safe_unescape_cp (str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); if (str) { g_assert (str_free_5 != str); g_assert_cmpstr (str_free_5, ==, str); @@ -7934,11 +7935,11 @@ _do_test_utils_str_utf8safe (const char *str, gsize str_len, const char *expecte str_free_6 = g_strcompress (str_safe); g_assert_cmpstr (str, ==, str_free_6); - str_free_7 = nm_utils_str_utf8safe_unescape_cp (str_safe); + str_free_7 = nm_utils_str_utf8safe_unescape_cp (str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); g_assert (str_free_7 != str); g_assert_cmpstr (str_free_7, ==, str); - s = nm_utils_str_utf8safe_unescape (str_safe, &str_free_8); + s = nm_utils_str_utf8safe_unescape (str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &str_free_8); g_assert (str_free_8 != str); g_assert (s == str_free_8); g_assert_cmpstr (str_free_8, ==, str); diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c index 47a27571..23149b61 100644 --- a/libnm-core/tests/test-keyfile.c +++ b/libnm-core/tests/test-keyfile.c @@ -143,7 +143,7 @@ _nm_keyfile_write (NMConnection *connection, g_assert (NM_IS_CONNECTION (connection)); - kf = nm_keyfile_write (connection, handler, user_data, &error); + kf = nm_keyfile_write (connection, NM_KEYFILE_HANDLER_FLAGS_NONE, handler, user_data, &error); g_assert_no_error (error); g_assert (kf); return kf; @@ -167,7 +167,7 @@ _nm_keyfile_read (GKeyFile *keyfile, base_dir = g_path_get_dirname (keyfile_name); filename = g_path_get_basename (keyfile_name); - con = nm_keyfile_read (keyfile, base_dir, read_handler, read_data, &error); + con = nm_keyfile_read (keyfile, base_dir, NM_KEYFILE_HANDLER_FLAGS_NONE, read_handler, read_data, &error); g_assert_no_error (error); g_assert (NM_IS_CONNECTION (con)); diff --git a/libnm-core/tests/test-setting.c b/libnm-core/tests/test-setting.c index 072be57a..359c20c4 100644 --- a/libnm-core/tests/test-setting.c +++ b/libnm-core/tests/test-setting.c @@ -1499,7 +1499,53 @@ test_team_setting (void) /*****************************************************************************/ static void -test_ethtool_1 (void) +_setting_ethtool_set_feature (NMSettingEthtool *s_ethtool, + const char *opt_name, + NMTernary value) +{ + g_assert (NM_IS_SETTING_ETHTOOL (s_ethtool)); + + if (nmtst_get_rand_bool ()) { + nm_setting_ethtool_set_feature (s_ethtool, opt_name, value); + return; + } + + if (value == NM_TERNARY_DEFAULT) { + nm_setting_option_set (NM_SETTING (s_ethtool), opt_name, NULL); + return; + } + + if (nmtst_get_rand_bool ()) + nm_setting_option_set_boolean (NM_SETTING (s_ethtool), opt_name, value); + else + nm_setting_option_set (NM_SETTING (s_ethtool), opt_name, g_variant_new_boolean (value)); +} + +static NMTernary +_setting_ethtool_get_feature (NMSettingEthtool *s_ethtool, + const char *opt_name) +{ + GVariant *v; + gboolean b; + + switch (nmtst_get_rand_uint32 () % 3) { + case 0: + return nm_setting_ethtool_get_feature (s_ethtool, opt_name); + case 1: + if (!nm_setting_option_get_boolean (NM_SETTING (s_ethtool), opt_name, &b)) + return NM_TERNARY_DEFAULT; + return b; + default: + v = nm_setting_option_get (NM_SETTING (s_ethtool), opt_name); + if ( !v + || !g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) + return NM_TERNARY_DEFAULT; + return g_variant_get_boolean (v); + } +} + +static void +test_ethtool_features (void) { gs_unref_object NMConnection *con = NULL; gs_unref_object NMConnection *con2 = NULL; @@ -1519,16 +1565,87 @@ test_ethtool_1 (void) s_ethtool = NM_SETTING_ETHTOOL (nm_setting_ethtool_new ()); nm_connection_add_setting (con, NM_SETTING (s_ethtool)); - nm_setting_ethtool_set_feature (s_ethtool, - NM_ETHTOOL_OPTNAME_FEATURE_RX, - NM_TERNARY_TRUE); - nm_setting_ethtool_set_feature (s_ethtool, - NM_ETHTOOL_OPTNAME_FEATURE_LRO, - NM_TERNARY_FALSE); + _setting_ethtool_set_feature (s_ethtool, + NM_ETHTOOL_OPTNAME_FEATURE_RX, + NM_TERNARY_TRUE); + _setting_ethtool_set_feature (s_ethtool, + NM_ETHTOOL_OPTNAME_FEATURE_LRO, + NM_TERNARY_FALSE); + + g_assert_cmpint (_setting_ethtool_get_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RX), ==, NM_TERNARY_TRUE); + g_assert_cmpint (_setting_ethtool_get_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_LRO), ==, NM_TERNARY_FALSE); + g_assert_cmpint (_setting_ethtool_get_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_SG), ==, NM_TERNARY_DEFAULT); + + nmtst_connection_normalize (con); + + variant = nm_connection_to_dbus (con, NM_CONNECTION_SERIALIZE_ALL); + + con2 = nm_simple_connection_new_from_dbus (variant, &error); + nmtst_assert_success (con2, error); + + s_ethtool2 = NM_SETTING_ETHTOOL (nm_connection_get_setting (con2, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_cmpint (_setting_ethtool_get_feature (s_ethtool2, NM_ETHTOOL_OPTNAME_FEATURE_RX), ==, NM_TERNARY_TRUE); + g_assert_cmpint (_setting_ethtool_get_feature (s_ethtool2, NM_ETHTOOL_OPTNAME_FEATURE_LRO), ==, NM_TERNARY_FALSE); + g_assert_cmpint (_setting_ethtool_get_feature (s_ethtool2, NM_ETHTOOL_OPTNAME_FEATURE_SG), ==, NM_TERNARY_DEFAULT); + + nmtst_assert_connection_verifies_without_normalization (con2); + + nmtst_assert_connection_equals (con, FALSE, con2, FALSE); + + keyfile = nm_keyfile_write (con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + nmtst_assert_success (keyfile, error); + + con3 = nm_keyfile_read (keyfile, + "/ignored/current/working/directory/for/loading/relative/paths", + NM_KEYFILE_HANDLER_FLAGS_NONE, + NULL, + NULL, + &error); + nmtst_assert_success (con3, error); + + nm_keyfile_read_ensure_id (con3, "unused-because-already-has-id"); + nm_keyfile_read_ensure_uuid (con3, "unused-because-already-has-uuid"); + + nmtst_connection_normalize (con3); + + nmtst_assert_connection_equals (con, FALSE, con3, FALSE); + + s_ethtool3 = NM_SETTING_ETHTOOL (nm_connection_get_setting (con3, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_cmpint (_setting_ethtool_get_feature (s_ethtool3, NM_ETHTOOL_OPTNAME_FEATURE_RX), ==, NM_TERNARY_TRUE); + g_assert_cmpint (_setting_ethtool_get_feature (s_ethtool3, NM_ETHTOOL_OPTNAME_FEATURE_LRO), ==, NM_TERNARY_FALSE); + g_assert_cmpint (_setting_ethtool_get_feature (s_ethtool3, NM_ETHTOOL_OPTNAME_FEATURE_SG), ==, NM_TERNARY_DEFAULT); +} + +static void +test_ethtool_coalesce (void) +{ + gs_unref_object NMConnection *con = NULL; + gs_unref_object NMConnection *con2 = NULL; + gs_unref_object NMConnection *con3 = NULL; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + gs_unref_keyfile GKeyFile *keyfile = NULL; + NMSettingConnection *s_con; + NMSettingEthtool *s_ethtool; + NMSettingEthtool *s_ethtool2; + NMSettingEthtool *s_ethtool3; + guint32 u32; + + con = nmtst_create_minimal_connection ("ethtool-coalesce", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + s_ethtool = NM_SETTING_ETHTOOL (nm_setting_ethtool_new ()); + nm_connection_add_setting (con, NM_SETTING (s_ethtool)); + + nm_setting_option_set_uint32 (NM_SETTING (s_ethtool), + NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, + 4); - g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RX), ==, NM_TERNARY_TRUE); - g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_LRO), ==, NM_TERNARY_FALSE); - g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_SG), ==, NM_TERNARY_DEFAULT); + g_assert_true (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, &u32)); + g_assert_cmpuint (u32, ==, 4); nmtst_connection_normalize (con); @@ -1539,19 +1656,19 @@ test_ethtool_1 (void) s_ethtool2 = NM_SETTING_ETHTOOL (nm_connection_get_setting (con2, NM_TYPE_SETTING_ETHTOOL)); - g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool2, NM_ETHTOOL_OPTNAME_FEATURE_RX), ==, NM_TERNARY_TRUE); - g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool2, NM_ETHTOOL_OPTNAME_FEATURE_LRO), ==, NM_TERNARY_FALSE); - g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool2, NM_ETHTOOL_OPTNAME_FEATURE_SG), ==, NM_TERNARY_DEFAULT); + g_assert_true (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool2), NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, &u32)); + g_assert_cmpuint (u32, ==, 4); nmtst_assert_connection_verifies_without_normalization (con2); nmtst_assert_connection_equals (con, FALSE, con2, FALSE); - keyfile = nm_keyfile_write (con, NULL, NULL, &error); + keyfile = nm_keyfile_write (con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); nmtst_assert_success (keyfile, error); con3 = nm_keyfile_read (keyfile, "/ignored/current/working/directory/for/loading/relative/paths", + NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); @@ -1566,9 +1683,109 @@ test_ethtool_1 (void) s_ethtool3 = NM_SETTING_ETHTOOL (nm_connection_get_setting (con3, NM_TYPE_SETTING_ETHTOOL)); - g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool3, NM_ETHTOOL_OPTNAME_FEATURE_RX), ==, NM_TERNARY_TRUE); - g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool3, NM_ETHTOOL_OPTNAME_FEATURE_LRO), ==, NM_TERNARY_FALSE); - g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool3, NM_ETHTOOL_OPTNAME_FEATURE_SG), ==, NM_TERNARY_DEFAULT); + g_assert_true (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool3), NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, &u32)); + g_assert_cmpuint (u32, ==, 4); + + + nm_setting_option_set (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, NULL); + g_assert_false (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, NULL)); + + nm_setting_option_set_uint32 (NM_SETTING (s_ethtool), + NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES, + 8); + + g_assert_true (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES, &u32)); + g_assert_cmpuint (u32, ==, 8); + + nm_setting_option_clear_by_name (NM_SETTING (s_ethtool), nm_ethtool_optname_is_coalesce); + g_assert_false (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, NULL)); + g_assert_false (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES, NULL)); + g_assert_false (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_COALESCE_TX_USECS, NULL)); +} + +static void +test_ethtool_ring (void) +{ + gs_unref_object NMConnection *con = NULL; + gs_unref_object NMConnection *con2 = NULL; + gs_unref_object NMConnection *con3 = NULL; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + gs_unref_keyfile GKeyFile *keyfile = NULL; + NMSettingConnection *s_con; + NMSettingEthtool *s_ethtool; + NMSettingEthtool *s_ethtool2; + NMSettingEthtool *s_ethtool3; + guint32 out_value; + + con = nmtst_create_minimal_connection ("ethtool-ring", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + s_ethtool = NM_SETTING_ETHTOOL (nm_setting_ethtool_new ()); + nm_connection_add_setting (con, NM_SETTING (s_ethtool)); + + nm_setting_option_set_uint32 (NM_SETTING (s_ethtool), + NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, + 4); + + g_assert_true (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, &out_value)); + g_assert_cmpuint (out_value, ==, 4); + + nmtst_connection_normalize (con); + + variant = nm_connection_to_dbus (con, NM_CONNECTION_SERIALIZE_ALL); + + con2 = nm_simple_connection_new_from_dbus (variant, &error); + nmtst_assert_success (con2, error); + + s_ethtool2 = NM_SETTING_ETHTOOL (nm_connection_get_setting (con2, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_true (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool2), NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, &out_value)); + g_assert_cmpuint (out_value, ==, 4); + + nmtst_assert_connection_verifies_without_normalization (con2); + + nmtst_assert_connection_equals (con, FALSE, con2, FALSE); + + keyfile = nm_keyfile_write (con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + nmtst_assert_success (keyfile, error); + + con3 = nm_keyfile_read (keyfile, + "/ignored/current/working/directory/for/loading/relative/paths", + NM_KEYFILE_HANDLER_FLAGS_NONE, + NULL, + NULL, + &error); + nmtst_assert_success (con3, error); + + nm_keyfile_read_ensure_id (con3, "unused-because-already-has-id"); + nm_keyfile_read_ensure_uuid (con3, "unused-because-already-has-uuid"); + + nmtst_connection_normalize (con3); + + nmtst_assert_connection_equals (con, FALSE, con3, FALSE); + + s_ethtool3 = NM_SETTING_ETHTOOL (nm_connection_get_setting (con3, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_true (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool3), NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, &out_value)); + g_assert_cmpuint (out_value, ==, 4); + + + nm_setting_option_set (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, NULL); + g_assert_false (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, NULL)); + + nm_setting_option_set_uint32 (NM_SETTING (s_ethtool), + NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, + 8); + + g_assert_true (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, &out_value)); + g_assert_cmpuint (out_value, ==, 8); + + nm_setting_option_clear_by_name (NM_SETTING (s_ethtool), nm_ethtool_optname_is_ring); + g_assert_false (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, NULL)); + g_assert_false (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_RING_RX, NULL)); + g_assert_false (nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), NM_ETHTOOL_OPTNAME_RING_TX, NULL)); } /*****************************************************************************/ @@ -1917,14 +2134,16 @@ _test_verify_options_bridge (gboolean expected_result, case G_TYPE_UINT: { guint uvalue; - uvalue = _nm_utils_ascii_str_to_uint64 (option_val, 10, 0, G_MAXUINT, 0); + uvalue = _nm_utils_ascii_str_to_uint64 (option_val, 10, 0, G_MAXUINT, -1); + g_assert (errno == 0); g_object_set (s_bridge, option_key, uvalue, NULL); } break; case G_TYPE_BOOLEAN: { - gboolean bvalue; + int bvalue; - bvalue = _nm_utils_ascii_str_to_bool (option_val, FALSE); + bvalue = _nm_utils_ascii_str_to_bool (option_val, -1); + g_assert (bvalue != -1); g_object_set (s_bridge, option_key, bvalue, NULL); } break; @@ -1980,10 +2199,10 @@ test_bridge_verify (void) /* multicast-router */ test_verify_options_bridge (FALSE, "multicast-router", "nonsense"); - test_verify_options_bridge (FALSE, + test_verify_options_bridge (TRUE, "multicast-snooping", "no", "multicast-router", "auto"); - test_verify_options_bridge (FALSE, + test_verify_options_bridge (TRUE, "multicast-snooping", "no", "multicast-router", "enabled"); test_verify_options_bridge (TRUE, @@ -1998,6 +2217,13 @@ test_bridge_verify (void) test_verify_options_bridge (TRUE, "multicast-snooping", "yes", "multicast-router", "disabled"); + /* multicast-hash-max */ + test_verify_options_bridge (TRUE, + "multicast-hash-max", "1024"); + test_verify_options_bridge (TRUE, + "multicast-hash-max", "8192"); + test_verify_options_bridge (FALSE, + "multicast-hash-max", "3"); } /*****************************************************************************/ @@ -2008,6 +2234,7 @@ test_tc_config_qdisc (void) NMTCQdisc *qdisc1, *qdisc2; char *str; GError *error = NULL; + GVariant *variant; qdisc1 = nm_tc_qdisc_new ("fq_codel", TC_H_ROOT, &error); nmtst_assert_success (qdisc1, error); @@ -2070,6 +2297,43 @@ test_tc_config_qdisc (void) nm_tc_qdisc_unref (qdisc1); nm_tc_qdisc_unref (qdisc2); + +#define CHECK_ATTRIBUTE(qdisc, name, vtype, type, value) \ + variant = nm_tc_qdisc_get_attribute (qdisc, name); \ + g_assert (variant); \ + g_assert (g_variant_is_of_type (variant, vtype)); \ + g_assert_cmpint (g_variant_get_ ## type(variant), ==, value); + + qdisc1 = nm_utils_tc_qdisc_from_str ("handle 1235 root sfq perturb 10 quantum 1480 " + "limit 9000 flows 1024 divisor 500 depth 12", + &error); + nmtst_assert_success (qdisc1, error); + + g_assert_cmpstr (nm_tc_qdisc_get_kind (qdisc1), ==, "sfq"); + g_assert (nm_tc_qdisc_get_handle (qdisc1) == TC_H_MAKE (0x1235u << 16, 0x0000u)); + g_assert (nm_tc_qdisc_get_parent (qdisc1) == TC_H_ROOT); + CHECK_ATTRIBUTE (qdisc1, "perturb", G_VARIANT_TYPE_INT32, int32, 10); + CHECK_ATTRIBUTE (qdisc1, "quantum", G_VARIANT_TYPE_UINT32, uint32, 1480); + CHECK_ATTRIBUTE (qdisc1, "limit", G_VARIANT_TYPE_UINT32, uint32, 9000); + CHECK_ATTRIBUTE (qdisc1, "flows", G_VARIANT_TYPE_UINT32, uint32, 1024); + CHECK_ATTRIBUTE (qdisc1, "divisor", G_VARIANT_TYPE_UINT32, uint32, 500); + CHECK_ATTRIBUTE (qdisc1, "depth", G_VARIANT_TYPE_UINT32, uint32, 12); + nm_tc_qdisc_unref (qdisc1); + + qdisc1 = nm_utils_tc_qdisc_from_str ("handle 1235 root tbf rate 1000000 burst 5000 limit 10000", + &error); + nmtst_assert_success (qdisc1, error); + + g_assert_cmpstr (nm_tc_qdisc_get_kind (qdisc1), ==, "tbf"); + g_assert (nm_tc_qdisc_get_handle (qdisc1) == TC_H_MAKE (0x1235u << 16, 0x0000u)); + g_assert (nm_tc_qdisc_get_parent (qdisc1) == TC_H_ROOT); + CHECK_ATTRIBUTE (qdisc1, "rate", G_VARIANT_TYPE_UINT64, uint64, 1000000); + CHECK_ATTRIBUTE (qdisc1, "burst", G_VARIANT_TYPE_UINT32, uint32, 5000); + CHECK_ATTRIBUTE (qdisc1, "limit", G_VARIANT_TYPE_UINT32, uint32, 10000); + nm_tc_qdisc_unref (qdisc1); + + +#undef CHECK_ATTRIBUTE } static void @@ -3045,7 +3309,7 @@ test_roundtrip_conversion (gconstpointer test_data) { gs_unref_keyfile GKeyFile *kf = NULL; - kf = nm_keyfile_write (con, NULL, NULL, &error); + kf = nm_keyfile_write (con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); nmtst_assert_success (kf, error); /* the first kf_data_arr entry is special: it must be what the writer would @@ -3140,7 +3404,7 @@ test_roundtrip_conversion (gconstpointer test_data) { gs_unref_keyfile GKeyFile *kf = NULL; - kf = nm_keyfile_write (con2, NULL, NULL, &error); + kf = nm_keyfile_write (con2, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); nmtst_assert_success (kf, error); nmtst_keyfile_assert_data (kf, kf_data_arr->pdata[0], -1); } @@ -3444,7 +3708,7 @@ test_empty_setting (void) nmtst_assert_connection_verifies_without_normalization (con); - kf = nm_keyfile_write (con, NULL, NULL, &error); + kf = nm_keyfile_write (con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); nmtst_assert_success (kf, error); g_assert (g_key_file_has_group (kf, "gsm")); @@ -3452,6 +3716,7 @@ test_empty_setting (void) con2 = nm_keyfile_read (kf, "/ignored/current/working/directory/for/loading/relative/paths", + NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); @@ -3763,7 +4028,9 @@ main (int argc, char **argv) g_test_add_func ("/libnm/settings/dcb/priorities", test_dcb_priorities_valid); g_test_add_func ("/libnm/settings/dcb/bandwidth-sums", test_dcb_bandwidth_sums); - g_test_add_func ("/libnm/settings/ethtool/1", test_ethtool_1); + g_test_add_func ("/libnm/settings/ethtool/features", test_ethtool_features); + g_test_add_func ("/libnm/settings/ethtool/coalesce", test_ethtool_coalesce); + g_test_add_func ("/libnm/settings/ethtool/ring", test_ethtool_ring); g_test_add_func ("/libnm/settings/sriov/vf", test_sriov_vf); g_test_add_func ("/libnm/settings/sriov/vf-dup", test_sriov_vf_dup); |