summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2018-07-16 15:42:07 +0200
committerThomas Haller <thaller@redhat.com>2018-08-10 10:38:19 +0200
commitc085b6e3a7d0ab62857878394d8f4c94909bed79 (patch)
treed32f1c1698e006e83f6aab0c0c7d7790aa833d11
parent14f963cde33591e8a25763322e034ddaf0124ee8 (diff)
downloadNetworkManager-c085b6e3a7d0ab62857878394d8f4c94909bed79.tar.gz
platform/ethtool: add code to get/set offload features via ethtool
Also, add two more features "tx-tcp-segmentation" and "tx-tcp6-segmentation". There are two reasons for that: - systemd-networkd supports setting these two features, so lets support them too (apparently they are important enough for networkd). - these two features are already implicitly covered by "tso". Like for the "ethtool" program, "tso" is an alias for several actual features. By adding two features that are already also covered by an alias (which sets multiple kernel names at once), we showcase how aliases for the same feature can coexist. In particular, note how setting "tso on tx-tcp6-segmentation off" will behave as one would expect: all 4 tso features covered by the alias are enabled, except that particular one.
-rw-r--r--clients/common/nm-meta-setting-desc.c2
-rw-r--r--libnm-core/nm-setting-ethtool.h24
-rw-r--r--shared/nm-ethtool-utils.c4
-rw-r--r--shared/nm-ethtool-utils.h4
-rw-r--r--src/platform/nm-platform-utils.c376
-rw-r--r--src/platform/nm-platform-utils.h56
-rw-r--r--src/platform/nm-platform.c26
-rw-r--r--src/platform/nm-platform.h11
-rw-r--r--src/platform/tests/test-link.c98
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c24
10 files changed, 599 insertions, 26 deletions
diff --git a/clients/common/nm-meta-setting-desc.c b/clients/common/nm-meta-setting-desc.c
index a2833be1d3..0595640ef2 100644
--- a/clients/common/nm-meta-setting-desc.c
+++ b/clients/common/nm-meta-setting-desc.c
@@ -5881,6 +5881,8 @@ static const NMMetaPropertyInfo *const property_infos_ETHTOOL[] = {
PROPERTY_INFO_ETHTOOL (FEATURE_TSO),
PROPERTY_INFO_ETHTOOL (FEATURE_TX),
PROPERTY_INFO_ETHTOOL (FEATURE_TXVLAN),
+ PROPERTY_INFO_ETHTOOL (FEATURE_TX_TCP6_SEGMENTATION),
+ PROPERTY_INFO_ETHTOOL (FEATURE_TX_TCP_SEGMENTATION),
NULL,
};
diff --git a/libnm-core/nm-setting-ethtool.h b/libnm-core/nm-setting-ethtool.h
index 763d2691ee..10f5651b19 100644
--- a/libnm-core/nm-setting-ethtool.h
+++ b/libnm-core/nm-setting-ethtool.h
@@ -31,17 +31,19 @@ G_BEGIN_DECLS
/*****************************************************************************/
-#define NM_ETHTOOL_OPTNAME_FEATURE_GRO "feature-gro"
-#define NM_ETHTOOL_OPTNAME_FEATURE_GSO "feature-gso"
-#define NM_ETHTOOL_OPTNAME_FEATURE_LRO "feature-lro"
-#define NM_ETHTOOL_OPTNAME_FEATURE_NTUPLE "feature-ntuple"
-#define NM_ETHTOOL_OPTNAME_FEATURE_RX "feature-rx"
-#define NM_ETHTOOL_OPTNAME_FEATURE_RXHASH "feature-rxhash"
-#define NM_ETHTOOL_OPTNAME_FEATURE_RXVLAN "feature-rxvlan"
-#define NM_ETHTOOL_OPTNAME_FEATURE_SG "feature-sg"
-#define NM_ETHTOOL_OPTNAME_FEATURE_TSO "feature-tso"
-#define NM_ETHTOOL_OPTNAME_FEATURE_TX "feature-tx"
-#define NM_ETHTOOL_OPTNAME_FEATURE_TXVLAN "feature-txvlan"
+#define NM_ETHTOOL_OPTNAME_FEATURE_GRO "feature-gro"
+#define NM_ETHTOOL_OPTNAME_FEATURE_GSO "feature-gso"
+#define NM_ETHTOOL_OPTNAME_FEATURE_LRO "feature-lro"
+#define NM_ETHTOOL_OPTNAME_FEATURE_NTUPLE "feature-ntuple"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX "feature-rx"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RXHASH "feature-rxhash"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RXVLAN "feature-rxvlan"
+#define NM_ETHTOOL_OPTNAME_FEATURE_SG "feature-sg"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TSO "feature-tso"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX "feature-tx"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TXVLAN "feature-txvlan"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_TCP6_SEGMENTATION "feature-tx-tcp6-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_TCP_SEGMENTATION "feature-tx-tcp-segmentation"
gboolean nm_ethtool_optname_is_feature (const char *optname);
diff --git a/shared/nm-ethtool-utils.c b/shared/nm-ethtool-utils.c
index 1e6d405a88..a57228040c 100644
--- a/shared/nm-ethtool-utils.c
+++ b/shared/nm-ethtool-utils.c
@@ -46,6 +46,8 @@ const NMEthtoolData *const nm_ethtool_data[_NM_ETHTOOL_ID_NUM + 1] = {
ETHT_DATA (FEATURE_TSO),
ETHT_DATA (FEATURE_TX),
ETHT_DATA (FEATURE_TXVLAN),
+ ETHT_DATA (FEATURE_TX_TCP6_SEGMENTATION),
+ ETHT_DATA (FEATURE_TX_TCP_SEGMENTATION),
[_NM_ETHTOOL_ID_NUM] = NULL,
};
@@ -61,6 +63,8 @@ const guint8 const _by_name[_NM_ETHTOOL_ID_NUM] = {
NM_ETHTOOL_ID_FEATURE_SG,
NM_ETHTOOL_ID_FEATURE_TSO,
NM_ETHTOOL_ID_FEATURE_TX,
+ NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION,
+ NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION,
NM_ETHTOOL_ID_FEATURE_TXVLAN,
};
diff --git a/shared/nm-ethtool-utils.h b/shared/nm-ethtool-utils.h
index 06956d0462..3846450803 100644
--- a/shared/nm-ethtool-utils.h
+++ b/shared/nm-ethtool-utils.h
@@ -40,7 +40,9 @@ typedef enum {
NM_ETHTOOL_ID_FEATURE_TSO,
NM_ETHTOOL_ID_FEATURE_TX,
NM_ETHTOOL_ID_FEATURE_TXVLAN,
- _NM_ETHTOOL_ID_FEATURE_LAST = NM_ETHTOOL_ID_FEATURE_TXVLAN,
+ NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION,
+ NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION,
+ _NM_ETHTOOL_ID_FEATURE_LAST = NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION,
_NM_ETHTOOL_ID_FEATURE_NUM = (_NM_ETHTOOL_ID_FEATURE_LAST - _NM_ETHTOOL_ID_FEATURE_FIRST + 1),
_NM_ETHTOOL_ID_LAST = _NM_ETHTOOL_ID_FEATURE_LAST,
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 74f8b3a998..24f8dfa8d3 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -36,9 +36,12 @@
#include "nm-utils.h"
#include "nm-setting-wired.h"
+#include "nm-ethtool-utils.h"
#include "nm-core-utils.h"
+#define ONOFF(bool_val) ((bool_val) ? "on" : "off")
+
/******************************************************************************
* utils
*****************************************************************************/
@@ -113,6 +116,7 @@ NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32,
NM_UTILS_ENUM2STR (ETHTOOL_GSTATS, "ETHTOOL_GSTATS"),
NM_UTILS_ENUM2STR (ETHTOOL_GSTRINGS, "ETHTOOL_GSTRINGS"),
NM_UTILS_ENUM2STR (ETHTOOL_GWOL, "ETHTOOL_GWOL"),
+ NM_UTILS_ENUM2STR (ETHTOOL_SFEATURES, "ETHTOOL_SFEATURES"),
NM_UTILS_ENUM2STR (ETHTOOL_SSET, "ETHTOOL_SSET"),
NM_UTILS_ENUM2STR (ETHTOOL_SWOL, "ETHTOOL_SWOL"),
);
@@ -255,6 +259,378 @@ ethtool_get_stringset_index (SocketHandle *shandle, int stringset_id, const char
return -1;
}
+/*****************************************************************************/
+
+static const NMEthtoolFeatureInfo _ethtool_feature_infos[_NM_ETHTOOL_ID_FEATURE_NUM] = {
+#define ETHT_FEAT(eid, ...) \
+ { \
+ .ethtool_id = eid, \
+ .n_kernel_names = NM_NARG (__VA_ARGS__), \
+ .kernel_names = ((const char *const[]) { __VA_ARGS__ }), \
+ }
+
+ /* the order does only matter for one thing: if it happens that more than one NMEthtoolID
+ * reference the same kernel-name, then the one that is mentioned *later* will win in
+ * case these NMEthtoolIDs are set. That mostly only makes sense for ethtool-ids which
+ * refer to multiple features ("feature-tso"), while also having more specific ids
+ * ("feature-tx-tcp-segmentation"). */
+
+ /* names from ethtool utility, which are aliases for multiple features. */
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_SG, "tx-scatter-gather",
+ "tx-scatter-gather-fraglist"),
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TSO, "tx-tcp-segmentation",
+ "tx-tcp-ecn-segmentation",
+ "tx-tcp-mangleid-segmentation",
+ "tx-tcp6-segmentation"),
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX, "tx-checksum-ipv4",
+ "tx-checksum-ip-generic",
+ "tx-checksum-ipv6",
+ "tx-checksum-fcoe-crc",
+ "tx-checksum-sctp"),
+
+ /* names from ethtool utility, which are aliases for one feature. */
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_GRO, "rx-gro"),
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_GSO, "tx-generic-segmentation"),
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_LRO, "rx-lro"),
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter"),
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX, "rx-checksum"),
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing"),
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse"),
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert"),
+
+ /* names of features, as known by kernel. */
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, "tx-tcp6-segmentation"),
+ ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"),
+};
+
+/* the number of kernel features that we handle. It essentially is the sum of all
+ * kernel_names. So, all ethtool-ids that reference exactly one kernel-name
+ * (_NM_ETHTOOL_ID_FEATURE_NUM) + some extra, for ethtool-ids that are aliases
+ * for multiple kernel-names. */
+#define N_ETHTOOL_KERNEL_FEATURES (((guint) _NM_ETHTOOL_ID_FEATURE_NUM) + 8u)
+
+static void
+_ASSERT_ethtool_feature_infos (void)
+{
+#if NM_MORE_ASSERTS > 10
+ guint i, k, n;
+ bool found[_NM_ETHTOOL_ID_FEATURE_NUM] = { };
+
+ G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (_ethtool_feature_infos) == _NM_ETHTOOL_ID_FEATURE_NUM);
+
+ n = 0;
+ for (i = 0; i < G_N_ELEMENTS (_ethtool_feature_infos); i++) {
+ NMEthtoolFeatureState kstate;
+ const NMEthtoolFeatureInfo *inf = &_ethtool_feature_infos[i];
+
+ g_assert (inf->ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST);
+ g_assert (inf->ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST);
+ g_assert (inf->n_kernel_names > 0);
+
+ for (k = 0; k < i; k++)
+ g_assert (inf->ethtool_id != _ethtool_feature_infos[k].ethtool_id);
+
+ g_assert (!found[inf->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]);
+ found[inf->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = TRUE;
+
+ kstate.idx_kernel_name = inf->n_kernel_names - 1;
+ g_assert ((guint) kstate.idx_kernel_name == (guint) (inf->n_kernel_names - 1));
+
+ n += inf->n_kernel_names;
+ for (k = 0; k < inf->n_kernel_names; k++) {
+ g_assert (nm_utils_strv_find_first ((char **) inf->kernel_names,
+ k,
+ inf->kernel_names[k]) < 0);
+ }
+ }
+
+ for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++)
+ g_assert (found[i]);
+
+ g_assert (n == N_ETHTOOL_KERNEL_FEATURES);
+#endif
+}
+
+static NMEthtoolFeatureStates *
+ethtool_get_features (SocketHandle *shandle)
+{
+ gs_free NMEthtoolFeatureStates *states = NULL;
+ gs_free struct ethtool_gstrings *ss_features = NULL;
+
+ _ASSERT_ethtool_feature_infos ();
+
+ ss_features = ethtool_get_stringset (shandle, ETH_SS_FEATURES);
+ if (!ss_features)
+ return NULL;
+
+ if (ss_features->len > 0) {
+ gs_free struct ethtool_gfeatures *gfeatures = NULL;
+ guint idx;
+ const NMEthtoolFeatureState *states_list0 = NULL;
+ const NMEthtoolFeatureState *const*states_plist0 = NULL;
+ guint states_plist_n = 0;
+
+ gfeatures = g_malloc0 ( sizeof (struct ethtool_gfeatures)
+ + (NM_DIV_ROUND_UP (ss_features->len, 32u) * sizeof(gfeatures->features[0])));
+
+ gfeatures->cmd = ETHTOOL_GFEATURES;
+ gfeatures->size = NM_DIV_ROUND_UP (ss_features->len, 32u);
+ if (ethtool_call_handle (shandle, gfeatures) < 0)
+ return NULL;
+
+ for (idx = 0; idx < G_N_ELEMENTS (_ethtool_feature_infos); idx++) {
+ const NMEthtoolFeatureInfo *info = &_ethtool_feature_infos[idx];
+ guint idx_kernel_name;
+
+ for (idx_kernel_name = 0; idx_kernel_name < info->n_kernel_names; idx_kernel_name++) {
+ NMEthtoolFeatureState *kstate;
+ const char *kernel_name = info->kernel_names[idx_kernel_name];
+ int i_feature;
+ guint i_block;
+ guint32 i_flag;
+
+ i_feature = ethtool_gstrings_find (ss_features, kernel_name);
+ if (i_feature < 0)
+ continue;
+
+ i_block = ((guint) i_feature) / 32u;
+ i_flag = (guint32) (1u << (((guint) i_feature) % 32u));
+
+ if (!states) {
+ states = g_malloc0 (sizeof (NMEthtoolFeatureStates)
+ + (N_ETHTOOL_KERNEL_FEATURES * sizeof (NMEthtoolFeatureState))
+ + ((N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos)) * sizeof (NMEthtoolFeatureState *)));
+ states_list0 = &states->states_list[0];
+ states_plist0 = (gpointer) &states_list0[N_ETHTOOL_KERNEL_FEATURES];
+ states->n_ss_features = ss_features->len;
+ }
+
+ nm_assert (states->n_states < N_ETHTOOL_KERNEL_FEATURES);
+ kstate = (NMEthtoolFeatureState *) &states_list0[states->n_states];
+ states->n_states++;
+
+ kstate->info = info;
+ kstate->idx_ss_features = i_feature;
+ kstate->idx_kernel_name = idx_kernel_name;
+ kstate->available = !!(gfeatures->features[i_block].available & i_flag);
+ kstate->requested = !!(gfeatures->features[i_block].requested & i_flag);
+ kstate->active = !!(gfeatures->features[i_block].active & i_flag);
+ kstate->never_changed = !!(gfeatures->features[i_block].never_changed & i_flag);
+
+ nm_assert (states_plist_n < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos));
+
+ if (!states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST])
+ states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = &states_plist0[states_plist_n];
+ ((const NMEthtoolFeatureState **) states_plist0)[states_plist_n] = kstate;
+ states_plist_n++;
+ }
+
+ if (states && states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]) {
+ nm_assert (states_plist_n < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos));
+ nm_assert (!states_plist0[states_plist_n]);
+ states_plist_n++;
+ }
+ }
+ }
+
+ return g_steal_pointer (&states);
+}
+
+NMEthtoolFeatureStates *
+nmp_utils_ethtool_get_features (int ifindex)
+{
+ nm_auto_socket_handle SocketHandle shandle = { };
+ NMEthtoolFeatureStates *features;
+ int r;
+
+ g_return_val_if_fail (ifindex > 0, 0);
+
+ if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+ nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
+ ifindex,
+ "get-features",
+ g_strerror (-r));
+ return FALSE;
+ }
+
+ features = ethtool_get_features (&shandle);
+
+ if (!features) {
+ nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure getting features",
+ ifindex,
+ "get-features");
+ return NULL;
+ }
+
+ nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: retrieved kernel features",
+ ifindex,
+ "get-features");
+ return features;
+}
+
+static const char *
+_ethtool_feature_state_to_string (char *buf, gsize buf_size, const NMEthtoolFeatureState *s, const char *prefix)
+{
+ int l;
+
+ l = g_snprintf (buf, buf_size,
+ "%s %s%s",
+ prefix ?: "",
+ ONOFF (s->active),
+ (!s->available || s->never_changed)
+ ? ", [fixed]"
+ : ((s->requested != s->active)
+ ? (s->requested ? ", [requested on]" : ", [requested off]")
+ : ""));
+ nm_assert (l < buf_size);
+ return buf;
+}
+
+gboolean
+nmp_utils_ethtool_set_features (int ifindex,
+ const NMEthtoolFeatureStates *features,
+ const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+ gboolean do_set /* or reset */)
+{
+ nm_auto_socket_handle SocketHandle shandle = { };
+ gs_free struct ethtool_sfeatures *sfeatures = NULL;
+ int r;
+ guint i, j;
+ struct {
+ const NMEthtoolFeatureState *f_state;
+ NMTernary requested;
+ } set_states[N_ETHTOOL_KERNEL_FEATURES];
+ guint set_states_n = 0;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (ifindex > 0, 0);
+ g_return_val_if_fail (features, 0);
+ g_return_val_if_fail (requested, 0);
+
+ nm_assert (features->n_states <= N_ETHTOOL_KERNEL_FEATURES);
+
+ for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) {
+ const NMEthtoolFeatureState *const*states_indexed;
+
+ if (requested[i] == NM_TERNARY_DEFAULT)
+ continue;
+
+ if (!(states_indexed = features->states_indexed[i])) {
+ if (do_set) {
+ nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: set feature %s: skip (not found)",
+ ifindex,
+ "set-features",
+ nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname);
+ success = FALSE;
+ }
+ continue;
+ }
+
+ for (j = 0; states_indexed[j]; j++) {
+ const NMEthtoolFeatureState *s = states_indexed[j];
+ char sbuf[255];
+
+ if (set_states_n >= G_N_ELEMENTS (set_states))
+ g_return_val_if_reached (FALSE);
+
+ if (s->never_changed) {
+ nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s feature %s (%s): %s, %s (skip feature marked as never changed)",
+ ifindex,
+ "set-features",
+ do_set ? "set" : "reset",
+ nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+ s->info->kernel_names[s->idx_kernel_name],
+ ONOFF (do_set ? requested[i] == NM_TERNARY_TRUE : s->active),
+ _ethtool_feature_state_to_string (sbuf, sizeof (sbuf), s, do_set ? " currently:" : " before:"));
+ continue;
+ }
+
+ nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s feature %s (%s): %s, %s",
+ ifindex,
+ "set-features",
+ do_set ? "set" : "reset",
+ nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+ s->info->kernel_names[s->idx_kernel_name],
+ ONOFF (do_set ? requested[i] == NM_TERNARY_TRUE : s->active),
+ _ethtool_feature_state_to_string (sbuf, sizeof (sbuf), s, do_set ? " currently:" : " before:"));
+
+ if ( do_set
+ && (!s->available || s->never_changed)
+ && (s->active != (requested[i] == NM_TERNARY_TRUE))) {
+ /* we request to change a flag which kernel reported as fixed.
+ * While the ethtool operation will silently succeed, mark the request
+ * as failure. */
+ success = FALSE;
+ }
+
+ set_states[set_states_n].f_state = s;
+ set_states[set_states_n].requested = requested[i];
+ set_states_n++;
+ }
+ }
+
+ if (set_states_n == 0) {
+ nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: no feature requested",
+ ifindex,
+ "set-features");
+ return TRUE;
+ }
+
+ if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+ nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
+ ifindex,
+ "set-features",
+ g_strerror (-r));
+ return FALSE;
+ }
+
+ sfeatures = g_malloc0 (sizeof (struct ethtool_sfeatures)
+ + (NM_DIV_ROUND_UP (features->n_ss_features, 32U) * sizeof(sfeatures->features[0])));
+ sfeatures->cmd = ETHTOOL_SFEATURES;
+ sfeatures->size = NM_DIV_ROUND_UP (features->n_ss_features, 32U);
+
+ for (i = 0; i < set_states_n; i++) {
+ const NMEthtoolFeatureState *s = set_states[i].f_state;
+ guint i_block;
+ guint32 i_flag;
+ gboolean is_requested;
+
+ i_block = s->idx_ss_features / 32u;
+ i_flag = (guint32) (1u << (s->idx_ss_features % 32u));
+
+ sfeatures->features[i_block].valid |= i_flag;
+
+ if (do_set)
+ is_requested = (set_states[i].requested == NM_TERNARY_TRUE);
+ else
+ is_requested = s->active;
+
+ if (is_requested)
+ sfeatures->features[i_block].requested |= i_flag;
+ else
+ sfeatures->features[i_block].requested &= ~i_flag;
+ }
+
+ if ((r = ethtool_call_handle (&shandle, sfeatures)) < 0) {
+ success = FALSE;
+ nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure setting features (%s)",
+ ifindex,
+ "set-features",
+ g_strerror (-r));
+ return FALSE;
+ }
+
+ nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s",
+ ifindex,
+ "set-features",
+ success
+ ? "successfully setting features"
+ : "at least some of the features were not successfuly set");
+ return success;
+}
+
+/*****************************************************************************/
+
gboolean
nmp_utils_ethtool_get_driver_info (int ifindex,
NMPUtilsEthtoolDriverInfo *data)
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
index 97f5630a4d..bad77aee38 100644
--- a/src/platform/nm-platform-utils.h
+++ b/src/platform/nm-platform-utils.h
@@ -23,6 +23,9 @@
#include "nm-platform.h"
#include "nm-setting-wired.h"
+#include "nm-ethtool-utils.h"
+
+/*****************************************************************************/
const char *nmp_utils_ethtool_get_driver (int ifindex);
gboolean nmp_utils_ethtool_supports_carrier_detect (int ifindex);
@@ -35,6 +38,10 @@ gboolean nmp_utils_ethtool_set_wake_on_lan (int ifindex, NMSettingWiredWakeOnLan
gboolean nmp_utils_ethtool_get_link_settings (int ifindex, gboolean *out_autoneg, guint32 *out_speed, NMPlatformLinkDuplexType *out_duplex);
gboolean nmp_utils_ethtool_set_link_settings (int ifindex, gboolean autoneg, guint32 speed, NMPlatformLinkDuplexType duplex);
+gboolean nmp_utils_ethtool_get_permanent_address (int ifindex,
+ guint8 *buf,
+ size_t *length);
+
typedef struct {
/* We don't want to include <linux/ethtool.h> in header files,
* thus create a ABI compatible version of struct ethtool_drvinfo.*/
@@ -55,9 +62,52 @@ typedef struct {
gboolean nmp_utils_ethtool_get_driver_info (int ifindex,
NMPUtilsEthtoolDriverInfo *data);
-gboolean nmp_utils_ethtool_get_permanent_address (int ifindex,
- guint8 *buf,
- size_t *length);
+typedef struct {
+ NMEthtoolID ethtool_id;
+
+ guint8 n_kernel_names;
+
+ /* one NMEthtoolID refers to one or more kernel_names. The reason for supporting this complexity
+ * (where one NMSettingEthtool option refers to multiple kernel features) is to follow what
+ * ethtool does, where "tx" is an alias for multiple features. */
+ const char *const*kernel_names;
+} NMEthtoolFeatureInfo;
+
+typedef struct {
+ const NMEthtoolFeatureInfo *info;
+
+ guint idx_ss_features;
+
+ /* one NMEthtoolFeatureInfo references one or more kernel_names. This is the index
+ * of the matching info->kernel_names */
+ guint8 idx_kernel_name;
+
+ bool available:1;
+ bool requested:1;
+ bool active:1;
+ bool never_changed:1;
+} NMEthtoolFeatureState;
+
+struct _NMEthtoolFeatureStates {
+ guint n_states;
+
+ guint n_ss_features;
+
+ /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */
+ const NMEthtoolFeatureState *const*states_indexed[_NM_ETHTOOL_ID_FEATURE_NUM];
+
+ /* the same content, here as a list of n_states entries. */
+ const NMEthtoolFeatureState states_list[];
+};
+
+NMEthtoolFeatureStates *nmp_utils_ethtool_get_features (int ifindex);
+
+gboolean nmp_utils_ethtool_set_features (int ifindex,
+ const NMEthtoolFeatureStates *features,
+ const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+ gboolean do_set /* or reset */);
+
+/*****************************************************************************/
gboolean nmp_utils_mii_supports_carrier_detect (int ifindex);
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 22af971505..0020acc924 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -3213,6 +3213,32 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_platform_link_duplex_type_to_string, NMPlatformLi
/*****************************************************************************/
+NMEthtoolFeatureStates *
+nm_platform_ethtool_get_link_features (NMPlatform *self, int ifindex)
+{
+ _CHECK_SELF_NETNS (self, klass, netns, NULL);
+
+ g_return_val_if_fail (ifindex > 0, NULL);
+
+ return nmp_utils_ethtool_get_features (ifindex);
+}
+
+gboolean
+nm_platform_ethtool_set_features (NMPlatform *self,
+ int ifindex,
+ const NMEthtoolFeatureStates *features,
+ const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+ gboolean do_set /* or reset */)
+{
+ _CHECK_SELF_NETNS (self, klass, netns, FALSE);
+
+ g_return_val_if_fail (ifindex > 0, FALSE);
+
+ return nmp_utils_ethtool_set_features (ifindex, features, requested, do_set);
+}
+
+/*****************************************************************************/
+
const NMDedupMultiHeadEntry *
nm_platform_lookup_all (NMPlatform *platform,
NMPCacheIdType cache_id_type,
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index bbc9fccabc..994e041719 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -1534,6 +1534,17 @@ int nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPla
gboolean nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, int ifindex, NMSettingWiredWakeOnLan wol, const char *wol_password);
gboolean nm_platform_ethtool_set_link_settings (NMPlatform *self, int ifindex, gboolean autoneg, guint32 speed, NMPlatformLinkDuplexType duplex);
gboolean nm_platform_ethtool_get_link_settings (NMPlatform *self, int ifindex, gboolean *out_autoneg, guint32 *out_speed, NMPlatformLinkDuplexType *out_duplex);
+
+typedef struct _NMEthtoolFeatureStates NMEthtoolFeatureStates;
+
+NMEthtoolFeatureStates *nm_platform_ethtool_get_link_features (NMPlatform *self,
+ int ifindex);
+gboolean nm_platform_ethtool_set_features (NMPlatform *self,
+ int ifindex,
+ const NMEthtoolFeatureStates *features,
+ const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+ gboolean do_set /* or reset */);
+
const char * nm_platform_link_duplex_type_to_string (NMPlatformLinkDuplexType duplex);
void nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 854285357a..20b3374b28 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -2713,6 +2713,102 @@ test_sysctl_netns_switch (void)
/*****************************************************************************/
+static void
+ethtool_features_dump (const NMEthtoolFeatureStates *features)
+{
+ guint i, j;
+
+ g_assert (features);
+
+ _LOGT (">>> %u features (%u ss-features)", features->n_states, features->n_ss_features);
+
+ for (i = 0; i < features->n_states; i++) {
+ const NMEthtoolFeatureState *s = &features->states_list[i];
+
+ _LOGT (">>> feature-list[%3u]: %3d = %-32s (%3u) | %s %s %s %s",
+ i,
+ (int) s->info->ethtool_id,
+ s->info->kernel_names[s->idx_kernel_name],
+ s->idx_ss_features,
+ s->active ? "ACT" : "act",
+ s->available ? "AVA" : "ava",
+ s->never_changed ? "NCH" : "nch",
+ s->requested ? "REQ" : "req");
+ }
+ for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) {
+ _LOGT (">>> feature-idx [%3u]: %-32s = %u features",
+ i + (guint) _NM_ETHTOOL_ID_FEATURE_FIRST,
+ nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+ (guint) NM_PTRARRAY_LEN (features->states_indexed[i]));
+ for (j = 0; features->states_indexed[i] && features->states_indexed[i][j]; j++) {
+ const NMEthtoolFeatureState *s = features->states_indexed[i][j];
+
+ _LOGT (">>> %3u: %-32s | %s %s %s %s",
+ j,
+ s->info->kernel_names[s->idx_kernel_name],
+ s->active ? "ACT" : "act",
+ s->available ? "AVA" : "ava",
+ s->never_changed ? "NCH" : "nch",
+ s->requested ? "REQ" : "req");
+ }
+ }
+}
+
+static void
+test_ethtool_features_get (void)
+{
+ gs_unref_ptrarray GPtrArray *gfree_keeper = g_ptr_array_new_with_free_func (g_free);
+ const int IFINDEX = 1;
+ guint i;
+ guint i_run;
+
+ for (i_run = 0; i_run < 5; i_run++) {
+ NMEthtoolFeatureStates *features;
+ NMTernary *requested;
+ gboolean do_set = TRUE;
+
+ requested = g_new (NMTernary, _NM_ETHTOOL_ID_FEATURE_NUM);
+ for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++)
+ requested[i] = NM_TERNARY_DEFAULT;
+ g_ptr_array_add (gfree_keeper, requested);
+
+ if (i_run == 0) {
+ requested[NM_ETHTOOL_ID_FEATURE_RX] = NM_TERNARY_FALSE;
+ requested[NM_ETHTOOL_ID_FEATURE_TSO] = NM_TERNARY_FALSE;
+ requested[NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION] = NM_TERNARY_FALSE;
+ } else if (i_run == 1)
+ do_set = FALSE;
+ else if (i_run == 2) {
+ requested[NM_ETHTOOL_ID_FEATURE_TSO] = NM_TERNARY_FALSE;
+ requested[NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION] = NM_TERNARY_TRUE;
+ } else if (i_run == 3)
+ do_set = FALSE;
+
+ _LOGT (">>> ethtool-features-get RUN %u (do-set=%s", i_run, do_set ? "set" : "reset");
+
+ features = nmp_utils_ethtool_get_features (IFINDEX);
+ g_ptr_array_add (gfree_keeper, features);
+
+ ethtool_features_dump (features);
+
+ if (_LOGT_ENABLED ()) {
+ int ignore;
+
+ ignore = system ("ethtool -k lo");
+ (void) ignore;
+ }
+
+ if (!do_set) {
+ requested = gfree_keeper->pdata[i_run * 2 - 2];
+ features = gfree_keeper->pdata[i_run * 2 - 1];
+ }
+
+ nmp_utils_ethtool_set_features (IFINDEX, features, requested, do_set);
+ }
+}
+
+/*****************************************************************************/
+
NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
void
@@ -2774,5 +2870,7 @@ _nmtstp_setup_tests (void)
g_test_add_func ("/general/sysctl/rename", test_sysctl_rename);
g_test_add_func ("/general/sysctl/netns-switch", test_sysctl_netns_switch);
+
+ g_test_add_func ("/link/ethtool/features/get", test_ethtool_features_get);
}
}
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index fecce50e29..12da58cf40 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -477,17 +477,19 @@ const char *const _nm_ethtool_ifcfg_names[] = {
#define ETHT_NAME(eid, ename) \
[eid - _NM_ETHTOOL_ID_FEATURE_FIRST] = ""ename""
/* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_GRO, "gro"),
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_GSO, "gso"),
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_LRO, "lro"),
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_NTUPLE, "ntuple"),
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RX, "rx"),
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RXHASH, "rxhash"),
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RXVLAN, "rxvlan"),
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_SG, "sg"),
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TSO, "tso"),
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX, "tx"),
- ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TXVLAN, "txvlan"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_GRO, "gro"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_GSO, "gso"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_LRO, "lro"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_NTUPLE, "ntuple"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RX, "rx"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RXHASH, "rxhash"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RXVLAN, "rxvlan"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_SG, "sg"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TSO, "tso"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX, "tx"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TXVLAN, "txvlan"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, "tx-tcp6-segmentation"),
+ ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"),
};
const NMEthtoolData *