NetworkManager/libnm-core/nm-setting-ethtool.c

622 lines
17 KiB
C
Raw Normal View History

// SPDX-License-Identifier: LGPL-2.1+
/*
* Copyright (C) 2018 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-setting-ethtool.h"
#include "nm-setting-private.h"
shared: build helper "libnm-libnm-core-{intern|aux}.la" library for libnm-core "libnm-core" implements common functionality for "NetworkManager" and "libnm". Note that clients like "nmcli" cannot access the internal API provided by "libnm-core". So, if nmcli wants to do something that is also done by "libnm-core", , "libnm", or "NetworkManager", the code would have to be duplicated. Instead, such code can be in "libnm-libnm-core-{intern|aux}.la". Note that: 0) "libnm-libnm-core-intern.la" is used by libnm-core itsself. On the other hand, "libnm-libnm-core-aux.la" is not used by libnm-core, but provides utilities on top of it. 1) they both extend "libnm-core" with utlities that are not public API of libnm itself. Maybe part of the code should one day become public API of libnm. On the other hand, this is code for which we may not want to commit to a stable interface or which we don't want to provide as part of the API. 2) "libnm-libnm-core-intern.la" is statically linked by "libnm-core" and thus directly available to "libnm" and "NetworkManager". On the other hand, "libnm-libnm-core-aux.la" may be used by "libnm" and "NetworkManager". Both libraries may be statically linked by libnm clients (like nmcli). 3) it must only use glib, libnm-glib-aux.la, and the public API of libnm-core. This is important: it must not use "libnm-core/nm-core-internal.h" nor "libnm-core/nm-utils-private.h" so the static library is usable by nmcli which couldn't access these. Note that "shared/nm-meta-setting.c" is an entirely different case, because it behaves differently depending on whether linking against "libnm-core" or the client programs. As such, this file must be compiled twice. (cherry picked from commit af07ed01c04867e281cc3982a7ab0d244d4f8e2e)
2019-04-15 09:26:53 +02:00
#include "nm-libnm-core-intern/nm-ethtool-utils.h"
/*****************************************************************************/
/**
* SECTION:nm-setting-ethtool
* @short_description: Describes connection properties for ethtool related options
*
* The #NMSettingEthtool object is a #NMSetting subclass that describes properties
* to control network driver and hardware settings.
**/
/*****************************************************************************/
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
*
* Checks whether @optname is a valid option name for an offload feature.
*
* %Returns: %TRUE, if @optname is valid
*
* Since: 1.20
*
* Note that nm_ethtool_optname_is_feature() was first added to the libnm header files
* in 1.14.0 but forgot to actually add to the library. This happened belatedly in 1.20.0 and
* the stable versions 1.18.2, 1.16.4 and 1.14.8 (with linker version "libnm_1_14_8").
*/
gboolean
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));
}
/*****************************************************************************/
/**
* NMSettingEthtool:
*
* Ethtool Ethernet Settings
*
* Since: 1.14
*/
struct _NMSettingEthtool {
NMSetting parent;
};
struct _NMSettingEthtoolClass {
NMSettingClass parent;
};
G_DEFINE_TYPE (NMSettingEthtool, nm_setting_ethtool, NM_TYPE_SETTING)
#define NM_SETTING_ETHTOOL_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSettingEthtool, NM_IS_SETTING_ETHTOOL, NMSetting)
/*****************************************************************************/
static void
_notify_attributes (NMSettingEthtool *self)
{
_nm_setting_option_notify (NM_SETTING (self), TRUE);
}
/*****************************************************************************/
/**
* nm_setting_ethtool_get_feature:
* @setting: the #NMSettingEthtool
* @optname: option name of the offload feature to get
*
* Gets and offload feature setting. Returns %NM_TERNARY_DEFAULT if the
* feature is not set.
*
* Note that @optname must be a valid name for a feature, according to
* nm_ethtool_optname_is_feature().
*
* Returns: a #NMTernary value indicating whether the offload feature
* is enabled, disabled, or left untouched.
*
* Since: 1.14
*/
NMTernary
nm_setting_ethtool_get_feature (NMSettingEthtool *setting,
const char *optname)
{
GVariant *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);
libnm: add API for setting gendata options to NMSetting (nm_setting_option_get()) NMSettingEthtool is implemented using "gendata", meaning a hash of GVariant. This is different from most other settings that have properties implemented as GObject properties. There are two reasons for this approach: - The setting is transferred via D-Bus as "a{sv}" dictionary. By unpacking the dictionary into GObject properties, the setting cannot handle unknown properties. To be forward compatible (and due to sloppy programming), unknown dictionary keys are silently ignored when parsing a NMSetting. That is error prone and also prevents settings to be treated loss-less. Instead, we should at first accept all values from the dictionary. Only in a second step, nm_connection_verify() rejects invalid settings with an error reason. This way, the user can create a NMSetting, but in a separate step handle if the setting doesn't verify. "gendata" solves this by tracking the full GVariant dictionary. This is still not entirely lossless, because multiple keys are combined. This is for example interesting if an libnm client fetches a connection from a newer NetworkManager version. Now the user can modify the properties that she knows about, while leaving all unknown properties (from newer versions) in place. - the approach aims to reduce the necessary boiler plate to create GObject properties. Adding a new property should require less new code. This approach was always be intended to be suitable for all settings, not only NMSettingEthtool. We should not once again try to add API like nm_setting_ethtool_set_feature(), nm_setting_ethtool_set_coalesce(), etc. Note that the option name already fully encodes whether it is a feature, a coalesce option, or whatever. We should not have "nm_setting_set_$SUB_GROUP (setting, $ONE_NAME_FROM_GROUP)" API, but simply "nm_setting_option_set (setting, $OPTION)" accessors. Also, when parsing a NMSettingEthtool from a GVariant, then a feature option can be any kind of variant. Only nm_setting_verify() rejects variants of the wrong type. As such, nm_setting_option_set*() also doesn't validate whether the variant type matches the option. Of course, if you set a value of wrong type, verify() will reject the setting. Add new general purpose API for this and expose it for NMSetting.
2020-05-14 09:16:32 +02:00
v = nm_setting_option_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;
}
/**
* nm_setting_ethtool_set_feature:
* @setting: the #NMSettingEthtool
* @optname: option name of the offload feature to get
* @value: the new value to set. The special value %NM_TERNARY_DEFAULT
* means to clear the offload feature setting.
*
* Sets and offload feature setting.
*
* Note that @optname must be a valid name for a feature, according to
* nm_ethtool_optname_is_feature().
*
* Since: 1.14
*/
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_option_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);
}
/**
* nm_setting_ethtool_clear_features:
* @setting: the #NMSettingEthtool
*
* Clears all offload features settings
*
* Since: 1.14
*/
void
nm_setting_ethtool_clear_features (NMSettingEthtool *setting)
{
g_return_if_fail (NM_IS_SETTING_ETHTOOL (setting));
if (_nm_setting_option_clear_all (NM_SETTING (setting),
&nm_ethtool_optname_is_feature))
_notify_attributes (setting);
}
guint
nm_setting_ethtool_init_features (NMSettingEthtool *setting,
NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */)
{
GHashTable *hash;
GHashTableIter iter;
guint i;
guint n_req = 0;
const char *name;
GVariant *variant;
nm_assert (NM_IS_SETTING_ETHTOOL (setting));
nm_assert (requested);
for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++)
requested[i] = NM_TERNARY_DEFAULT;
hash = _nm_setting_option_hash (NM_SETTING (setting), FALSE);
if (!hash)
return 0;
g_hash_table_iter_init (&iter, hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &variant)) {
NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name (name);
if (!nm_ethtool_id_is_feature (ethtool_id))
continue;
if (!g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN))
continue;
requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX (ethtool_id)] = g_variant_get_boolean (variant)
? NM_TERNARY_TRUE
: NM_TERNARY_FALSE;
n_req++;
}
return n_req;
}
/**
* nm_setting_ethtool_get_coalesce:
* @setting: the #NMSettingEthtool
* @optname: option name of the coalescing setting to get
* @out_value (out) (allow-none): value of the coalescing setting
*
* Gets the value of coalescing setting.
*
* Note that @optname must be a valid name for a setting, according to
* nm_ethtool_optname_is_coalesce().
*
*
* Returns: %TRUE and places the coalesce setting value in @out_value or %FALSE if unset.
*
* Since: 1.26
*/
gboolean
nm_setting_ethtool_get_coalesce (NMSettingEthtool *setting,
const char *optname,
guint32 *out_value)
{
g_return_val_if_fail (NM_IS_SETTING_ETHTOOL (setting), FALSE);
g_return_val_if_fail (nm_ethtool_optname_is_coalesce (optname), FALSE);
return nm_setting_option_get_uint32 (NM_SETTING (setting),
optname,
out_value);
}
/**
* nm_setting_ethtool_set_coalesce:
* @setting: the #NMSettingEthtool
* @optname: option name of the coalesce setting
* @value: the new value to set.
*
* Sets a coalesce setting.
*
* Note that @optname must be a valid name for a coalesce setting, according to
* nm_ethtool_optname_is_coalesce().
*
* Since: 1.26
*/
void
nm_setting_ethtool_set_coalesce (NMSettingEthtool *setting,
const char *optname,
guint32 value)
{
NMEthtoolID ethtool_id;
g_return_if_fail (NM_IS_SETTING_ETHTOOL (setting));
ethtool_id = nm_ethtool_id_get_by_name (optname);
g_return_if_fail (nm_ethtool_id_is_coalesce (ethtool_id));
if (NM_IN_SET (ethtool_id,
NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX,
NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX))
value = !!value;
nm_setting_option_set_uint32 (NM_SETTING (setting),
optname,
value);
_notify_attributes (setting);
}
/**
* nm_setting_ethtool_clear_coalesce:
* @setting: the #NMSettingEthtool
* @optname: option name of the coalesce setting
*
* Clear a coalesce setting
*
* Since: 1.26
*/
void
nm_setting_ethtool_clear_coalesce (NMSettingEthtool *setting,
const char *optname)
{
g_return_if_fail (NM_IS_SETTING_ETHTOOL (setting));
g_return_if_fail (nm_str_not_empty (optname));
if (_nm_setting_option_clear (NM_SETTING (setting), optname))
_notify_attributes (setting);
}
/**
* nm_setting_ethtool_clear_coalesce_all:
* @setting: the #NMSettingEthtool
*
* Clears all coalesce settings
*
* Since: 1.26
*/
void
nm_setting_ethtool_clear_coalesce_all (NMSettingEthtool *setting)
{
g_return_if_fail (NM_IS_SETTING_ETHTOOL (setting));
if (_nm_setting_option_clear_all (NM_SETTING (setting),
&nm_ethtool_optname_is_coalesce))
_notify_attributes (setting);
}
/**
* nm_setting_ethtool_get_ring:
* @setting: the #NMSettingEthtool
* @optname: option name of the ring setting to get
* @out_value (out) (allow-none): value of the ring setting
*
* Gets the value of ring setting.
*
* Note that @optname must be a valid name for a setting, according to
* nm_ethtool_optname_is_ring().
*
*
* Returns: %TRUE and places the ring setting value in @out_value or %FALSE if unset.
*
* Since: 1.26
*/
gboolean
nm_setting_ethtool_get_ring (NMSettingEthtool *setting,
const char *optname,
guint32 *out_value)
{
g_return_val_if_fail (NM_IS_SETTING_ETHTOOL (setting), FALSE);
g_return_val_if_fail (nm_ethtool_optname_is_ring (optname), FALSE);
return nm_setting_option_get_uint32 (NM_SETTING (setting),
optname,
out_value);
}
/**
* nm_setting_ethtool_set_ring:
* @setting: the #NMSettingEthtool
* @optname: option name of the ring setting
* @value: the new value to set.
*
* Sets a ring setting.
*
* Note that @optname must be a valid name for a ring setting, according to
* nm_ethtool_optname_is_ring().
*
* Since: 1.26
*/
void
nm_setting_ethtool_set_ring (NMSettingEthtool *setting,
const char *optname,
guint32 value)
{
NMEthtoolID ethtool_id;
g_return_if_fail (NM_IS_SETTING_ETHTOOL (setting));
ethtool_id = nm_ethtool_id_get_by_name (optname);
g_return_if_fail (nm_ethtool_id_is_ring (ethtool_id));
nm_setting_option_set_uint32 (NM_SETTING (setting),
optname,
value);
_notify_attributes (setting);
}
/**
* nm_setting_ethtool_clear_ring:
* @setting: the #NMSettingEthtool
* @optname: option name of the ring setting
*
* Clear a ring setting
*
* Since: 1.26
*/
void
nm_setting_ethtool_clear_ring (NMSettingEthtool *setting,
const char *optname)
{
g_return_if_fail (NM_IS_SETTING_ETHTOOL (setting));
g_return_if_fail (nm_str_not_empty (optname));
if (_nm_setting_option_clear (NM_SETTING (setting), optname))
_notify_attributes (setting);
}
/**
* nm_setting_ethtool_clear_ring_all:
* @setting: the #NMSettingEthtool
*
* Clears all ring settings
*
* Since: 1.26
*/
void
nm_setting_ethtool_clear_ring_all (NMSettingEthtool *setting)
{
g_return_if_fail (NM_IS_SETTING_ETHTOOL (setting));
if (_nm_setting_option_clear_all (NM_SETTING (setting),
&nm_ethtool_optname_is_ring))
_notify_attributes (setting);
}
/*****************************************************************************/
/**
* nm_setting_ethtool_get_optnames:
* @setting: the #NMSettingEthtool instance.
* @out_length: (out) (optional): return location for the number of keys returned, or %NULL
*
* This returns all options names that are set. This includes the feature names
* like %NM_ETHTOOL_OPTNAME_FEATURE_GRO. See nm_ethtool_optname_is_feature() to
* check whether the option name is valid for offload features.
*
* Returns: (array zero-terminated=1) (transfer container): list of set option
* names or %NULL if no options are set. The option names are still owned by
* @setting and may get invalidated when @setting gets modified.
*
* Since: 1.20
*/
const char **
nm_setting_ethtool_get_optnames (NMSettingEthtool *setting,
guint *out_length)
{
g_return_val_if_fail (NM_IS_SETTING_ETHTOOL (setting), NULL);
return nm_utils_strdict_get_keys (_nm_setting_option_hash (NM_SETTING (setting), FALSE),
TRUE,
out_length);
}
/*****************************************************************************/
static gboolean
verify (NMSetting *setting, NMConnection *connection, GError **error)
{
GHashTable *hash;
GHashTableIter iter;
const char *optname;
GVariant *variant;
hash = _nm_setting_option_hash (setting, FALSE);
if (!hash)
return TRUE;
g_hash_table_iter_init (&iter, hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &optname, (gpointer *) &variant)) {
const GVariantType *variant_type;
NMEthtoolID ethtool_id;
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 ethtool setting"));
g_prefix_error (error, "%s.%s: ", NM_SETTING_ETHTOOL_SETTING_NAME, optname);
return FALSE;
}
if (!g_variant_is_of_type (variant, variant_type)) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("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;
}
}
}
return TRUE;
}
/*****************************************************************************/
static const GVariantType *
get_variant_type (const NMSettInfoSetting *sett_info,
const char *name,
GError **error)
{
const GVariantType *variant_type;
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;
}
/*****************************************************************************/
static void
nm_setting_ethtool_init (NMSettingEthtool *setting)
{
}
/**
* nm_setting_ethtool_new:
*
* Creates a new #NMSettingEthtool object with default values.
*
* Returns: (transfer full): the new empty #NMSettingEthtool object
*
* Since: 1.14
**/
NMSetting *
nm_setting_ethtool_new (void)
{
return g_object_new (NM_TYPE_SETTING_ETHTOOL, NULL);
}
static void
nm_setting_ethtool_class_init (NMSettingEthtoolClass *klass)
{
NMSettingClass *setting_class = NM_SETTING_CLASS (klass);
setting_class->verify = verify;
_nm_setting_class_commit_full (setting_class,
NM_META_SETTING_TYPE_ETHTOOL,
NM_SETT_INFO_SETT_DETAIL (
.gendata_info = NM_SETT_INFO_SETT_GENDATA (
.get_variant_type = get_variant_type,
),
),
NULL);
}