summaryrefslogtreecommitdiffstats
path: root/netlink
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:34:36 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:34:36 +0000
commit74ebeae0b4c411df9900224b90a6072b16098458 (patch)
treefee8f5c9e37f1a9f0842e026876c8af541fa2e86 /netlink
parentInitial commit. (diff)
downloadethtool-74ebeae0b4c411df9900224b90a6072b16098458.tar.xz
ethtool-74ebeae0b4c411df9900224b90a6072b16098458.zip
Adding upstream version 1:6.7.upstream/1%6.7
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--netlink/bitset.c259
-rw-r--r--netlink/bitset.h28
-rw-r--r--netlink/cable_test.c595
-rw-r--r--netlink/channels.c143
-rw-r--r--netlink/coalesce.c335
-rw-r--r--netlink/desc-ethtool.c595
-rw-r--r--netlink/desc-genlctrl.c113
-rw-r--r--netlink/desc-rtnl.c96
-rw-r--r--netlink/eee.c189
-rw-r--r--netlink/extapi.h136
-rw-r--r--netlink/features.c569
-rw-r--r--netlink/fec.c360
-rw-r--r--netlink/mm.c270
-rw-r--r--netlink/module-eeprom.c310
-rw-r--r--netlink/module.c179
-rw-r--r--netlink/monitor.c324
-rw-r--r--netlink/msgbuff.c256
-rw-r--r--netlink/msgbuff.h123
-rw-r--r--netlink/netlink.c527
-rw-r--r--netlink/netlink.h178
-rw-r--r--netlink/nlsock.c405
-rw-r--r--netlink/nlsock.h45
-rw-r--r--netlink/parser.c1141
-rw-r--r--netlink/parser.h153
-rw-r--r--netlink/pause.c331
-rw-r--r--netlink/permaddr.c114
-rw-r--r--netlink/plca.c296
-rw-r--r--netlink/prettymsg.c262
-rw-r--r--netlink/prettymsg.h146
-rw-r--r--netlink/privflags.c158
-rw-r--r--netlink/pse-pd.c193
-rw-r--r--netlink/rings.c237
-rw-r--r--netlink/rss.c230
-rw-r--r--netlink/settings.c1377
-rw-r--r--netlink/stats.c333
-rw-r--r--netlink/strset.c297
-rw-r--r--netlink/strset.h25
-rw-r--r--netlink/tsinfo.c124
-rw-r--r--netlink/tunnels.c236
39 files changed, 11688 insertions, 0 deletions
diff --git a/netlink/bitset.c b/netlink/bitset.c
new file mode 100644
index 0000000..10ce8e9
--- /dev/null
+++ b/netlink/bitset.c
@@ -0,0 +1,259 @@
+/*
+ * bitset.h - netlink bitset helpers
+ *
+ * Functions for easier handling of ethtool netlink bitset attributes.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "../common.h"
+#include "netlink.h"
+#include "bitset.h"
+
+uint32_t bitset_get_count(const struct nlattr *bitset, int *retptr)
+{
+ const struct nlattr *attr;
+
+ mnl_attr_for_each_nested(attr, bitset) {
+ if (mnl_attr_get_type(attr) != ETHTOOL_A_BITSET_SIZE)
+ continue;
+ *retptr = 0;
+ return mnl_attr_get_u32(attr);
+ }
+
+ *retptr = -EFAULT;
+ return 0;
+}
+
+bool bitset_is_compact(const struct nlattr *bitset)
+{
+ const struct nlattr *attr;
+
+ mnl_attr_for_each_nested(attr, bitset) {
+ switch(mnl_attr_get_type(attr)) {
+ case ETHTOOL_A_BITSET_BITS:
+ return 0;
+ case ETHTOOL_A_BITSET_VALUE:
+ case ETHTOOL_A_BITSET_MASK:
+ return 1;
+ }
+ }
+
+ return false;
+}
+
+bool bitset_get_bit(const struct nlattr *bitset, bool mask, unsigned int idx,
+ int *retptr)
+{
+ const struct nlattr *bitset_tb[ETHTOOL_A_BITSET_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(bitset_tb);
+ const struct nlattr *bits;
+ const struct nlattr *bit;
+ bool nomask;
+ int ret;
+
+ *retptr = 0;
+ ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+ if (ret < 0)
+ goto err;
+
+ nomask = bitset_tb[ETHTOOL_A_BITSET_NOMASK];
+ if (mask && nomask) {
+ /* Trying to determine if a bit is set in the mask of a "no
+ * mask" bitset doesn't make sense.
+ */
+ ret = -EFAULT;
+ goto err;
+ }
+
+ bits = mask ? bitset_tb[ETHTOOL_A_BITSET_MASK] :
+ bitset_tb[ETHTOOL_A_BITSET_VALUE];
+ if (bits) {
+ const uint32_t *bitmap =
+ (const uint32_t *)mnl_attr_get_payload(bits);
+
+ if (idx >= 8 * mnl_attr_get_payload_len(bits))
+ return false;
+ return bitmap[idx / 32] & (1U << (idx % 32));
+ }
+
+ bits = bitset_tb[ETHTOOL_A_BITSET_BITS];
+ if (!bits)
+ goto err;
+ mnl_attr_for_each_nested(bit, bits) {
+ const struct nlattr *tb[ETHTOOL_A_BITSET_BIT_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ unsigned int my_idx;
+
+ if (mnl_attr_get_type(bit) != ETHTOOL_A_BITSET_BITS_BIT)
+ continue;
+ ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+ if (ret < 0)
+ goto err;
+ ret = -EFAULT;
+ if (!tb[ETHTOOL_A_BITSET_BIT_INDEX])
+ goto err;
+
+ my_idx = mnl_attr_get_u32(tb[ETHTOOL_A_BITSET_BIT_INDEX]);
+ if (my_idx == idx)
+ return mask || nomask || tb[ETHTOOL_A_BITSET_BIT_VALUE];
+ }
+
+ return false;
+err:
+ fprintf(stderr, "malformed netlink message (bitset)\n");
+ *retptr = ret;
+ return false;
+}
+
+bool bitset_is_empty(const struct nlattr *bitset, bool mask, int *retptr)
+{
+ const struct nlattr *bitset_tb[ETHTOOL_A_BITSET_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(bitset_tb);
+ const struct nlattr *bits;
+ const struct nlattr *bit;
+ int ret;
+
+ *retptr = 0;
+ ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+ if (ret < 0)
+ goto err;
+
+ bits = mask ? bitset_tb[ETHTOOL_A_BITSET_MASK] :
+ bitset_tb[ETHTOOL_A_BITSET_VALUE];
+ if (bits) {
+ const uint32_t *bitmap =
+ (const uint32_t *)mnl_attr_get_payload(bits);
+ unsigned int n = mnl_attr_get_payload_len(bits);
+ unsigned int i;
+
+ ret = -EFAULT;
+ if (n % 4)
+ goto err;
+ for (i = 0; i < n / 4; i++)
+ if (bitmap[i])
+ return false;
+ return true;
+ }
+
+ bits = bitset_tb[ETHTOOL_A_BITSET_BITS];
+ if (!bits)
+ goto err;
+ mnl_attr_for_each_nested(bit, bits) {
+ const struct nlattr *tb[ETHTOOL_A_BITSET_BIT_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+
+ if (mnl_attr_get_type(bit) != ETHTOOL_A_BITSET_BITS_BIT)
+ continue;
+ if (mask || bitset_tb[ETHTOOL_A_BITSET_NOMASK])
+ return false;
+
+ ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+ if (ret < 0)
+ goto err;
+ if (tb[ETHTOOL_A_BITSET_BIT_VALUE])
+ return false;
+ }
+
+ return true;
+err:
+ fprintf(stderr, "malformed netlink message (bitset)\n");
+ *retptr = ret;
+ return true;
+}
+
+static uint32_t *get_compact_bitset_attr(const struct nlattr *bitset,
+ uint16_t type)
+{
+ const struct nlattr *tb[ETHTOOL_A_BITSET_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ unsigned int count;
+ int ret;
+
+ ret = mnl_attr_parse_nested(bitset, attr_cb, &tb_info);
+ if (ret < 0)
+ return NULL;
+ if (!tb[ETHTOOL_A_BITSET_SIZE] || !tb[ETHTOOL_A_BITSET_VALUE] ||
+ !tb[type])
+ return NULL;
+ count = mnl_attr_get_u32(tb[ETHTOOL_A_BITSET_SIZE]);
+ if (8 * mnl_attr_get_payload_len(tb[type]) < count)
+ return NULL;
+
+ return mnl_attr_get_payload(tb[type]);
+}
+
+uint32_t *get_compact_bitset_value(const struct nlattr *bitset)
+{
+ return get_compact_bitset_attr(bitset, ETHTOOL_A_BITSET_VALUE);
+}
+
+uint32_t *get_compact_bitset_mask(const struct nlattr *bitset)
+{
+ return get_compact_bitset_attr(bitset, ETHTOOL_A_BITSET_MASK);
+}
+
+int walk_bitset(const struct nlattr *bitset, const struct stringset *labels,
+ bitset_walk_callback cb, void *data)
+{
+ const struct nlattr *bitset_tb[ETHTOOL_A_BITSET_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(bitset_tb);
+ const struct nlattr *bits;
+ const struct nlattr *bit;
+ bool is_list;
+ int ret;
+
+ ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+ if (ret < 0)
+ return ret;
+ is_list = bitset_tb[ETHTOOL_A_BITSET_NOMASK];
+
+ bits = bitset_tb[ETHTOOL_A_BITSET_VALUE];
+ if (bits) {
+ const struct nlattr *mask = bitset_tb[ETHTOOL_A_BITSET_MASK];
+ unsigned int count, nwords, idx;
+ uint32_t *val_bm;
+ uint32_t *mask_bm;
+
+ if (!bitset_tb[ETHTOOL_A_BITSET_SIZE])
+ return -EFAULT;
+ count = mnl_attr_get_u32(bitset_tb[ETHTOOL_A_BITSET_SIZE]);
+ nwords = (count + 31) / 32;
+ if ((mnl_attr_get_payload_len(bits) / 4 < nwords) ||
+ (mask && mnl_attr_get_payload_len(mask) / 4 < nwords))
+ return -EFAULT;
+
+ val_bm = mnl_attr_get_payload(bits);
+ mask_bm = mask ? mnl_attr_get_payload(mask) : NULL;
+ for (idx = 0; idx < count; idx++)
+ if (!mask_bm || (mask_bm[idx / 32] & (1 << (idx % 32))))
+ cb(idx, get_string(labels, idx),
+ val_bm[idx / 32] & (1 << (idx % 32)), data);
+ return 0;
+ }
+
+ bits = bitset_tb[ETHTOOL_A_BITSET_BITS];
+ if (!bits)
+ return -EFAULT;
+ mnl_attr_for_each_nested(bit, bits) {
+ const struct nlattr *tb[ETHTOOL_A_BITSET_BIT_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ const char *name;
+ unsigned int idx;
+
+ if (mnl_attr_get_type(bit) != ETHTOOL_A_BITSET_BITS_BIT)
+ continue;
+
+ ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+ if (ret < 0 || !tb[ETHTOOL_A_BITSET_BIT_INDEX] ||
+ !tb[ETHTOOL_A_BITSET_BIT_NAME])
+ return -EFAULT;
+
+ idx = mnl_attr_get_u32(tb[ETHTOOL_A_BITSET_BIT_INDEX]);
+ name = mnl_attr_get_str(tb[ETHTOOL_A_BITSET_BIT_NAME]);
+ cb(idx, name, is_list || tb[ETHTOOL_A_BITSET_BIT_VALUE], data);
+ }
+
+ return 0;
+}
diff --git a/netlink/bitset.h b/netlink/bitset.h
new file mode 100644
index 0000000..4c9cdac
--- /dev/null
+++ b/netlink/bitset.h
@@ -0,0 +1,28 @@
+/*
+ * bitset.h - netlink bitset helpers
+ *
+ * Declarations of helpers for handling ethtool netlink bitsets.
+ */
+
+#ifndef ETHTOOL_NETLINK_BITSET_H__
+#define ETHTOOL_NETLINK_BITSET_H__
+
+#include <libmnl/libmnl.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#include <linux/ethtool_netlink.h>
+#include "strset.h"
+
+typedef void (*bitset_walk_callback)(unsigned int, const char *, bool, void *);
+
+uint32_t bitset_get_count(const struct nlattr *bitset, int *retptr);
+bool bitset_get_bit(const struct nlattr *bitset, bool mask, unsigned int idx,
+ int *retptr);
+bool bitset_is_compact(const struct nlattr *bitset);
+bool bitset_is_empty(const struct nlattr *bitset, bool mask, int *retptr);
+uint32_t *get_compact_bitset_value(const struct nlattr *bitset);
+uint32_t *get_compact_bitset_mask(const struct nlattr *bitset);
+int walk_bitset(const struct nlattr *bitset, const struct stringset *labels,
+ bitset_walk_callback cb, void *data);
+
+#endif /* ETHTOOL_NETLINK_BITSET_H__ */
diff --git a/netlink/cable_test.c b/netlink/cable_test.c
new file mode 100644
index 0000000..9305a47
--- /dev/null
+++ b/netlink/cable_test.c
@@ -0,0 +1,595 @@
+/*
+ * cable_test.c - netlink implementation of cable test command
+ *
+ * Implementation of ethtool --cable-test <dev>
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+struct cable_test_context {
+ bool breakout;
+};
+
+static int nl_get_cable_test_result(const struct nlattr *nest, uint8_t *pair,
+ uint16_t *code)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_RESULT_MAX+1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 ||
+ !tb[ETHTOOL_A_CABLE_RESULT_PAIR] ||
+ !tb[ETHTOOL_A_CABLE_RESULT_CODE])
+ return -EFAULT;
+
+ *pair = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_RESULT_PAIR]);
+ *code = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_RESULT_CODE]);
+
+ return 0;
+}
+
+static int nl_get_cable_test_fault_length(const struct nlattr *nest,
+ uint8_t *pair, unsigned int *cm)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_FAULT_LENGTH_MAX+1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 ||
+ !tb[ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR] ||
+ !tb[ETHTOOL_A_CABLE_FAULT_LENGTH_CM])
+ return -EFAULT;
+
+ *pair = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR]);
+ *cm = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_FAULT_LENGTH_CM]);
+
+ return 0;
+}
+
+static char *nl_code2txt(uint16_t code)
+{
+ switch (code) {
+ case ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC:
+ default:
+ return "Unknown";
+ case ETHTOOL_A_CABLE_RESULT_CODE_OK:
+ return "OK";
+ case ETHTOOL_A_CABLE_RESULT_CODE_OPEN:
+ return "Open Circuit";
+ case ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT:
+ return "Short within Pair";
+ case ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT:
+ return "Short to another pair";
+ }
+}
+
+static char *nl_pair2txt(uint8_t pair)
+{
+ switch (pair) {
+ case ETHTOOL_A_CABLE_PAIR_A:
+ return "Pair A";
+ case ETHTOOL_A_CABLE_PAIR_B:
+ return "Pair B";
+ case ETHTOOL_A_CABLE_PAIR_C:
+ return "Pair C";
+ case ETHTOOL_A_CABLE_PAIR_D:
+ return "Pair D";
+ default:
+ return "Unexpected pair";
+ }
+}
+
+static int nl_cable_test_ntf_attr(struct nlattr *evattr)
+{
+ unsigned int cm;
+ uint16_t code;
+ uint8_t pair;
+ int ret;
+
+ switch (mnl_attr_get_type(evattr)) {
+ case ETHTOOL_A_CABLE_NEST_RESULT:
+ ret = nl_get_cable_test_result(evattr, &pair, &code);
+ if (ret < 0)
+ return ret;
+
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "pair", "%s ", nl_pair2txt(pair));
+ print_string(PRINT_ANY, "code", "code %s\n", nl_code2txt(code));
+ close_json_object();
+ break;
+
+ case ETHTOOL_A_CABLE_NEST_FAULT_LENGTH:
+ ret = nl_get_cable_test_fault_length(evattr, &pair, &cm);
+ if (ret < 0)
+ return ret;
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "pair", "%s, ", nl_pair2txt(pair));
+ print_float(PRINT_ANY, "length", "fault length: %0.2fm\n",
+ (float)cm / 100);
+ close_json_object();
+ break;
+ }
+ return 0;
+}
+
+static void cable_test_ntf_nest(const struct nlattr *nest)
+{
+ struct nlattr *pos;
+ int ret;
+
+ mnl_attr_for_each_nested(pos, nest) {
+ ret = nl_cable_test_ntf_attr(pos);
+ if (ret < 0)
+ return;
+ }
+}
+
+/* Returns MNL_CB_STOP when the test is complete. Used when executing
+ * a test, but not suitable for monitor.
+ */
+static int cable_test_ntf_stop_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_TEST_NTF_MAX + 1] = {};
+ u8 status = ETHTOOL_A_CABLE_TEST_NTF_STATUS_UNSPEC;
+ struct cable_test_context *ctctx;
+ struct nl_context *nlctx = data;
+ DECLARE_ATTR_TB_INFO(tb);
+ bool silent;
+ int err_ret;
+ int ret;
+
+ ctctx = nlctx->cmd_private;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_CABLE_TEST_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS])
+ status = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS]);
+
+ switch (status) {
+ case ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED:
+ print_string(PRINT_FP, "status",
+ "Cable test started for device %s.\n",
+ nlctx->devname);
+ break;
+ case ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED:
+ print_string(PRINT_FP, "status",
+ "Cable test completed for device %s.\n",
+ nlctx->devname);
+ break;
+ default:
+ break;
+ }
+
+ if (tb[ETHTOOL_A_CABLE_TEST_NTF_NEST])
+ cable_test_ntf_nest(tb[ETHTOOL_A_CABLE_TEST_NTF_NEST]);
+
+ if (status == ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED) {
+ if (ctctx)
+ ctctx->breakout = true;
+ return MNL_CB_STOP;
+ }
+
+ return MNL_CB_OK;
+}
+
+/* Wrapper around cable_test_ntf_stop_cb() which does not return STOP,
+ * used for monitor
+ */
+int cable_test_ntf_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ int status = cable_test_ntf_stop_cb(nlhdr, data);
+
+ if (status == MNL_CB_STOP)
+ status = MNL_CB_OK;
+
+ return status;
+}
+
+static int nl_cable_test_results_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+
+ if (ghdr->cmd != ETHTOOL_MSG_CABLE_TEST_NTF)
+ return MNL_CB_OK;
+
+ return cable_test_ntf_stop_cb(nlhdr, data);
+}
+
+/* Receive the broadcasted messages until we get the cable test
+ * results
+ */
+static int nl_cable_test_process_results(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ struct cable_test_context ctctx;
+ int err;
+
+ nlctx->is_monitor = true;
+ nlsk->port = 0;
+ nlsk->seq = 0;
+ nlctx->filter_devname = ctx->devname;
+
+ ctctx.breakout = false;
+ nlctx->cmd_private = &ctctx;
+
+ while (!ctctx.breakout) {
+ err = nlsock_process_reply(nlsk, nl_cable_test_results_cb,
+ nlctx);
+ if (err)
+ return err;
+ }
+
+ return err;
+}
+
+int nl_cable_test(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ uint32_t grpid = nlctx->ethnl_mongrp;
+ int ret;
+
+ /* Join the multicast group so we can receive the results in a
+ * race free way.
+ */
+ if (!grpid) {
+ fprintf(stderr, "multicast group 'monitor' not found\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = mnl_socket_setsockopt(nlsk->sk, NETLINK_ADD_MEMBERSHIP,
+ &grpid, sizeof(grpid));
+ if (ret < 0)
+ return ret;
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_CABLE_TEST_ACT,
+ ETHTOOL_A_CABLE_TEST_HEADER, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ fprintf(stderr, "Cannot start cable test\n");
+ else {
+ new_json_obj(ctx->json);
+
+ ret = nl_cable_test_process_results(ctx);
+
+ delete_json_obj();
+ }
+
+ return ret;
+}
+
+static int nl_get_cable_test_tdr_amplitude(const struct nlattr *nest,
+ uint8_t *pair, int16_t *mV)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_AMPLITUDE_MAX+1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ uint16_t mV_unsigned;
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 ||
+ !tb[ETHTOOL_A_CABLE_AMPLITUDE_PAIR] ||
+ !tb[ETHTOOL_A_CABLE_AMPLITUDE_mV])
+ return -EFAULT;
+
+ *pair = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_AMPLITUDE_PAIR]);
+ mV_unsigned = mnl_attr_get_u16(tb[ETHTOOL_A_CABLE_AMPLITUDE_mV]);
+ *mV = (int16_t)(mV_unsigned);
+
+ return 0;
+}
+
+static int nl_get_cable_test_tdr_pulse(const struct nlattr *nest, uint16_t *mV)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_PULSE_MAX+1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 ||
+ !tb[ETHTOOL_A_CABLE_PULSE_mV])
+ return -EFAULT;
+
+ *mV = mnl_attr_get_u16(tb[ETHTOOL_A_CABLE_PULSE_mV]);
+
+ return 0;
+}
+
+static int nl_get_cable_test_tdr_step(const struct nlattr *nest,
+ uint32_t *first, uint32_t *last,
+ uint32_t *step)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_STEP_MAX+1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 ||
+ !tb[ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE] ||
+ !tb[ETHTOOL_A_CABLE_STEP_LAST_DISTANCE] ||
+ !tb[ETHTOOL_A_CABLE_STEP_STEP_DISTANCE])
+ return -EFAULT;
+
+ *first = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE]);
+ *last = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_STEP_LAST_DISTANCE]);
+ *step = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_STEP_STEP_DISTANCE]);
+
+ return 0;
+}
+
+static int nl_cable_test_tdr_ntf_attr(struct nlattr *evattr)
+{
+ uint32_t first, last, step;
+ uint8_t pair;
+ int ret;
+
+ switch (mnl_attr_get_type(evattr)) {
+ case ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE: {
+ int16_t mV;
+
+ ret = nl_get_cable_test_tdr_amplitude(
+ evattr, &pair, &mV);
+ if (ret < 0)
+ return ret;
+
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "pair", "%s ", nl_pair2txt(pair));
+ print_int(PRINT_ANY, "amplitude", "Amplitude %4d\n", mV);
+ close_json_object();
+ break;
+ }
+ case ETHTOOL_A_CABLE_TDR_NEST_PULSE: {
+ uint16_t mV;
+
+ ret = nl_get_cable_test_tdr_pulse(evattr, &mV);
+ if (ret < 0)
+ return ret;
+
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "pulse", "TDR Pulse %dmV\n", mV);
+ close_json_object();
+ break;
+ }
+ case ETHTOOL_A_CABLE_TDR_NEST_STEP:
+ ret = nl_get_cable_test_tdr_step(evattr, &first, &last, &step);
+ if (ret < 0)
+ return ret;
+
+ open_json_object(NULL);
+ print_float(PRINT_ANY, "first", "Step configuration: %.2f-",
+ (float)first / 100);
+ print_float(PRINT_ANY, "last", "%.2f meters ",
+ (float)last / 100);
+ print_float(PRINT_ANY, "step", "in %.2fm steps\n",
+ (float)step / 100);
+ close_json_object();
+ break;
+ }
+ return 0;
+}
+
+static void cable_test_tdr_ntf_nest(const struct nlattr *nest)
+{
+ struct nlattr *pos;
+ int ret;
+
+ mnl_attr_for_each_nested(pos, nest) {
+ ret = nl_cable_test_tdr_ntf_attr(pos);
+ if (ret < 0)
+ return;
+ }
+}
+
+/* Returns MNL_CB_STOP when the test is complete. Used when executing
+ * a test, but not suitable for monitor.
+ */
+int cable_test_tdr_ntf_stop_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_NTF_MAX + 1] = {};
+ u8 status = ETHTOOL_A_CABLE_TEST_NTF_STATUS_UNSPEC;
+ struct cable_test_context *ctctx;
+ struct nl_context *nlctx = data;
+
+ DECLARE_ATTR_TB_INFO(tb);
+ bool silent;
+ int err_ret;
+ int ret;
+
+ ctctx = nlctx->cmd_private;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS])
+ status = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS]);
+
+ switch (status) {
+ case ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED:
+ print_string(PRINT_FP, "status",
+ "Cable test TDR started for device %s.\n",
+ nlctx->devname);
+ break;
+ case ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED:
+ print_string(PRINT_FP, "status",
+ "Cable test TDR completed for device %s.\n",
+ nlctx->devname);
+ break;
+ default:
+ break;
+ }
+
+ if (tb[ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST])
+ cable_test_tdr_ntf_nest(tb[ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST]);
+
+ if (status == ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED) {
+ if (ctctx)
+ ctctx->breakout = true;
+ return MNL_CB_STOP;
+ }
+
+ return MNL_CB_OK;
+}
+
+/* Wrapper around cable_test_tdr_ntf_stop_cb() which does not return
+ * STOP, used for monitor
+ */
+int cable_test_tdr_ntf_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ int status = cable_test_tdr_ntf_stop_cb(nlhdr, data);
+
+ if (status == MNL_CB_STOP)
+ status = MNL_CB_OK;
+
+ return status;
+}
+
+static int nl_cable_test_tdr_results_cb(const struct nlmsghdr *nlhdr,
+ void *data)
+{
+ const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+
+ if (ghdr->cmd != ETHTOOL_MSG_CABLE_TEST_TDR_NTF)
+ return MNL_CB_OK;
+
+ cable_test_tdr_ntf_cb(nlhdr, data);
+
+ return MNL_CB_STOP;
+}
+
+/* Receive the broadcasted messages until we get the cable test
+ * results
+ */
+static int nl_cable_test_tdr_process_results(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ struct cable_test_context ctctx;
+ int err;
+
+ nlctx->is_monitor = true;
+ nlsk->port = 0;
+ nlsk->seq = 0;
+ nlctx->filter_devname = ctx->devname;
+
+ ctctx.breakout = false;
+ nlctx->cmd_private = &ctctx;
+
+ while (!ctctx.breakout) {
+ err = nlsock_process_reply(nlsk, nl_cable_test_tdr_results_cb,
+ nlctx);
+ if (err)
+ return err;
+ }
+
+ return err;
+}
+
+static const struct param_parser tdr_params[] = {
+ {
+ .arg = "first",
+ .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST,
+ .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
+ .handler = nl_parse_direct_m2cm,
+ },
+ {
+ .arg = "last",
+ .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST,
+ .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
+ .handler = nl_parse_direct_m2cm,
+ },
+ {
+ .arg = "step",
+ .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP,
+ .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
+ .handler = nl_parse_direct_m2cm,
+ },
+ {
+ .arg = "pair",
+ .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR,
+ .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
+ .handler = nl_parse_direct_u8,
+ },
+ {}
+};
+
+int nl_cable_test_tdr(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ uint32_t grpid = nlctx->ethnl_mongrp;
+ struct nl_msg_buff *msgbuff;
+ int ret;
+
+ nlctx->cmd = "--cable-test-tdr";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ msgbuff = &nlsk->msgbuff;
+
+ /* Join the multicast group so we can receive the results in a
+ * race free way.
+ */
+ if (!grpid) {
+ fprintf(stderr, "multicast group 'monitor' not found\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = mnl_socket_setsockopt(nlsk->sk, NETLINK_ADD_MEMBERSHIP,
+ &grpid, sizeof(grpid));
+ if (ret < 0)
+ return ret;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_CABLE_TEST_TDR_ACT,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_CABLE_TEST_TDR_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, tdr_params, NULL, PARSER_GROUP_NEST, NULL);
+ if (ret < 0)
+ return ret;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ fprintf(stderr, "Cannot start cable test TDR\n");
+ else {
+ new_json_obj(ctx->json);
+
+ ret = nl_cable_test_tdr_process_results(ctx);
+
+ delete_json_obj();
+ }
+
+ return ret;
+}
diff --git a/netlink/channels.c b/netlink/channels.c
new file mode 100644
index 0000000..5cae227
--- /dev/null
+++ b/netlink/channels.c
@@ -0,0 +1,143 @@
+/*
+ * channels.c - netlink implementation of channel commands
+ *
+ * Implementation of "ethtool -l <dev>" and "ethtool -L <dev> ..."
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+/* CHANNELS_GET */
+
+int channels_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_CHANNELS_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_CHANNELS_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (silent)
+ putchar('\n');
+ printf("Channel parameters for %s:\n", nlctx->devname);
+ printf("Pre-set maximums:\n");
+ show_u32("rx-max", "RX:\t\t", tb[ETHTOOL_A_CHANNELS_RX_MAX]);
+ show_u32("tx-max", "TX:\t\t", tb[ETHTOOL_A_CHANNELS_TX_MAX]);
+ show_u32("other-max", "Other:\t\t", tb[ETHTOOL_A_CHANNELS_OTHER_MAX]);
+ show_u32("combined-max", "Combined:\t",
+ tb[ETHTOOL_A_CHANNELS_COMBINED_MAX]);
+ printf("Current hardware settings:\n");
+ show_u32("rx", "RX:\t\t", tb[ETHTOOL_A_CHANNELS_RX_COUNT]);
+ show_u32("tx", "TX:\t\t", tb[ETHTOOL_A_CHANNELS_TX_COUNT]);
+ show_u32("other", "Other:\t\t", tb[ETHTOOL_A_CHANNELS_OTHER_COUNT]);
+ show_u32("combined", "Combined:\t",
+ tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT]);
+
+ return MNL_CB_OK;
+}
+
+int nl_gchannels(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_CHANNELS_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_CHANNELS_GET,
+ ETHTOOL_A_CHANNELS_HEADER, 0);
+ if (ret < 0)
+ return ret;
+ return nlsock_send_get_request(nlsk, channels_reply_cb);
+}
+
+/* CHANNELS_SET */
+
+static const struct param_parser schannels_params[] = {
+ {
+ .arg = "rx",
+ .type = ETHTOOL_A_CHANNELS_RX_COUNT,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx",
+ .type = ETHTOOL_A_CHANNELS_TX_COUNT,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "other",
+ .type = ETHTOOL_A_CHANNELS_OTHER_COUNT,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "combined",
+ .type = ETHTOOL_A_CHANNELS_COMBINED_COUNT,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_schannels(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_CHANNELS_SET, false))
+ return -EOPNOTSUPP;
+
+ nlctx->cmd = "-L";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_CHANNELS_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_CHANNELS_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, schannels_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return 1;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret == 0)
+ return 0;
+ else
+ return nlctx->exit_code ?: 1;
+}
diff --git a/netlink/coalesce.c b/netlink/coalesce.c
new file mode 100644
index 0000000..bc34d3d
--- /dev/null
+++ b/netlink/coalesce.c
@@ -0,0 +1,335 @@
+/*
+ * coalesce.c - netlink implementation of coalescing commands
+ *
+ * Implementation of "ethtool -c <dev>" and "ethtool -C <dev> ..."
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+/* COALESCE_GET */
+
+int coalesce_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_COALESCE_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_COALESCE_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ open_json_object(NULL);
+
+ if (silent)
+ show_cr();
+ print_string(PRINT_ANY, "ifname", "Coalesce parameters for %s:\n",
+ nlctx->devname);
+ show_bool("rx", "Adaptive RX: %s ",
+ tb[ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX]);
+ show_bool("tx", "TX: %s\n", tb[ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX]);
+ show_u32("stats-block-usecs", "stats-block-usecs:\t",
+ tb[ETHTOOL_A_COALESCE_STATS_BLOCK_USECS]);
+ show_u32("sample-interval", "sample-interval:\t",
+ tb[ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL]);
+ show_u32("pkt-rate-low", "pkt-rate-low:\t\t",
+ tb[ETHTOOL_A_COALESCE_PKT_RATE_LOW]);
+ show_u32("pkt-rate-high", "pkt-rate-high:\t\t",
+ tb[ETHTOOL_A_COALESCE_PKT_RATE_HIGH]);
+ show_cr();
+ show_u32("rx-usecs", "rx-usecs:\t", tb[ETHTOOL_A_COALESCE_RX_USECS]);
+ show_u32("rx-frames", "rx-frames:\t",
+ tb[ETHTOOL_A_COALESCE_RX_MAX_FRAMES]);
+ show_u32("rx-usecs-irq", "rx-usecs-irq:\t",
+ tb[ETHTOOL_A_COALESCE_RX_USECS_IRQ]);
+ show_u32("rx-frames-irq", "rx-frames-irq:\t",
+ tb[ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ]);
+ show_cr();
+ show_u32("tx-usecs", "tx-usecs:\t", tb[ETHTOOL_A_COALESCE_TX_USECS]);
+ show_u32("tx-frames", "tx-frames:\t",
+ tb[ETHTOOL_A_COALESCE_TX_MAX_FRAMES]);
+ show_u32("tx-usecs-irq", "tx-usecs-irq:\t",
+ tb[ETHTOOL_A_COALESCE_TX_USECS_IRQ]);
+ show_u32("tx-frames-irq", "tx-frames-irq:\t",
+ tb[ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ]);
+ show_cr();
+ show_u32("rx-usecs-low", "rx-usecs-low:\t",
+ tb[ETHTOOL_A_COALESCE_RX_USECS_LOW]);
+ show_u32("rx-frame-low", "rx-frame-low:\t",
+ tb[ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW]);
+ show_u32("tx-usecs-low", "tx-usecs-low:\t",
+ tb[ETHTOOL_A_COALESCE_TX_USECS_LOW]);
+ show_u32("tx-frame-low", "tx-frame-low:\t",
+ tb[ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW]);
+ show_cr();
+ show_u32("rx-usecs-high", "rx-usecs-high:\t",
+ tb[ETHTOOL_A_COALESCE_RX_USECS_HIGH]);
+ show_u32("rx-frame-high", "rx-frame-high:\t",
+ tb[ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH]);
+ show_u32("tx-usecs-high", "tx-usecs-high:\t",
+ tb[ETHTOOL_A_COALESCE_TX_USECS_HIGH]);
+ show_u32("tx-frame-high", "tx-frame-high:\t",
+ tb[ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH]);
+ show_cr();
+ show_bool("rx", "CQE mode RX: %s ",
+ tb[ETHTOOL_A_COALESCE_USE_CQE_MODE_RX]);
+ show_bool("tx", "TX: %s\n", tb[ETHTOOL_A_COALESCE_USE_CQE_MODE_TX]);
+ show_cr();
+ show_u32("tx-aggr-max-bytes", "tx-aggr-max-bytes:\t",
+ tb[ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES]);
+ show_u32("tx-aggr-max-frames", "tx-aggr-max-frames:\t",
+ tb[ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES]);
+ show_u32("tx-aggr-time-usecs", "tx-aggr-time-usecs\t",
+ tb[ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS]);
+ show_cr();
+
+ close_json_object();
+
+ return MNL_CB_OK;
+}
+
+int nl_gcoalesce(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_COALESCE_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_COALESCE_GET,
+ ETHTOOL_A_COALESCE_HEADER, 0);
+ if (ret < 0)
+ return ret;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, coalesce_reply_cb);
+ delete_json_obj();
+ return ret;
+}
+
+/* COALESCE_SET */
+
+static const struct param_parser scoalesce_params[] = {
+ {
+ .arg = "adaptive-rx",
+ .type = ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "adaptive-tx",
+ .type = ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "sample-interval",
+ .type = ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "stats-block-usecs",
+ .type = ETHTOOL_A_COALESCE_STATS_BLOCK_USECS,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "pkt-rate-low",
+ .type = ETHTOOL_A_COALESCE_PKT_RATE_LOW,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "pkt-rate-high",
+ .type = ETHTOOL_A_COALESCE_PKT_RATE_HIGH,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-usecs",
+ .type = ETHTOOL_A_COALESCE_RX_USECS,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-frames",
+ .type = ETHTOOL_A_COALESCE_RX_MAX_FRAMES,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-usecs-irq",
+ .type = ETHTOOL_A_COALESCE_RX_USECS_IRQ,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-frames-irq",
+ .type = ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-usecs",
+ .type = ETHTOOL_A_COALESCE_TX_USECS,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-frames",
+ .type = ETHTOOL_A_COALESCE_TX_MAX_FRAMES,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-usecs-irq",
+ .type = ETHTOOL_A_COALESCE_TX_USECS_IRQ,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-frames-irq",
+ .type = ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-usecs-low",
+ .type = ETHTOOL_A_COALESCE_RX_USECS_LOW,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-frames-low",
+ .type = ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-usecs-low",
+ .type = ETHTOOL_A_COALESCE_TX_USECS_LOW,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-frames-low",
+ .type = ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-usecs-high",
+ .type = ETHTOOL_A_COALESCE_RX_USECS_HIGH,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-frames-high",
+ .type = ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-usecs-high",
+ .type = ETHTOOL_A_COALESCE_TX_USECS_HIGH,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-frames-high",
+ .type = ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "cqe-mode-rx",
+ .type = ETHTOOL_A_COALESCE_USE_CQE_MODE_RX,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "cqe-mode-tx",
+ .type = ETHTOOL_A_COALESCE_USE_CQE_MODE_TX,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-aggr-max-bytes",
+ .type = ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-aggr-max-frames",
+ .type = ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-aggr-time-usecs",
+ .type = ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_scoalesce(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_COALESCE_SET, false))
+ return -EOPNOTSUPP;
+
+ nlctx->cmd = "-C";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_COALESCE_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_COALESCE_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, scoalesce_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return 1;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret == 0)
+ return 0;
+ else
+ return nlctx->exit_code ?: 1;
+}
diff --git a/netlink/desc-ethtool.c b/netlink/desc-ethtool.c
new file mode 100644
index 0000000..661de26
--- /dev/null
+++ b/netlink/desc-ethtool.c
@@ -0,0 +1,595 @@
+/*
+ * desc-ethtool.c - ethtool netlink format descriptions
+ *
+ * Descriptions of ethtool netlink messages and attributes for pretty print.
+ */
+
+#include "../internal.h"
+#include <linux/ethtool_netlink.h>
+
+#include "prettymsg.h"
+
+static const struct pretty_nla_desc __header_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_HEADER_UNSPEC),
+ NLATTR_DESC_U32(ETHTOOL_A_HEADER_DEV_INDEX),
+ NLATTR_DESC_STRING(ETHTOOL_A_HEADER_DEV_NAME),
+ NLATTR_DESC_X32(ETHTOOL_A_HEADER_FLAGS),
+};
+
+static const struct pretty_nla_desc __bitset_bit_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_BITSET_BIT_UNSPEC),
+ NLATTR_DESC_U32(ETHTOOL_A_BITSET_BIT_INDEX),
+ NLATTR_DESC_STRING(ETHTOOL_A_BITSET_BIT_NAME),
+ NLATTR_DESC_FLAG(ETHTOOL_A_BITSET_BIT_VALUE),
+};
+
+static const struct pretty_nla_desc __bitset_bits_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_BITSET_BITS_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_BITSET_BITS_BIT, bitset_bit),
+};
+
+static const struct pretty_nla_desc __bitset_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_BITSET_UNSPEC),
+ NLATTR_DESC_FLAG(ETHTOOL_A_BITSET_NOMASK),
+ NLATTR_DESC_U32(ETHTOOL_A_BITSET_SIZE),
+ NLATTR_DESC_NESTED(ETHTOOL_A_BITSET_BITS, bitset_bits),
+ NLATTR_DESC_BINARY(ETHTOOL_A_BITSET_VALUE),
+ NLATTR_DESC_BINARY(ETHTOOL_A_BITSET_MASK),
+};
+
+static const struct pretty_nla_desc __string_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_STRING_UNSPEC),
+ NLATTR_DESC_U32(ETHTOOL_A_STRING_INDEX),
+ NLATTR_DESC_STRING(ETHTOOL_A_STRING_VALUE),
+};
+
+static const struct pretty_nla_desc __strings_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_STRINGS_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STRINGS_STRING, string),
+};
+
+static const struct pretty_nla_desc __stringset_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_STRINGSET_UNSPEC),
+ NLATTR_DESC_U32(ETHTOOL_A_STRINGSET_ID),
+ NLATTR_DESC_U32(ETHTOOL_A_STRINGSET_COUNT),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STRINGSET_STRINGS, strings),
+};
+
+static const struct pretty_nla_desc __stringsets_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_STRINGSETS_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STRINGSETS_STRINGSET, stringset),
+};
+
+static const struct pretty_nla_desc __strset_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_STRSET_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STRSET_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STRSET_STRINGSETS, stringsets),
+ NLATTR_DESC_FLAG(ETHTOOL_A_STRSET_COUNTS_ONLY),
+};
+
+static const struct pretty_nla_desc __linkinfo_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_LINKINFO_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_LINKINFO_HEADER, header),
+ NLATTR_DESC_U8(ETHTOOL_A_LINKINFO_PORT),
+ NLATTR_DESC_U8(ETHTOOL_A_LINKINFO_PHYADDR),
+ NLATTR_DESC_U8(ETHTOOL_A_LINKINFO_TP_MDIX),
+ NLATTR_DESC_U8(ETHTOOL_A_LINKINFO_TP_MDIX_CTRL),
+ NLATTR_DESC_U8(ETHTOOL_A_LINKINFO_TRANSCEIVER),
+};
+
+static const char *__linkmodes_rate_matching_names[] = {
+ [RATE_MATCH_NONE] = "RATE_MATCH_NONE",
+ [RATE_MATCH_PAUSE] = "RATE_MATCH_PAUSE",
+ [RATE_MATCH_CRS] = "RATE_MATCH_CRS",
+ [RATE_MATCH_OPEN_LOOP] = "RATE_MATCH_OPEN_LOOP",
+};
+
+static const struct pretty_nla_desc __linkmodes_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_LINKMODES_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_LINKMODES_HEADER, header),
+ NLATTR_DESC_BOOL(ETHTOOL_A_LINKMODES_AUTONEG),
+ NLATTR_DESC_NESTED(ETHTOOL_A_LINKMODES_OURS, bitset),
+ NLATTR_DESC_NESTED(ETHTOOL_A_LINKMODES_PEER, bitset),
+ NLATTR_DESC_U32(ETHTOOL_A_LINKMODES_SPEED),
+ NLATTR_DESC_U8(ETHTOOL_A_LINKMODES_DUPLEX),
+ NLATTR_DESC_U8(ETHTOOL_A_LINKMODES_MASTER_SLAVE_CFG),
+ NLATTR_DESC_U8(ETHTOOL_A_LINKMODES_MASTER_SLAVE_STATE),
+ NLATTR_DESC_U32(ETHTOOL_A_LINKMODES_LANES),
+ NLATTR_DESC_U8_ENUM(ETHTOOL_A_LINKMODES_RATE_MATCHING,
+ linkmodes_rate_matching),
+};
+
+static const struct pretty_nla_desc __linkstate_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_LINKSTATE_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_LINKSTATE_HEADER, header),
+ NLATTR_DESC_BOOL(ETHTOOL_A_LINKSTATE_LINK),
+ NLATTR_DESC_U32(ETHTOOL_A_LINKSTATE_SQI),
+ NLATTR_DESC_U32(ETHTOOL_A_LINKSTATE_SQI_MAX),
+ NLATTR_DESC_U8(ETHTOOL_A_LINKSTATE_EXT_STATE),
+ NLATTR_DESC_U8(ETHTOOL_A_LINKSTATE_EXT_SUBSTATE),
+};
+
+static const struct pretty_nla_desc __debug_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_DEBUG_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_DEBUG_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_DEBUG_MSGMASK, bitset),
+};
+
+static const struct pretty_nla_desc __wol_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_WOL_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_WOL_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_WOL_MODES, bitset),
+ NLATTR_DESC_BINARY(ETHTOOL_A_WOL_SOPASS),
+};
+
+static const struct pretty_nla_desc __features_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_FEATURES_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_FEATURES_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_FEATURES_HW, bitset),
+ NLATTR_DESC_NESTED(ETHTOOL_A_FEATURES_WANTED, bitset),
+ NLATTR_DESC_NESTED(ETHTOOL_A_FEATURES_ACTIVE, bitset),
+ NLATTR_DESC_NESTED(ETHTOOL_A_FEATURES_NOCHANGE, bitset),
+};
+
+static const struct pretty_nla_desc __privflags_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_PRIVFLAGS_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_PRIVFLAGS_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_PRIVFLAGS_FLAGS, bitset),
+};
+
+static const char *__rings_tcp_data_split_names[] = {
+ [ETHTOOL_TCP_DATA_SPLIT_UNKNOWN] = "ETHTOOL_TCP_DATA_SPLIT_UNKNOWN",
+ [ETHTOOL_TCP_DATA_SPLIT_DISABLED] = "ETHTOOL_TCP_DATA_SPLIT_DISABLED",
+ [ETHTOOL_TCP_DATA_SPLIT_ENABLED] = "ETHTOOL_TCP_DATA_SPLIT_ENABLED",
+};
+
+static const struct pretty_nla_desc __rings_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_RINGS_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_RINGS_HEADER, header),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_RX_MAX),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_RX_MINI_MAX),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_RX_JUMBO_MAX),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_TX_MAX),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_RX),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_RX_MINI),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_RX_JUMBO),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_TX),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_RX_BUF_LEN),
+ NLATTR_DESC_U8_ENUM(ETHTOOL_A_RINGS_TCP_DATA_SPLIT, rings_tcp_data_split),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_CQE_SIZE),
+ NLATTR_DESC_BOOL(ETHTOOL_A_RINGS_TX_PUSH),
+ NLATTR_DESC_BOOL(ETHTOOL_A_RINGS_RX_PUSH),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN),
+ NLATTR_DESC_U32(ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX),
+};
+
+static const struct pretty_nla_desc __channels_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CHANNELS_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CHANNELS_HEADER, header),
+ NLATTR_DESC_U32(ETHTOOL_A_CHANNELS_RX_MAX),
+ NLATTR_DESC_U32(ETHTOOL_A_CHANNELS_TX_MAX),
+ NLATTR_DESC_U32(ETHTOOL_A_CHANNELS_OTHER_MAX),
+ NLATTR_DESC_U32(ETHTOOL_A_CHANNELS_COMBINED_MAX),
+ NLATTR_DESC_U32(ETHTOOL_A_CHANNELS_RX_COUNT),
+ NLATTR_DESC_U32(ETHTOOL_A_CHANNELS_TX_COUNT),
+ NLATTR_DESC_U32(ETHTOOL_A_CHANNELS_OTHER_COUNT),
+ NLATTR_DESC_U32(ETHTOOL_A_CHANNELS_COMBINED_COUNT),
+};
+
+static const struct pretty_nla_desc __coalesce_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_COALESCE_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_COALESCE_HEADER, header),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_RX_USECS),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_RX_MAX_FRAMES),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_RX_USECS_IRQ),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_USECS),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_MAX_FRAMES),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_USECS_IRQ),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_STATS_BLOCK_USECS),
+ NLATTR_DESC_BOOL(ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX),
+ NLATTR_DESC_BOOL(ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_PKT_RATE_LOW),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_RX_USECS_LOW),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_USECS_LOW),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_PKT_RATE_HIGH),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_RX_USECS_HIGH),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_USECS_HIGH),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL),
+ NLATTR_DESC_BOOL(ETHTOOL_A_COALESCE_USE_CQE_MODE_TX),
+ NLATTR_DESC_BOOL(ETHTOOL_A_COALESCE_USE_CQE_MODE_RX),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES),
+ NLATTR_DESC_U32(ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS),
+};
+
+static const struct pretty_nla_desc __pause_stats_desc[] = {
+ NLATTR_DESC_BINARY(ETHTOOL_A_PAUSE_STAT_PAD),
+ NLATTR_DESC_U64(ETHTOOL_A_PAUSE_STAT_TX_FRAMES),
+ NLATTR_DESC_U64(ETHTOOL_A_PAUSE_STAT_RX_FRAMES),
+};
+
+static const struct pretty_nla_desc __pause_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_PAUSE_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_PAUSE_HEADER, header),
+ NLATTR_DESC_BOOL(ETHTOOL_A_PAUSE_AUTONEG),
+ NLATTR_DESC_BOOL(ETHTOOL_A_PAUSE_RX),
+ NLATTR_DESC_BOOL(ETHTOOL_A_PAUSE_TX),
+ NLATTR_DESC_NESTED(ETHTOOL_A_PAUSE_STATS, pause_stats),
+};
+
+static const struct pretty_nla_desc __eee_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_EEE_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_EEE_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_EEE_MODES_OURS, bitset),
+ NLATTR_DESC_NESTED(ETHTOOL_A_EEE_MODES_PEER, bitset),
+ NLATTR_DESC_BOOL(ETHTOOL_A_EEE_ACTIVE),
+ NLATTR_DESC_BOOL(ETHTOOL_A_EEE_ENABLED),
+ NLATTR_DESC_BOOL(ETHTOOL_A_EEE_TX_LPI_ENABLED),
+ NLATTR_DESC_U32(ETHTOOL_A_EEE_TX_LPI_TIMER),
+};
+
+static const struct pretty_nla_desc __tsinfo_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_TSINFO_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_TSINFO_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_TSINFO_TIMESTAMPING, bitset),
+ NLATTR_DESC_NESTED(ETHTOOL_A_TSINFO_TX_TYPES, bitset),
+ NLATTR_DESC_NESTED(ETHTOOL_A_TSINFO_RX_FILTERS, bitset),
+ NLATTR_DESC_U32(ETHTOOL_A_TSINFO_PHC_INDEX),
+};
+
+static const struct pretty_nla_desc __cable_test_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_TEST_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_TEST_HEADER, header),
+};
+
+static const struct pretty_nla_desc __cable_test_result_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_RESULT_UNSPEC),
+ NLATTR_DESC_U8(ETHTOOL_A_CABLE_RESULT_PAIR),
+ NLATTR_DESC_U8(ETHTOOL_A_CABLE_RESULT_CODE),
+};
+
+static const struct pretty_nla_desc __cable_test_flength_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_FAULT_LENGTH_UNSPEC),
+ NLATTR_DESC_U8(ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR),
+ NLATTR_DESC_U32(ETHTOOL_A_CABLE_FAULT_LENGTH_CM),
+};
+
+static const struct pretty_nla_desc __cable_nest_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_NEST_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_NEST_RESULT, cable_test_result),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_NEST_FAULT_LENGTH,
+ cable_test_flength),
+};
+
+static const struct pretty_nla_desc __cable_test_ntf_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_TEST_NTF_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_TEST_NTF_HEADER, header),
+ NLATTR_DESC_U8(ETHTOOL_A_CABLE_TEST_NTF_STATUS),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_TEST_NTF_NEST, cable_nest),
+};
+
+static const struct pretty_nla_desc __cable_test_tdr_cfg_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_TEST_TDR_CFG_UNSPEC),
+ NLATTR_DESC_U32(ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST),
+ NLATTR_DESC_U32(ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST),
+ NLATTR_DESC_U32(ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP),
+ NLATTR_DESC_U8(ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR),
+};
+
+static const struct pretty_nla_desc __cable_test_tdr_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_TEST_TDR_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_TEST_TDR_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_TEST_TDR_CFG, cable_test_tdr_cfg),
+};
+
+static const struct pretty_nla_desc __cable_step_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_STEP_UNSPEC),
+ NLATTR_DESC_U32(ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE),
+ NLATTR_DESC_U32(ETHTOOL_A_CABLE_STEP_LAST_DISTANCE),
+ NLATTR_DESC_U32(ETHTOOL_A_CABLE_STEP_STEP_DISTANCE),
+};
+
+static const struct pretty_nla_desc __cable_amplitude_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_AMPLITUDE_UNSPEC),
+ NLATTR_DESC_U8(ETHTOOL_A_CABLE_AMPLITUDE_PAIR),
+ NLATTR_DESC_S16(ETHTOOL_A_CABLE_AMPLITUDE_mV),
+};
+
+static const struct pretty_nla_desc __cable_pulse_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_PULSE_UNSPEC),
+ NLATTR_DESC_S16(ETHTOOL_A_CABLE_PULSE_mV),
+};
+
+static const struct pretty_nla_desc __cable_test_tdr_nest_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_TDR_NEST_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_TDR_NEST_STEP, cable_step),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE, cable_amplitude),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_TDR_NEST_PULSE, cable_pulse),
+};
+
+static const struct pretty_nla_desc __cable_test_tdr_ntf_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_CABLE_TEST_TDR_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_TEST_TDR_NTF_HEADER, header),
+ NLATTR_DESC_U8(ETHTOOL_A_CABLE_TEST_TDR_NTF_STATUS),
+ NLATTR_DESC_NESTED(ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST,
+ cable_test_tdr_nest),
+};
+
+const struct pretty_nla_desc __tunnel_udp_entry_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_TUNNEL_UDP_ENTRY_UNSPEC),
+ NLATTR_DESC_U16(ETHTOOL_A_TUNNEL_UDP_ENTRY_PORT),
+ NLATTR_DESC_U32(ETHTOOL_A_TUNNEL_UDP_ENTRY_TYPE),
+};
+
+const struct pretty_nla_desc __tunnel_udp_table_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_TUNNEL_UDP_TABLE_UNSPEC),
+ NLATTR_DESC_U32(ETHTOOL_A_TUNNEL_UDP_TABLE_SIZE),
+ NLATTR_DESC_NESTED(ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES, bitset),
+ NLATTR_DESC_NESTED(ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY, tunnel_udp_entry),
+};
+
+const struct pretty_nla_desc __tunnel_udp_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_TUNNEL_UDP_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_TUNNEL_UDP_TABLE, tunnel_udp_table),
+};
+
+const struct pretty_nla_desc __tunnel_info_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_TUNNEL_INFO_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_TUNNEL_INFO_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_TUNNEL_INFO_UDP_PORTS, tunnel_udp),
+};
+
+const struct pretty_nla_desc __fec_stats_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_FEC_STAT_UNSPEC),
+ NLATTR_DESC_BINARY(ETHTOOL_A_FEC_STAT_PAD),
+ NLATTR_DESC_U64(ETHTOOL_A_FEC_STAT_CORRECTED),
+ NLATTR_DESC_U64(ETHTOOL_A_FEC_STAT_UNCORR),
+ NLATTR_DESC_U64(ETHTOOL_A_FEC_STAT_CORR_BITS),
+};
+
+static const struct pretty_nla_desc __fec_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_FEC_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_FEC_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_FEC_MODES, bitset),
+ NLATTR_DESC_BOOL(ETHTOOL_A_FEC_AUTO),
+ NLATTR_DESC_U32(ETHTOOL_A_FEC_ACTIVE),
+ NLATTR_DESC_NESTED(ETHTOOL_A_FEC_STATS, fec_stats),
+};
+
+const struct pretty_nla_desc __module_eeprom_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_MODULE_EEPROM_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_MODULE_EEPROM_HEADER, header),
+ NLATTR_DESC_U32(ETHTOOL_A_MODULE_EEPROM_OFFSET),
+ NLATTR_DESC_U32(ETHTOOL_A_MODULE_EEPROM_LENGTH),
+ NLATTR_DESC_U8(ETHTOOL_A_MODULE_EEPROM_PAGE),
+ NLATTR_DESC_U8(ETHTOOL_A_MODULE_EEPROM_BANK),
+ NLATTR_DESC_U8(ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS),
+ NLATTR_DESC_BINARY(ETHTOOL_A_MODULE_EEPROM_DATA)
+};
+
+static const struct pretty_nla_desc __stats_grp_stat_desc[] = {
+ NLATTR_DESC_U64(0), NLATTR_DESC_U64(1), NLATTR_DESC_U64(2),
+ NLATTR_DESC_U64(3), NLATTR_DESC_U64(4), NLATTR_DESC_U64(5),
+ NLATTR_DESC_U64(6), NLATTR_DESC_U64(7), NLATTR_DESC_U64(8),
+ NLATTR_DESC_U64(9), NLATTR_DESC_U64(10), NLATTR_DESC_U64(11),
+ NLATTR_DESC_U64(12), NLATTR_DESC_U64(13), NLATTR_DESC_U64(14),
+ NLATTR_DESC_U64(15), NLATTR_DESC_U64(16), NLATTR_DESC_U64(17),
+ NLATTR_DESC_U64(18), NLATTR_DESC_U64(19), NLATTR_DESC_U64(20),
+ NLATTR_DESC_U64(21), NLATTR_DESC_U64(22), NLATTR_DESC_U64(23),
+ NLATTR_DESC_U64(24), NLATTR_DESC_U64(25), NLATTR_DESC_U64(26),
+ NLATTR_DESC_U64(27), NLATTR_DESC_U64(28), NLATTR_DESC_U64(29),
+};
+
+static const struct pretty_nla_desc __stats_grp_hist_desc[] = {
+ NLATTR_DESC_U32(ETHTOOL_A_STATS_GRP_HIST_BKT_LOW),
+ NLATTR_DESC_U32(ETHTOOL_A_STATS_GRP_HIST_BKT_HI),
+ NLATTR_DESC_U64(ETHTOOL_A_STATS_GRP_HIST_VAL),
+};
+
+static const struct pretty_nla_desc __stats_grp_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_STATS_GRP_UNSPEC),
+ NLATTR_DESC_INVALID(ETHTOOL_A_STATS_GRP_PAD),
+ NLATTR_DESC_U32(ETHTOOL_A_STATS_GRP_ID),
+ NLATTR_DESC_U32(ETHTOOL_A_STATS_GRP_SS_ID),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STATS_GRP_STAT, stats_grp_stat),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STATS_GRP_HIST_RX, stats_grp_hist),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STATS_GRP_HIST_TX, stats_grp_hist),
+};
+
+static const struct pretty_nla_desc __stats_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_STATS_UNSPEC),
+ NLATTR_DESC_INVALID(ETHTOOL_A_STATS_PAD),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STATS_HEADER, header),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STATS_GROUPS, bitset),
+ NLATTR_DESC_NESTED(ETHTOOL_A_STATS_GRP, stats_grp),
+};
+
+static const struct pretty_nla_desc __phc_vclocks_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_PHC_VCLOCKS_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_PHC_VCLOCKS_HEADER, header),
+ NLATTR_DESC_U32(ETHTOOL_A_PHC_VCLOCKS_NUM),
+ NLATTR_DESC_BINARY(ETHTOOL_A_PHC_VCLOCKS_INDEX),
+};
+
+static const struct pretty_nla_desc __module_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_MODULE_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_MODULE_HEADER, header),
+ NLATTR_DESC_U8(ETHTOOL_A_MODULE_POWER_MODE_POLICY),
+ NLATTR_DESC_U8(ETHTOOL_A_MODULE_POWER_MODE),
+};
+
+static const char *__pse_admin_state_names[] = {
+ [ETHTOOL_PODL_PSE_ADMIN_STATE_UNKNOWN] = "ETHTOOL_PODL_PSE_ADMIN_STATE_UNKNOWN",
+ [ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED] = "ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED",
+ [ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED] = "ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED",
+};
+
+static const char *__pse_pw_d_status_names[] = {
+ [ETHTOOL_PODL_PSE_PW_D_STATUS_UNKNOWN] = "ETHTOOL_PODL_PSE_PW_D_STATUS_UNKNOWN",
+ [ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED] = "ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED",
+ [ETHTOOL_PODL_PSE_PW_D_STATUS_SEARCHING] = "ETHTOOL_PODL_PSE_PW_D_STATUS_SEARCHING",
+ [ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING] = "ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING",
+ [ETHTOOL_PODL_PSE_PW_D_STATUS_SLEEP] = "ETHTOOL_PODL_PSE_PW_D_STATUS_SLEEP",
+ [ETHTOOL_PODL_PSE_PW_D_STATUS_IDLE] = "ETHTOOL_PODL_PSE_PW_D_STATUS_IDLE",
+ [ETHTOOL_PODL_PSE_PW_D_STATUS_ERROR] = "ETHTOOL_PODL_PSE_PW_D_STATUS_ERROR",
+};
+
+static const struct pretty_nla_desc __pse_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_PSE_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_PSE_HEADER, header),
+ NLATTR_DESC_U32_ENUM(ETHTOOL_A_PODL_PSE_ADMIN_STATE, pse_admin_state),
+ NLATTR_DESC_U32_ENUM(ETHTOOL_A_PODL_PSE_ADMIN_CONTROL, pse_admin_state),
+ NLATTR_DESC_U32_ENUM(ETHTOOL_A_PODL_PSE_PW_D_STATUS, pse_pw_d_status),
+};
+
+static const struct pretty_nla_desc __rss_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_RSS_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_RSS_HEADER, header),
+ NLATTR_DESC_U32(ETHTOOL_A_RSS_CONTEXT),
+ NLATTR_DESC_U32(ETHTOOL_A_RSS_HFUNC),
+ NLATTR_DESC_BINARY(ETHTOOL_A_RSS_INDIR),
+ NLATTR_DESC_BINARY(ETHTOOL_A_RSS_HKEY),
+};
+
+static const struct pretty_nla_desc __plca_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_PLCA_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_PLCA_HEADER, header),
+ NLATTR_DESC_U16(ETHTOOL_A_PLCA_VERSION),
+ NLATTR_DESC_U8(ETHTOOL_A_PLCA_ENABLED),
+ NLATTR_DESC_U8(ETHTOOL_A_PLCA_STATUS),
+ NLATTR_DESC_U32(ETHTOOL_A_PLCA_NODE_CNT),
+ NLATTR_DESC_U32(ETHTOOL_A_PLCA_NODE_ID),
+ NLATTR_DESC_U32(ETHTOOL_A_PLCA_TO_TMR),
+ NLATTR_DESC_U32(ETHTOOL_A_PLCA_BURST_CNT),
+ NLATTR_DESC_U32(ETHTOOL_A_PLCA_BURST_TMR),
+};
+
+static const struct pretty_nla_desc __mm_stat_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_MM_STAT_UNSPEC),
+ NLATTR_DESC_BINARY(ETHTOOL_A_MM_STAT_PAD),
+ NLATTR_DESC_U64(ETHTOOL_A_MM_STAT_REASSEMBLY_ERRORS),
+ NLATTR_DESC_U64(ETHTOOL_A_MM_STAT_SMD_ERRORS),
+ NLATTR_DESC_U64(ETHTOOL_A_MM_STAT_REASSEMBLY_OK),
+ NLATTR_DESC_U64(ETHTOOL_A_MM_STAT_RX_FRAG_COUNT),
+ NLATTR_DESC_U64(ETHTOOL_A_MM_STAT_TX_FRAG_COUNT),
+ NLATTR_DESC_U64(ETHTOOL_A_MM_STAT_HOLD_COUNT),
+};
+
+static const struct pretty_nla_desc __mm_desc[] = {
+ NLATTR_DESC_INVALID(ETHTOOL_A_MM_UNSPEC),
+ NLATTR_DESC_NESTED(ETHTOOL_A_MM_HEADER, header),
+ NLATTR_DESC_U8(ETHTOOL_A_MM_PMAC_ENABLED),
+ NLATTR_DESC_U8(ETHTOOL_A_MM_TX_ENABLED),
+ NLATTR_DESC_U8(ETHTOOL_A_MM_TX_ACTIVE),
+ NLATTR_DESC_U32(ETHTOOL_A_MM_TX_MIN_FRAG_SIZE),
+ NLATTR_DESC_U32(ETHTOOL_A_MM_RX_MIN_FRAG_SIZE),
+ NLATTR_DESC_U8(ETHTOOL_A_MM_VERIFY_ENABLED),
+ NLATTR_DESC_U8(ETHTOOL_A_MM_VERIFY_STATUS),
+ NLATTR_DESC_U32(ETHTOOL_A_MM_VERIFY_TIME),
+ NLATTR_DESC_U32(ETHTOOL_A_MM_MAX_VERIFY_TIME),
+ NLATTR_DESC_NESTED(ETHTOOL_A_MM_STATS, mm_stat),
+};
+
+const struct pretty_nlmsg_desc ethnl_umsg_desc[] = {
+ NLMSG_DESC_INVALID(ETHTOOL_MSG_USER_NONE),
+ NLMSG_DESC(ETHTOOL_MSG_STRSET_GET, strset),
+ NLMSG_DESC(ETHTOOL_MSG_LINKINFO_GET, linkinfo),
+ NLMSG_DESC(ETHTOOL_MSG_LINKINFO_SET, linkinfo),
+ NLMSG_DESC(ETHTOOL_MSG_LINKMODES_GET, linkmodes),
+ NLMSG_DESC(ETHTOOL_MSG_LINKMODES_SET, linkmodes),
+ NLMSG_DESC(ETHTOOL_MSG_LINKSTATE_GET, linkstate),
+ NLMSG_DESC(ETHTOOL_MSG_DEBUG_GET, debug),
+ NLMSG_DESC(ETHTOOL_MSG_DEBUG_SET, debug),
+ NLMSG_DESC(ETHTOOL_MSG_WOL_GET, wol),
+ NLMSG_DESC(ETHTOOL_MSG_WOL_SET, wol),
+ NLMSG_DESC(ETHTOOL_MSG_FEATURES_GET, features),
+ NLMSG_DESC(ETHTOOL_MSG_FEATURES_SET, features),
+ NLMSG_DESC(ETHTOOL_MSG_PRIVFLAGS_GET, privflags),
+ NLMSG_DESC(ETHTOOL_MSG_PRIVFLAGS_SET, privflags),
+ NLMSG_DESC(ETHTOOL_MSG_RINGS_GET, rings),
+ NLMSG_DESC(ETHTOOL_MSG_RINGS_SET, rings),
+ NLMSG_DESC(ETHTOOL_MSG_CHANNELS_GET, channels),
+ NLMSG_DESC(ETHTOOL_MSG_CHANNELS_SET, channels),
+ NLMSG_DESC(ETHTOOL_MSG_COALESCE_GET, coalesce),
+ NLMSG_DESC(ETHTOOL_MSG_COALESCE_SET, coalesce),
+ NLMSG_DESC(ETHTOOL_MSG_PAUSE_GET, pause),
+ NLMSG_DESC(ETHTOOL_MSG_PAUSE_SET, pause),
+ NLMSG_DESC(ETHTOOL_MSG_EEE_GET, eee),
+ NLMSG_DESC(ETHTOOL_MSG_EEE_SET, eee),
+ NLMSG_DESC(ETHTOOL_MSG_TSINFO_GET, tsinfo),
+ NLMSG_DESC(ETHTOOL_MSG_CABLE_TEST_ACT, cable_test),
+ NLMSG_DESC(ETHTOOL_MSG_CABLE_TEST_TDR_ACT, cable_test_tdr),
+ NLMSG_DESC(ETHTOOL_MSG_TUNNEL_INFO_GET, tunnel_info),
+ NLMSG_DESC(ETHTOOL_MSG_FEC_GET, fec),
+ NLMSG_DESC(ETHTOOL_MSG_FEC_SET, fec),
+ NLMSG_DESC(ETHTOOL_MSG_MODULE_EEPROM_GET, module_eeprom),
+ NLMSG_DESC(ETHTOOL_MSG_STATS_GET, stats),
+ NLMSG_DESC(ETHTOOL_MSG_PHC_VCLOCKS_GET, phc_vclocks),
+ NLMSG_DESC(ETHTOOL_MSG_MODULE_GET, module),
+ NLMSG_DESC(ETHTOOL_MSG_MODULE_SET, module),
+ NLMSG_DESC(ETHTOOL_MSG_PSE_GET, pse),
+ NLMSG_DESC(ETHTOOL_MSG_PSE_SET, pse),
+ NLMSG_DESC(ETHTOOL_MSG_RSS_GET, rss),
+ NLMSG_DESC(ETHTOOL_MSG_PLCA_GET_CFG, plca),
+ NLMSG_DESC(ETHTOOL_MSG_PLCA_SET_CFG, plca),
+ NLMSG_DESC(ETHTOOL_MSG_PLCA_GET_STATUS, plca),
+ NLMSG_DESC(ETHTOOL_MSG_MM_GET, mm),
+ NLMSG_DESC(ETHTOOL_MSG_MM_SET, mm),
+};
+
+const unsigned int ethnl_umsg_n_desc = ARRAY_SIZE(ethnl_umsg_desc);
+
+const struct pretty_nlmsg_desc ethnl_kmsg_desc[] = {
+ NLMSG_DESC_INVALID(ETHTOOL_MSG_KERNEL_NONE),
+ NLMSG_DESC(ETHTOOL_MSG_STRSET_GET_REPLY, strset),
+ NLMSG_DESC(ETHTOOL_MSG_LINKINFO_GET_REPLY, linkinfo),
+ NLMSG_DESC(ETHTOOL_MSG_LINKINFO_NTF, linkinfo),
+ NLMSG_DESC(ETHTOOL_MSG_LINKMODES_GET_REPLY, linkmodes),
+ NLMSG_DESC(ETHTOOL_MSG_LINKMODES_NTF, linkmodes),
+ NLMSG_DESC(ETHTOOL_MSG_LINKSTATE_GET_REPLY, linkstate),
+ NLMSG_DESC(ETHTOOL_MSG_DEBUG_GET_REPLY, debug),
+ NLMSG_DESC(ETHTOOL_MSG_DEBUG_NTF, debug),
+ NLMSG_DESC(ETHTOOL_MSG_WOL_GET_REPLY, wol),
+ NLMSG_DESC(ETHTOOL_MSG_WOL_NTF, wol),
+ NLMSG_DESC(ETHTOOL_MSG_FEATURES_GET_REPLY, features),
+ NLMSG_DESC(ETHTOOL_MSG_FEATURES_SET_REPLY, features),
+ NLMSG_DESC(ETHTOOL_MSG_FEATURES_NTF, features),
+ NLMSG_DESC(ETHTOOL_MSG_PRIVFLAGS_GET_REPLY, privflags),
+ NLMSG_DESC(ETHTOOL_MSG_PRIVFLAGS_NTF, privflags),
+ NLMSG_DESC(ETHTOOL_MSG_RINGS_GET_REPLY, rings),
+ NLMSG_DESC(ETHTOOL_MSG_RINGS_NTF, rings),
+ NLMSG_DESC(ETHTOOL_MSG_CHANNELS_GET_REPLY, channels),
+ NLMSG_DESC(ETHTOOL_MSG_CHANNELS_NTF, channels),
+ NLMSG_DESC(ETHTOOL_MSG_COALESCE_GET_REPLY, coalesce),
+ NLMSG_DESC(ETHTOOL_MSG_COALESCE_NTF, coalesce),
+ NLMSG_DESC(ETHTOOL_MSG_PAUSE_GET_REPLY, pause),
+ NLMSG_DESC(ETHTOOL_MSG_PAUSE_NTF, pause),
+ NLMSG_DESC(ETHTOOL_MSG_EEE_GET_REPLY, eee),
+ NLMSG_DESC(ETHTOOL_MSG_EEE_NTF, eee),
+ NLMSG_DESC(ETHTOOL_MSG_TSINFO_GET_REPLY, tsinfo),
+ NLMSG_DESC(ETHTOOL_MSG_CABLE_TEST_NTF, cable_test_ntf),
+ NLMSG_DESC(ETHTOOL_MSG_CABLE_TEST_TDR_NTF, cable_test_tdr_ntf),
+ NLMSG_DESC(ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY, tunnel_info),
+ NLMSG_DESC(ETHTOOL_MSG_FEC_GET_REPLY, fec),
+ NLMSG_DESC(ETHTOOL_MSG_FEC_NTF, fec),
+ NLMSG_DESC(ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY, module_eeprom),
+ NLMSG_DESC(ETHTOOL_MSG_STATS_GET_REPLY, stats),
+ NLMSG_DESC(ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY, phc_vclocks),
+ NLMSG_DESC(ETHTOOL_MSG_MODULE_GET_REPLY, module),
+ NLMSG_DESC(ETHTOOL_MSG_MODULE_NTF, module),
+ NLMSG_DESC(ETHTOOL_MSG_PSE_GET_REPLY, pse),
+ NLMSG_DESC(ETHTOOL_MSG_RSS_GET_REPLY, rss),
+ NLMSG_DESC(ETHTOOL_MSG_PLCA_GET_CFG_REPLY, plca),
+ NLMSG_DESC(ETHTOOL_MSG_PLCA_GET_STATUS_REPLY, plca),
+ NLMSG_DESC(ETHTOOL_MSG_PLCA_NTF, plca),
+ NLMSG_DESC(ETHTOOL_MSG_MM_GET_REPLY, mm),
+ NLMSG_DESC(ETHTOOL_MSG_MM_NTF, mm),
+};
+
+const unsigned int ethnl_kmsg_n_desc = ARRAY_SIZE(ethnl_kmsg_desc);
diff --git a/netlink/desc-genlctrl.c b/netlink/desc-genlctrl.c
new file mode 100644
index 0000000..43b41ab
--- /dev/null
+++ b/netlink/desc-genlctrl.c
@@ -0,0 +1,113 @@
+/*
+ * desc-genlctrl.c - genetlink control format descriptions
+ *
+ * Descriptions of genetlink control messages and attributes for pretty print.
+ */
+
+#include <linux/genetlink.h>
+
+#include "../internal.h"
+#include "prettymsg.h"
+
+static const struct pretty_nla_desc __attrop_desc[] = {
+ NLATTR_DESC_INVALID(CTRL_ATTR_OP_UNSPEC),
+ NLATTR_DESC_U32(CTRL_ATTR_OP_ID),
+ NLATTR_DESC_X32(CTRL_ATTR_OP_FLAGS),
+};
+
+static const struct pretty_nla_desc __attrops_desc[] = {
+ NLATTR_DESC_NESTED(0, attrop),
+};
+
+static const struct pretty_nla_desc __mcgrp_desc[] = {
+ NLATTR_DESC_INVALID(CTRL_ATTR_MCAST_GRP_UNSPEC),
+ NLATTR_DESC_STRING(CTRL_ATTR_MCAST_GRP_NAME),
+ NLATTR_DESC_U32(CTRL_ATTR_MCAST_GRP_ID),
+};
+
+static const struct pretty_nla_desc __mcgrps_desc[] = {
+ NLATTR_DESC_NESTED(0, mcgrp),
+};
+
+static const char *__policy_attr_type_names[] = {
+ [NL_ATTR_TYPE_INVALID] = "NL_ATTR_TYPE_INVALID",
+ [NL_ATTR_TYPE_FLAG] = "NL_ATTR_TYPE_FLAG",
+ [NL_ATTR_TYPE_U8] = "NL_ATTR_TYPE_U8",
+ [NL_ATTR_TYPE_U16] = "NL_ATTR_TYPE_U16",
+ [NL_ATTR_TYPE_U32] = "NL_ATTR_TYPE_U32",
+ [NL_ATTR_TYPE_U64] = "NL_ATTR_TYPE_U64",
+ [NL_ATTR_TYPE_S8] = "NL_ATTR_TYPE_S8",
+ [NL_ATTR_TYPE_S16] = "NL_ATTR_TYPE_S16",
+ [NL_ATTR_TYPE_S32] = "NL_ATTR_TYPE_S32",
+ [NL_ATTR_TYPE_S64] = "NL_ATTR_TYPE_S64",
+ [NL_ATTR_TYPE_BINARY] = "NL_ATTR_TYPE_BINARY",
+ [NL_ATTR_TYPE_STRING] = "NL_ATTR_TYPE_STRING",
+ [NL_ATTR_TYPE_NUL_STRING] = "NL_ATTR_TYPE_NUL_STRING",
+ [NL_ATTR_TYPE_NESTED] = "NL_ATTR_TYPE_NESTED",
+ [NL_ATTR_TYPE_NESTED_ARRAY] = "NL_ATTR_TYPE_NESTED_ARRAY",
+ [NL_ATTR_TYPE_BITFIELD32] = "NL_ATTR_TYPE_BITFIELD32",
+};
+
+static const struct pretty_nla_desc __policy_attr_desc[] = {
+ NLATTR_DESC_INVALID(NL_POLICY_TYPE_ATTR_UNSPEC),
+ NLATTR_DESC_U32_ENUM(NL_POLICY_TYPE_ATTR_TYPE, policy_attr_type),
+ NLATTR_DESC_S64(NL_POLICY_TYPE_ATTR_MIN_VALUE_S),
+ NLATTR_DESC_S64(NL_POLICY_TYPE_ATTR_MAX_VALUE_S),
+ NLATTR_DESC_U64(NL_POLICY_TYPE_ATTR_MIN_VALUE_U),
+ NLATTR_DESC_U64(NL_POLICY_TYPE_ATTR_MAX_VALUE_U),
+ NLATTR_DESC_U32(NL_POLICY_TYPE_ATTR_MIN_LENGTH),
+ NLATTR_DESC_U32(NL_POLICY_TYPE_ATTR_MAX_LENGTH),
+ NLATTR_DESC_U32(NL_POLICY_TYPE_ATTR_POLICY_IDX),
+ NLATTR_DESC_U32(NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE),
+ NLATTR_DESC_X32(NL_POLICY_TYPE_ATTR_BITFIELD32_MASK),
+ NLATTR_DESC_X64(NL_POLICY_TYPE_ATTR_PAD),
+ NLATTR_DESC_BINARY(NL_POLICY_TYPE_ATTR_MASK),
+};
+
+static const struct pretty_nla_desc __policy_attrs_desc[] = {
+ NLATTR_DESC_NESTED(0, policy_attr),
+};
+
+static const struct pretty_nla_desc __policies_desc[] = {
+ NLATTR_DESC_ARRAY(0, policy_attrs),
+};
+
+static const struct pretty_nla_desc __op_policy_desc[] = {
+ NLATTR_DESC_INVALID(CTRL_ATTR_POLICY_UNSPEC),
+ NLATTR_DESC_U32(CTRL_ATTR_POLICY_DO),
+ NLATTR_DESC_U32(CTRL_ATTR_POLICY_DUMP),
+};
+
+static const struct pretty_nla_desc __op_policies_desc[] = {
+ NLATTR_DESC_NESTED(0, op_policy),
+};
+
+static const struct pretty_nla_desc __attr_desc[] = {
+ NLATTR_DESC_INVALID(CTRL_ATTR_UNSPEC),
+ NLATTR_DESC_U16(CTRL_ATTR_FAMILY_ID),
+ NLATTR_DESC_STRING(CTRL_ATTR_FAMILY_NAME),
+ NLATTR_DESC_U32(CTRL_ATTR_VERSION),
+ NLATTR_DESC_U32(CTRL_ATTR_HDRSIZE),
+ NLATTR_DESC_U32(CTRL_ATTR_MAXATTR),
+ NLATTR_DESC_ARRAY(CTRL_ATTR_OPS, attrops),
+ NLATTR_DESC_ARRAY(CTRL_ATTR_MCAST_GROUPS, mcgrps),
+ NLATTR_DESC_ARRAY(CTRL_ATTR_POLICY, policies),
+ NLATTR_DESC_ARRAY(CTRL_ATTR_OP_POLICY, op_policies),
+ NLATTR_DESC_U32(CTRL_ATTR_OP),
+};
+
+const struct pretty_nlmsg_desc genlctrl_msg_desc[] = {
+ NLMSG_DESC_INVALID(CTRL_CMD_UNSPEC),
+ NLMSG_DESC(CTRL_CMD_NEWFAMILY, attr),
+ NLMSG_DESC(CTRL_CMD_DELFAMILY, attr),
+ NLMSG_DESC(CTRL_CMD_GETFAMILY, attr),
+ NLMSG_DESC(CTRL_CMD_NEWOPS, attr),
+ NLMSG_DESC(CTRL_CMD_DELOPS, attr),
+ NLMSG_DESC(CTRL_CMD_GETOPS, attr),
+ NLMSG_DESC(CTRL_CMD_NEWMCAST_GRP, attr),
+ NLMSG_DESC(CTRL_CMD_DELMCAST_GRP, attr),
+ NLMSG_DESC(CTRL_CMD_GETMCAST_GRP, attr),
+ NLMSG_DESC(CTRL_CMD_GETPOLICY, attr),
+};
+
+const unsigned int genlctrl_msg_n_desc = ARRAY_SIZE(genlctrl_msg_desc);
diff --git a/netlink/desc-rtnl.c b/netlink/desc-rtnl.c
new file mode 100644
index 0000000..e15e6f0
--- /dev/null
+++ b/netlink/desc-rtnl.c
@@ -0,0 +1,96 @@
+/*
+ * desc-rtnl.c - rtnetlink message descriptions
+ *
+ * Descriptions of rtnetlink messages and attributes for pretty print.
+ */
+
+#include <linux/rtnetlink.h>
+
+#include "../internal.h"
+#include "prettymsg.h"
+
+static const struct pretty_nla_desc __link_desc[] = {
+ NLATTR_DESC_INVALID(IFLA_UNSPEC),
+ NLATTR_DESC_BINARY(IFLA_ADDRESS),
+ NLATTR_DESC_BINARY(IFLA_BROADCAST),
+ NLATTR_DESC_STRING(IFLA_IFNAME),
+ NLATTR_DESC_U32(IFLA_MTU),
+ NLATTR_DESC_U32(IFLA_LINK),
+ NLATTR_DESC_STRING(IFLA_QDISC),
+ NLATTR_DESC_BINARY(IFLA_STATS),
+ NLATTR_DESC_INVALID(IFLA_COST),
+ NLATTR_DESC_INVALID(IFLA_PRIORITY),
+ NLATTR_DESC_U32(IFLA_MASTER),
+ NLATTR_DESC_BINARY(IFLA_WIRELESS),
+ NLATTR_DESC_NESTED_NODESC(IFLA_PROTINFO),
+ NLATTR_DESC_U32(IFLA_TXQLEN),
+ NLATTR_DESC_BINARY(IFLA_MAP),
+ NLATTR_DESC_U32(IFLA_WEIGHT),
+ NLATTR_DESC_U8(IFLA_OPERSTATE),
+ NLATTR_DESC_U8(IFLA_LINKMODE),
+ NLATTR_DESC_NESTED_NODESC(IFLA_LINKINFO),
+ NLATTR_DESC_U32(IFLA_NET_NS_PID),
+ NLATTR_DESC_STRING(IFLA_IFALIAS),
+ NLATTR_DESC_U32(IFLA_NUM_VF),
+ NLATTR_DESC_NESTED_NODESC(IFLA_VFINFO_LIST),
+ NLATTR_DESC_BINARY(IFLA_STATS64),
+ NLATTR_DESC_NESTED_NODESC(IFLA_VF_PORTS),
+ NLATTR_DESC_NESTED_NODESC(IFLA_PORT_SELF),
+ NLATTR_DESC_NESTED_NODESC(IFLA_AF_SPEC),
+ NLATTR_DESC_U32(IFLA_GROUP),
+ NLATTR_DESC_U32(IFLA_NET_NS_FD),
+ NLATTR_DESC_U32(IFLA_EXT_MASK),
+ NLATTR_DESC_U32(IFLA_PROMISCUITY),
+ NLATTR_DESC_U32(IFLA_NUM_TX_QUEUES),
+ NLATTR_DESC_U32(IFLA_NUM_RX_QUEUES),
+ NLATTR_DESC_U8(IFLA_CARRIER),
+ NLATTR_DESC_BINARY(IFLA_PHYS_PORT_ID),
+ NLATTR_DESC_U32(IFLA_CARRIER_CHANGES),
+ NLATTR_DESC_BINARY(IFLA_PHYS_SWITCH_ID),
+ NLATTR_DESC_S32(IFLA_LINK_NETNSID),
+ NLATTR_DESC_STRING(IFLA_PHYS_PORT_NAME),
+ NLATTR_DESC_U8(IFLA_PROTO_DOWN),
+ NLATTR_DESC_U32(IFLA_GSO_MAX_SEGS),
+ NLATTR_DESC_U32(IFLA_GSO_MAX_SIZE),
+ NLATTR_DESC_BINARY(IFLA_PAD),
+ NLATTR_DESC_U32(IFLA_XDP),
+ NLATTR_DESC_U32(IFLA_EVENT),
+ NLATTR_DESC_S32(IFLA_NEW_NETNSID),
+ NLATTR_DESC_S32(IFLA_IF_NETNSID),
+ NLATTR_DESC_U32(IFLA_CARRIER_UP_COUNT),
+ NLATTR_DESC_U32(IFLA_CARRIER_DOWN_COUNT),
+ NLATTR_DESC_S32(IFLA_NEW_IFINDEX),
+ NLATTR_DESC_U32(IFLA_MIN_MTU),
+ NLATTR_DESC_U32(IFLA_MAX_MTU),
+ NLATTR_DESC_NESTED_NODESC(IFLA_PROP_LIST),
+ NLATTR_DESC_STRING(IFLA_ALT_IFNAME),
+ NLATTR_DESC_BINARY(IFLA_PERM_ADDRESS),
+};
+
+const struct pretty_nlmsg_desc rtnl_msg_desc[] = {
+ NLMSG_DESC(RTM_NEWLINK, link),
+ NLMSG_DESC(RTM_DELLINK, link),
+ NLMSG_DESC(RTM_GETLINK, link),
+ NLMSG_DESC(RTM_SETLINK, link),
+};
+
+const unsigned int rtnl_msg_n_desc = ARRAY_SIZE(rtnl_msg_desc);
+
+#define RTNL_MSGHDR_LEN(_name, _struct) \
+ [((RTM_ ## _name) - RTM_BASE) / 4] = sizeof(struct _struct)
+#define RTNL_MSGHDR_NOLEN(_name) \
+ [((RTM_ ## _name) - RTM_BASE) / 4] = USHRT_MAX
+
+const unsigned short rtnl_msghdr_lengths[] = {
+ RTNL_MSGHDR_LEN(NEWLINK, ifinfomsg),
+ RTNL_MSGHDR_LEN(NEWADDR, ifaddrmsg),
+ RTNL_MSGHDR_LEN(NEWROUTE, rtmsg),
+ RTNL_MSGHDR_LEN(NEWNEIGH, ndmsg),
+ RTNL_MSGHDR_LEN(NEWRULE, rtmsg),
+ RTNL_MSGHDR_LEN(NEWQDISC, tcmsg),
+ RTNL_MSGHDR_LEN(NEWTCLASS, tcmsg),
+ RTNL_MSGHDR_LEN(NEWTFILTER, tcmsg),
+ RTNL_MSGHDR_LEN(NEWACTION, tcamsg),
+};
+
+const unsigned int rtnl_msghdr_n_len = ARRAY_SIZE(rtnl_msghdr_lengths);
diff --git a/netlink/eee.c b/netlink/eee.c
new file mode 100644
index 0000000..04d8f0b
--- /dev/null
+++ b/netlink/eee.c
@@ -0,0 +1,189 @@
+/*
+ * eee.c - netlink implementation of eee commands
+ *
+ * Implementation of "ethtool --show-eee <dev>" and
+ * "ethtool --set-eee <dev> ..."
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "bitset.h"
+#include "parser.h"
+
+/* EEE_GET */
+
+int eee_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_EEE_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ bool enabled, active, tx_lpi_enabled;
+ struct nl_context *nlctx = data;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_EEE_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (!tb[ETHTOOL_A_EEE_MODES_OURS] ||
+ !tb[ETHTOOL_A_EEE_ACTIVE] || !tb[ETHTOOL_A_EEE_ENABLED] ||
+ !tb[ETHTOOL_A_EEE_TX_LPI_ENABLED] ||
+ !tb[ETHTOOL_A_EEE_TX_LPI_TIMER]) {
+ fprintf(stderr, "Malformed response from kernel\n");
+ return err_ret;
+ }
+ active = mnl_attr_get_u8(tb[ETHTOOL_A_EEE_ACTIVE]);
+ enabled = mnl_attr_get_u8(tb[ETHTOOL_A_EEE_ENABLED]);
+ tx_lpi_enabled = mnl_attr_get_u8(tb[ETHTOOL_A_EEE_TX_LPI_ENABLED]);
+
+ if (silent)
+ putchar('\n');
+ printf("EEE settings for %s:\n", nlctx->devname);
+ printf("\tEEE status: ");
+ if (bitset_is_empty(tb[ETHTOOL_A_EEE_MODES_OURS], true, &ret)) {
+ printf("not supported\n");
+ return MNL_CB_OK;
+ }
+ if (!enabled)
+ printf("disabled\n");
+ else
+ printf("enabled - %s\n", active ? "active" : "inactive");
+ printf("\tTx LPI: ");
+ if (tx_lpi_enabled)
+ printf("%u (us)\n",
+ mnl_attr_get_u32(tb[ETHTOOL_A_EEE_TX_LPI_TIMER]));
+ else
+ printf("disabled\n");
+
+ ret = dump_link_modes(nlctx, tb[ETHTOOL_A_EEE_MODES_OURS], true,
+ LM_CLASS_REAL,
+ "Supported EEE link modes: ", NULL, "\n",
+ "Not reported");
+ if (ret < 0)
+ return err_ret;
+ ret = dump_link_modes(nlctx, tb[ETHTOOL_A_EEE_MODES_OURS], false,
+ LM_CLASS_REAL,
+ "Advertised EEE link modes: ", NULL, "\n",
+ "Not reported");
+ if (ret < 0)
+ return err_ret;
+ ret = dump_link_modes(nlctx, tb[ETHTOOL_A_EEE_MODES_PEER], false,
+ LM_CLASS_REAL,
+ "Link partner advertised EEE link modes: ", NULL,
+ "\n", "Not reported");
+ if (ret < 0)
+ return err_ret;
+
+ return MNL_CB_OK;
+}
+
+int nl_geee(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_EEE_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_EEE_GET,
+ ETHTOOL_A_EEE_HEADER, 0);
+ if (ret < 0)
+ return ret;
+ return nlsock_send_get_request(nlsk, eee_reply_cb);
+}
+
+/* EEE_SET */
+
+static const struct bitset_parser_data advertise_parser_data = {
+ .no_mask = false,
+ .force_hex = true,
+};
+
+static const struct param_parser seee_params[] = {
+ {
+ .arg = "advertise",
+ .type = ETHTOOL_A_EEE_MODES_OURS,
+ .handler = nl_parse_bitset,
+ .handler_data = &advertise_parser_data,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-lpi",
+ .type = ETHTOOL_A_EEE_TX_LPI_ENABLED,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-timer",
+ .type = ETHTOOL_A_EEE_TX_LPI_TIMER,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "eee",
+ .type = ETHTOOL_A_EEE_ENABLED,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_seee(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_EEE_SET, false))
+ return -EOPNOTSUPP;
+ if (!ctx->argc) {
+ fprintf(stderr, "ethtool (--set-eee): parameters missing\n");
+ return 1;
+ }
+
+ nlctx->cmd = "--set-eee";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_EEE_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_EEE_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, seee_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return 76;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret == 0)
+ return 0;
+ else
+ return nlctx->exit_code ?: 76;
+}
diff --git a/netlink/extapi.h b/netlink/extapi.h
new file mode 100644
index 0000000..e2d6b71
--- /dev/null
+++ b/netlink/extapi.h
@@ -0,0 +1,136 @@
+/*
+ * extapi.h - external interface of netlink code
+ *
+ * Declarations needed by non-netlink code (mostly ethtool.c).
+ */
+
+#ifndef ETHTOOL_EXTAPI_H__
+#define ETHTOOL_EXTAPI_H__
+
+struct cmd_context;
+struct nl_context;
+
+typedef int (*nl_func_t)(struct cmd_context *);
+typedef bool (*nl_chk_t)(struct cmd_context *);
+
+#ifdef ETHTOOL_ENABLE_NETLINK
+
+void netlink_run_handler(struct cmd_context *ctx, nl_chk_t nlchk,
+ nl_func_t nlfunc, bool no_fallback);
+
+int nl_gset(struct cmd_context *ctx);
+int nl_sset(struct cmd_context *ctx);
+int nl_permaddr(struct cmd_context *ctx);
+int nl_gfeatures(struct cmd_context *ctx);
+int nl_sfeatures(struct cmd_context *ctx);
+int nl_gprivflags(struct cmd_context *ctx);
+int nl_sprivflags(struct cmd_context *ctx);
+int nl_gring(struct cmd_context *ctx);
+int nl_sring(struct cmd_context *ctx);
+int nl_gchannels(struct cmd_context *ctx);
+int nl_schannels(struct cmd_context *ctx);
+int nl_gcoalesce(struct cmd_context *ctx);
+int nl_scoalesce(struct cmd_context *ctx);
+int nl_gpause(struct cmd_context *ctx);
+int nl_spause(struct cmd_context *ctx);
+int nl_geee(struct cmd_context *ctx);
+int nl_seee(struct cmd_context *ctx);
+int nl_tsinfo(struct cmd_context *ctx);
+int nl_cable_test(struct cmd_context *ctx);
+int nl_cable_test_tdr(struct cmd_context *ctx);
+int nl_gtunnels(struct cmd_context *ctx);
+int nl_gfec(struct cmd_context *ctx);
+int nl_sfec(struct cmd_context *ctx);
+bool nl_gstats_chk(struct cmd_context *ctx);
+int nl_gstats(struct cmd_context *ctx);
+int nl_gmodule(struct cmd_context *ctx);
+int nl_smodule(struct cmd_context *ctx);
+int nl_monitor(struct cmd_context *ctx);
+int nl_getmodule(struct cmd_context *ctx);
+int nl_grss(struct cmd_context *ctx);
+int nl_plca_get_cfg(struct cmd_context *ctx);
+int nl_plca_set_cfg(struct cmd_context *ctx);
+int nl_plca_get_status(struct cmd_context *ctx);
+int nl_get_mm(struct cmd_context *ctx);
+int nl_set_mm(struct cmd_context *ctx);
+int nl_gpse(struct cmd_context *ctx);
+int nl_spse(struct cmd_context *ctx);
+
+void nl_monitor_usage(void);
+
+int nl_get_eeprom_page(struct cmd_context *ctx,
+ struct ethtool_module_eeprom *request);
+
+#else /* ETHTOOL_ENABLE_NETLINK */
+
+static inline void netlink_run_handler(struct cmd_context *ctx __maybe_unused,
+ nl_chk_t nlchk __maybe_unused,
+ nl_func_t nlfunc __maybe_unused,
+ bool no_fallback)
+{
+ if (no_fallback) {
+ fprintf(stderr,
+ "Command requires kernel netlink support which is not "
+ "enabled in this ethtool binary\n");
+ exit(1);
+ }
+}
+
+static inline int nl_monitor(struct cmd_context *ctx __maybe_unused)
+{
+ fprintf(stderr, "Netlink not supported by ethtool, option --monitor unsupported.\n");
+ return -EOPNOTSUPP;
+}
+
+static inline void nl_monitor_usage(void)
+{
+}
+
+static inline int
+nl_get_eeprom_page(struct cmd_context *ctx __maybe_unused,
+ struct ethtool_module_eeprom *request __maybe_unused)
+{
+ fprintf(stderr, "Netlink not supported by ethtool.\n");
+ return -EOPNOTSUPP;
+}
+
+#define nl_gset NULL
+#define nl_sset NULL
+#define nl_permaddr NULL
+#define nl_gfeatures NULL
+#define nl_sfeatures NULL
+#define nl_gprivflags NULL
+#define nl_sprivflags NULL
+#define nl_gring NULL
+#define nl_sring NULL
+#define nl_gchannels NULL
+#define nl_schannels NULL
+#define nl_gcoalesce NULL
+#define nl_scoalesce NULL
+#define nl_gpause NULL
+#define nl_spause NULL
+#define nl_geee NULL
+#define nl_seee NULL
+#define nl_tsinfo NULL
+#define nl_cable_test NULL
+#define nl_cable_test_tdr NULL
+#define nl_gtunnels NULL
+#define nl_gfec NULL
+#define nl_sfec NULL
+#define nl_gstats_chk NULL
+#define nl_gstats NULL
+#define nl_getmodule NULL
+#define nl_gmodule NULL
+#define nl_smodule NULL
+#define nl_grss NULL
+#define nl_plca_get_cfg NULL
+#define nl_plca_set_cfg NULL
+#define nl_plca_get_status NULL
+#define nl_get_mm NULL
+#define nl_set_mm NULL
+#define nl_gpse NULL
+#define nl_spse NULL
+
+#endif /* ETHTOOL_ENABLE_NETLINK */
+
+#endif /* ETHTOOL_EXTAPI_H__ */
diff --git a/netlink/features.c b/netlink/features.c
new file mode 100644
index 0000000..5711ff4
--- /dev/null
+++ b/netlink/features.c
@@ -0,0 +1,569 @@
+/*
+ * features.c - netlink implementation of netdev features commands
+ *
+ * Implementation of "ethtool -k <dev>".
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "strset.h"
+#include "bitset.h"
+
+/* FEATURES_GET */
+
+struct feature_results {
+ uint32_t *hw;
+ uint32_t *wanted;
+ uint32_t *active;
+ uint32_t *nochange;
+ unsigned int count;
+ unsigned int words;
+};
+
+static int prepare_feature_results(const struct nlattr *const *tb,
+ struct feature_results *dest)
+{
+ unsigned int count;
+ int ret;
+
+ memset(dest, '\0', sizeof(*dest));
+ if (!tb[ETHTOOL_A_FEATURES_HW] || !tb[ETHTOOL_A_FEATURES_WANTED] ||
+ !tb[ETHTOOL_A_FEATURES_ACTIVE] || !tb[ETHTOOL_A_FEATURES_NOCHANGE])
+ return -EFAULT;
+ count = bitset_get_count(tb[ETHTOOL_A_FEATURES_HW], &ret);
+ if (ret < 0)
+ return -EFAULT;
+ if ((bitset_get_count(tb[ETHTOOL_A_FEATURES_WANTED], &ret) != count) ||
+ (bitset_get_count(tb[ETHTOOL_A_FEATURES_ACTIVE], &ret) != count) ||
+ (bitset_get_count(tb[ETHTOOL_A_FEATURES_NOCHANGE], &ret) != count))
+ return -EFAULT;
+ dest->hw = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_HW]);
+ dest->wanted = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_WANTED]);
+ dest->active = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_ACTIVE]);
+ dest->nochange =
+ get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_NOCHANGE]);
+ if (!dest->hw || !dest->wanted || !dest->active || !dest->nochange)
+ return -EFAULT;
+ dest->count = count;
+ dest->words = (count + 31) / 32;
+
+ return 0;
+}
+
+static bool feature_on(const uint32_t *bitmap, unsigned int idx)
+{
+ return bitmap[idx / 32] & (1 << (idx % 32));
+}
+
+static void dump_feature(const struct feature_results *results,
+ const uint32_t *ref, const uint32_t *ref_mask,
+ unsigned int idx, const char *name, const char *prefix)
+{
+ const char *suffix = "";
+
+ if (!name || !*name)
+ return;
+ if (ref) {
+ if (ref_mask && !feature_on(ref_mask, idx))
+ return;
+ if ((!ref_mask || feature_on(ref_mask, idx)) &&
+ (feature_on(results->active, idx) == feature_on(ref, idx)))
+ return;
+ }
+
+ if (!feature_on(results->hw, idx) || feature_on(results->nochange, idx))
+ suffix = " [fixed]";
+ else if (feature_on(results->active, idx) !=
+ feature_on(results->wanted, idx))
+ suffix = feature_on(results->wanted, idx) ?
+ " [requested on]" : " [requested off]";
+ if (is_json_context()) {
+ open_json_object(name);
+ print_bool(PRINT_JSON, "active", NULL, feature_on(results->active, idx));
+ print_bool(PRINT_JSON, "fixed", NULL,
+ (!feature_on(results->hw, idx) || feature_on(results->nochange, idx)));
+ print_bool(PRINT_JSON, "requested", NULL, feature_on(results->wanted, idx));
+ close_json_object();
+ } else {
+ printf("%s%s: %s%s\n", prefix, name,
+ feature_on(results->active, idx) ? "on" : "off", suffix);
+ }
+}
+
+/* this assumes pattern contains no more than one asterisk */
+static bool flag_pattern_match(const char *name, const char *pattern)
+{
+ const char *p_ast = strchr(pattern, '*');
+
+ if (p_ast) {
+ size_t name_len = strlen(name);
+ size_t pattern_len = strlen(pattern);
+
+ if (name_len + 1 < pattern_len)
+ return false;
+ if (strncmp(name, pattern, p_ast - pattern))
+ return false;
+ pattern_len -= (p_ast - pattern) + 1;
+ name += name_len - pattern_len;
+ pattern = p_ast + 1;
+ }
+ return !strcmp(name, pattern);
+}
+
+int dump_features(const struct nlattr *const *tb,
+ const struct stringset *feature_names)
+{
+ unsigned int *feature_flags = NULL;
+ struct feature_results results;
+ unsigned int i, j;
+ int ret;
+
+ ret = prepare_feature_results(tb, &results);
+ if (ret < 0)
+ return -EFAULT;
+ feature_flags = calloc(results.count, sizeof(feature_flags[0]));
+ if (!feature_flags)
+ return -ENOMEM;
+
+ /* map netdev features to legacy flags */
+ for (i = 0; i < results.count; i++) {
+ const char *name = get_string(feature_names, i);
+ feature_flags[i] = UINT_MAX;
+
+ if (!name || !*name)
+ continue;
+ for (j = 0; j < OFF_FLAG_DEF_SIZE; j++) {
+ const char *flag_name = off_flag_def[j].kernel_name;
+
+ if (flag_pattern_match(name, flag_name)) {
+ feature_flags[i] = j;
+ break;
+ }
+ }
+ }
+ /* show legacy flags and their matching features first */
+ for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
+ unsigned int n_match = 0;
+ bool flag_value = false;
+
+ /* no kernel with netlink interface supports UFO */
+ if (off_flag_def[i].value == ETH_FLAG_UFO)
+ continue;
+
+ for (j = 0; j < results.count; j++) {
+ if (feature_flags[j] == i) {
+ n_match++;
+ flag_value = flag_value ||
+ feature_on(results.active, j);
+ }
+ }
+ if (n_match != 1) {
+ if (is_json_context()) {
+ open_json_object(off_flag_def[i].long_name);
+ print_bool(PRINT_JSON, "active", NULL, flag_value);
+ print_null(PRINT_JSON, "fixed", NULL, NULL);
+ print_null(PRINT_JSON, "requested", NULL, NULL);
+ close_json_object();
+ } else {
+ printf("%s: %s\n", off_flag_def[i].long_name,
+ flag_value ? "on" : "off");
+ }
+ }
+ if (n_match == 0)
+ continue;
+ for (j = 0; j < results.count; j++) {
+ const char *name = get_string(feature_names, j);
+
+ if (feature_flags[j] != i)
+ continue;
+ if (n_match == 1)
+ dump_feature(&results, NULL, NULL, j,
+ off_flag_def[i].long_name, "");
+ else
+ dump_feature(&results, NULL, NULL, j, name,
+ "\t");
+ }
+ }
+ /* and, finally, remaining netdev_features not matching legacy flags */
+ for (i = 0; i < results.count; i++) {
+ const char *name = get_string(feature_names, i);
+
+ if (!name || !*name || feature_flags[i] != UINT_MAX)
+ continue;
+ dump_feature(&results, NULL, NULL, i, name, "");
+ }
+
+ free(feature_flags);
+ return 0;
+}
+
+int features_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_FEATURES_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ const struct stringset *feature_names;
+ struct nl_context *nlctx = data;
+ bool silent;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ if (!nlctx->is_monitor) {
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+ }
+ feature_names = global_stringset(ETH_SS_FEATURES, nlctx->ethnl2_socket);
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return silent ? MNL_CB_OK : MNL_CB_ERROR;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_FEATURES_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ if (silent)
+ putchar('\n');
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "ifname", "Features for %s:\n", nlctx->devname);
+ ret = dump_features(tb, feature_names);
+ close_json_object();
+ return (silent || !ret) ? MNL_CB_OK : MNL_CB_ERROR;
+}
+
+int nl_gfeatures(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_FEATURES_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_FEATURES_GET,
+ ETHTOOL_A_FEATURES_HEADER,
+ ETHTOOL_FLAG_COMPACT_BITSETS);
+ if (ret < 0)
+ return ret;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, features_reply_cb);
+ delete_json_obj();
+
+ return ret;
+}
+
+/* FEATURES_SET */
+
+struct sfeatures_context {
+ bool nothing_changed;
+ uint32_t req_mask[];
+};
+
+static int find_feature(const char *name,
+ const struct stringset *feature_names)
+{
+ const unsigned int count = get_count(feature_names);
+ unsigned int i;
+
+ for (i = 0; i < count; i++)
+ if (!strcmp(name, get_string(feature_names, i)))
+ return i;
+
+ return -1;
+}
+
+static int fill_feature(struct nl_msg_buff *msgbuff, const char *name, bool val)
+{
+ struct nlattr *bit_attr;
+
+ bit_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS_BIT);
+ if (!bit_attr)
+ return -EMSGSIZE;
+ if (ethnla_put_strz(msgbuff, ETHTOOL_A_BITSET_BIT_NAME, name))
+ return -EMSGSIZE;
+ if (ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_BIT_VALUE, val))
+ return -EMSGSIZE;
+ mnl_attr_nest_end(msgbuff->nlhdr, bit_attr);
+
+ return 0;
+}
+
+static void set_sf_req_mask(struct nl_context *nlctx, unsigned int idx)
+{
+ struct sfeatures_context *sfctx = nlctx->cmd_private;
+
+ sfctx->req_mask[idx / 32] |= (1 << (idx % 32));
+}
+
+static int fill_legacy_flag(struct nl_context *nlctx, const char *flag_name,
+ const struct stringset *feature_names, bool val)
+{
+ struct nl_msg_buff *msgbuff = &nlctx->ethnl_socket->msgbuff;
+ const unsigned int count = get_count(feature_names);
+ unsigned int i, j;
+ int ret;
+
+ for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
+ const char *pattern;
+
+ if (strcmp(flag_name, off_flag_def[i].short_name) &&
+ strcmp(flag_name, off_flag_def[i].long_name))
+ continue;
+ pattern = off_flag_def[i].kernel_name;
+
+ for (j = 0; j < count; j++) {
+ const char *name = get_string(feature_names, j);
+
+ if (flag_pattern_match(name, pattern)) {
+ ret = fill_feature(msgbuff, name, val);
+ if (ret < 0)
+ return ret;
+ set_sf_req_mask(nlctx, j);
+ }
+ }
+
+ return 0;
+ }
+
+ return 1;
+}
+
+int fill_sfeatures_bitmap(struct nl_context *nlctx,
+ const struct stringset *feature_names)
+{
+ struct nl_msg_buff *msgbuff = &nlctx->ethnl_socket->msgbuff;
+ struct nlattr *bitset_attr;
+ struct nlattr *bits_attr;
+ int ret;
+
+ ret = -EMSGSIZE;
+ bitset_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_FEATURES_WANTED);
+ if (!bitset_attr)
+ return ret;
+ bits_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS);
+ if (!bits_attr)
+ goto err;
+
+ while (nlctx->argc > 0) {
+ bool val;
+
+ if (!strcmp(*nlctx->argp, "--")) {
+ nlctx->argp++;
+ nlctx->argc--;
+ break;
+ }
+ ret = -EINVAL;
+ if (nlctx->argc < 2 ||
+ (strcmp(nlctx->argp[1], "on") &&
+ strcmp(nlctx->argp[1], "off"))) {
+ fprintf(stderr,
+ "ethtool (%s): flag '%s' for parameter '%s' is"
+ " not followed by 'on' or 'off'\n",
+ nlctx->cmd, nlctx->argp[1], nlctx->param);
+ goto err;
+ }
+
+ val = !strcmp(nlctx->argp[1], "on");
+ ret = fill_legacy_flag(nlctx, nlctx->argp[0], feature_names,
+ val);
+ if (ret > 0) {
+ ret = fill_feature(msgbuff, nlctx->argp[0], val);
+ if (ret == 0) {
+ int idx = find_feature(nlctx->argp[0],
+ feature_names);
+
+ if (idx >= 0)
+ set_sf_req_mask(nlctx, idx);
+ }
+ }
+ if (ret < 0)
+ goto err;
+
+ nlctx->argp += 2;
+ nlctx->argc -= 2;
+ }
+
+ ethnla_nest_end(msgbuff, bits_attr);
+ ethnla_nest_end(msgbuff, bitset_attr);
+ return 0;
+err:
+ ethnla_nest_cancel(msgbuff, bitset_attr);
+ return ret;
+}
+
+static void show_feature_changes(struct nl_context *nlctx,
+ const struct nlattr *const *tb)
+{
+ struct sfeatures_context *sfctx = nlctx->cmd_private;
+ const struct stringset *feature_names;
+ const uint32_t *wanted_mask;
+ const uint32_t *active_mask;
+ const uint32_t *wanted_val;
+ const uint32_t *active_val;
+ unsigned int count, words;
+ unsigned int i;
+ bool diff;
+ int ret;
+
+ feature_names = global_stringset(ETH_SS_FEATURES, nlctx->ethnl_socket);
+ count = get_count(feature_names);
+ words = DIV_ROUND_UP(count, 32);
+
+ if (!tb[ETHTOOL_A_FEATURES_WANTED] || !tb[ETHTOOL_A_FEATURES_ACTIVE])
+ goto err;
+ if (bitset_get_count(tb[ETHTOOL_A_FEATURES_WANTED], &ret) != count ||
+ ret < 0)
+ goto err;
+ if (bitset_get_count(tb[ETHTOOL_A_FEATURES_ACTIVE], &ret) != count ||
+ ret < 0)
+ goto err;
+ wanted_val = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_WANTED]);
+ wanted_mask = get_compact_bitset_mask(tb[ETHTOOL_A_FEATURES_WANTED]);
+ active_val = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_ACTIVE]);
+ active_mask = get_compact_bitset_mask(tb[ETHTOOL_A_FEATURES_ACTIVE]);
+ if (!wanted_val || !wanted_mask || !active_val || !active_mask)
+ goto err;
+
+ sfctx->nothing_changed = true;
+ diff = false;
+ for (i = 0; i < words; i++) {
+ if (wanted_mask[i] != sfctx->req_mask[i])
+ sfctx->nothing_changed = false;
+ if (wanted_mask[i] || (active_mask[i] & ~sfctx->req_mask[i]))
+ diff = true;
+ }
+ if (!diff)
+ return;
+
+ /* result is not exactly as requested, show differences */
+ printf("Actual changes:\n");
+ for (i = 0; i < count; i++) {
+ const char *name = get_string(feature_names, i);
+
+ if (!name)
+ continue;
+ if (!feature_on(wanted_mask, i) && !feature_on(active_mask, i))
+ continue;
+ printf("%s: ", name);
+ if (feature_on(wanted_mask, i))
+ /* we requested a value but result is different */
+ printf("%s [requested %s]",
+ feature_on(wanted_val, i) ? "off" : "on",
+ feature_on(wanted_val, i) ? "on" : "off");
+ else if (!feature_on(sfctx->req_mask, i))
+ /* not requested but changed anyway */
+ printf("%s [not requested]",
+ feature_on(active_val, i) ? "on" : "off");
+ else
+ printf("%s", feature_on(active_val, i) ? "on" : "off");
+ fputc('\n', stdout);
+ }
+
+ return;
+err:
+ fprintf(stderr, "malformed diff info from kernel\n");
+}
+
+int sfeatures_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+ const struct nlattr *tb[ETHTOOL_A_FEATURES_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ const char *devname;
+ int ret;
+
+ if (ghdr->cmd != ETHTOOL_MSG_FEATURES_SET_REPLY) {
+ fprintf(stderr, "warning: unexpected reply message type %u\n",
+ ghdr->cmd);
+ return MNL_CB_OK;
+ }
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ devname = get_dev_name(tb[ETHTOOL_A_FEATURES_HEADER]);
+ if (strcmp(devname, nlctx->devname)) {
+ fprintf(stderr, "warning: unexpected message for device %s\n",
+ devname);
+ return MNL_CB_OK;
+ }
+
+ show_feature_changes(nlctx, tb);
+ return MNL_CB_OK;
+}
+
+int nl_sfeatures(struct cmd_context *ctx)
+{
+ const struct stringset *feature_names;
+ struct nl_context *nlctx = ctx->nlctx;
+ struct sfeatures_context *sfctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ unsigned int words;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_FEATURES_SET, false))
+ return -EOPNOTSUPP;
+
+ nlctx->cmd = "-K";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->cmd_private = &sfctx;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ feature_names = global_stringset(ETH_SS_FEATURES, nlctx->ethnl_socket);
+ words = (get_count(feature_names) + 31) / 32;
+ sfctx = malloc(sizeof(*sfctx) + words * sizeof(sfctx->req_mask[0]));
+ if (!sfctx)
+ return -ENOMEM;
+ memset(sfctx, '\0',
+ sizeof(*sfctx) + words * sizeof(sfctx->req_mask[0]));
+ nlctx->cmd_private = sfctx;
+
+ nlctx->devname = ctx->devname;
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_FEATURES_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0) {
+ free(sfctx);
+ return 2;
+ }
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_FEATURES_HEADER, ctx->devname,
+ ETHTOOL_FLAG_COMPACT_BITSETS)) {
+ free(sfctx);
+ return -EMSGSIZE;
+ }
+ ret = fill_sfeatures_bitmap(nlctx, feature_names);
+ if (ret < 0) {
+ free(sfctx);
+ return ret;
+ }
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0) {
+ free(sfctx);
+ return 92;
+ }
+ ret = nlsock_process_reply(nlsk, sfeatures_reply_cb, nlctx);
+ if (sfctx->nothing_changed) {
+ fprintf(stderr, "Could not change any device features\n");
+ free(sfctx);
+ return nlctx->exit_code ?: 1;
+ }
+ if (ret == 0) {
+ free(sfctx);
+ return 0;
+ }
+ free(sfctx);
+ return nlctx->exit_code ?: 92;
+}
diff --git a/netlink/fec.c b/netlink/fec.c
new file mode 100644
index 0000000..6027dc0
--- /dev/null
+++ b/netlink/fec.c
@@ -0,0 +1,360 @@
+/*
+ * fec.c - netlink implementation of FEC commands
+ *
+ * Implementation of "ethtool --show-fec <dev>" and
+ * "ethtool --set-fec <dev> ..."
+ */
+
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "bitset.h"
+#include "parser.h"
+
+/* FEC_GET */
+
+static void
+fec_mode_walk(unsigned int idx, const char *name, bool val, void *data)
+{
+ bool *empty = data;
+
+ if (!val)
+ return;
+ if (empty)
+ *empty = false;
+
+ /* Rename None to Off - in legacy ioctl None means "not supported"
+ * rather than supported but disabled.
+ */
+ if (idx == ETHTOOL_LINK_MODE_FEC_NONE_BIT)
+ name = "Off";
+ /* Rename to match the ioctl letter case */
+ else if (idx == ETHTOOL_LINK_MODE_FEC_BASER_BIT)
+ name = "BaseR";
+
+ print_string(PRINT_ANY, NULL, " %s", name);
+}
+
+static int fec_show_stats(const struct nlattr *nest)
+{
+ const struct nlattr *tb[ETHTOOL_A_FEC_STAT_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ static const struct {
+ unsigned int attr;
+ char *name;
+ } stats[] = {
+ { ETHTOOL_A_FEC_STAT_CORRECTED, "corrected_blocks" },
+ { ETHTOOL_A_FEC_STAT_UNCORR, "uncorrectable_blocks" },
+ { ETHTOOL_A_FEC_STAT_CORR_BITS, "corrected_bits" },
+ };
+ bool header = false;
+ unsigned int i;
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+
+ open_json_object("statistics");
+ for (i = 0; i < ARRAY_SIZE(stats); i++) {
+ uint64_t *vals;
+ int lanes, l;
+
+ if (!tb[stats[i].attr] ||
+ !mnl_attr_get_payload_len(tb[stats[i].attr]))
+ continue;
+
+ if (!header && !is_json_context()) {
+ printf("Statistics:\n");
+ header = true;
+ }
+
+ if (mnl_attr_get_payload_len(tb[stats[i].attr]) % 8) {
+ fprintf(stderr, "malformed netlink message (statistic)\n");
+ goto err_close_stats;
+ }
+
+ vals = mnl_attr_get_payload(tb[stats[i].attr]);
+ lanes = mnl_attr_get_payload_len(tb[stats[i].attr]) / 8 - 1;
+
+ if (!is_json_context()) {
+ fprintf(stdout, " %s: %" PRIu64 "\n",
+ stats[i].name, *vals++);
+ } else {
+ open_json_object(stats[i].name);
+ print_u64(PRINT_JSON, "total", NULL, *vals++);
+ }
+
+ if (lanes)
+ open_json_array("lanes", "");
+ for (l = 0; l < lanes; l++) {
+ if (!is_json_context())
+ fprintf(stdout, " Lane %d: %" PRIu64 "\n",
+ l, *vals++);
+ else
+ print_u64(PRINT_JSON, NULL, NULL, *vals++);
+ }
+ if (lanes)
+ close_json_array("");
+
+ close_json_object();
+ }
+ close_json_object();
+
+ return 0;
+
+err_close_stats:
+ close_json_object();
+ return -1;
+}
+
+int fec_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_FEC_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ const struct stringset *lm_strings;
+ const char *name;
+ bool fa, empty;
+ bool silent;
+ int err_ret;
+ u32 active;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_FEC_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return err_ret;
+ lm_strings = global_stringset(ETH_SS_LINK_MODES, nlctx->ethnl2_socket);
+
+ active = 0;
+ if (tb[ETHTOOL_A_FEC_ACTIVE])
+ active = mnl_attr_get_u32(tb[ETHTOOL_A_FEC_ACTIVE]);
+
+ if (silent)
+ print_nl();
+
+ open_json_object(NULL);
+
+ print_string(PRINT_ANY, "ifname", "FEC parameters for %s:\n",
+ nlctx->devname);
+
+ open_json_array("config", "Supported/Configured FEC encodings:");
+ fa = tb[ETHTOOL_A_FEC_AUTO] && mnl_attr_get_u8(tb[ETHTOOL_A_FEC_AUTO]);
+ if (fa)
+ print_string(PRINT_ANY, NULL, " %s", "Auto");
+ empty = !fa;
+
+ ret = walk_bitset(tb[ETHTOOL_A_FEC_MODES], lm_strings, fec_mode_walk,
+ &empty);
+ if (ret < 0)
+ goto err_close_dev;
+ if (empty)
+ print_string(PRINT_ANY, NULL, " %s", "None");
+ close_json_array("\n");
+
+ open_json_array("active", "Active FEC encoding:");
+ if (active) {
+ name = get_string(lm_strings, active);
+ if (name)
+ /* Take care of renames */
+ fec_mode_walk(active, name, true, NULL);
+ else
+ print_uint(PRINT_ANY, NULL, " BIT%u", active);
+ } else {
+ print_string(PRINT_ANY, NULL, " %s", "None");
+ }
+ close_json_array("\n");
+
+ if (tb[ETHTOOL_A_FEC_STATS]) {
+ ret = fec_show_stats(tb[ETHTOOL_A_FEC_STATS]);
+ if (ret < 0)
+ goto err_close_dev;
+ }
+
+ close_json_object();
+
+ return MNL_CB_OK;
+
+err_close_dev:
+ close_json_object();
+ return err_ret;
+}
+
+int nl_gfec(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ u32 flags;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_FEC_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ flags = get_stats_flag(nlctx, ETHTOOL_MSG_FEC_GET,
+ ETHTOOL_A_FEC_HEADER);
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_FEC_GET,
+ ETHTOOL_A_FEC_HEADER, flags);
+ if (ret < 0)
+ return ret;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, fec_reply_cb);
+ delete_json_obj();
+ return ret;
+}
+
+/* FEC_SET */
+
+static void strupc(char *dst, const char *src)
+{
+ while (*src)
+ *dst++ = toupper(*src++);
+ *dst = '\0';
+}
+
+static int fec_parse_bitset(struct nl_context *nlctx, uint16_t type,
+ const void *data __maybe_unused,
+ struct nl_msg_buff *msgbuff, void *dest)
+{
+ struct nlattr *bitset_attr;
+ struct nlattr *bits_attr;
+ struct nlattr *bit_attr;
+ char upper[ETH_GSTRING_LEN];
+ bool fec_auto = false;
+ int ret;
+
+ if (!type || dest) {
+ fprintf(stderr, "ethtool (%s): internal error parsing '%s'\n",
+ nlctx->cmd, nlctx->param);
+ return -EFAULT;
+ }
+
+ bitset_attr = ethnla_nest_start(msgbuff, type);
+ if (!bitset_attr)
+ return -EMSGSIZE;
+ ret = -EMSGSIZE;
+ if (ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, true))
+ goto err;
+ bits_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS);
+ if (!bits_attr)
+ goto err;
+
+ while (nlctx->argc > 0) {
+ const char *name = *nlctx->argp;
+
+ if (!strcmp(name, "--")) {
+ nlctx->argp++;
+ nlctx->argc--;
+ break;
+ }
+
+ if (!strcasecmp(name, "auto")) {
+ fec_auto = true;
+ goto next;
+ }
+ if (!strcasecmp(name, "off")) {
+ name = "None";
+ } else {
+ strupc(upper, name);
+ name = upper;
+ }
+
+ ret = -EMSGSIZE;
+ bit_attr = ethnla_nest_start(msgbuff,
+ ETHTOOL_A_BITSET_BITS_BIT);
+ if (!bit_attr)
+ goto err;
+ if (ethnla_put_strz(msgbuff, ETHTOOL_A_BITSET_BIT_NAME, name))
+ goto err;
+ ethnla_nest_end(msgbuff, bit_attr);
+
+next:
+ nlctx->argp++;
+ nlctx->argc--;
+ }
+
+ ethnla_nest_end(msgbuff, bits_attr);
+ ethnla_nest_end(msgbuff, bitset_attr);
+
+ if (ethnla_put_u8(msgbuff, ETHTOOL_A_FEC_AUTO, fec_auto))
+ goto err;
+
+ return 0;
+err:
+ ethnla_nest_cancel(msgbuff, bitset_attr);
+ return ret;
+}
+
+static const struct param_parser sfec_params[] = {
+ {
+ .arg = "encoding",
+ .type = ETHTOOL_A_FEC_MODES,
+ .handler = fec_parse_bitset,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_sfec(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_FEC_SET, false))
+ return -EOPNOTSUPP;
+ if (!ctx->argc) {
+ fprintf(stderr, "ethtool (--set-fec): parameters missing\n");
+ return 1;
+ }
+
+ nlctx->cmd = "--set-fec";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_FEC_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_FEC_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, sfec_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return 83;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret == 0)
+ return 0;
+ else
+ return nlctx->exit_code ?: 83;
+}
diff --git a/netlink/mm.c b/netlink/mm.c
new file mode 100644
index 0000000..d026bc3
--- /dev/null
+++ b/netlink/mm.c
@@ -0,0 +1,270 @@
+/*
+ * mm.c - netlink implementation of MAC merge layer settings
+ *
+ * Implementation of "ethtool --show-mm <dev>" and "ethtool --set-mm <dev> ..."
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "bitset.h"
+#include "parser.h"
+
+/* MM_GET */
+
+static const char *
+mm_verify_state_to_string(enum ethtool_mm_verify_status state)
+{
+ switch (state) {
+ case ETHTOOL_MM_VERIFY_STATUS_INITIAL:
+ return "INITIAL";
+ case ETHTOOL_MM_VERIFY_STATUS_VERIFYING:
+ return "VERIFYING";
+ case ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED:
+ return "SUCCEEDED";
+ case ETHTOOL_MM_VERIFY_STATUS_FAILED:
+ return "FAILED";
+ case ETHTOOL_MM_VERIFY_STATUS_DISABLED:
+ return "DISABLED";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static int show_mm_stats(const struct nlattr *nest)
+{
+ const struct nlattr *tb[ETHTOOL_A_MM_STAT_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ static const struct {
+ unsigned int attr;
+ char *name;
+ } stats[] = {
+ { ETHTOOL_A_MM_STAT_REASSEMBLY_ERRORS, "MACMergeFrameAssErrorCount" },
+ { ETHTOOL_A_MM_STAT_SMD_ERRORS, "MACMergeFrameSmdErrorCount" },
+ { ETHTOOL_A_MM_STAT_REASSEMBLY_OK, "MACMergeFrameAssOkCount" },
+ { ETHTOOL_A_MM_STAT_RX_FRAG_COUNT, "MACMergeFragCountRx" },
+ { ETHTOOL_A_MM_STAT_TX_FRAG_COUNT, "MACMergeFragCountTx" },
+ { ETHTOOL_A_MM_STAT_HOLD_COUNT, "MACMergeHoldCount" },
+ };
+ bool header = false;
+ unsigned int i;
+ size_t n;
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+
+ open_json_object("statistics");
+ for (i = 0; i < ARRAY_SIZE(stats); i++) {
+ char fmt[64];
+
+ if (!tb[stats[i].attr])
+ continue;
+
+ if (!header && !is_json_context()) {
+ printf("Statistics:\n");
+ header = true;
+ }
+
+ if (mnl_attr_validate(tb[stats[i].attr], MNL_TYPE_U64)) {
+ fprintf(stderr, "malformed netlink message (statistic)\n");
+ goto err_close_stats;
+ }
+
+ n = snprintf(fmt, sizeof(fmt), " %s: %%" PRIu64 "\n",
+ stats[i].name);
+ if (n >= sizeof(fmt)) {
+ fprintf(stderr, "internal error - malformed label\n");
+ continue;
+ }
+
+ print_u64(PRINT_ANY, stats[i].name, fmt,
+ mnl_attr_get_u64(tb[stats[i].attr]));
+ }
+ close_json_object();
+
+ return 0;
+
+err_close_stats:
+ close_json_object();
+ return -1;
+}
+
+int mm_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_MM_MAX + 1] = {};
+ struct nl_context *nlctx = data;
+ DECLARE_ATTR_TB_INFO(tb);
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_MM_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (silent)
+ print_nl();
+
+ open_json_object(NULL);
+
+ print_string(PRINT_ANY, "ifname", "MAC Merge layer state for %s:\n",
+ nlctx->devname);
+
+ show_bool("pmac-enabled", "pMAC enabled: %s\n",
+ tb[ETHTOOL_A_MM_PMAC_ENABLED]);
+ show_bool("tx-enabled", "TX enabled: %s\n",
+ tb[ETHTOOL_A_MM_TX_ENABLED]);
+ show_bool("tx-active", "TX active: %s\n", tb[ETHTOOL_A_MM_TX_ACTIVE]);
+ show_u32("tx-min-frag-size", "TX minimum fragment size: ",
+ tb[ETHTOOL_A_MM_TX_MIN_FRAG_SIZE]);
+ show_u32("rx-min-frag-size", "RX minimum fragment size: ",
+ tb[ETHTOOL_A_MM_RX_MIN_FRAG_SIZE]);
+ show_bool("verify-enabled", "Verify enabled: %s\n",
+ tb[ETHTOOL_A_MM_VERIFY_ENABLED]);
+ show_u32("verify-time", "Verify time: ",
+ tb[ETHTOOL_A_MM_VERIFY_TIME]);
+ show_u32("max-verify-time", "Max verify time: ",
+ tb[ETHTOOL_A_MM_MAX_VERIFY_TIME]);
+
+ if (tb[ETHTOOL_A_MM_VERIFY_STATUS]) {
+ u8 val = mnl_attr_get_u8(tb[ETHTOOL_A_MM_VERIFY_STATUS]);
+
+ print_string(PRINT_ANY, "verify-status", "Verification status: %s\n",
+ mm_verify_state_to_string(val));
+ }
+
+ if (tb[ETHTOOL_A_MM_STATS]) {
+ ret = show_mm_stats(tb[ETHTOOL_A_MM_STATS]);
+ if (ret) {
+ fprintf(stderr, "Failed to print stats: %d\n", ret);
+ goto err;
+ }
+ }
+
+ if (!silent)
+ print_nl();
+
+ close_json_object();
+
+ return MNL_CB_OK;
+
+err:
+ close_json_object();
+ return err_ret;
+}
+
+int nl_get_mm(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ u32 flags;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_MM_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ flags = get_stats_flag(nlctx, ETHTOOL_MSG_MM_GET, ETHTOOL_A_MM_HEADER);
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_MM_GET,
+ ETHTOOL_A_MM_HEADER, flags);
+ if (ret)
+ return ret;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, mm_reply_cb);
+ delete_json_obj();
+ return ret;
+}
+
+/* MM_SET */
+
+static const struct param_parser mm_set_params[] = {
+ {
+ .arg = "verify-enabled",
+ .type = ETHTOOL_A_MM_VERIFY_ENABLED,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "verify-time",
+ .type = ETHTOOL_A_MM_VERIFY_TIME,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-enabled",
+ .type = ETHTOOL_A_MM_TX_ENABLED,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "pmac-enabled",
+ .type = ETHTOOL_A_MM_PMAC_ENABLED,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-min-frag-size",
+ .type = ETHTOOL_A_MM_TX_MIN_FRAG_SIZE,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_set_mm(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_MM_SET, false))
+ return -EOPNOTSUPP;
+
+ nlctx->cmd = "--set-mm";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_MM_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret)
+ return ret;
+
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_MM_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, mm_set_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret)
+ return ret;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return ret;
+
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret)
+ return nlctx->exit_code;
+
+ return 0;
+}
diff --git a/netlink/module-eeprom.c b/netlink/module-eeprom.c
new file mode 100644
index 0000000..fe02c5a
--- /dev/null
+++ b/netlink/module-eeprom.c
@@ -0,0 +1,310 @@
+/*
+ * module-eeprom.c - netlink implementation of module eeprom get command
+ *
+ * ethtool -m <dev>
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#include "../sff-common.h"
+#include "../qsfp.h"
+#include "../cmis.h"
+#include "../internal.h"
+#include "../common.h"
+#include "../list.h"
+#include "netlink.h"
+#include "parser.h"
+
+#define ETH_I2C_ADDRESS_LOW 0x50
+#define ETH_I2C_MAX_ADDRESS 0x7F
+
+struct cmd_params {
+ u8 dump_hex;
+ u8 dump_raw;
+ u32 offset;
+ u32 length;
+ u32 page;
+ u32 bank;
+ u32 i2c_address;
+};
+
+static const struct param_parser getmodule_params[] = {
+ {
+ .arg = "hex",
+ .handler = nl_parse_u8bool,
+ .dest_offset = offsetof(struct cmd_params, dump_hex),
+ .min_argc = 1,
+ },
+ {
+ .arg = "raw",
+ .handler = nl_parse_u8bool,
+ .dest_offset = offsetof(struct cmd_params, dump_raw),
+ .min_argc = 1,
+ },
+ {
+ .arg = "offset",
+ .handler = nl_parse_direct_u32,
+ .dest_offset = offsetof(struct cmd_params, offset),
+ .min_argc = 1,
+ },
+ {
+ .arg = "length",
+ .handler = nl_parse_direct_u32,
+ .dest_offset = offsetof(struct cmd_params, length),
+ .min_argc = 1,
+ },
+ {
+ .arg = "page",
+ .handler = nl_parse_direct_u32,
+ .dest_offset = offsetof(struct cmd_params, page),
+ .min_argc = 1,
+ },
+ {
+ .arg = "bank",
+ .handler = nl_parse_direct_u32,
+ .dest_offset = offsetof(struct cmd_params, bank),
+ .min_argc = 1,
+ },
+ {
+ .arg = "i2c",
+ .handler = nl_parse_direct_u32,
+ .dest_offset = offsetof(struct cmd_params, i2c_address),
+ .min_argc = 1,
+ },
+ {}
+};
+
+static struct list_head eeprom_page_list = LIST_HEAD_INIT(eeprom_page_list);
+
+struct eeprom_page_entry {
+ struct list_head list; /* Member of eeprom_page_list */
+ void *data;
+};
+
+static int eeprom_page_list_add(void *data)
+{
+ struct eeprom_page_entry *entry;
+
+ entry = malloc(sizeof(*entry));
+ if (!entry)
+ return -ENOMEM;
+
+ entry->data = data;
+ list_add(&entry->list, &eeprom_page_list);
+
+ return 0;
+}
+
+static void eeprom_page_list_flush(void)
+{
+ struct eeprom_page_entry *entry;
+ struct list_head *head, *next;
+
+ list_for_each_safe(head, next, &eeprom_page_list) {
+ entry = (struct eeprom_page_entry *) head;
+ free(entry->data);
+ list_del(head);
+ free(entry);
+ }
+}
+
+static int get_eeprom_page_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_MODULE_EEPROM_DATA + 1] = {};
+ struct ethtool_module_eeprom *request = data;
+ DECLARE_ATTR_TB_INFO(tb);
+ u8 *eeprom_data;
+ int ret;
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+
+ if (!tb[ETHTOOL_A_MODULE_EEPROM_DATA])
+ return MNL_CB_ERROR;
+
+ eeprom_data = mnl_attr_get_payload(tb[ETHTOOL_A_MODULE_EEPROM_DATA]);
+ request->data = malloc(request->length);
+ if (!request->data)
+ return MNL_CB_ERROR;
+ memcpy(request->data, eeprom_data, request->length);
+
+ ret = eeprom_page_list_add(request->data);
+ if (ret < 0)
+ goto err_list_add;
+
+ return MNL_CB_OK;
+
+err_list_add:
+ free(request->data);
+ return MNL_CB_ERROR;
+}
+
+int nl_get_eeprom_page(struct cmd_context *ctx,
+ struct ethtool_module_eeprom *request)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsock;
+ struct nl_msg_buff *msg;
+ int ret;
+
+ if (!request || request->i2c_address > ETH_I2C_MAX_ADDRESS)
+ return -EINVAL;
+
+ nlsock = nlctx->ethnl_socket;
+ msg = &nlsock->msgbuff;
+
+ ret = nlsock_prep_get_request(nlsock, ETHTOOL_MSG_MODULE_EEPROM_GET,
+ ETHTOOL_A_MODULE_EEPROM_HEADER, 0);
+ if (ret < 0)
+ return ret;
+
+ if (ethnla_put_u32(msg, ETHTOOL_A_MODULE_EEPROM_LENGTH,
+ request->length) ||
+ ethnla_put_u32(msg, ETHTOOL_A_MODULE_EEPROM_OFFSET,
+ request->offset) ||
+ ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_PAGE,
+ request->page) ||
+ ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_BANK,
+ request->bank) ||
+ ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS,
+ request->i2c_address))
+ return -EMSGSIZE;
+
+ ret = nlsock_sendmsg(nlsock, NULL);
+ if (ret < 0)
+ return ret;
+ return nlsock_process_reply(nlsock, get_eeprom_page_reply_cb,
+ (void *)request);
+}
+
+static int eeprom_dump_hex(struct cmd_context *ctx)
+{
+ struct ethtool_module_eeprom request = {
+ .length = 128,
+ .i2c_address = ETH_I2C_ADDRESS_LOW,
+ };
+ int ret;
+
+ ret = nl_get_eeprom_page(ctx, &request);
+ if (ret < 0)
+ return ret;
+
+ dump_hex(stdout, request.data, request.length, request.offset);
+
+ return 0;
+}
+
+static int eeprom_parse(struct cmd_context *ctx)
+{
+ struct ethtool_module_eeprom request = {
+ .length = 1,
+ .i2c_address = ETH_I2C_ADDRESS_LOW,
+ };
+ int ret;
+
+ /* Fetch the SFF-8024 Identifier Value. For all supported standards, it
+ * is located at I2C address 0x50, byte 0. See section 4.1 in SFF-8024,
+ * revision 4.9.
+ */
+ ret = nl_get_eeprom_page(ctx, &request);
+ if (ret < 0)
+ return ret;
+
+ switch (request.data[0]) {
+#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
+ case SFF8024_ID_GBIC:
+ case SFF8024_ID_SOLDERED_MODULE:
+ case SFF8024_ID_SFP:
+ return sff8079_show_all_nl(ctx);
+ case SFF8024_ID_QSFP:
+ case SFF8024_ID_QSFP28:
+ case SFF8024_ID_QSFP_PLUS:
+ return sff8636_show_all_nl(ctx);
+ case SFF8024_ID_QSFP_DD:
+ case SFF8024_ID_OSFP:
+ case SFF8024_ID_DSFP:
+ case SFF8024_ID_QSFP_PLUS_CMIS:
+ case SFF8024_ID_SFP_DD_CMIS:
+ case SFF8024_ID_SFP_PLUS_CMIS:
+ return cmis_show_all_nl(ctx);
+#endif
+ default:
+ /* If we cannot recognize the memory map, default to dumping
+ * the first 128 bytes in hex.
+ */
+ return eeprom_dump_hex(ctx);
+ }
+}
+
+int nl_getmodule(struct cmd_context *ctx)
+{
+ struct cmd_params getmodule_cmd_params = {};
+ struct ethtool_module_eeprom request = {0};
+ struct nl_context *nlctx = ctx->nlctx;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_MODULE_EEPROM_GET, false))
+ return -EOPNOTSUPP;
+
+ nlctx->cmd = "-m";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ ret = nl_parser(nlctx, getmodule_params, &getmodule_cmd_params, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return ret;
+
+ if (getmodule_cmd_params.dump_hex && getmodule_cmd_params.dump_raw) {
+ fprintf(stderr, "Hex and raw dump cannot be specified together\n");
+ return -EINVAL;
+ }
+
+ /* When complete hex/raw dump of the EEPROM is requested, fallback to
+ * ioctl. Netlink can only request specific pages.
+ */
+ if ((getmodule_cmd_params.dump_hex || getmodule_cmd_params.dump_raw) &&
+ !getmodule_cmd_params.page && !getmodule_cmd_params.bank &&
+ !getmodule_cmd_params.i2c_address) {
+ nlctx->ioctl_fallback = true;
+ return -EOPNOTSUPP;
+ }
+
+#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
+ if (getmodule_cmd_params.page || getmodule_cmd_params.bank ||
+ getmodule_cmd_params.offset || getmodule_cmd_params.length)
+#endif
+ getmodule_cmd_params.dump_hex = true;
+
+ request.offset = getmodule_cmd_params.offset;
+ request.length = getmodule_cmd_params.length ?: 128;
+ request.page = getmodule_cmd_params.page;
+ request.bank = getmodule_cmd_params.bank;
+ request.i2c_address = getmodule_cmd_params.i2c_address ?: ETH_I2C_ADDRESS_LOW;
+
+ if (request.page && !request.offset)
+ request.offset = 128;
+
+ if (getmodule_cmd_params.dump_hex || getmodule_cmd_params.dump_raw) {
+ ret = nl_get_eeprom_page(ctx, &request);
+ if (ret < 0)
+ goto cleanup;
+
+ if (getmodule_cmd_params.dump_raw)
+ fwrite(request.data, 1, request.length, stdout);
+ else
+ dump_hex(stdout, request.data, request.length,
+ request.offset);
+ } else {
+ ret = eeprom_parse(ctx);
+ if (ret < 0)
+ goto cleanup;
+ }
+
+cleanup:
+ eeprom_page_list_flush();
+ return ret;
+}
diff --git a/netlink/module.c b/netlink/module.c
new file mode 100644
index 0000000..54aa6d0
--- /dev/null
+++ b/netlink/module.c
@@ -0,0 +1,179 @@
+/*
+ * module.c - netlink implementation of module commands
+ *
+ * Implementation of "ethtool --show-module <dev>" and
+ * "ethtool --set-module <dev> ..."
+ */
+
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+/* MODULE_GET */
+
+static const char *module_power_mode_policy_name(u8 val)
+{
+ switch (val) {
+ case ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH:
+ return "high";
+ case ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO:
+ return "auto";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *module_power_mode_name(u8 val)
+{
+ switch (val) {
+ case ETHTOOL_MODULE_POWER_MODE_LOW:
+ return "low";
+ case ETHTOOL_MODULE_POWER_MODE_HIGH:
+ return "high";
+ default:
+ return "unknown";
+ }
+}
+
+int module_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_MODULE_MAX + 1] = {};
+ struct nl_context *nlctx = data;
+ DECLARE_ATTR_TB_INFO(tb);
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_MODULE_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (silent)
+ print_nl();
+
+ open_json_object(NULL);
+
+ print_string(PRINT_ANY, "ifname", "Module parameters for %s:\n",
+ nlctx->devname);
+
+ if (tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY]) {
+ u8 val;
+
+ val = mnl_attr_get_u8(tb[ETHTOOL_A_MODULE_POWER_MODE_POLICY]);
+ print_string(PRINT_ANY, "power-mode-policy",
+ "power-mode-policy: %s\n",
+ module_power_mode_policy_name(val));
+ }
+
+ if (tb[ETHTOOL_A_MODULE_POWER_MODE]) {
+ u8 val;
+
+ val = mnl_attr_get_u8(tb[ETHTOOL_A_MODULE_POWER_MODE]);
+ print_string(PRINT_ANY, "power-mode",
+ "power-mode: %s\n", module_power_mode_name(val));
+ }
+
+ close_json_object();
+
+ return MNL_CB_OK;
+}
+
+int nl_gmodule(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_MODULE_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ nlsk = nlctx->ethnl_socket;
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_MODULE_GET,
+ ETHTOOL_A_MODULE_HEADER, 0);
+ if (ret < 0)
+ return ret;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, module_reply_cb);
+ delete_json_obj();
+ return ret;
+}
+
+/* MODULE_SET */
+
+static const struct lookup_entry_u8 power_mode_policy_values[] = {
+ { .arg = "high", .val = ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH },
+ { .arg = "auto", .val = ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO },
+ {}
+};
+
+static const struct param_parser smodule_params[] = {
+ {
+ .arg = "power-mode-policy",
+ .type = ETHTOOL_A_MODULE_POWER_MODE_POLICY,
+ .handler = nl_parse_lookup_u8,
+ .handler_data = power_mode_policy_values,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_smodule(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_MODULE_SET, false))
+ return -EOPNOTSUPP;
+ if (!ctx->argc) {
+ fprintf(stderr, "ethtool (--set-module): parameters missing\n");
+ return 1;
+ }
+
+ nlctx->cmd = "--set-module";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_MODULE_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_MODULE_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, smodule_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return 83;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret == 0)
+ return 0;
+ else
+ return nlctx->exit_code ?: 83;
+}
diff --git a/netlink/monitor.c b/netlink/monitor.c
new file mode 100644
index 0000000..ace9b25
--- /dev/null
+++ b/netlink/monitor.c
@@ -0,0 +1,324 @@
+/*
+ * monitor.c - netlink notification monitor
+ *
+ * Implementation of "ethtool --monitor" for watching netlink notifications.
+ */
+
+#include <errno.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "nlsock.h"
+#include "strset.h"
+
+static struct {
+ uint8_t cmd;
+ mnl_cb_t cb;
+} monitor_callbacks[] = {
+ {
+ .cmd = ETHTOOL_MSG_LINKMODES_NTF,
+ .cb = linkmodes_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_LINKINFO_NTF,
+ .cb = linkinfo_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_WOL_NTF,
+ .cb = wol_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_DEBUG_NTF,
+ .cb = debug_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_FEATURES_NTF,
+ .cb = features_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_PRIVFLAGS_NTF,
+ .cb = privflags_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_RINGS_NTF,
+ .cb = rings_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_CHANNELS_NTF,
+ .cb = channels_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_COALESCE_NTF,
+ .cb = coalesce_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_PAUSE_NTF,
+ .cb = pause_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_EEE_NTF,
+ .cb = eee_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_CABLE_TEST_NTF,
+ .cb = cable_test_ntf_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_CABLE_TEST_TDR_NTF,
+ .cb = cable_test_tdr_ntf_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_FEC_NTF,
+ .cb = fec_reply_cb,
+ },
+ {
+ .cmd = ETHTOOL_MSG_MODULE_NTF,
+ .cb = module_reply_cb,
+ },
+};
+
+static void clear_filter(struct nl_context *nlctx)
+{
+ unsigned int i;
+
+ for (i = 0; i < CMDMASK_WORDS; i++)
+ nlctx->filter_cmds[i] = 0;
+}
+
+static bool test_filter_cmd(const struct nl_context *nlctx, unsigned int cmd)
+{
+ return nlctx->filter_cmds[cmd / 32] & (1U << (cmd % 32));
+}
+
+static void set_filter_cmd(struct nl_context *nlctx, unsigned int cmd)
+{
+ nlctx->filter_cmds[cmd / 32] |= (1U << (cmd % 32));
+}
+
+static void set_filter_all(struct nl_context *nlctx)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(monitor_callbacks); i++)
+ set_filter_cmd(nlctx, monitor_callbacks[i].cmd);
+}
+
+static int monitor_any_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+ struct nl_context *nlctx = data;
+ unsigned int i;
+
+ if (!test_filter_cmd(nlctx, ghdr->cmd))
+ return MNL_CB_OK;
+
+ for (i = 0; i < MNL_ARRAY_SIZE(monitor_callbacks); i++)
+ if (monitor_callbacks[i].cmd == ghdr->cmd)
+ return monitor_callbacks[i].cb(nlhdr, data);
+
+ return MNL_CB_OK;
+}
+
+struct monitor_option {
+ const char *pattern;
+ uint8_t cmd;
+ uint32_t info_mask;
+};
+
+static struct monitor_option monitor_opts[] = {
+ {
+ .pattern = "|--all",
+ .cmd = 0,
+ },
+ {
+ .pattern = "-s|--change",
+ .cmd = ETHTOOL_MSG_LINKINFO_NTF,
+ },
+ {
+ .pattern = "-s|--change",
+ .cmd = ETHTOOL_MSG_LINKMODES_NTF,
+ },
+ {
+ .pattern = "-s|--change",
+ .cmd = ETHTOOL_MSG_WOL_NTF,
+ },
+ {
+ .pattern = "-s|--change",
+ .cmd = ETHTOOL_MSG_DEBUG_NTF,
+ },
+ {
+ .pattern = "-k|--show-features|--show-offload|-K|--features|--offload",
+ .cmd = ETHTOOL_MSG_FEATURES_NTF,
+ },
+ {
+ .pattern = "--show-priv-flags|--set-priv-flags",
+ .cmd = ETHTOOL_MSG_PRIVFLAGS_NTF,
+ },
+ {
+ .pattern = "-g|--show-ring|-G|--set-ring",
+ .cmd = ETHTOOL_MSG_RINGS_NTF,
+ },
+ {
+ .pattern = "-l|--show-channels|-L|--set-channels",
+ .cmd = ETHTOOL_MSG_CHANNELS_NTF,
+ },
+ {
+ .pattern = "-c|--show-coalesce|-C|--coalesce",
+ .cmd = ETHTOOL_MSG_COALESCE_NTF,
+ },
+ {
+ .pattern = "-a|--show-pause|-A|--pause",
+ .cmd = ETHTOOL_MSG_PAUSE_NTF,
+ },
+ {
+ .pattern = "--show-eee|--set-eee",
+ .cmd = ETHTOOL_MSG_EEE_NTF,
+ },
+ {
+ .pattern = "--cable-test",
+ .cmd = ETHTOOL_MSG_CABLE_TEST_NTF,
+ },
+ {
+ .pattern = "--cable-test-tdr",
+ .cmd = ETHTOOL_MSG_CABLE_TEST_TDR_NTF,
+ },
+ {
+ .pattern = "--show-module|--set-module",
+ .cmd = ETHTOOL_MSG_MODULE_NTF,
+ },
+};
+
+static bool pattern_match(const char *s, const char *pattern)
+{
+ const char *opt = pattern;
+ const char *next;
+ int slen = strlen(s);
+ int optlen;
+
+ do {
+ next = opt;
+ while (*next && *next != '|')
+ next++;
+ optlen = next - opt;
+ if (slen == optlen && !strncmp(s, opt, optlen))
+ return true;
+
+ opt = next;
+ if (*opt == '|')
+ opt++;
+ } while (*opt);
+
+ return false;
+}
+
+static int parse_monitor(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ char **argp = ctx->argp;
+ int argc = ctx->argc;
+ const char *opt = "";
+ bool opt_found;
+ unsigned int i;
+
+ if (*argp && argp[0][0] == '-') {
+ opt = *argp;
+ argp++;
+ argc--;
+ }
+ opt_found = false;
+ clear_filter(nlctx);
+ for (i = 0; i < MNL_ARRAY_SIZE(monitor_opts); i++) {
+ if (pattern_match(opt, monitor_opts[i].pattern)) {
+ unsigned int cmd = monitor_opts[i].cmd;
+
+ if (!cmd)
+ set_filter_all(nlctx);
+ else
+ set_filter_cmd(nlctx, cmd);
+ opt_found = true;
+ }
+ }
+ if (!opt_found) {
+ fprintf(stderr, "monitoring for option '%s' not supported\n",
+ *argp);
+ return -1;
+ }
+
+ if (*argp && strcmp(*argp, WILDCARD_DEVNAME))
+ ctx->devname = *argp;
+ return 0;
+}
+
+int nl_monitor(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx;
+ struct nl_socket *nlsk;
+ uint32_t grpid;
+ bool is_dev;
+ int ret;
+
+ ret = netlink_init(ctx);
+ if (ret < 0) {
+ fprintf(stderr, "Netlink interface initialization failed, option --monitor not supported.\n");
+ return ret;
+ }
+ nlctx = ctx->nlctx;
+ nlsk = nlctx->ethnl_socket;
+ grpid = nlctx->ethnl_mongrp;
+ if (!grpid) {
+ fprintf(stderr, "multicast group 'monitor' not found\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (parse_monitor(ctx) < 0)
+ return 1;
+ is_dev = ctx->devname && strcmp(ctx->devname, WILDCARD_DEVNAME);
+
+ ret = preload_global_strings(nlsk);
+ if (ret < 0)
+ return ret;
+ ret = mnl_socket_setsockopt(nlsk->sk, NETLINK_ADD_MEMBERSHIP,
+ &grpid, sizeof(grpid));
+ if (ret < 0)
+ return ret;
+ if (is_dev) {
+ ret = preload_perdev_strings(nlsk, ctx->devname);
+ if (ret < 0)
+ goto out_strings;
+ }
+
+ nlctx->filter_devname = ctx->devname;
+ nlctx->is_monitor = true;
+ nlsk->port = 0;
+ nlsk->seq = 0;
+
+ fputs("listening...\n", stdout);
+ fflush(stdout);
+ ret = nlsock_process_reply(nlsk, monitor_any_cb, nlctx);
+
+out_strings:
+ cleanup_all_strings();
+ return ret;
+}
+
+void nl_monitor_usage(void)
+{
+ unsigned int i;
+ const char *p;
+
+ fputs(" ethtool --monitor Show kernel notifications\n",
+ stdout);
+ fputs(" ( [ --all ]", stdout);
+ for (i = 1; i < MNL_ARRAY_SIZE(monitor_opts); i++) {
+ if (!strcmp(monitor_opts[i].pattern, monitor_opts[i - 1].pattern))
+ continue;
+ fputs("\n | ", stdout);
+ for (p = monitor_opts[i].pattern; *p; p++)
+ if (*p == '|')
+ fputs(" | ", stdout);
+ else
+ fputc(*p, stdout);
+ }
+ fputs(" )\n", stdout);
+ fputs(" [ DEVNAME | * ]\n", stdout);
+}
diff --git a/netlink/msgbuff.c b/netlink/msgbuff.c
new file mode 100644
index 0000000..216f5b9
--- /dev/null
+++ b/netlink/msgbuff.c
@@ -0,0 +1,256 @@
+/*
+ * msgbuff.c - netlink message buffer
+ *
+ * Data structures and code for flexible message buffer abstraction.
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "msgbuff.h"
+
+#define MAX_MSG_SIZE (4 << 20) /* 4 MB */
+
+/**
+ * msgbuff_realloc() - reallocate buffer if needed
+ * @msgbuff: message buffer
+ * @new_size: requested minimum size (add MNL_SOCKET_BUFFER_SIZE if zero)
+ *
+ * Make sure allocated buffer has size at least @new_size. If @new_size is
+ * shorter than current size, do nothing. If @new_size is 0, grow buffer by
+ * MNL_SOCKET_BUFFER_SIZE. Fail if new size would exceed MAX_MSG_SIZE.
+ *
+ * Return: 0 on success or negative error code
+ */
+int msgbuff_realloc(struct nl_msg_buff *msgbuff, unsigned int new_size)
+{
+ unsigned int nlhdr_off, genlhdr_off, payload_off;
+ unsigned int old_size = msgbuff->size;
+ char *nbuff;
+
+ nlhdr_off = (char *)msgbuff->nlhdr - msgbuff->buff;
+ genlhdr_off = (char *)msgbuff->genlhdr - msgbuff->buff;
+ payload_off = (char *)msgbuff->payload - msgbuff->buff;
+
+ if (!new_size)
+ new_size = old_size + MNL_SOCKET_BUFFER_SIZE;
+ if (new_size <= old_size)
+ return 0;
+ if (new_size > MAX_MSG_SIZE)
+ return -EMSGSIZE;
+ nbuff = realloc(msgbuff->buff, new_size);
+ if (!nbuff) {
+ msgbuff->buff = NULL;
+ msgbuff->size = 0;
+ msgbuff->left = 0;
+ return -ENOMEM;
+ }
+ if (nbuff != msgbuff->buff) {
+ if (new_size > old_size)
+ memset(nbuff + old_size, '\0', new_size - old_size);
+ msgbuff->nlhdr = (struct nlmsghdr *)(nbuff + nlhdr_off);
+ msgbuff->genlhdr = (struct genlmsghdr *)(nbuff + genlhdr_off);
+ msgbuff->payload = nbuff + payload_off;
+ msgbuff->buff = nbuff;
+ }
+ msgbuff->size = new_size;
+ msgbuff->left += (new_size - old_size);
+
+ return 0;
+}
+
+/**
+ * msgbuff_append() - add contents of another message buffer
+ * @dest: target message buffer
+ * @src: source message buffer
+ *
+ * Append contents of @src at the end of @dest. Fail if target buffer cannot
+ * be reallocated to sufficient size.
+ *
+ * Return: 0 on success or negative error code.
+ */
+int msgbuff_append(struct nl_msg_buff *dest, struct nl_msg_buff *src)
+{
+ unsigned int src_len = mnl_nlmsg_get_payload_len(src->nlhdr);
+ unsigned int dest_len = MNL_ALIGN(msgbuff_len(dest));
+ int ret;
+
+ src_len -= GENL_HDRLEN;
+ ret = msgbuff_realloc(dest, dest_len + src_len);
+ if (ret < 0)
+ return ret;
+ memcpy(mnl_nlmsg_get_payload_tail(dest->nlhdr), src->payload, src_len);
+ msgbuff_reset(dest, dest_len + src_len);
+
+ return 0;
+}
+
+/**
+ * ethnla_put - write a netlink attribute to message buffer
+ * @msgbuff: message buffer
+ * @type: attribute type
+ * @len: attribute payload length
+ * @data: attribute payload
+ *
+ * Appends a netlink attribute with header to message buffer, reallocates
+ * if needed. This is mostly used via specific ethnla_put_* wrappers for
+ * basic data types.
+ *
+ * Return: false on success, true on error (reallocation failed)
+ */
+bool ethnla_put(struct nl_msg_buff *msgbuff, uint16_t type, size_t len,
+ const void *data)
+{
+ struct nlmsghdr *nlhdr = msgbuff->nlhdr;
+
+ while (!mnl_attr_put_check(nlhdr, msgbuff->left, type, len, data)) {
+ int ret = msgbuff_realloc(msgbuff, 0);
+
+ if (ret < 0)
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * ethnla_nest_start - start a nested attribute
+ * @msgbuff: message buffer
+ * @type: nested attribute type (NLA_F_NESTED is added automatically)
+ *
+ * Return: pointer to the nest attribute or null of error
+ */
+struct nlattr *ethnla_nest_start(struct nl_msg_buff *msgbuff, uint16_t type)
+{
+ struct nlmsghdr *nlhdr = msgbuff->nlhdr;
+ struct nlattr *attr;
+
+ do {
+ attr = mnl_attr_nest_start_check(nlhdr, msgbuff->left, type);
+ if (attr)
+ return attr;
+ } while (msgbuff_realloc(msgbuff, 0) == 0);
+
+ return NULL;
+}
+
+/**
+ * ethnla_fill_header() - write standard ethtool request header to message
+ * @msgbuff: message buffer
+ * @type: attribute type for header nest
+ * @devname: device name (NULL to omit)
+ * @flags: request flags (omitted if 0)
+ *
+ * Return: pointer to the nest attribute or null of error
+ */
+bool ethnla_fill_header(struct nl_msg_buff *msgbuff, uint16_t type,
+ const char *devname, uint32_t flags)
+{
+ struct nlattr *nest;
+
+ nest = ethnla_nest_start(msgbuff, type);
+ if (!nest)
+ return true;
+
+ if ((devname &&
+ ethnla_put_strz(msgbuff, ETHTOOL_A_HEADER_DEV_NAME, devname)) ||
+ (flags &&
+ ethnla_put_u32(msgbuff, ETHTOOL_A_HEADER_FLAGS, flags)))
+ goto err;
+
+ ethnla_nest_end(msgbuff, nest);
+ return false;
+
+err:
+ ethnla_nest_cancel(msgbuff, nest);
+ return true;
+}
+
+/**
+ * __msg_init() - init a genetlink message, fill netlink and genetlink header
+ * @msgbuff: message buffer
+ * @family: genetlink family
+ * @cmd: genetlink command (genlmsghdr::cmd)
+ * @flags: netlink flags (nlmsghdr::nlmsg_flags)
+ * @version: genetlink family version (genlmsghdr::version)
+ *
+ * Initialize a new genetlink message, fill netlink and genetlink header and
+ * set pointers in struct nl_msg_buff.
+ *
+ * Return: 0 on success or negative error code.
+ */
+int __msg_init(struct nl_msg_buff *msgbuff, int family, int cmd,
+ unsigned int flags, int version)
+{
+ struct nlmsghdr *nlhdr;
+ struct genlmsghdr *genlhdr;
+ int ret;
+
+ ret = msgbuff_realloc(msgbuff, MNL_SOCKET_BUFFER_SIZE);
+ if (ret < 0)
+ return ret;
+ memset(msgbuff->buff, '\0', NLMSG_HDRLEN + GENL_HDRLEN);
+
+ nlhdr = mnl_nlmsg_put_header(msgbuff->buff);
+ nlhdr->nlmsg_type = family;
+ nlhdr->nlmsg_flags = flags;
+ msgbuff->nlhdr = nlhdr;
+
+ genlhdr = mnl_nlmsg_put_extra_header(nlhdr, sizeof(*genlhdr));
+ genlhdr->cmd = cmd;
+ genlhdr->version = version;
+ msgbuff->genlhdr = genlhdr;
+
+ msgbuff->payload = mnl_nlmsg_get_payload_offset(nlhdr, GENL_HDRLEN);
+
+ return 0;
+}
+
+/**
+ * msg_init() - init an ethtool netlink message
+ * @msgbuff: message buffer
+ * @cmd: genetlink command (genlmsghdr::cmd)
+ * @flags: netlink flags (nlmsghdr::nlmsg_flags)
+ *
+ * Initialize a new ethtool netlink message, fill netlink and genetlink header
+ * and set pointers in struct nl_msg_buff.
+ *
+ * Return: 0 on success or negative error code.
+ */
+int msg_init(struct nl_context *nlctx, struct nl_msg_buff *msgbuff, int cmd,
+ unsigned int flags)
+{
+ return __msg_init(msgbuff, nlctx->ethnl_fam, cmd, flags,
+ ETHTOOL_GENL_VERSION);
+}
+
+/**
+ * msgbuff_init() - initialize a message buffer
+ * @msgbuff: message buffer
+ *
+ * Initialize a message buffer structure before first use. Buffer length is
+ * set to zero and the buffer is not allocated until the first call to
+ * msgbuff_reallocate().
+ */
+void msgbuff_init(struct nl_msg_buff *msgbuff)
+{
+ memset(msgbuff, '\0', sizeof(*msgbuff));
+}
+
+/**
+ * msg_done() - destroy a message buffer
+ * @msgbuff: message buffer
+ *
+ * Free the buffer and reset size and remaining size.
+ */
+void msgbuff_done(struct nl_msg_buff *msgbuff)
+{
+ free(msgbuff->buff);
+ msgbuff->buff = NULL;
+ msgbuff->size = 0;
+ msgbuff->left = 0;
+}
diff --git a/netlink/msgbuff.h b/netlink/msgbuff.h
new file mode 100644
index 0000000..7d6731f
--- /dev/null
+++ b/netlink/msgbuff.h
@@ -0,0 +1,123 @@
+/*
+ * msgbuff.h - netlink message buffer
+ *
+ * Declarations of netlink message buffer and related functions.
+ */
+
+#ifndef ETHTOOL_NETLINK_MSGBUFF_H__
+#define ETHTOOL_NETLINK_MSGBUFF_H__
+
+#include <string.h>
+#include <libmnl/libmnl.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+
+struct nl_context;
+
+/**
+ * struct nl_msg_buff - message buffer abstraction
+ * @buff: pointer to buffer
+ * @size: total size of allocated buffer
+ * @left: remaining length current message end to end of buffer
+ * @nlhdr: pointer to netlink header of current message
+ * @genlhdr: pointer to genetlink header of current message
+ * @payload: pointer to message payload (after genetlink header)
+ */
+struct nl_msg_buff {
+ char *buff;
+ unsigned int size;
+ unsigned int left;
+ struct nlmsghdr *nlhdr;
+ struct genlmsghdr *genlhdr;
+ void *payload;
+};
+
+void msgbuff_init(struct nl_msg_buff *msgbuff);
+void msgbuff_done(struct nl_msg_buff *msgbuff);
+int msgbuff_realloc(struct nl_msg_buff *msgbuff, unsigned int new_size);
+int msgbuff_append(struct nl_msg_buff *dest, struct nl_msg_buff *src);
+
+int __msg_init(struct nl_msg_buff *msgbuff, int family, int cmd,
+ unsigned int flags, int version);
+int msg_init(struct nl_context *nlctx, struct nl_msg_buff *msgbuff, int cmd,
+ unsigned int flags);
+
+bool ethnla_put(struct nl_msg_buff *msgbuff, uint16_t type, size_t len,
+ const void *data);
+struct nlattr *ethnla_nest_start(struct nl_msg_buff *msgbuff, uint16_t type);
+bool ethnla_fill_header(struct nl_msg_buff *msgbuff, uint16_t type,
+ const char *devname, uint32_t flags);
+
+/* length of current message */
+static inline unsigned int msgbuff_len(const struct nl_msg_buff *msgbuff)
+{
+ return msgbuff->nlhdr->nlmsg_len;
+}
+
+/* reset message length to position returned by msgbuff_len() */
+static inline void msgbuff_reset(const struct nl_msg_buff *msgbuff,
+ unsigned int len)
+{
+ msgbuff->nlhdr->nlmsg_len = len;
+}
+
+/* put data wrappers */
+
+static inline void ethnla_nest_end(struct nl_msg_buff *msgbuff,
+ struct nlattr *nest)
+{
+ mnl_attr_nest_end(msgbuff->nlhdr, nest);
+}
+
+static inline void ethnla_nest_cancel(struct nl_msg_buff *msgbuff,
+ struct nlattr *nest)
+{
+ mnl_attr_nest_cancel(msgbuff->nlhdr, nest);
+}
+
+static inline bool ethnla_put_u32(struct nl_msg_buff *msgbuff, uint16_t type,
+ uint32_t data)
+{
+ return ethnla_put(msgbuff, type, sizeof(uint32_t), &data);
+}
+
+static inline bool ethnla_put_u16(struct nl_msg_buff *msgbuff, uint16_t type,
+ uint16_t data)
+{
+ return ethnla_put(msgbuff, type, sizeof(uint16_t), &data);
+}
+
+static inline bool ethnla_put_u8(struct nl_msg_buff *msgbuff, uint16_t type,
+ uint8_t data)
+{
+ return ethnla_put(msgbuff, type, sizeof(uint8_t), &data);
+}
+
+static inline bool ethnla_put_flag(struct nl_msg_buff *msgbuff, uint16_t type,
+ bool val)
+{
+ if (val)
+ return ethnla_put(msgbuff, type, 0, &val);
+ else
+ return false;
+}
+
+static inline bool ethnla_put_bitfield32(struct nl_msg_buff *msgbuff,
+ uint16_t type, uint32_t value,
+ uint32_t selector)
+{
+ struct nla_bitfield32 val = {
+ .value = value,
+ .selector = selector,
+ };
+
+ return ethnla_put(msgbuff, type, sizeof(val), &val);
+}
+
+static inline bool ethnla_put_strz(struct nl_msg_buff *msgbuff, uint16_t type,
+ const char *data)
+{
+ return ethnla_put(msgbuff, type, strlen(data) + 1, data);
+}
+
+#endif /* ETHTOOL_NETLINK_MSGBUFF_H__ */
diff --git a/netlink/netlink.c b/netlink/netlink.c
new file mode 100644
index 0000000..ef0d825
--- /dev/null
+++ b/netlink/netlink.c
@@ -0,0 +1,527 @@
+/*
+ * netlink.c - basic infrastructure for netlink code
+ *
+ * Heart of the netlink interface implementation.
+ */
+
+#include <errno.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "extapi.h"
+#include "msgbuff.h"
+#include "nlsock.h"
+#include "strset.h"
+
+/* Used as reply callback for requests where no reply is expected (e.g. most
+ * "set" type commands)
+ */
+int nomsg_reply_cb(const struct nlmsghdr *nlhdr, void *data __maybe_unused)
+{
+ const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+
+ fprintf(stderr, "received unexpected message: len=%u type=%u cmd=%u\n",
+ nlhdr->nlmsg_len, nlhdr->nlmsg_type, ghdr->cmd);
+ return MNL_CB_OK;
+}
+
+/* standard attribute parser callback; it fills provided array with pointers
+ * to attributes like kernel nla_parse(). We must expect to run on top of
+ * a newer kernel which may send attributes that we do not know (yet). Rather
+ * than treating them as an error, just ignore them.
+ */
+int attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct attr_tb_info *tb_info = data;
+ uint16_t type = mnl_attr_get_type(attr);
+
+ if (type <= tb_info->max_type)
+ tb_info->tb[type] = attr;
+
+ return MNL_CB_OK;
+}
+
+/* misc helpers */
+
+const char *get_dev_name(const struct nlattr *nest)
+{
+ const struct nlattr *tb[ETHTOOL_A_HEADER_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ if (!nest)
+ return NULL;
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 || !tb[ETHTOOL_A_HEADER_DEV_NAME])
+ return "(none)";
+ return mnl_attr_get_str(tb[ETHTOOL_A_HEADER_DEV_NAME]);
+}
+
+int get_dev_info(const struct nlattr *nest, int *ifindex, char *ifname)
+{
+ const struct nlattr *tb[ETHTOOL_A_HEADER_MAX + 1] = {};
+ const struct nlattr *index_attr;
+ const struct nlattr *name_attr;
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ if (ifindex)
+ *ifindex = 0;
+ if (ifname)
+ memset(ifname, '\0', ALTIFNAMSIZ);
+
+ if (!nest)
+ return -EFAULT;
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ index_attr = tb[ETHTOOL_A_HEADER_DEV_INDEX];
+ name_attr = tb[ETHTOOL_A_HEADER_DEV_NAME];
+ if (ret < 0 || (ifindex && !index_attr) || (ifname && !name_attr))
+ return -EFAULT;
+
+ if (ifindex)
+ *ifindex = mnl_attr_get_u32(index_attr);
+ if (ifname) {
+ strncpy(ifname, mnl_attr_get_str(name_attr), ALTIFNAMSIZ);
+ if (ifname[ALTIFNAMSIZ - 1]) {
+ ifname[ALTIFNAMSIZ - 1] = '\0';
+ fprintf(stderr, "kernel device name too long: '%s'\n",
+ mnl_attr_get_str(name_attr));
+ return -EFAULT;
+ }
+ }
+ return 0;
+}
+
+/**
+ * netlink_cmd_check() - check support for netlink command
+ * @ctx: ethtool command context
+ * @cmd: netlink command id
+ * @devname: device name from user
+ * @allow_wildcard: wildcard dumps supported
+ *
+ * Check if command @cmd is known to be unsupported based on ops information
+ * from genetlink family id request. Set nlctx->ioctl_fallback if ethtool
+ * should fall back to ioctl, i.e. when we do not know in advance that
+ * netlink request is supported. Set nlctx->wildcard_unsupported if "*" was
+ * used as device name but the request does not support wildcards (on either
+ * side).
+ *
+ * Return: true if we know the netlink request is not supported and should
+ * fail (and possibly fall back) without actually sending it to kernel.
+ */
+bool netlink_cmd_check(struct cmd_context *ctx, unsigned int cmd,
+ bool allow_wildcard)
+{
+ bool is_dump = !strcmp(ctx->devname, WILDCARD_DEVNAME);
+ uint32_t cap = is_dump ? GENL_CMD_CAP_DUMP : GENL_CMD_CAP_DO;
+ struct nl_context *nlctx = ctx->nlctx;
+
+ if (is_dump && !allow_wildcard) {
+ nlctx->wildcard_unsupported = true;
+ return true;
+ }
+ if (!nlctx->ops_info) {
+ nlctx->ioctl_fallback = true;
+ return false;
+ }
+ if (cmd > ETHTOOL_MSG_USER_MAX || !nlctx->ops_info[cmd].op_flags) {
+ nlctx->ioctl_fallback = true;
+ return true;
+ }
+
+ if (is_dump && !(nlctx->ops_info[cmd].op_flags & GENL_CMD_CAP_DUMP))
+ nlctx->wildcard_unsupported = true;
+
+ return !(nlctx->ops_info[cmd].op_flags & cap);
+}
+
+struct ethtool_op_policy_query_ctx {
+ struct nl_context *nlctx;
+ unsigned int op;
+ unsigned int op_hdr_attr;
+
+ bool op_policy_found;
+ bool hdr_policy_found;
+ unsigned int op_policy_idx;
+ unsigned int hdr_policy_idx;
+ uint64_t flag_mask;
+};
+
+static int family_policy_find_op(struct ethtool_op_policy_query_ctx *policy_ctx,
+ const struct nlattr *op_policy)
+{
+ const struct nlattr *attr;
+ unsigned int type;
+ int ret;
+
+ type = policy_ctx->nlctx->is_dump ?
+ CTRL_ATTR_POLICY_DUMP : CTRL_ATTR_POLICY_DO;
+
+ mnl_attr_for_each_nested(attr, op_policy) {
+ const struct nlattr *tb[CTRL_ATTR_POLICY_DUMP_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+
+ if (mnl_attr_get_type(attr) != policy_ctx->op)
+ continue;
+
+ ret = mnl_attr_parse_nested(attr, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+
+ if (!tb[type])
+ continue;
+
+ policy_ctx->op_policy_found = true;
+ policy_ctx->op_policy_idx = mnl_attr_get_u32(tb[type]);
+ break;
+ }
+
+ return 0;
+}
+
+static int family_policy_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tba[NL_POLICY_TYPE_ATTR_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tba);
+ const struct nlattr *tb[CTRL_ATTR_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct ethtool_op_policy_query_ctx *policy_ctx = data;
+ const struct nlattr *policy_attr, *attr_attr, *attr;
+ unsigned int attr_idx, policy_idx;
+ int ret;
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+
+ if (!policy_ctx->op_policy_found) {
+ if (!tb[CTRL_ATTR_OP_POLICY]) {
+ fprintf(stderr, "Error: op policy map not present\n");
+ return MNL_CB_ERROR;
+ }
+ ret = family_policy_find_op(policy_ctx, tb[CTRL_ATTR_OP_POLICY]);
+ return ret < 0 ? MNL_CB_ERROR : MNL_CB_OK;
+ }
+
+ if (!tb[CTRL_ATTR_POLICY])
+ return MNL_CB_OK;
+
+ policy_attr = mnl_attr_get_payload(tb[CTRL_ATTR_POLICY]);
+ policy_idx = mnl_attr_get_type(policy_attr);
+ attr_attr = mnl_attr_get_payload(policy_attr);
+ attr_idx = mnl_attr_get_type(attr_attr);
+
+ ret = mnl_attr_parse_nested(attr_attr, attr_cb, &tba_info);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+
+ if (policy_idx == policy_ctx->op_policy_idx &&
+ attr_idx == policy_ctx->op_hdr_attr) {
+ attr = tba[NL_POLICY_TYPE_ATTR_POLICY_IDX];
+ if (!attr) {
+ fprintf(stderr, "Error: no policy index in what was expected to be ethtool header attribute\n");
+ return MNL_CB_ERROR;
+ }
+ policy_ctx->hdr_policy_found = true;
+ policy_ctx->hdr_policy_idx = mnl_attr_get_u32(attr);
+ }
+
+ if (policy_ctx->hdr_policy_found &&
+ policy_ctx->hdr_policy_idx == policy_idx &&
+ attr_idx == ETHTOOL_A_HEADER_FLAGS) {
+ attr = tba[NL_POLICY_TYPE_ATTR_MASK];
+ if (!attr) {
+ fprintf(stderr, "Error: validation mask not reported for ethtool header flags\n");
+ return MNL_CB_ERROR;
+ }
+
+ policy_ctx->flag_mask = mnl_attr_get_u64(attr);
+ }
+
+ return MNL_CB_OK;
+}
+
+static int read_flags_policy(struct nl_context *nlctx, struct nl_socket *nlsk,
+ unsigned int nlcmd, unsigned int hdrattr)
+{
+ struct ethtool_op_policy_query_ctx policy_ctx;
+ struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+ int ret;
+
+ if (nlctx->ops_info[nlcmd].hdr_policy_loaded)
+ return 0;
+
+ memset(&policy_ctx, 0, sizeof(policy_ctx));
+ policy_ctx.nlctx = nlctx;
+ policy_ctx.op = nlcmd;
+ policy_ctx.op_hdr_attr = hdrattr;
+
+ ret = __msg_init(msgbuff, GENL_ID_CTRL, CTRL_CMD_GETPOLICY,
+ NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, 1);
+ if (ret < 0)
+ return ret;
+ ret = -EMSGSIZE;
+ if (ethnla_put_u16(msgbuff, CTRL_ATTR_FAMILY_ID, nlctx->ethnl_fam))
+ return ret;
+ if (ethnla_put_u32(msgbuff, CTRL_ATTR_OP, nlcmd))
+ return ret;
+
+ nlsock_sendmsg(nlsk, NULL);
+ nlsock_process_reply(nlsk, family_policy_cb, &policy_ctx);
+
+ nlctx->ops_info[nlcmd].hdr_policy_loaded = 1;
+ nlctx->ops_info[nlcmd].hdr_flags = policy_ctx.flag_mask;
+ return 0;
+}
+
+u32 get_stats_flag(struct nl_context *nlctx, unsigned int nlcmd,
+ unsigned int hdrattr)
+{
+ if (!nlctx->ctx->show_stats)
+ return 0;
+ if (nlcmd > ETHTOOL_MSG_USER_MAX ||
+ !(nlctx->ops_info[nlcmd].op_flags & GENL_CMD_CAP_HASPOL))
+ return 0;
+
+ if (read_flags_policy(nlctx, nlctx->ethnl_socket, nlcmd, hdrattr) < 0)
+ return 0;
+
+ return nlctx->ops_info[nlcmd].hdr_flags & ETHTOOL_FLAG_STATS;
+}
+
+/* initialization */
+
+static int genl_read_ops(struct nl_context *nlctx,
+ const struct nlattr *ops_attr)
+{
+ struct nl_op_info *ops_info;
+ struct nlattr *op_attr;
+ int ret;
+
+ ops_info = calloc(__ETHTOOL_MSG_USER_CNT, sizeof(ops_info[0]));
+ if (!ops_info)
+ return -ENOMEM;
+
+ mnl_attr_for_each_nested(op_attr, ops_attr) {
+ const struct nlattr *tb[CTRL_ATTR_OP_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ uint32_t op_id;
+
+ ret = mnl_attr_parse_nested(op_attr, attr_cb, &tb_info);
+ if (ret < 0)
+ goto err;
+
+ if (!tb[CTRL_ATTR_OP_ID] || !tb[CTRL_ATTR_OP_FLAGS])
+ continue;
+ op_id = mnl_attr_get_u32(tb[CTRL_ATTR_OP_ID]);
+ if (op_id >= __ETHTOOL_MSG_USER_CNT)
+ continue;
+
+ ops_info[op_id].op_flags =
+ mnl_attr_get_u32(tb[CTRL_ATTR_OP_FLAGS]);
+ }
+
+ nlctx->ops_info = ops_info;
+ return 0;
+err:
+ free(ops_info);
+ return ret;
+}
+
+static void find_mc_group(struct nl_context *nlctx, struct nlattr *nest)
+{
+ const struct nlattr *grp_tb[CTRL_ATTR_MCAST_GRP_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(grp_tb);
+ struct nlattr *grp_attr;
+ int ret;
+
+ mnl_attr_for_each_nested(grp_attr, nest) {
+ ret = mnl_attr_parse_nested(grp_attr, attr_cb, &grp_tb_info);
+ if (ret < 0)
+ return;
+ if (!grp_tb[CTRL_ATTR_MCAST_GRP_NAME] ||
+ !grp_tb[CTRL_ATTR_MCAST_GRP_ID])
+ continue;
+ if (strcmp(mnl_attr_get_str(grp_tb[CTRL_ATTR_MCAST_GRP_NAME]),
+ ETHTOOL_MCGRP_MONITOR_NAME))
+ continue;
+ nlctx->ethnl_mongrp =
+ mnl_attr_get_u32(grp_tb[CTRL_ATTR_MCAST_GRP_ID]);
+ return;
+ }
+}
+
+static int __maybe_unused family_info_cb(const struct nlmsghdr *nlhdr,
+ void *data)
+{
+ struct nl_context *nlctx = data;
+ struct nlattr *attr;
+ int ret;
+
+ mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
+ switch (mnl_attr_get_type(attr)) {
+ case CTRL_ATTR_FAMILY_ID:
+ nlctx->ethnl_fam = mnl_attr_get_u16(attr);
+ break;
+ case CTRL_ATTR_OPS:
+ ret = genl_read_ops(nlctx, attr);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+ break;
+ case CTRL_ATTR_MCAST_GROUPS:
+ find_mc_group(nlctx, attr);
+ break;
+ }
+ }
+
+ return MNL_CB_OK;
+}
+
+#ifdef TEST_ETHTOOL
+static int get_genl_family(struct nl_context *nlctx __maybe_unused,
+ struct nl_socket *nlsk __maybe_unused)
+{
+ return 0;
+}
+#else
+static int get_genl_family(struct nl_context *nlctx, struct nl_socket *nlsk)
+{
+ struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+ int ret;
+
+ nlctx->suppress_nlerr = 2;
+ ret = __msg_init(msgbuff, GENL_ID_CTRL, CTRL_CMD_GETFAMILY,
+ NLM_F_REQUEST | NLM_F_ACK, 1);
+ if (ret < 0)
+ goto out;
+ ret = -EMSGSIZE;
+ if (ethnla_put_strz(msgbuff, CTRL_ATTR_FAMILY_NAME, ETHTOOL_GENL_NAME))
+ goto out;
+
+ nlsock_sendmsg(nlsk, NULL);
+ nlsock_process_reply(nlsk, family_info_cb, nlctx);
+ ret = nlctx->ethnl_fam ? 0 : -EADDRNOTAVAIL;
+
+out:
+ nlctx->suppress_nlerr = 0;
+ return ret;
+}
+#endif
+
+int netlink_init(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx;
+ int ret;
+
+ nlctx = calloc(1, sizeof(*nlctx));
+ if (!nlctx)
+ return -ENOMEM;
+ nlctx->ctx = ctx;
+ ret = nlsock_init(nlctx, &nlctx->ethnl_socket, NETLINK_GENERIC);
+ if (ret < 0)
+ goto out_free;
+ ret = get_genl_family(nlctx, nlctx->ethnl_socket);
+ if (ret < 0)
+ goto out_nlsk;
+
+ ctx->nlctx = nlctx;
+ return 0;
+
+out_nlsk:
+ nlsock_done(nlctx->ethnl_socket);
+out_free:
+ free(nlctx->ops_info);
+ free(nlctx);
+ return ret;
+}
+
+static void netlink_done(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+
+ if (!nlctx)
+ return;
+
+ nlsock_done(nlctx->ethnl_socket);
+ nlsock_done(nlctx->ethnl2_socket);
+ nlsock_done(nlctx->rtnl_socket);
+ free(nlctx->ops_info);
+ free(nlctx);
+ ctx->nlctx = NULL;
+ cleanup_all_strings();
+}
+
+/**
+ * netlink_run_handler() - run netlink handler for subcommand
+ * @ctx: command context
+ * @nlchk: netlink capability check
+ * @nlfunc: subcommand netlink handler to call
+ * @no_fallback: there is no ioctl fallback handler
+ *
+ * This function returns only if ioctl() handler should be run as fallback.
+ * Otherwise it exits with appropriate return code.
+ */
+void netlink_run_handler(struct cmd_context *ctx, nl_chk_t nlchk,
+ nl_func_t nlfunc, bool no_fallback)
+{
+ bool wildcard = ctx->devname && !strcmp(ctx->devname, WILDCARD_DEVNAME);
+ bool wildcard_unsupported, ioctl_fallback;
+ struct nl_context *nlctx;
+ const char *reason;
+ int ret;
+
+ if (nlchk && !nlchk(ctx)) {
+ reason = "ioctl-only request";
+ goto no_support;
+ }
+ if (ctx->devname && strlen(ctx->devname) >= ALTIFNAMSIZ) {
+ fprintf(stderr, "device name '%s' longer than %u characters\n",
+ ctx->devname, ALTIFNAMSIZ - 1);
+ exit(1);
+ }
+
+ if (!nlfunc) {
+ reason = "ethtool netlink support for subcommand missing";
+ goto no_support;
+ }
+ if (netlink_init(ctx)) {
+ reason = "netlink interface initialization failed";
+ goto no_support;
+ }
+ nlctx = ctx->nlctx;
+
+ ret = nlfunc(ctx);
+ wildcard_unsupported = nlctx->wildcard_unsupported;
+ ioctl_fallback = nlctx->ioctl_fallback;
+ netlink_done(ctx);
+
+ if (no_fallback || ret != -EOPNOTSUPP || !ioctl_fallback) {
+ if (wildcard_unsupported)
+ fprintf(stderr, "%s\n",
+ "subcommand does not support wildcard dump");
+ exit(ret >= 0 ? ret : 1);
+ }
+ if (wildcard_unsupported)
+ reason = "subcommand does not support wildcard dump";
+ else
+ reason = "kernel netlink support for subcommand missing";
+
+no_support:
+ if (no_fallback) {
+ fprintf(stderr, "%s, subcommand not supported by ioctl\n",
+ reason);
+ exit(1);
+ }
+ if (wildcard) {
+ fprintf(stderr, "%s, wildcard dump not supported\n", reason);
+ exit(1);
+ }
+ if (ctx->devname && strlen(ctx->devname) >= IFNAMSIZ) {
+ fprintf(stderr,
+ "%s, device name longer than %u not supported\n",
+ reason, IFNAMSIZ - 1);
+ exit(1);
+ }
+
+ /* fallback to ioctl() */
+}
diff --git a/netlink/netlink.h b/netlink/netlink.h
new file mode 100644
index 0000000..1274a3b
--- /dev/null
+++ b/netlink/netlink.h
@@ -0,0 +1,178 @@
+/*
+ * netlink.h - common interface for all netlink code
+ *
+ * Declarations of data structures, global data and helpers for netlink code
+ */
+
+#ifndef ETHTOOL_NETLINK_INT_H__
+#define ETHTOOL_NETLINK_INT_H__
+
+#include <libmnl/libmnl.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#include <linux/ethtool_netlink.h>
+#include "nlsock.h"
+
+#define WILDCARD_DEVNAME "*"
+#define CMDMASK_WORDS DIV_ROUND_UP(__ETHTOOL_MSG_KERNEL_CNT, 32)
+
+enum link_mode_class {
+ LM_CLASS_UNKNOWN,
+ LM_CLASS_REAL,
+ LM_CLASS_AUTONEG,
+ LM_CLASS_PORT,
+ LM_CLASS_PAUSE,
+ LM_CLASS_FEC,
+};
+
+struct nl_op_info {
+ uint32_t op_flags;
+ uint32_t hdr_flags;
+ uint8_t hdr_policy_loaded:1;
+};
+
+struct nl_context {
+ struct cmd_context *ctx;
+ void *cmd_private;
+ const char *devname;
+ bool is_dump;
+ int exit_code;
+ unsigned int suppress_nlerr;
+ uint16_t ethnl_fam;
+ uint32_t ethnl_mongrp;
+ struct nl_op_info *ops_info;
+ struct nl_socket *ethnl_socket;
+ struct nl_socket *ethnl2_socket;
+ struct nl_socket *rtnl_socket;
+ bool is_monitor;
+ uint32_t filter_cmds[CMDMASK_WORDS];
+ const char *filter_devname;
+ bool no_banner;
+ const char *cmd;
+ const char *param;
+ char **argp;
+ unsigned int argc;
+ bool ioctl_fallback;
+ bool wildcard_unsupported;
+};
+
+struct attr_tb_info {
+ const struct nlattr **tb;
+ unsigned int max_type;
+};
+
+#define DECLARE_ATTR_TB_INFO(tbl) \
+ struct attr_tb_info tbl ## _info = { (tbl), (MNL_ARRAY_SIZE(tbl) - 1) }
+
+int nomsg_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int attr_cb(const struct nlattr *attr, void *data);
+
+int netlink_init(struct cmd_context *ctx);
+bool netlink_cmd_check(struct cmd_context *ctx, unsigned int cmd,
+ bool allow_wildcard);
+const char *get_dev_name(const struct nlattr *nest);
+int get_dev_info(const struct nlattr *nest, int *ifindex, char *ifname);
+u32 get_stats_flag(struct nl_context *nlctx, unsigned int nlcmd,
+ unsigned int hdrattr);
+
+int linkmodes_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int linkinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int wol_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int debug_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int features_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int privflags_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int rings_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int channels_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int coalesce_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int pause_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int eee_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int cable_test_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int cable_test_ntf_cb(const struct nlmsghdr *nlhdr, void *data);
+int cable_test_tdr_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int cable_test_tdr_ntf_cb(const struct nlmsghdr *nlhdr, void *data);
+int fec_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int module_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+
+/* dump helpers */
+
+int dump_link_modes(struct nl_context *nlctx, const struct nlattr *bitset,
+ bool mask, unsigned int class, const char *before,
+ const char *between, const char *after,
+ const char *if_none);
+
+static inline void show_u32(const char *key,
+ const char *fmt,
+ const struct nlattr *attr)
+{
+ if (is_json_context()) {
+ if (attr)
+ print_uint(PRINT_JSON, key, NULL,
+ mnl_attr_get_u32(attr));
+ } else {
+ if (attr)
+ printf("%s%u\n", fmt, mnl_attr_get_u32(attr));
+ else
+ printf("%sn/a\n", fmt);
+ }
+}
+
+static inline const char *u8_to_bool(const uint8_t *val)
+{
+ if (val)
+ return *val ? "on" : "off";
+ else
+ return "n/a";
+}
+
+static inline void show_bool_val(const char *key, const char *fmt, uint8_t *val)
+{
+ if (is_json_context()) {
+ if (val)
+ print_bool(PRINT_JSON, key, NULL, *val);
+ } else {
+ print_string(PRINT_FP, NULL, fmt, u8_to_bool(val));
+ }
+}
+
+static inline void show_bool(const char *key, const char *fmt,
+ const struct nlattr *attr)
+{
+ show_bool_val(key, fmt, attr ? mnl_attr_get_payload(attr) : NULL);
+}
+
+static inline void show_cr(void)
+{
+ if (!is_json_context())
+ putchar('\n');
+}
+
+/* misc */
+
+static inline void copy_devname(char *dst, const char *src)
+{
+ strncpy(dst, src, ALTIFNAMSIZ);
+ dst[ALTIFNAMSIZ - 1] = '\0';
+}
+
+static inline bool dev_ok(const struct nl_context *nlctx)
+{
+ return !nlctx->filter_devname ||
+ (nlctx->devname &&
+ !strcmp(nlctx->devname, nlctx->filter_devname));
+}
+
+static inline int netlink_init_ethnl2_socket(struct nl_context *nlctx)
+{
+ if (nlctx->ethnl2_socket)
+ return 0;
+ return nlsock_init(nlctx, &nlctx->ethnl2_socket, NETLINK_GENERIC);
+}
+
+static inline int netlink_init_rtnl_socket(struct nl_context *nlctx)
+{
+ if (nlctx->rtnl_socket)
+ return 0;
+ return nlsock_init(nlctx, &nlctx->rtnl_socket, NETLINK_ROUTE);
+}
+
+#endif /* ETHTOOL_NETLINK_INT_H__ */
diff --git a/netlink/nlsock.c b/netlink/nlsock.c
new file mode 100644
index 0000000..0ec2738
--- /dev/null
+++ b/netlink/nlsock.c
@@ -0,0 +1,405 @@
+/*
+ * nlsock.c - netlink socket
+ *
+ * Data structure and code for netlink socket abstraction.
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include "../internal.h"
+#include "nlsock.h"
+#include "netlink.h"
+#include "prettymsg.h"
+
+#define NLSOCK_RECV_BUFFSIZE 65536
+
+static void ctrl_msg_summary(const struct nlmsghdr *nlhdr)
+{
+ const struct nlmsgerr *nlerr;
+
+ switch (nlhdr->nlmsg_type) {
+ case NLMSG_NOOP:
+ printf(" noop\n");
+ break;
+ case NLMSG_ERROR:
+ printf(" error");
+ if (nlhdr->nlmsg_len < NLMSG_HDRLEN + sizeof(*nlerr)) {
+ printf(" malformed\n");
+ break;
+ }
+ nlerr = mnl_nlmsg_get_payload(nlhdr);
+ printf(" errno=%d\n", nlerr->error);
+ break;
+ case NLMSG_DONE:
+ printf(" done\n");
+ break;
+ case NLMSG_OVERRUN:
+ printf(" overrun\n");
+ break;
+ default:
+ printf(" type %u\n", nlhdr->nlmsg_type);
+ break;
+ }
+}
+
+static void genl_msg_summary(const struct nlmsghdr *nlhdr, int ethnl_fam,
+ bool outgoing, bool pretty)
+{
+ if (nlhdr->nlmsg_type == ethnl_fam) {
+ const struct pretty_nlmsg_desc *msg_desc;
+ const struct genlmsghdr *ghdr;
+ unsigned int n_desc;
+
+ printf(" ethool");
+ if (nlhdr->nlmsg_len < NLMSG_HDRLEN + GENL_HDRLEN) {
+ printf(" malformed\n");
+ return;
+ }
+ ghdr = mnl_nlmsg_get_payload(nlhdr);
+
+ msg_desc = outgoing ? ethnl_umsg_desc : ethnl_kmsg_desc;
+ n_desc = outgoing ? ethnl_umsg_n_desc : ethnl_kmsg_n_desc;
+ if (ghdr->cmd < n_desc && msg_desc[ghdr->cmd].name)
+ printf(" %s", msg_desc[ghdr->cmd].name);
+ else
+ printf(" cmd %u", ghdr->cmd);
+ fputc('\n', stdout);
+
+ if (pretty)
+ pretty_print_genlmsg(nlhdr, msg_desc, n_desc, 0);
+ return;
+ }
+
+ if (nlhdr->nlmsg_type == GENL_ID_CTRL) {
+ printf(" genl-ctrl\n");
+ if (pretty)
+ pretty_print_genlmsg(nlhdr, genlctrl_msg_desc,
+ genlctrl_msg_n_desc, 0);
+ } else {
+ fputc('\n', stdout);
+ if (pretty)
+ pretty_print_genlmsg(nlhdr, NULL, 0, 0);
+ }
+}
+
+static void rtnl_msg_summary(const struct nlmsghdr *nlhdr, bool pretty)
+{
+ unsigned int type = nlhdr->nlmsg_type;
+
+ if (type < rtnl_msg_n_desc && rtnl_msg_desc[type].name)
+ printf(" %s\n", rtnl_msg_desc[type].name);
+ else
+ printf(" type %u\n", type);
+
+ if (pretty)
+ pretty_print_rtnlmsg(nlhdr, 0);
+}
+
+static void debug_msg_summary(const struct nlmsghdr *nlhdr, int ethnl_fam,
+ int nl_fam, bool outgoing, bool pretty)
+{
+ printf(" msg length %u", nlhdr->nlmsg_len);
+
+ if (nlhdr->nlmsg_type < NLMSG_MIN_TYPE) {
+ ctrl_msg_summary(nlhdr);
+ return;
+ }
+
+ switch(nl_fam) {
+ case NETLINK_GENERIC:
+ genl_msg_summary(nlhdr, ethnl_fam, outgoing, pretty);
+ break;
+ case NETLINK_ROUTE:
+ rtnl_msg_summary(nlhdr, pretty);
+ break;
+ default:
+ fputc('\n', stdout);
+ break;
+ }
+}
+
+static void debug_msg(struct nl_socket *nlsk, const void *msg, unsigned int len,
+ bool outgoing)
+{
+ const char *dirlabel = outgoing ? "sending" : "received";
+ uint32_t debug = nlsk->nlctx->ctx->debug;
+ const struct nlmsghdr *nlhdr = msg;
+ bool summary, dump, pretty;
+ const char *nl_fam_label;
+ int left = len;
+
+ summary = debug_on(debug, DEBUG_NL_MSGS);
+ dump = debug_on(debug,
+ outgoing ? DEBUG_NL_DUMP_SND : DEBUG_NL_DUMP_RCV);
+ pretty = debug_on(debug, DEBUG_NL_PRETTY_MSG);
+ if (!summary && !dump)
+ return;
+ switch(nlsk->nl_fam) {
+ case NETLINK_GENERIC:
+ nl_fam_label = "genetlink";
+ break;
+ case NETLINK_ROUTE:
+ nl_fam_label = "rtnetlink";
+ break;
+ default:
+ nl_fam_label = "netlink";
+ break;
+ }
+ printf("%s %s packet (%u bytes):\n", dirlabel, nl_fam_label, len);
+
+ while (nlhdr && left > 0 && mnl_nlmsg_ok(nlhdr, left)) {
+ if (summary)
+ debug_msg_summary(nlhdr, nlsk->nlctx->ethnl_fam,
+ nlsk->nl_fam, outgoing, pretty);
+ if (dump)
+ mnl_nlmsg_fprintf(stdout, nlhdr, nlhdr->nlmsg_len,
+ GENL_HDRLEN);
+
+ nlhdr = mnl_nlmsg_next(nlhdr, &left);
+ }
+}
+
+/**
+ * nlsock_process_ack() - process NLMSG_ERROR message from kernel
+ * @nlhdr: pointer to netlink header
+ * @len: length of received data (from nlhdr to end of buffer)
+ * @suppress_nlerr: 0 show all errors, 1 silence -EOPNOTSUPP, 2 silence all
+ *
+ * Return: error code extracted from the message
+ */
+static int nlsock_process_ack(struct nlmsghdr *nlhdr, unsigned long len,
+ unsigned int suppress_nlerr, bool pretty)
+{
+ const struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ unsigned int err_offset = 0;
+ unsigned int tlv_offset;
+ struct nlmsgerr *nlerr;
+ bool silent;
+
+ if ((len < NLMSG_HDRLEN + sizeof(*nlerr)) || (len < nlhdr->nlmsg_len))
+ return -EFAULT;
+ nlerr = mnl_nlmsg_get_payload(nlhdr);
+ silent = suppress_nlerr >= 2 ||
+ (suppress_nlerr && nlerr->error == -EOPNOTSUPP);
+ if (silent || !(nlhdr->nlmsg_flags & NLM_F_ACK_TLVS))
+ goto tlv_done;
+
+ tlv_offset = sizeof(*nlerr);
+ if (!(nlhdr->nlmsg_flags & NLM_F_CAPPED))
+ tlv_offset += MNL_ALIGN(mnl_nlmsg_get_payload_len(&nlerr->msg));
+ if (mnl_attr_parse(nlhdr, tlv_offset, attr_cb, &tb_info) < 0)
+ goto tlv_done;
+
+ if (tb[NLMSGERR_ATTR_MSG]) {
+ const char *msg = mnl_attr_get_str(tb[NLMSGERR_ATTR_MSG]);
+
+ fprintf(stderr, "netlink %s: %s",
+ nlerr->error ? "error" : "warning", msg);
+ if (!pretty && tb[NLMSGERR_ATTR_OFFS])
+ fprintf(stderr, " (offset %u)",
+ mnl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]));
+ fputc('\n', stderr);
+ }
+ if (tb[NLMSGERR_ATTR_OFFS])
+ err_offset = mnl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]);
+
+tlv_done:
+ if (nlerr->error && !silent) {
+ errno = -nlerr->error;
+ perror("netlink error");
+ }
+ if (pretty && !(nlhdr->nlmsg_flags & NLM_F_CAPPED) &&
+ nlhdr->nlmsg_len >= NLMSG_HDRLEN + nlerr->msg.nlmsg_len) {
+ fprintf(stderr, "offending message%s:\n",
+ err_offset ? " and attribute" : "");
+ pretty_print_genlmsg(&nlerr->msg, ethnl_umsg_desc,
+ ethnl_umsg_n_desc, err_offset);
+ }
+ return nlerr->error;
+}
+
+/**
+ * nlsock_process_reply() - process reply packet(s) from kernel
+ * @nlsk: netlink socket to read from
+ * @reply_cb: callback to process each message
+ * @data: pointer passed as argument to @reply_cb callback
+ *
+ * Read packets from kernel and pass reply messages to @reply_cb callback
+ * until an error is encountered or NLMSG_ERR message is received. In the
+ * latter case, return value is the error code extracted from it.
+ *
+ * Return: 0 on success or negative error code
+ */
+int nlsock_process_reply(struct nl_socket *nlsk, mnl_cb_t reply_cb, void *data)
+{
+ struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+ struct nlmsghdr *nlhdr;
+ ssize_t len;
+ char *buff;
+ int ret;
+
+ ret = msgbuff_realloc(msgbuff, NLSOCK_RECV_BUFFSIZE);
+ if (ret < 0)
+ return ret;
+ buff = msgbuff->buff;
+
+ do {
+ len = mnl_socket_recvfrom(nlsk->sk, buff, msgbuff->size);
+ if (len <= 0)
+ return (len ? -EFAULT : 0);
+ debug_msg(nlsk, buff, len, false);
+ if (len < NLMSG_HDRLEN)
+ return -EFAULT;
+
+ nlhdr = (struct nlmsghdr *)buff;
+ if (nlhdr->nlmsg_type == NLMSG_ERROR) {
+ unsigned int suppress = nlsk->nlctx->suppress_nlerr;
+ bool pretty;
+
+ pretty = debug_on(nlsk->nlctx->ctx->debug,
+ DEBUG_NL_PRETTY_MSG);
+ return nlsock_process_ack(nlhdr, len, suppress, pretty);
+ }
+
+ msgbuff->nlhdr = nlhdr;
+ msgbuff->genlhdr = mnl_nlmsg_get_payload(nlhdr);
+ msgbuff->payload =
+ mnl_nlmsg_get_payload_offset(nlhdr, GENL_HDRLEN);
+ ret = mnl_cb_run(buff, len, nlsk->seq, nlsk->port, reply_cb,
+ data);
+ } while (ret > 0);
+
+ return ret;
+}
+
+int nlsock_prep_get_request(struct nl_socket *nlsk, unsigned int nlcmd,
+ uint16_t hdr_attrtype, u32 flags)
+{
+ unsigned int nlm_flags = NLM_F_REQUEST | NLM_F_ACK;
+ struct nl_context *nlctx = nlsk->nlctx;
+ const char *devname = nlctx->ctx->devname;
+ int ret;
+
+ if (devname && !strcmp(devname, WILDCARD_DEVNAME)) {
+ devname = NULL;
+ nlm_flags |= NLM_F_DUMP;
+ }
+ nlctx->is_dump = !devname;
+
+ ret = msg_init(nlctx, &nlsk->msgbuff, nlcmd, nlm_flags);
+ if (ret < 0)
+ return ret;
+ if (ethnla_fill_header(&nlsk->msgbuff, hdr_attrtype, devname, flags))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+#ifndef TEST_ETHTOOL
+/**
+ * nlsock_sendmsg() - send a netlink message to kernel
+ * @nlsk: netlink socket
+ * @altbuff: alternative message buffer; if null, use buffer embedded in @nlsk
+ *
+ * Return: sent size or negative error code
+ */
+ssize_t nlsock_sendmsg(struct nl_socket *nlsk, struct nl_msg_buff *altbuff)
+{
+ struct nl_msg_buff *msgbuff = altbuff ?: &nlsk->msgbuff;
+ struct nlmsghdr *nlhdr = msgbuff->nlhdr;
+
+ nlhdr->nlmsg_seq = ++nlsk->seq;
+ debug_msg(nlsk, msgbuff->buff, nlhdr->nlmsg_len, true);
+ return mnl_socket_sendto(nlsk->sk, nlhdr, nlhdr->nlmsg_len);
+}
+#endif
+
+/**
+ * nlsock_send_get_request() - send request and process reply
+ * @nlsk: netlink socket
+ * @cb: callback to process reply message(s)
+ *
+ * This simple helper only handles the most common case when the embedded
+ * message buffer is sent and @cb takes netlink context (struct nl_context)
+ * as last argument.
+ */
+int nlsock_send_get_request(struct nl_socket *nlsk, mnl_cb_t cb)
+{
+ int ret;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ goto err;
+ ret = nlsock_process_reply(nlsk, cb, nlsk->nlctx);
+ if (ret == 0)
+ return 0;
+err:
+ return nlsk->nlctx->exit_code ?: 1;
+}
+
+/**
+ * nlsock_init() - allocate and initialize netlink socket
+ * @nlctx: netlink context
+ * @__nlsk: store pointer to the allocated socket here
+ * @nl_fam: netlink family (e.g. NETLINK_GENERIC or NETLINK_ROUTE)
+ *
+ * Allocate and initialize netlink socket and its embedded message buffer.
+ * Cleans up on error, caller is responsible for destroying the socket with
+ * nlsock_done() on success.
+ *
+ * Return: 0 on success or negative error code
+ */
+int nlsock_init(struct nl_context *nlctx, struct nl_socket **__nlsk, int nl_fam)
+{
+ struct nl_socket *nlsk;
+ int val;
+ int ret;
+
+ nlsk = calloc(1, sizeof(*nlsk));
+ if (!nlsk)
+ return -ENOMEM;
+ nlsk->nlctx = nlctx;
+ msgbuff_init(&nlsk->msgbuff);
+
+ ret = -ECONNREFUSED;
+ nlsk->sk = mnl_socket_open(nl_fam);
+ if (!nlsk->sk)
+ goto out_msgbuff;
+ val = 1;
+ mnl_socket_setsockopt(nlsk->sk, NETLINK_EXT_ACK, &val, sizeof(val));
+ ret = mnl_socket_bind(nlsk->sk, 0, MNL_SOCKET_AUTOPID);
+ if (ret < 0)
+ goto out_close;
+ nlsk->port = mnl_socket_get_portid(nlsk->sk);
+ nlsk->nl_fam = nl_fam;
+
+ *__nlsk = nlsk;
+ return 0;
+
+out_close:
+ if (nlsk->sk)
+ mnl_socket_close(nlsk->sk);
+out_msgbuff:
+ msgbuff_done(&nlsk->msgbuff);
+ free(nlsk);
+ return ret;
+}
+
+/**
+ * nlsock_done() - destroy a netlink socket
+ * @nlsk: netlink socket
+ *
+ * Close the socket and free the structure and related data.
+ */
+void nlsock_done(struct nl_socket *nlsk)
+{
+ if (!nlsk)
+ return;
+ if (nlsk->sk)
+ mnl_socket_close(nlsk->sk);
+ msgbuff_done(&nlsk->msgbuff);
+ memset(nlsk, '\0', sizeof(*nlsk));
+ free(nlsk);
+}
diff --git a/netlink/nlsock.h b/netlink/nlsock.h
new file mode 100644
index 0000000..b015f86
--- /dev/null
+++ b/netlink/nlsock.h
@@ -0,0 +1,45 @@
+/*
+ * nlsock.h - netlink socket
+ *
+ * Declarations of netlink socket structure and related functions.
+ */
+
+#ifndef ETHTOOL_NETLINK_NLSOCK_H__
+#define ETHTOOL_NETLINK_NLSOCK_H__
+
+#include <libmnl/libmnl.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#include <linux/ethtool_netlink.h>
+#include "msgbuff.h"
+
+struct nl_context;
+
+/**
+ * struct nl_socket - netlink socket abstraction
+ * @nlctx: netlink context
+ * @sk: libmnl socket handle
+ * @msgbuff: embedded message buffer used by default
+ * @port: port number for netlink header
+ * @seq: autoincremented sequence number for netlink header
+ * @nl_fam: netlink family (e.g. NETLINK_GENERIC or NETLINK_ROUTE)
+ */
+struct nl_socket {
+ struct nl_context *nlctx;
+ struct mnl_socket *sk;
+ struct nl_msg_buff msgbuff;
+ unsigned int port;
+ unsigned int seq;
+ int nl_fam;
+};
+
+int nlsock_init(struct nl_context *nlctx, struct nl_socket **__nlsk,
+ int nl_fam);
+void nlsock_done(struct nl_socket *nlsk);
+int nlsock_prep_get_request(struct nl_socket *nlsk, unsigned int nlcmd,
+ uint16_t hdr_attrtype, u32 flags);
+ssize_t nlsock_sendmsg(struct nl_socket *nlsk, struct nl_msg_buff *__msgbuff);
+int nlsock_send_get_request(struct nl_socket *nlsk, mnl_cb_t cb);
+int nlsock_process_reply(struct nl_socket *nlsk, mnl_cb_t reply_cb, void *data);
+
+#endif /* ETHTOOL_NETLINK_NLSOCK_H__ */
diff --git a/netlink/parser.c b/netlink/parser.c
new file mode 100644
index 0000000..6f86361
--- /dev/null
+++ b/netlink/parser.c
@@ -0,0 +1,1141 @@
+/*
+ * parser.c - netlink command line parser
+ *
+ * Implementation of command line parser used by netlink code.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+static void parser_err_unknown_param(struct nl_context *nlctx)
+{
+ fprintf(stderr, "ethtool (%s): unknown parameter '%s'\n", nlctx->cmd,
+ nlctx->param);
+}
+
+static void parser_err_dup_param(struct nl_context *nlctx)
+{
+ fprintf(stderr, "ethtool (%s): duplicate parameter '%s'\n", nlctx->cmd,
+ nlctx->param);
+}
+
+static void parser_err_min_argc(struct nl_context *nlctx, unsigned int min_argc)
+{
+ if (min_argc == 1)
+ fprintf(stderr, "ethtool (%s): no value for parameter '%s'\n",
+ nlctx->cmd, nlctx->param);
+ else
+ fprintf(stderr,
+ "ethtool (%s): parameter '%s' requires %u words\n",
+ nlctx->cmd, nlctx->param, min_argc);
+}
+
+static void parser_err_invalid_value(struct nl_context *nlctx, const char *val)
+{
+ fprintf(stderr, "ethtool (%s): invalid value '%s' for parameter '%s'\n",
+ nlctx->cmd, val, nlctx->param);
+}
+
+static void parser_err_invalid_flag(struct nl_context *nlctx, const char *flag)
+{
+ fprintf(stderr, "ethtool (%s): flag '%s' for parameter '%s' is not followed by 'on' or 'off'\n",
+ nlctx->cmd, flag, nlctx->param);
+}
+
+static bool __prefix_0x(const char *p)
+{
+ return p[0] == '0' && (p[1] == 'x' || p[1] == 'X');
+}
+
+static float parse_float(const char *arg, float *result, float min,
+ float max)
+{
+ char *endptr;
+ float val;
+
+ if (!arg || !arg[0])
+ return -EINVAL;
+ val = strtof(arg, &endptr);
+ if (*endptr || val < min || val > max)
+ return -EINVAL;
+
+ *result = val;
+ return 0;
+}
+
+static int __parse_u32(const char *arg, uint32_t *result, uint32_t min,
+ uint32_t max, int base)
+{
+ unsigned long long val;
+ char *endptr;
+
+ if (!arg || !arg[0])
+ return -EINVAL;
+ val = strtoul(arg, &endptr, base);
+ if (*endptr || val < min || val > max)
+ return -EINVAL;
+
+ *result = (uint32_t)val;
+ return 0;
+}
+
+static int parse_u32d(const char *arg, uint32_t *result)
+{
+ return __parse_u32(arg, result, 0, 0xffffffff, 10);
+}
+
+static int parse_x32(const char *arg, uint32_t *result)
+{
+ return __parse_u32(arg, result, 0, 0xffffffff, 16);
+}
+
+int parse_u32(const char *arg, uint32_t *result)
+{
+ if (!arg)
+ return -EINVAL;
+ if (__prefix_0x(arg))
+ return parse_x32(arg + 2, result);
+ else
+ return parse_u32d(arg, result);
+}
+
+static int parse_u8(const char *arg, uint8_t *result)
+{
+ uint32_t val;
+ int ret = parse_u32(arg, &val);
+
+ if (ret < 0)
+ return ret;
+ if (val > UINT8_MAX)
+ return -EINVAL;
+
+ *result = (uint8_t)val;
+ return 0;
+}
+
+static int lookup_u32(const char *arg, uint32_t *result,
+ const struct lookup_entry_u32 *tbl)
+{
+ if (!arg)
+ return -EINVAL;
+ while (tbl->arg) {
+ if (!strcmp(tbl->arg, arg)) {
+ *result = tbl->val;
+ return 0;
+ }
+ tbl++;
+ }
+
+ return -EINVAL;
+}
+
+static int lookup_u8(const char *arg, uint8_t *result,
+ const struct lookup_entry_u8 *tbl)
+{
+ if (!arg)
+ return -EINVAL;
+ while (tbl->arg) {
+ if (!strcmp(tbl->arg, arg)) {
+ *result = tbl->val;
+ return 0;
+ }
+ tbl++;
+ }
+
+ return -EINVAL;
+}
+
+/* Parser handler for a flag. Expects a name (with no additional argument),
+ * generates NLA_FLAG or sets a bool (if the name was present).
+ */
+int nl_parse_flag(struct nl_context *nlctx __maybe_unused, uint16_t type,
+ const void *data __maybe_unused, struct nl_msg_buff *msgbuff,
+ void *dest)
+{
+ if (dest)
+ *(bool *)dest = true;
+ return (type && ethnla_put_flag(msgbuff, type, true)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for null terminated string. Expects a string argument,
+ * generates NLA_NUL_STRING or fills const char *
+ */
+int nl_parse_string(struct nl_context *nlctx, uint16_t type,
+ const void *data __maybe_unused,
+ struct nl_msg_buff *msgbuff, void *dest)
+{
+ const char *arg = *nlctx->argp;
+
+ nlctx->argp++;
+ nlctx->argc--;
+
+ if (dest)
+ *(const char **)dest = arg;
+ return (type && ethnla_put_strz(msgbuff, type, arg)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for unsigned 32-bit integer. Expects a numeric argument
+ * (may use 0x prefix), generates NLA_U32 or fills an uint32_t.
+ */
+int nl_parse_direct_u32(struct nl_context *nlctx, uint16_t type,
+ const void *data __maybe_unused,
+ struct nl_msg_buff *msgbuff, void *dest)
+{
+ const char *arg = *nlctx->argp;
+ uint32_t val;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+ ret = parse_u32(arg, &val);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ return ret;
+ }
+
+ if (dest)
+ *(uint32_t *)dest = val;
+ return (type && ethnla_put_u32(msgbuff, type, val)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for unsigned 32-bit integer. Expects a numeric argument
+ * (may use 0x prefix), generates NLA_U32 or fills an uint32_t.
+ */
+int nl_parse_direct_u8(struct nl_context *nlctx, uint16_t type,
+ const void *data __maybe_unused,
+ struct nl_msg_buff *msgbuff, void *dest)
+{
+ const char *arg = *nlctx->argp;
+ uint8_t val;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+ ret = parse_u8(arg, &val);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ return ret;
+ }
+
+ if (dest)
+ *(uint8_t *)dest = val;
+ return (type && ethnla_put_u8(msgbuff, type, val)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for float meters and convert it to cm. Generates
+ * NLA_U32 or fills an uint32_t.
+ */
+int nl_parse_direct_m2cm(struct nl_context *nlctx, uint16_t type,
+ const void *data __maybe_unused,
+ struct nl_msg_buff *msgbuff, void *dest)
+{
+ const char *arg = *nlctx->argp;
+ float meters = 0.0;
+ uint32_t cm;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+ ret = parse_float(arg, &meters, 0, 150);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ return ret;
+ }
+
+ cm = (uint32_t)(meters * 100 + 0.5);
+ if (dest)
+ *(uint32_t *)dest = cm;
+ return (type && ethnla_put_u32(msgbuff, type, cm)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for (tri-state) bool. Expects "name on|off", generates
+ * NLA_U8 which is 1 for "on" and 0 for "off".
+ */
+int nl_parse_u8bool(struct nl_context *nlctx, uint16_t type,
+ const void *data __maybe_unused,
+ struct nl_msg_buff *msgbuff, void *dest)
+{
+ const char *arg = *nlctx->argp;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+ if (!strcmp(arg, "on")) {
+ if (dest)
+ *(uint8_t *)dest = 1;
+ ret = type ? ethnla_put_u8(msgbuff, type, 1) : 0;
+ } else if (!strcmp(arg, "off")) {
+ if (dest)
+ *(uint8_t *)dest = 0;
+ ret = type ? ethnla_put_u8(msgbuff, type, 0) : 0;
+ } else {
+ parser_err_invalid_value(nlctx, arg);
+ return -EINVAL;
+ }
+
+ return ret ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for 32-bit lookup value. Expects a string argument, looks it
+ * up in a table, generates NLA_U32 or fills uint32_t variable. The @data
+ * parameter is a null terminated array of struct lookup_entry_u32.
+ */
+int nl_parse_lookup_u32(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest)
+{
+ const char *arg = *nlctx->argp;
+ uint32_t val;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+ ret = lookup_u32(arg, &val, data);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ return ret;
+ }
+
+ if (dest)
+ *(uint32_t *)dest = val;
+ return (type && ethnla_put_u32(msgbuff, type, val)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for 8-bit lookup value. Expects a string argument, looks it
+ * up in a table, generates NLA_U8 or fills uint8_t variable. The @data
+ * parameter is a null terminated array of struct lookup_entry_u8.
+ */
+int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest)
+{
+ const char *arg = *nlctx->argp;
+ uint8_t val;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+ ret = lookup_u8(arg, &val, data);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ return ret;
+ }
+
+ if (dest)
+ *(uint8_t *)dest = val;
+ return (type && ethnla_put_u8(msgbuff, type, val)) ? -EMSGSIZE : 0;
+}
+
+/* number of significant bits */
+static unsigned int __nsb(uint32_t x)
+{
+ unsigned int ret = 0;
+
+ if (x & 0xffff0000U) {
+ x >>= 16;
+ ret += 16;
+ }
+ if (x & 0xff00U) {
+ x >>= 8;
+ ret += 8;
+ }
+ if (x & 0xf0U) {
+ x >>= 4;
+ ret += 4;
+ }
+ if (x & 0xcU) {
+ x >>= 2;
+ ret += 2;
+ }
+ if (x & 0x2U) {
+ x >>= 1;
+ ret += 1;
+ }
+
+ return ret + x;
+}
+
+static bool __is_hex(char c)
+{
+ if (isdigit(c))
+ return true;
+ else
+ return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+}
+
+static unsigned int __hex_val(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 0xa;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 0xa;
+ return 0;
+}
+
+static bool __bytestr_delim(const char *p, char delim)
+{
+ return !*p || (delim ? (*p == delim) : !__is_hex(*p));
+}
+
+/* Parser handler for generic byte string in MAC-like format. Expects string
+ * argument in the "[[:xdigit:]]{2}(:[[:xdigit:]]{2})*" format, generates
+ * NLA_BINARY or fills a struct byte_str_value (if @dest is not null and the
+ * handler succeeds, caller is responsible for freeing the value). The @data
+ * parameter points to struct byte_str_parser_data.
+ */
+int nl_parse_byte_str(struct nl_context *nlctx, uint16_t type, const void *data,
+ struct nl_msg_buff *msgbuff, void *dest)
+{
+ const struct byte_str_parser_data *pdata = data;
+ struct byte_str_value *dest_value = dest;
+ const char *arg = *nlctx->argp;
+ uint8_t *val = NULL;
+ unsigned int len, i;
+ const char *p;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+
+ len = 0;
+ p = arg;
+ if (!*p)
+ goto err;
+ while (true) {
+ len++;
+ if (!__bytestr_delim(p, pdata->delim))
+ p++;
+ if (!__bytestr_delim(p, pdata->delim))
+ p++;
+ if (!__bytestr_delim(p, pdata->delim))
+ goto err;
+ if (!*p)
+ break;
+ p++;
+ if (*p && __bytestr_delim(p, pdata->delim))
+ goto err;
+ }
+ if (len < pdata->min_len || (pdata->max_len && len > pdata->max_len))
+ goto err;
+ val = malloc(len);
+ if (!val)
+ return -ENOMEM;
+
+ p = arg;
+ for (i = 0; i < len; i++) {
+ uint8_t byte = 0;
+
+ if (!__is_hex(*p))
+ goto err;
+ while (__is_hex(*p))
+ byte = 16 * byte + __hex_val(*p++);
+ if (!__bytestr_delim(p, pdata->delim))
+ goto err;
+ val[i] = byte;
+ if (*p)
+ p++;
+ }
+ ret = type ? ethnla_put(msgbuff, type, len, val) : 0;
+ if (dest) {
+ dest_value->len = len;
+ dest_value->data = val;
+ } else {
+ free(val);
+ }
+ return ret;
+
+err:
+ free(val);
+ fprintf(stderr, "ethtool (%s): invalid value '%s' of parameter '%s'\n",
+ nlctx->cmd, arg, nlctx->param);
+ return -EINVAL;
+}
+
+/* Parser handler for parameters recognized for backward compatibility but
+ * supposed to fail without passing to kernel. Does not generate any netlink
+ * attributes of fill any variable. The @data parameter points to struct
+ * error_parser_params (error message, return value and number of extra
+ * arguments to skip).
+ */
+int nl_parse_error(struct nl_context *nlctx, uint16_t type __maybe_unused,
+ const void *data, struct nl_msg_buff *msgbuff __maybe_unused,
+ void *dest __maybe_unused)
+{
+ const struct error_parser_data *parser_data = data;
+ unsigned int skip = parser_data->extra_args;
+
+ fprintf(stderr, "ethtool (%s): ", nlctx->cmd);
+ fprintf(stderr, parser_data->err_msg, nlctx->param);
+ if (nlctx->argc < skip) {
+ fprintf(stderr, "ethtool (%s): too few arguments for parameter '%s' (expected %u)\n",
+ nlctx->cmd, nlctx->param, skip);
+ } else {
+ nlctx->argp += skip;
+ nlctx->argc -= skip;
+ }
+
+ return parser_data->ret_val;
+}
+
+/* bitset parser handlers */
+
+/* Return true if a bitset argument should be parsed as numeric, i.e.
+ * (a) it starts with '0x'
+ * (b) it consists only of hex digits and at most one slash which can be
+ * optionally followed by "0x"; if no_mask is true, slash is not allowed
+ */
+static bool is_numeric_bitset(const char *arg, bool no_mask)
+{
+ const char *p = arg;
+ bool has_slash = false;
+
+ if (!arg)
+ return false;
+ if (__prefix_0x(arg))
+ return true;
+ while (*p) {
+ if (*p == '/') {
+ if (has_slash || no_mask)
+ return false;
+ has_slash = true;
+ p++;
+ if (__prefix_0x(p))
+ p += 2;
+ continue;
+ }
+ if (!__is_hex(*p))
+ return false;
+ p++;
+ }
+ return true;
+}
+
+#define __MAX_U32_DIGITS 10
+
+/* Parse hex string (without leading "0x") into a bitmap consisting of 32-bit
+ * words. Caller must make sure arg is at least len characters long and dst has
+ * place for at least (len + 7) / 8 32-bit words. If force_hex is false, allow
+ * also base 10 unsigned 32-bit value.
+ *
+ * Returns number of significant bits in the bitmap on success and negative
+ * value on error.
+ */
+static int __parse_num_string(const char *arg, unsigned int len, uint32_t *dst,
+ bool force_hex)
+{
+ char buff[__MAX_U32_DIGITS + 1] = {};
+ unsigned int nbits = 0;
+ const char *p = arg;
+
+ if (!len)
+ return -EINVAL;
+ if (!force_hex && len <= __MAX_U32_DIGITS) {
+ strncpy(buff, arg, len);
+ if (!buff[__MAX_U32_DIGITS]) {
+ u32 val;
+ int ret;
+
+ ret = parse_u32d(buff, &val);
+ if (!ret) {
+ *dst = val;
+ return __nsb(val);
+ }
+ }
+ }
+
+ dst += (len - 1) / 8;
+ while (len > 0) {
+ unsigned int chunk = (len % 8) ?: 8;
+ unsigned long val;
+ char *endp;
+
+ memcpy(buff, p, chunk);
+ buff[chunk] = '\0';
+ val = strtoul(buff, &endp, 16);
+ if (*endp)
+ return -EINVAL;
+ *dst-- = (uint32_t)val;
+ if (nbits)
+ nbits += 4 * chunk;
+ else
+ nbits = __nsb(val);
+
+ p += chunk;
+ len -= chunk;
+ }
+ return nbits;
+}
+
+/* Parse bitset provided as a base 16 numeric value (@no_mask is true) or pair
+ * of base 16 numeric values separated by '/' (@no_mask is false). The "0x"
+ * prefix is optional. Generates bitset nested attribute in compact form.
+ */
+static int parse_numeric_bitset(struct nl_context *nlctx, uint16_t type,
+ bool no_mask, bool force_hex,
+ struct nl_msg_buff *msgbuff)
+{
+ unsigned int nwords, len1, len2;
+ const char *arg = *nlctx->argp;
+ bool force_hex1 = force_hex;
+ bool force_hex2 = force_hex;
+ uint32_t *value = NULL;
+ uint32_t *mask = NULL;
+ struct nlattr *nest;
+ const char *maskptr;
+ int ret = 0;
+ int nbits;
+
+ if (__prefix_0x(arg)) {
+ force_hex1 = true;
+ arg += 2;
+ }
+
+ maskptr = strchr(arg, '/');
+ if (maskptr && no_mask) {
+ parser_err_invalid_value(nlctx, arg);
+ return -EINVAL;
+ }
+ len1 = maskptr ? (unsigned int)(maskptr - arg) : strlen(arg);
+ nwords = DIV_ROUND_UP(len1, 8);
+ nbits = 0;
+
+ if (maskptr) {
+ maskptr++;
+ if (__prefix_0x(maskptr)) {
+ maskptr += 2;
+ force_hex2 = true;
+ }
+ len2 = strlen(maskptr);
+ if (len2 > len1)
+ nwords = DIV_ROUND_UP(len2, 8);
+ mask = calloc(nwords, sizeof(uint32_t));
+ if (!mask)
+ return -ENOMEM;
+ ret = __parse_num_string(maskptr, strlen(maskptr), mask,
+ force_hex2);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ goto out_free;
+ }
+ nbits = ret;
+ }
+
+ value = calloc(nwords, sizeof(uint32_t));
+ if (!value) {
+ free(mask);
+ return -ENOMEM;
+ }
+ ret = __parse_num_string(arg, len1, value, force_hex1);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ goto out_free;
+ }
+ nbits = (nbits < ret) ? ret : nbits;
+ nwords = (nbits + 31) / 32;
+
+ ret = 0;
+ if (!type)
+ goto out_free;
+ ret = -EMSGSIZE;
+ nest = ethnla_nest_start(msgbuff, type);
+ if (!nest)
+ goto out_free;
+ if (ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, !mask) ||
+ ethnla_put_u32(msgbuff, ETHTOOL_A_BITSET_SIZE, nbits) ||
+ ethnla_put(msgbuff, ETHTOOL_A_BITSET_VALUE,
+ nwords * sizeof(uint32_t), value) ||
+ (mask &&
+ ethnla_put(msgbuff, ETHTOOL_A_BITSET_MASK,
+ nwords * sizeof(uint32_t), mask)))
+ goto out_free;
+ ethnla_nest_end(msgbuff, nest);
+ ret = 0;
+
+out_free:
+ free(value);
+ free(mask);
+ nlctx->argp++;
+ nlctx->argc--;
+ return ret;
+}
+
+/* Parse bitset provided as series of "name on|off" pairs (@no_mask is false)
+ * or names (@no_mask is true). Generates bitset nested attribute in verbose
+ * form with names from command line.
+ */
+static int parse_name_bitset(struct nl_context *nlctx, uint16_t type,
+ bool no_mask, struct nl_msg_buff *msgbuff)
+{
+ struct nlattr *bitset_attr;
+ struct nlattr *bits_attr;
+ struct nlattr *bit_attr;
+ int ret;
+
+ bitset_attr = ethnla_nest_start(msgbuff, type);
+ if (!bitset_attr)
+ return -EMSGSIZE;
+ ret = -EMSGSIZE;
+ if (no_mask && ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, true))
+ goto err;
+ bits_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS);
+ if (!bits_attr)
+ goto err;
+
+ while (nlctx->argc > 0) {
+ bool bit_val = true;
+
+ if (!strcmp(*nlctx->argp, "--")) {
+ nlctx->argp++;
+ nlctx->argc--;
+ break;
+ }
+ ret = -EINVAL;
+ if (!no_mask) {
+ if (nlctx->argc < 2 ||
+ (strcmp(nlctx->argp[1], "on") &&
+ strcmp(nlctx->argp[1], "off"))) {
+ parser_err_invalid_flag(nlctx, *nlctx->argp);
+ goto err;
+ }
+ bit_val = !strcmp(nlctx->argp[1], "on");
+ }
+
+ ret = -EMSGSIZE;
+ bit_attr = ethnla_nest_start(msgbuff,
+ ETHTOOL_A_BITSET_BITS_BIT);
+ if (!bit_attr)
+ goto err;
+ if (ethnla_put_strz(msgbuff, ETHTOOL_A_BITSET_BIT_NAME,
+ nlctx->argp[0]))
+ goto err;
+ if (!no_mask &&
+ ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_BIT_VALUE,
+ bit_val))
+ goto err;
+ ethnla_nest_end(msgbuff, bit_attr);
+
+ nlctx->argp += (no_mask ? 1 : 2);
+ nlctx->argc -= (no_mask ? 1 : 2);
+ }
+
+ ethnla_nest_end(msgbuff, bits_attr);
+ ethnla_nest_end(msgbuff, bitset_attr);
+ return 0;
+err:
+ ethnla_nest_cancel(msgbuff, bitset_attr);
+ return ret;
+}
+
+static bool is_char_bitset(const char *arg,
+ const struct char_bitset_parser_data *data)
+{
+ bool mask = (arg[0] == '+' || arg[0] == '-');
+ unsigned int i;
+ const char *p;
+
+ for (p = arg; *p; p++) {
+ if (*p == data->reset_char)
+ continue;
+ if (mask && (*p == '+' || *p == '-'))
+ continue;
+ for (i = 0; i < data->nbits; i++)
+ if (*p == data->bit_chars[i])
+ goto found;
+ return false;
+found:
+ ;
+ }
+
+ return true;
+}
+
+/* Parse bitset provided as a string consisting of characters corresponding to
+ * bit indices. The "reset character" resets the no-mask bitset to empty. If
+ * the first character is '+' or '-', generated bitset has mask and '+' and
+ * '-' switch between enabling and disabling the following bits (i.e. their
+ * value being true/false). In such case, "reset character" is not allowed.
+ */
+static int parse_char_bitset(struct nl_context *nlctx, uint16_t type,
+ const struct char_bitset_parser_data *data,
+ struct nl_msg_buff *msgbuff)
+{
+ const char *arg = *nlctx->argp;
+ struct nlattr *bitset_attr;
+ struct nlattr *saved_pos;
+ struct nlattr *bits_attr;
+ struct nlattr *bit_attr;
+ unsigned int idx;
+ bool val = true;
+ const char *p;
+ bool no_mask;
+ int ret;
+
+ no_mask = data->no_mask || !(arg[0] == '+' || arg[0] == '-');
+ bitset_attr = ethnla_nest_start(msgbuff, type);
+ if (!bitset_attr)
+ return -EMSGSIZE;
+ ret = -EMSGSIZE;
+ if (no_mask && ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, true))
+ goto err;
+ bits_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS);
+ if (!bits_attr)
+ goto err;
+ saved_pos = mnl_nlmsg_get_payload_tail(msgbuff->nlhdr);
+
+ for (p = arg; *p; p++) {
+ if (*p == '+' || *p == '-') {
+ if (no_mask) {
+ parser_err_invalid_value(nlctx, arg);
+ ret = -EINVAL;
+ goto err;
+ }
+ val = (*p == '+');
+ continue;
+ }
+ if (*p == data->reset_char) {
+ if (no_mask) {
+ mnl_attr_nest_cancel(msgbuff->nlhdr, saved_pos);
+ continue;
+ }
+ fprintf(stderr, "ethtool (%s): invalid char '%c' in '%s' for parameter '%s'\n",
+ nlctx->cmd, *p, arg, nlctx->param);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ for (idx = 0; idx < data->nbits; idx++) {
+ if (data->bit_chars[idx] == *p)
+ break;
+ }
+ if (idx >= data->nbits) {
+ fprintf(stderr, "ethtool (%s): invalid char '%c' in '%s' for parameter '%s'\n",
+ nlctx->cmd, *p, arg, nlctx->param);
+ ret = -EINVAL;
+ goto err;
+ }
+ bit_attr = ethnla_nest_start(msgbuff,
+ ETHTOOL_A_BITSET_BITS_BIT);
+ if (!bit_attr)
+ goto err;
+ if (ethnla_put_u32(msgbuff, ETHTOOL_A_BITSET_BIT_INDEX, idx))
+ goto err;
+ if (!no_mask &&
+ ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_BIT_VALUE, val))
+ goto err;
+ ethnla_nest_end(msgbuff, bit_attr);
+ }
+
+ ethnla_nest_end(msgbuff, bits_attr);
+ ethnla_nest_end(msgbuff, bitset_attr);
+ nlctx->argp++;
+ nlctx->argc--;
+ return 0;
+err:
+ ethnla_nest_cancel(msgbuff, bitset_attr);
+ return ret;
+}
+
+/* Parser handler for bitset. Expects either a numeric value (base 16 or 10
+ * (unless force_hex is set)), optionally followed by '/' and another numeric
+ * value (mask, unless no_mask is set), or a series of "name on|off" pairs
+ * (no_mask not set) or names (no_mask set). In the latter case, names are
+ * passed on as they are and kernel performs their interpretation and
+ * validation. The @data parameter points to struct bitset_parser_data.
+ * Generates only a bitset nested attribute. Fails if @type is zero or @dest
+ * is not null.
+ */
+int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data,
+ struct nl_msg_buff *msgbuff, void *dest)
+{
+ const struct bitset_parser_data *parser_data = data;
+
+ if (!type || dest) {
+ fprintf(stderr, "ethtool (%s): internal error parsing '%s'\n",
+ nlctx->cmd, nlctx->param);
+ return -EFAULT;
+ }
+ if (is_numeric_bitset(*nlctx->argp, false))
+ return parse_numeric_bitset(nlctx, type, parser_data->no_mask,
+ parser_data->force_hex, msgbuff);
+ else
+ return parse_name_bitset(nlctx, type, parser_data->no_mask,
+ msgbuff);
+}
+
+/* Parser handler for bitset. Expects either a numeric value (base 10 or 16),
+ * optionally followed by '/' and another numeric value (mask, unless no_mask
+ * is set), or a string consisting of characters corresponding to bit indices.
+ * The @data parameter points to struct char_bitset_parser_data. Generates
+ * biset nested attribute. Fails if type is zero or if @dest is not null.
+ */
+int nl_parse_char_bitset(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest)
+{
+ const struct char_bitset_parser_data *parser_data = data;
+
+ if (!type || dest) {
+ fprintf(stderr, "ethtool (%s): internal error parsing '%s'\n",
+ nlctx->cmd, nlctx->param);
+ return -EFAULT;
+ }
+ if (is_char_bitset(*nlctx->argp, data) ||
+ !is_numeric_bitset(*nlctx->argp, false))
+ return parse_char_bitset(nlctx, type, parser_data, msgbuff);
+ else
+ return parse_numeric_bitset(nlctx, type, parser_data->no_mask,
+ false, msgbuff);
+}
+
+/* parser implementation */
+
+static const struct param_parser *find_parser(const struct param_parser *params,
+ const char *arg)
+{
+ const struct param_parser *parser;
+
+ for (parser = params; parser->arg; parser++)
+ if (!strcmp(arg, parser->arg))
+ return parser;
+ return NULL;
+}
+
+static bool __parser_bit(const uint64_t *map, unsigned int idx)
+{
+ return map[idx / 64] & (1 << (idx % 64));
+}
+
+static void __parser_set(uint64_t *map, unsigned int idx)
+{
+ map[idx / 64] |= (1 << (idx % 64));
+}
+
+static void __parser_set_group(const struct param_parser *params,
+ uint64_t *map, unsigned int alt_group)
+{
+ const struct param_parser *parser;
+ unsigned int idx = 0;
+
+ for (parser = params; parser->arg; parser++, idx++)
+ if (parser->alt_group == alt_group)
+ __parser_set(map, idx);
+}
+
+struct tmp_buff {
+ struct nl_msg_buff *msgbuff;
+ unsigned int id;
+ unsigned int orig_len;
+ struct tmp_buff *next;
+};
+
+static struct tmp_buff *tmp_buff_find(struct tmp_buff *head, unsigned int id)
+{
+ struct tmp_buff *buff;
+
+ for (buff = head; buff; buff = buff->next)
+ if (buff->id == id)
+ break;
+
+ return buff;
+}
+
+static struct tmp_buff *tmp_buff_find_or_create(struct tmp_buff **phead,
+ unsigned int id)
+{
+ struct tmp_buff **pbuff;
+ struct tmp_buff *new_buff;
+
+ for (pbuff = phead; *pbuff; pbuff = &(*pbuff)->next)
+ if ((*pbuff)->id == id)
+ return *pbuff;
+
+ new_buff = malloc(sizeof(*new_buff));
+ if (!new_buff)
+ return NULL;
+ new_buff->id = id;
+ new_buff->msgbuff = malloc(sizeof(*new_buff->msgbuff));
+ if (!new_buff->msgbuff) {
+ free(new_buff);
+ return NULL;
+ }
+ msgbuff_init(new_buff->msgbuff);
+ new_buff->next = NULL;
+ *pbuff = new_buff;
+
+ return new_buff;
+}
+
+static void tmp_buff_destroy(struct tmp_buff *head)
+{
+ struct tmp_buff *buff = head;
+ struct tmp_buff *next;
+
+ while (buff) {
+ next = buff->next;
+ if (buff->msgbuff) {
+ msgbuff_done(buff->msgbuff);
+ free(buff->msgbuff);
+ }
+ free(buff);
+ buff = next;
+ }
+}
+
+/* Main entry point of parser implementation.
+ * @nlctx: netlink context
+ * @params: array of struct param_parser describing expected arguments
+ * and their handlers; the array must be terminated by null
+ * element {}
+ * @dest: optional destination to copy parsed data to (at
+ * param_parser::offset)
+ * @group_style: defines if identifiers in .group represent separate messages,
+ * nested attributes or are not allowed
+ * @msgbuffs: (only used for @group_style = PARSER_GROUP_MSG) array to store
+ * pointers to composed messages; caller must make sure this
+ * array is sufficient, i.e. that it has at least as many entries
+ * as the number of different .group values in params array;
+ * entries are filled from the start, remaining entries are not
+ * modified; caller should zero initialize the array before
+ * calling nl_parser()
+ */
+int nl_parser(struct nl_context *nlctx, const struct param_parser *params,
+ void *dest, enum parser_group_style group_style,
+ struct nl_msg_buff **msgbuffs)
+{
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ const struct param_parser *parser;
+ struct tmp_buff *buffs = NULL;
+ unsigned int n_msgbuffs = 0;
+ struct tmp_buff *buff;
+ unsigned int n_params;
+ uint64_t *params_seen;
+ int ret;
+
+ n_params = 0;
+ for (parser = params; parser->arg; parser++) {
+ struct nl_msg_buff *msgbuff;
+ struct nlattr *nest;
+
+ n_params++;
+ if (group_style == PARSER_GROUP_NONE || !parser->group)
+ continue;
+ ret = -ENOMEM;
+ buff = tmp_buff_find_or_create(&buffs, parser->group);
+ if (!buff)
+ goto out_free_buffs;
+ msgbuff = buff->msgbuff;
+ ret = msg_init(nlctx, msgbuff, parser->group,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ goto out_free_buffs;
+
+ switch (group_style) {
+ case PARSER_GROUP_NEST:
+ ret = -EMSGSIZE;
+ nest = ethnla_nest_start(buff->msgbuff, parser->group);
+ if (!nest)
+ goto out_free_buffs;
+ break;
+ case PARSER_GROUP_MSG:
+ if (ethnla_fill_header(msgbuff,
+ ETHTOOL_A_LINKINFO_HEADER,
+ nlctx->devname, 0))
+ goto out_free_buffs;
+ break;
+ default:
+ break;
+ }
+
+ buff->orig_len = msgbuff_len(msgbuff);
+ }
+ ret = -ENOMEM;
+ params_seen = calloc(DIV_ROUND_UP(n_params, 64), sizeof(uint64_t));
+ if (!params_seen)
+ goto out_free_buffs;
+
+ while (nlctx->argc > 0) {
+ struct nl_msg_buff *msgbuff;
+ void *param_dest;
+
+ nlctx->param = *nlctx->argp;
+ ret = -EINVAL;
+ parser = find_parser(params, nlctx->param);
+ if (!parser) {
+ parser_err_unknown_param(nlctx);
+ goto out_free;
+ }
+
+ /* check duplicates and minimum number of arguments */
+ if (__parser_bit(params_seen, parser - params)) {
+ parser_err_dup_param(nlctx);
+ goto out_free;
+ }
+ nlctx->argc--;
+ nlctx->argp++;
+ if (nlctx->argc < parser->min_argc) {
+ parser_err_min_argc(nlctx, parser->min_argc);
+ goto out_free;
+ }
+ if (parser->alt_group)
+ __parser_set_group(params, params_seen,
+ parser->alt_group);
+ else
+ __parser_set(params_seen, parser - params);
+
+ buff = NULL;
+ if (parser->group)
+ buff = tmp_buff_find(buffs, parser->group);
+ msgbuff = buff ? buff->msgbuff : &nlsk->msgbuff;
+
+ param_dest = dest ? ((char *)dest + parser->dest_offset) : NULL;
+ ret = parser->handler(nlctx, parser->type, parser->handler_data,
+ msgbuff, param_dest);
+ if (ret < 0)
+ goto out_free;
+ }
+
+ if (group_style == PARSER_GROUP_MSG) {
+ ret = -EOPNOTSUPP;
+ for (buff = buffs; buff; buff = buff->next)
+ if (msgbuff_len(buff->msgbuff) > buff->orig_len &&
+ netlink_cmd_check(nlctx->ctx, buff->id, false))
+ goto out_free;
+ }
+ for (buff = buffs; buff; buff = buff->next) {
+ struct nl_msg_buff *msgbuff = buff->msgbuff;
+
+ if (group_style == PARSER_GROUP_NONE ||
+ msgbuff_len(msgbuff) == buff->orig_len)
+ continue;
+ switch (group_style) {
+ case PARSER_GROUP_NEST:
+ ethnla_nest_end(msgbuff, msgbuff->payload);
+ ret = msgbuff_append(&nlsk->msgbuff, msgbuff);
+ if (ret < 0)
+ goto out_free;
+ break;
+ case PARSER_GROUP_MSG:
+ msgbuffs[n_msgbuffs++] = msgbuff;
+ buff->msgbuff = NULL;
+ break;
+ default:
+ break;
+ }
+ }
+
+ ret = 0;
+out_free:
+ free(params_seen);
+out_free_buffs:
+ tmp_buff_destroy(buffs);
+ return ret;
+}
diff --git a/netlink/parser.h b/netlink/parser.h
new file mode 100644
index 0000000..8a4e8af
--- /dev/null
+++ b/netlink/parser.h
@@ -0,0 +1,153 @@
+/*
+ * parser.h - netlink command line parser
+ *
+ * Interface for command line parser used by netlink code.
+ */
+
+#ifndef ETHTOOL_NETLINK_PARSER_H__
+#define ETHTOOL_NETLINK_PARSER_H__
+
+#include <stddef.h>
+
+#include "netlink.h"
+
+/* Some commands need to generate multiple netlink messages or multiple nested
+ * attributes from one set of command line parameters. Argument @group_style
+ * of nl_parser() takes one of three values:
+ *
+ * PARSER_GROUP_NONE - no grouping, flat series of attributes (default)
+ * PARSER_GROUP_NEST - one nest for each @group value (@group is nest type)
+ * PARSER_GROUP_MSG - one message for each @group value (@group is command)
+ */
+enum parser_group_style {
+ PARSER_GROUP_NONE = 0,
+ PARSER_GROUP_NEST,
+ PARSER_GROUP_MSG,
+};
+
+typedef int (*param_parser_cb_t)(struct nl_context *, uint16_t, const void *,
+ struct nl_msg_buff *, void *);
+
+struct param_parser {
+ /* command line parameter handled by this entry */
+ const char *arg;
+ /* group id (see enum parser_group_style above) */
+ unsigned int group;
+ /* netlink attribute type */
+ uint16_t type; /* netlink attribute type */
+ /* function called to parse parameter and its value */
+ param_parser_cb_t handler;
+ /* pointer passed as @data argument of the handler */
+ const void *handler_data;
+ /* minimum number of extra arguments after this parameter */
+ unsigned int min_argc;
+ /* if @dest is passed to nl_parser(), offset to store value */
+ unsigned int dest_offset;
+ /* parameter alternative group - only one parameter from a group
+ * can be specified, 0 means no group
+ */
+ unsigned int alt_group;
+};
+
+/* data structures used for handler data */
+
+/* used by nl_parse_lookup_u32() */
+struct lookup_entry_u32 {
+ const char *arg;
+ uint32_t val;
+};
+
+/* used by nl_parse_lookup_u8() */
+struct lookup_entry_u8 {
+ const char *arg;
+ uint8_t val;
+};
+
+/* used by nl_parse_byte_str() */
+struct byte_str_parser_data {
+ unsigned int min_len;
+ unsigned int max_len;
+ char delim;
+};
+
+/* used for value stored by nl_parse_byte_str() */
+struct byte_str_value {
+ unsigned int len;
+ u8 *data;
+};
+
+/* used by nl_parse_error() */
+struct error_parser_data {
+ const char *err_msg;
+ int ret_val;
+ unsigned int extra_args;
+};
+
+/* used by nl_parse_bitset() */
+struct bitset_parser_data {
+ bool force_hex;
+ bool no_mask;
+};
+
+/* used by nl_parse_char_bitset() */
+struct char_bitset_parser_data {
+ const char *bit_chars;
+ unsigned int nbits;
+ char reset_char;
+ bool no_mask;
+};
+
+int parse_u32(const char *arg, uint32_t *result);
+
+/* parser handlers to use as param_parser::handler */
+
+/* NLA_FLAG represented by on | off */
+int nl_parse_flag(struct nl_context *nlctx, uint16_t type, const void *data,
+ struct nl_msg_buff *msgbuff, void *dest);
+/* NLA_NUL_STRING represented by a string argument */
+int nl_parse_string(struct nl_context *nlctx, uint16_t type, const void *data,
+ struct nl_msg_buff *msgbuff, void *dest);
+/* NLA_U32 represented as numeric argument */
+int nl_parse_direct_u32(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest);
+/* NLA_U8 represented as numeric argument */
+int nl_parse_direct_u8(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest);
+/* NLA_U32 represented as float number of meters, converted to cm. */
+int nl_parse_direct_m2cm(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest);
+/* NLA_U8 represented as on | off */
+int nl_parse_u8bool(struct nl_context *nlctx, uint16_t type, const void *data,
+ struct nl_msg_buff *msgbuff, void *dest);
+/* NLA_U32 represented by a string argument (lookup table) */
+int nl_parse_lookup_u32(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest);
+/* NLA_U8 represented by a string argument (lookup table) */
+int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest);
+/* always fail (for deprecated parameters) */
+int nl_parse_error(struct nl_context *nlctx, uint16_t type, const void *data,
+ struct nl_msg_buff *msgbuff, void *dest);
+/* NLA_BINARY represented by %x:%x:...%x (e.g. a MAC address) */
+int nl_parse_byte_str(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest);
+/* bitset represented by %x[/%x] or name on|off ... [--] */
+int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data,
+ struct nl_msg_buff *msgbuff, void *dest);
+/* bitset represented by %u[/%u] or string (characters for bits) */
+int nl_parse_char_bitset(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest);
+
+/* main entry point called to parse the command line */
+int nl_parser(struct nl_context *nlctx, const struct param_parser *params,
+ void *dest, enum parser_group_style group_style,
+ struct nl_msg_buff **msgbuffs);
+
+#endif /* ETHTOOL_NETLINK_PARSER_H__ */
diff --git a/netlink/pause.c b/netlink/pause.c
new file mode 100644
index 0000000..da444bd
--- /dev/null
+++ b/netlink/pause.c
@@ -0,0 +1,331 @@
+/*
+ * pause.c - netlink implementation of pause commands
+ *
+ * Implementation of "ethtool -a <dev>" and "ethtool -A <dev> ..."
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "bitset.h"
+#include "parser.h"
+
+/* PAUSE_GET */
+
+struct pause_autoneg_status {
+ bool pause;
+ bool asym_pause;
+};
+
+static void pause_autoneg_walker(unsigned int idx,
+ const char *name __maybe_unused, bool val,
+ void *data)
+{
+ struct pause_autoneg_status *status = data;
+
+ if (idx == ETHTOOL_LINK_MODE_Pause_BIT)
+ status->pause = val;
+ if (idx == ETHTOOL_LINK_MODE_Asym_Pause_BIT)
+ status->asym_pause = val;
+}
+
+static int pause_autoneg_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct pause_autoneg_status ours = {};
+ struct pause_autoneg_status peer = {};
+ struct nl_context *nlctx = data;
+ uint8_t rx_status = false;
+ uint8_t tx_status = false;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+
+ if (!tb[ETHTOOL_A_LINKMODES_OURS] || !tb[ETHTOOL_A_LINKMODES_PEER])
+ return MNL_CB_OK;
+ ret = walk_bitset(tb[ETHTOOL_A_LINKMODES_OURS], NULL,
+ pause_autoneg_walker, &ours);
+ if (ret < 0)
+ return err_ret;
+ ret = walk_bitset(tb[ETHTOOL_A_LINKMODES_PEER], NULL,
+ pause_autoneg_walker, &peer);
+ if (ret < 0)
+ return err_ret;
+
+ if (ours.pause && peer.pause) {
+ rx_status = true;
+ tx_status = true;
+ } else if (ours.asym_pause && peer.asym_pause) {
+ if (ours.pause)
+ rx_status = true;
+ else if (peer.pause)
+ tx_status = true;
+ }
+
+ open_json_object("negotiated");
+ show_bool_val("rx", "RX negotiated: %s\n", &rx_status);
+ show_bool_val("tx", "TX negotiated: %s\n", &tx_status);
+ close_json_object();
+
+ return MNL_CB_OK;
+}
+
+static int show_pause_autoneg_status(struct nl_context *nlctx)
+{
+ const char *saved_devname;
+ int ret;
+
+ saved_devname = nlctx->ctx->devname;
+ nlctx->ctx->devname = nlctx->devname;
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ goto out;
+
+ ret = nlsock_prep_get_request(nlctx->ethnl2_socket,
+ ETHTOOL_MSG_LINKMODES_GET,
+ ETHTOOL_A_LINKMODES_HEADER,
+ ETHTOOL_FLAG_COMPACT_BITSETS);
+ if (ret < 0)
+ goto out;
+ ret = nlsock_send_get_request(nlctx->ethnl2_socket, pause_autoneg_cb);
+
+out:
+ nlctx->ctx->devname = saved_devname;
+ return ret;
+}
+
+static int show_pause_stats(const struct nlattr *nest)
+{
+ const struct nlattr *tb[ETHTOOL_A_PAUSE_STAT_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ static const struct {
+ unsigned int attr;
+ char *name;
+ } stats[] = {
+ { ETHTOOL_A_PAUSE_STAT_TX_FRAMES, "tx_pause_frames" },
+ { ETHTOOL_A_PAUSE_STAT_RX_FRAMES, "rx_pause_frames" },
+ };
+ bool header = false;
+ unsigned int i;
+ size_t n;
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+
+ open_json_object("statistics");
+ for (i = 0; i < ARRAY_SIZE(stats); i++) {
+ char fmt[32];
+
+ if (!tb[stats[i].attr])
+ continue;
+
+ if (!header && !is_json_context()) {
+ printf("Statistics:\n");
+ header = true;
+ }
+
+ if (mnl_attr_validate(tb[stats[i].attr], MNL_TYPE_U64)) {
+ fprintf(stderr, "malformed netlink message (statistic)\n");
+ goto err_close_stats;
+ }
+
+ n = snprintf(fmt, sizeof(fmt), " %s: %%" PRIu64 "\n",
+ stats[i].name);
+ if (n >= sizeof(fmt)) {
+ fprintf(stderr, "internal error - malformed label\n");
+ goto err_close_stats;
+ }
+
+ print_u64(PRINT_ANY, stats[i].name, fmt,
+ mnl_attr_get_u64(tb[stats[i].attr]));
+ }
+ close_json_object();
+
+ return 0;
+
+err_close_stats:
+ close_json_object();
+ return -1;
+}
+
+int pause_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_PAUSE_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_PAUSE_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (silent)
+ print_nl();
+
+ open_json_object(NULL);
+
+ print_string(PRINT_ANY, "ifname", "Pause parameters for %s:\n",
+ nlctx->devname);
+
+ show_bool("autonegotiate", "Autonegotiate:\t%s\n",
+ tb[ETHTOOL_A_PAUSE_AUTONEG]);
+ show_bool("rx", "RX:\t\t%s\n", tb[ETHTOOL_A_PAUSE_RX]);
+ show_bool("tx", "TX:\t\t%s\n", tb[ETHTOOL_A_PAUSE_TX]);
+
+ if (!nlctx->is_monitor && tb[ETHTOOL_A_PAUSE_AUTONEG] &&
+ mnl_attr_get_u8(tb[ETHTOOL_A_PAUSE_AUTONEG])) {
+ ret = show_pause_autoneg_status(nlctx);
+ if (ret < 0)
+ goto err_close_dev;
+ }
+ if (tb[ETHTOOL_A_PAUSE_STATS]) {
+ ret = show_pause_stats(tb[ETHTOOL_A_PAUSE_STATS]);
+ if (ret < 0)
+ goto err_close_dev;
+ }
+ if (!silent)
+ print_nl();
+
+ close_json_object();
+
+ return MNL_CB_OK;
+
+err_close_dev:
+ close_json_object();
+ return err_ret;
+}
+
+static const struct lookup_entry_u32 stats_src_values[] = {
+ { .arg = "aggregate", .val = ETHTOOL_MAC_STATS_SRC_AGGREGATE },
+ { .arg = "emac", .val = ETHTOOL_MAC_STATS_SRC_EMAC },
+ { .arg = "pmac", .val = ETHTOOL_MAC_STATS_SRC_PMAC },
+ {}
+};
+
+static const struct param_parser gpause_params[] = {
+ {
+ .arg = "--src",
+ .type = ETHTOOL_A_PAUSE_STATS_SRC,
+ .handler = nl_parse_lookup_u32,
+ .handler_data = stats_src_values,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_gpause(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ u32 flags;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_PAUSE_GET, true))
+ return -EOPNOTSUPP;
+
+ flags = get_stats_flag(nlctx, ETHTOOL_MSG_PAUSE_GET,
+ ETHTOOL_A_PAUSE_HEADER);
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_PAUSE_GET,
+ ETHTOOL_A_PAUSE_HEADER, flags);
+ if (ret < 0)
+ return ret;
+
+ nlctx->cmd = "-a";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+
+ ret = nl_parser(nlctx, gpause_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, pause_reply_cb);
+ delete_json_obj();
+ return ret;
+}
+
+/* PAUSE_SET */
+
+static const struct param_parser spause_params[] = {
+ {
+ .arg = "autoneg",
+ .type = ETHTOOL_A_PAUSE_AUTONEG,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx",
+ .type = ETHTOOL_A_PAUSE_RX,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx",
+ .type = ETHTOOL_A_PAUSE_TX,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_spause(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_PAUSE_SET, false))
+ return -EOPNOTSUPP;
+
+ nlctx->cmd = "-A";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_PAUSE_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_PAUSE_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, spause_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return 76;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret == 0)
+ return 0;
+ else
+ return nlctx->exit_code ?: 76;
+}
diff --git a/netlink/permaddr.c b/netlink/permaddr.c
new file mode 100644
index 0000000..006eac6
--- /dev/null
+++ b/netlink/permaddr.c
@@ -0,0 +1,114 @@
+/*
+ * permaddr.c - netlink implementation of permanent address request
+ *
+ * Implementation of "ethtool -P <dev>"
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_link.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+
+/* PERMADDR_GET */
+
+static int permaddr_prep_request(struct nl_socket *nlsk)
+{
+ unsigned int nlm_flags = NLM_F_REQUEST | NLM_F_ACK;
+ struct nl_context *nlctx = nlsk->nlctx;
+ const char *devname = nlctx->ctx->devname;
+ struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+ struct ifinfomsg *ifinfo;
+ struct nlmsghdr *nlhdr;
+ int ret;
+
+ if (devname && !strcmp(devname, WILDCARD_DEVNAME)) {
+ devname = NULL;
+ nlm_flags |= NLM_F_DUMP;
+ }
+ nlctx->is_dump = !devname;
+
+ ret = msgbuff_realloc(msgbuff, MNL_SOCKET_BUFFER_SIZE);
+ if (ret < 0)
+ return ret;
+ memset(msgbuff->buff, '\0', NLMSG_HDRLEN + sizeof(*ifinfo));
+
+ nlhdr = mnl_nlmsg_put_header(msgbuff->buff);
+ nlhdr->nlmsg_type = RTM_GETLINK;
+ nlhdr->nlmsg_flags = nlm_flags;
+ msgbuff->nlhdr = nlhdr;
+ ifinfo = mnl_nlmsg_put_extra_header(nlhdr, sizeof(*ifinfo));
+
+ if (devname) {
+ uint16_t type = IFLA_IFNAME;
+
+ if (strlen(devname) >= IFNAMSIZ)
+ type = IFLA_ALT_IFNAME;
+ if (ethnla_put_strz(msgbuff, type, devname))
+ return -EMSGSIZE;
+ }
+ if (ethnla_put_u32(msgbuff, IFLA_EXT_MASK, RTEXT_FILTER_SKIP_STATS))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+int permaddr_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[__IFLA_MAX] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ const uint8_t *permaddr;
+ unsigned int i;
+ int ret;
+
+ if (nlhdr->nlmsg_type != RTM_NEWLINK)
+ goto err;
+ ret = mnl_attr_parse(nlhdr, sizeof(struct ifinfomsg), attr_cb,
+ &tb_info);
+ if (ret < 0 || !tb[IFLA_IFNAME])
+ goto err;
+ nlctx->devname = mnl_attr_get_str(tb[IFLA_IFNAME]);
+ if (!dev_ok(nlctx))
+ goto err;
+
+ if (!tb[IFLA_PERM_ADDRESS]) {
+ if (!nlctx->is_dump)
+ printf("Permanent address: not set\n");
+ return MNL_CB_OK;
+ }
+
+ if (nlctx->is_dump)
+ printf("Permanent address of %s:", nlctx->devname);
+ else
+ printf("Permanent address:");
+ permaddr = mnl_attr_get_payload(tb[IFLA_PERM_ADDRESS]);
+ for (i = 0; i < mnl_attr_get_payload_len(tb[IFLA_PERM_ADDRESS]); i++)
+ printf("%c%02x", i ? ':' : ' ', permaddr[i]);
+ putchar('\n');
+ return MNL_CB_OK;
+
+err:
+ if (nlctx->is_dump || nlctx->is_monitor)
+ return MNL_CB_OK;
+ nlctx->exit_code = 2;
+ return MNL_CB_ERROR;
+}
+
+int nl_permaddr(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ int ret;
+
+ ret = netlink_init_rtnl_socket(nlctx);
+ if (ret < 0)
+ return ret;
+ ret = permaddr_prep_request(nlctx->rtnl_socket);
+ if (ret < 0)
+ return ret;
+ return nlsock_send_get_request(nlctx->rtnl_socket, permaddr_reply_cb);
+}
diff --git a/netlink/plca.c b/netlink/plca.c
new file mode 100644
index 0000000..7d61e3b
--- /dev/null
+++ b/netlink/plca.c
@@ -0,0 +1,296 @@
+/*
+ * plca.c - netlink implementation of plca command
+ *
+ * Implementation of "ethtool --show-plca <dev>" and
+ * "ethtool --set-plca <dev> ..."
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "bitset.h"
+#include "parser.h"
+
+/* PLCA_GET_CFG */
+
+int plca_get_cfg_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_PLCA_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ bool silent;
+ int idv = 255;
+ int err_ret;
+ int val;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_PLCA_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (silent)
+ putchar('\n');
+
+ printf("PLCA settings for %s:\n", nlctx->devname);
+
+ // check if PLCA is enabled
+ printf("\tEnabled: ");
+
+ if (!tb[ETHTOOL_A_PLCA_ENABLED]) {
+ printf("not supported");
+ } else {
+ val = mnl_attr_get_u8(tb[ETHTOOL_A_PLCA_ENABLED]);
+ printf(val ? "Yes" : "No");
+ }
+ putchar('\n');
+
+ // get node ID
+ printf("\tlocal node ID: ");
+
+ if (!tb[ETHTOOL_A_PLCA_NODE_ID]) {
+ printf("not supported");
+ } else {
+ idv = mnl_attr_get_u32(tb[ETHTOOL_A_PLCA_NODE_ID]);
+ printf("%u (%s)", idv,
+ idv == 0 ? "coordinator" :
+ idv == 255 ? "unconfigured" : "follower");
+ }
+ putchar('\n');
+
+ // get node count
+ printf("\tNode count: ");
+ if (!tb[ETHTOOL_A_PLCA_NODE_CNT]) {
+ printf("not supported");
+ } else {
+ val = mnl_attr_get_u32(tb[ETHTOOL_A_PLCA_NODE_CNT]);
+ printf("%u", val);
+
+ // The node count is ignored by follower nodes. However, it can
+ // be pre-set to enable fast coordinator role switchover.
+ // Therefore, on a follower node we still wanto to show it,
+ // indicating it is not currently used.
+ if (tb[ETHTOOL_A_PLCA_NODE_ID] && idv != 0)
+ printf(" (ignored)");
+ }
+ putchar('\n');
+
+ // get TO timer (transmit opportunity timer)
+ printf("\tTO timer: ");
+ if (!tb[ETHTOOL_A_PLCA_TO_TMR]) {
+ printf("not supported");
+ } else {
+ val = mnl_attr_get_u32(tb[ETHTOOL_A_PLCA_TO_TMR]);
+ printf("%u BT", val);
+ }
+ putchar('\n');
+
+ // get burst count
+ printf("\tBurst count: ");
+ if (!tb[ETHTOOL_A_PLCA_BURST_CNT]) {
+ printf("not supported");
+ } else {
+ val = mnl_attr_get_u32(tb[ETHTOOL_A_PLCA_BURST_CNT]);
+ printf("%u (%s)", val,
+ val > 0 ? "enabled" : "disabled");
+ }
+ putchar('\n');
+
+ // get burst timer
+ printf("\tBurst timer: ");
+ if (!tb[ETHTOOL_A_PLCA_BURST_TMR]) {
+ printf("not supported");
+ } else {
+ val = mnl_attr_get_u32(tb[ETHTOOL_A_PLCA_BURST_TMR]);
+ printf("%u BT", val);
+ }
+ putchar('\n');
+
+ return MNL_CB_OK;
+}
+
+
+int nl_plca_get_cfg(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_PLCA_GET_CFG, true))
+ return -EOPNOTSUPP;
+
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_PLCA_GET_CFG,
+ ETHTOOL_A_PLCA_HEADER, 0);
+
+ if (ret < 0)
+ return ret;
+
+ return nlsock_send_get_request(nlsk, plca_get_cfg_reply_cb);
+}
+
+/* PLCA_SET_CFG */
+
+static const struct param_parser set_plca_params[] = {
+ {
+ .arg = "enable",
+ .type = ETHTOOL_A_PLCA_ENABLED,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "node-id",
+ .type = ETHTOOL_A_PLCA_NODE_ID,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "node-cnt",
+ .type = ETHTOOL_A_PLCA_NODE_CNT,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "to-tmr",
+ .type = ETHTOOL_A_PLCA_TO_TMR,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "burst-cnt",
+ .type = ETHTOOL_A_PLCA_BURST_CNT,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "burst-tmr",
+ .type = ETHTOOL_A_PLCA_BURST_TMR,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_plca_set_cfg(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_PLCA_SET_CFG, false))
+ return -EOPNOTSUPP;
+ if (!ctx->argc) {
+ fprintf(stderr,
+ "ethtool (--set-plca-cfg): parameters missing\n");
+ return 1;
+ }
+
+ nlctx->cmd = "--set-plca-cfg";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_PLCA_SET_CFG,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_PLCA_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, set_plca_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return 76;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret == 0)
+ return 0;
+ else
+ return nlctx->exit_code ?: 76;
+}
+
+/* PLCA_GET_STATUS */
+
+int plca_get_status_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_PLCA_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ bool silent;
+ int err_ret;
+ int ret;
+ u8 val;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_PLCA_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (silent)
+ putchar('\n');
+
+ printf("PLCA status of %s:\n", nlctx->devname);
+
+ // check whether the Open Alliance TC14 standard memory map is supported
+ printf("\tStatus: ");
+
+ if (!tb[ETHTOOL_A_PLCA_STATUS]) {
+ printf("not supported");
+ } else {
+ val = mnl_attr_get_u8(tb[ETHTOOL_A_PLCA_STATUS]);
+ printf(val ? "on" : "off");
+ }
+ putchar('\n');
+
+ return MNL_CB_OK;
+}
+
+
+int nl_plca_get_status(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_PLCA_GET_STATUS, true))
+ return -EOPNOTSUPP;
+
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_PLCA_GET_STATUS,
+ ETHTOOL_A_PLCA_HEADER, 0);
+
+ if (ret < 0)
+ return ret;
+
+ return nlsock_send_get_request(nlsk, plca_get_status_reply_cb);
+}
diff --git a/netlink/prettymsg.c b/netlink/prettymsg.c
new file mode 100644
index 0000000..fbf684f
--- /dev/null
+++ b/netlink/prettymsg.c
@@ -0,0 +1,262 @@
+/*
+ * prettymsg.c - human readable message dump
+ *
+ * Support for pretty print of an ethtool netlink message
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <linux/genetlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_link.h>
+#include <libmnl/libmnl.h>
+
+#include "prettymsg.h"
+
+#define __INDENT 4
+#define __DUMP_LINE 16
+#define __DUMP_BLOCK 4
+
+static void __print_binary_short(uint8_t *adata, unsigned int alen)
+{
+ unsigned int i;
+
+ if (!alen)
+ return;
+ printf("%02x", adata[0]);
+ for (i = 1; i < alen; i++)
+ printf("%c%02x", (i % __DUMP_BLOCK) ? ':' : ' ', adata[i]);
+}
+
+static void __print_binary_long(uint8_t *adata, unsigned int alen,
+ unsigned int level)
+{
+ unsigned int i;
+
+ for (i = 0; i < alen; i++) {
+ if (i % __DUMP_LINE == 0)
+ printf("\n%*s", __INDENT * (level + 2), "");
+ else if (i % __DUMP_BLOCK == 0)
+ printf(" ");
+ else
+ putchar(' ');
+ printf("%02x", adata[i]);
+ }
+}
+
+static int pretty_print_attr(const struct nlattr *attr,
+ const struct pretty_nla_desc *desc,
+ unsigned int ndesc, unsigned int level,
+ int err_offset, bool in_array)
+{
+ unsigned int alen = mnl_attr_get_payload_len(attr);
+ unsigned int atype = mnl_attr_get_type(attr);
+ unsigned int desc_idx = in_array ? 0 : atype;
+ void *adata = mnl_attr_get_payload(attr);
+ const struct pretty_nla_desc *adesc;
+ const char *prefix = " ";
+ bool nested;
+
+ adesc = (desc && desc_idx < ndesc) ? &desc[desc_idx] : NULL;
+ nested = (adesc && (adesc->format == NLA_NESTED ||
+ adesc->format == NLA_ARRAY)) ||
+ (attr->nla_type & NLA_F_NESTED);
+ if (err_offset >= 0 &&
+ err_offset < (nested ? NLA_HDRLEN : attr->nla_len)) {
+ prefix = "===>";
+ if (err_offset)
+ fprintf(stderr,
+ "ethtool: bad_attr inside an attribute (offset %d)\n",
+ err_offset);
+ }
+ if (adesc && adesc->name && !in_array)
+ printf("%s%*s%s", prefix, level * __INDENT, "", adesc->name);
+ else
+ printf("%s%*s[%u]", prefix, level * __INDENT, "", atype);
+
+ if (nested) {
+ struct nlattr *child;
+ int ret = 0;
+
+ putchar('\n');
+ mnl_attr_for_each_nested(child, attr) {
+ bool array = adesc && adesc->format == NLA_ARRAY;
+ unsigned int child_off;
+
+ child_off = (const char *)child - (const char *)attr;
+ ret = pretty_print_attr(child,
+ adesc ? adesc->children : NULL,
+ adesc ? adesc->n_children : 0,
+ level + 1,
+ err_offset - child_off, array);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+ }
+
+ printf(" = ");
+ switch(adesc ? adesc->format : NLA_BINARY) {
+ case NLA_U8:
+ printf("%u", mnl_attr_get_u8(attr));
+ break;
+ case NLA_U16:
+ printf("%u", mnl_attr_get_u16(attr));
+ break;
+ case NLA_U32:
+ printf("%u", mnl_attr_get_u32(attr));
+ break;
+ case NLA_U64:
+ printf("%" PRIu64, mnl_attr_get_u64(attr));
+ break;
+ case NLA_X8:
+ printf("0x%02x", mnl_attr_get_u8(attr));
+ break;
+ case NLA_X16:
+ printf("0x%04x", mnl_attr_get_u16(attr));
+ break;
+ case NLA_X32:
+ printf("0x%08x", mnl_attr_get_u32(attr));
+ break;
+ case NLA_X64:
+ printf("%" PRIx64, mnl_attr_get_u64(attr));
+ break;
+ case NLA_S8:
+ printf("%d", (int)mnl_attr_get_u8(attr));
+ break;
+ case NLA_S16:
+ printf("%d", (int)mnl_attr_get_u16(attr));
+ break;
+ case NLA_S32:
+ printf("%d", (int)mnl_attr_get_u32(attr));
+ break;
+ case NLA_S64:
+ printf("%" PRId64, (int64_t)mnl_attr_get_u64(attr));
+ break;
+ case NLA_STRING:
+ printf("\"%.*s\"", alen, (const char *)adata);
+ break;
+ case NLA_FLAG:
+ printf("true");
+ break;
+ case NLA_BOOL:
+ printf("%s", mnl_attr_get_u8(attr) ? "on" : "off");
+ break;
+ case NLA_U8_ENUM:
+ case NLA_U32_ENUM: {
+ uint32_t val;
+
+ if (adesc->format == NLA_U8_ENUM)
+ val = mnl_attr_get_u8(attr);
+ else
+ val = mnl_attr_get_u32(attr);
+
+ if (adesc && val < adesc->n_names && adesc->names[val])
+ printf("%s", adesc->names[val]);
+ else
+ printf("%u", val);
+ break;
+ }
+ default:
+ if (alen <= __DUMP_LINE)
+ __print_binary_short(adata, alen);
+ else
+ __print_binary_long(adata, alen, level);
+ }
+ putchar('\n');
+
+ return 0;
+}
+
+static int pretty_print_nlmsg(const struct nlmsghdr *nlhdr,
+ unsigned int payload_offset,
+ const struct pretty_nla_desc *desc,
+ unsigned int ndesc, unsigned int err_offset)
+{
+ const struct nlattr *attr;
+ int attr_offset;
+ int ret;
+
+ mnl_attr_for_each(attr, nlhdr, payload_offset) {
+ attr_offset = (const char *)attr - (const char *)nlhdr;
+ ret = pretty_print_attr(attr, desc, ndesc, 1,
+ err_offset - attr_offset, false);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+int pretty_print_genlmsg(const struct nlmsghdr *nlhdr,
+ const struct pretty_nlmsg_desc *desc,
+ unsigned int ndesc, unsigned int err_offset)
+{
+ const struct pretty_nlmsg_desc *msg_desc;
+ const struct genlmsghdr *genlhdr;
+
+ if (mnl_nlmsg_get_payload_len(nlhdr) < GENL_HDRLEN) {
+ fprintf(stderr, "ethtool: message too short (%u bytes)\n",
+ nlhdr->nlmsg_len);
+ return -EINVAL;
+ }
+ genlhdr = mnl_nlmsg_get_payload(nlhdr);
+ msg_desc = (desc && genlhdr->cmd < ndesc) ? &desc[genlhdr->cmd] : NULL;
+ if (msg_desc && msg_desc->name)
+ printf(" %s\n", msg_desc->name);
+ else
+ printf(" [%u]\n", genlhdr->cmd);
+
+ return pretty_print_nlmsg(nlhdr, GENL_HDRLEN,
+ msg_desc ? msg_desc->attrs : NULL,
+ msg_desc ? msg_desc->n_attrs : 0, err_offset);
+}
+
+static void rtm_link_summary(const struct ifinfomsg *ifinfo)
+{
+ if (ifinfo->ifi_family)
+ printf(" family=%u", ifinfo->ifi_family);
+ if (ifinfo->ifi_type)
+ printf(" type=0x%04x", ifinfo->ifi_type);
+ if (ifinfo->ifi_index)
+ printf(" ifindex=%d", ifinfo->ifi_index);
+ if (ifinfo->ifi_flags)
+ printf(" flags=0x%x", ifinfo->ifi_flags);
+ if (ifinfo->ifi_change)
+ printf(" change=0x%x", ifinfo->ifi_change);
+}
+
+int pretty_print_rtnlmsg(const struct nlmsghdr *nlhdr, unsigned int err_offset)
+{
+ const unsigned int idx = (nlhdr->nlmsg_type - RTM_BASE) / 4;
+ const struct pretty_nlmsg_desc *msg_desc = NULL;
+ unsigned int hdrlen = USHRT_MAX;
+
+ if (nlhdr->nlmsg_type < rtnl_msg_n_desc)
+ msg_desc = &rtnl_msg_desc[nlhdr->nlmsg_type];
+ if (idx < rtnl_msghdr_n_len)
+ hdrlen = rtnl_msghdr_lengths[idx];
+ if (hdrlen < USHRT_MAX && mnl_nlmsg_get_payload_len(nlhdr) < hdrlen) {
+ fprintf(stderr, "ethtool: message too short (%u bytes)\n",
+ nlhdr->nlmsg_len);
+ return -EINVAL;
+ }
+ if (msg_desc && msg_desc->name)
+ printf(" %s", msg_desc->name);
+ else
+ printf(" [%u]", nlhdr->nlmsg_type);
+ if (idx == (RTM_NEWLINK - RTM_BASE) / 4)
+ rtm_link_summary(mnl_nlmsg_get_payload(nlhdr));
+ putchar('\n');
+ if (hdrlen == USHRT_MAX)
+ return 0;
+
+ return pretty_print_nlmsg(nlhdr, hdrlen,
+ msg_desc ? msg_desc->attrs : NULL,
+ msg_desc ? msg_desc->n_attrs : 0, err_offset);
+}
diff --git a/netlink/prettymsg.h b/netlink/prettymsg.h
new file mode 100644
index 0000000..8ca1db3
--- /dev/null
+++ b/netlink/prettymsg.h
@@ -0,0 +1,146 @@
+/*
+ * prettymsg.h - human readable message dump
+ *
+ * Support for pretty print of an ethtool netlink message
+ */
+
+#ifndef ETHTOOL_NETLINK_PRETTYMSG_H__
+#define ETHTOOL_NETLINK_PRETTYMSG_H__
+
+#include <linux/netlink.h>
+
+/* data structures for message format descriptions */
+
+enum pretty_nla_format {
+ NLA_INVALID,
+ NLA_BINARY,
+ NLA_U8,
+ NLA_U16,
+ NLA_U32,
+ NLA_U64,
+ NLA_X8,
+ NLA_X16,
+ NLA_X32,
+ NLA_X64,
+ NLA_S8,
+ NLA_S16,
+ NLA_S32,
+ NLA_S64,
+ NLA_STRING,
+ NLA_FLAG,
+ NLA_BOOL,
+ NLA_NESTED,
+ NLA_ARRAY,
+ NLA_U8_ENUM,
+ NLA_U32_ENUM,
+};
+
+struct pretty_nla_desc {
+ enum pretty_nla_format format;
+ const char *name;
+ union {
+ const struct pretty_nla_desc *children;
+ const char *const *names;
+ };
+ union {
+ unsigned int n_children;
+ unsigned int n_names;
+ };
+};
+
+struct pretty_nlmsg_desc {
+ const char *name;
+ const struct pretty_nla_desc *attrs;
+ unsigned int n_attrs;
+};
+
+/* helper macros for message format descriptions */
+
+#define NLATTR_DESC(_name, _fmt) \
+ [_name] = { \
+ .format = _fmt, \
+ .name = #_name, \
+ }
+
+#define NLATTR_DESC_INVALID(_name) NLATTR_DESC(_name, NLA_INVALID)
+#define NLATTR_DESC_U8(_name) NLATTR_DESC(_name, NLA_U8)
+#define NLATTR_DESC_U16(_name) NLATTR_DESC(_name, NLA_U16)
+#define NLATTR_DESC_U32(_name) NLATTR_DESC(_name, NLA_U32)
+#define NLATTR_DESC_U64(_name) NLATTR_DESC(_name, NLA_U64)
+#define NLATTR_DESC_X8(_name) NLATTR_DESC(_name, NLA_X8)
+#define NLATTR_DESC_X16(_name) NLATTR_DESC(_name, NLA_X16)
+#define NLATTR_DESC_X32(_name) NLATTR_DESC(_name, NLA_X32)
+#define NLATTR_DESC_X64(_name) NLATTR_DESC(_name, NLA_X64)
+#define NLATTR_DESC_S8(_name) NLATTR_DESC(_name, NLA_U8)
+#define NLATTR_DESC_S16(_name) NLATTR_DESC(_name, NLA_U16)
+#define NLATTR_DESC_S32(_name) NLATTR_DESC(_name, NLA_U32)
+#define NLATTR_DESC_S64(_name) NLATTR_DESC(_name, NLA_S64)
+#define NLATTR_DESC_STRING(_name) NLATTR_DESC(_name, NLA_STRING)
+#define NLATTR_DESC_FLAG(_name) NLATTR_DESC(_name, NLA_FLAG)
+#define NLATTR_DESC_BOOL(_name) NLATTR_DESC(_name, NLA_BOOL)
+#define NLATTR_DESC_BINARY(_name) NLATTR_DESC(_name, NLA_BINARY)
+
+#define NLATTR_DESC_NESTED(_name, _children_desc) \
+ [_name] = { \
+ .format = NLA_NESTED, \
+ .name = #_name, \
+ .children = __ ## _children_desc ## _desc, \
+ .n_children = ARRAY_SIZE(__ ## _children_desc ## _desc), \
+ }
+#define NLATTR_DESC_NESTED_NODESC(_name) NLATTR_DESC(_name, NLA_NESTED)
+#define NLATTR_DESC_ARRAY(_name, _children_desc) \
+ [_name] = { \
+ .format = NLA_ARRAY, \
+ .name = #_name, \
+ .children = __ ## _children_desc ## _desc, \
+ .n_children = 1, \
+ }
+#define NLATTR_DESC_U8_ENUM(_name, _names_table) \
+ [_name] = { \
+ .format = NLA_U8_ENUM, \
+ .name = #_name, \
+ .names = __ ## _names_table ## _names, \
+ .n_children = ARRAY_SIZE(__ ## _names_table ## _names), \
+ }
+#define NLATTR_DESC_U32_ENUM(_name, _names_table) \
+ [_name] = { \
+ .format = NLA_U32_ENUM, \
+ .name = #_name, \
+ .names = __ ## _names_table ## _names, \
+ .n_children = ARRAY_SIZE(__ ## _names_table ## _names), \
+ }
+
+#define NLMSG_DESC(_name, _attrs) \
+ [_name] = { \
+ .name = #_name, \
+ .attrs = __ ## _attrs ## _desc, \
+ .n_attrs = ARRAY_SIZE(__ ## _attrs ## _desc), \
+ }
+
+#define NLMSG_DESC_INVALID(_name) \
+ [_name] = { \
+ .name = #_name, \
+ }
+
+/* function to pretty print a genetlink message */
+int pretty_print_genlmsg(const struct nlmsghdr *nlhdr,
+ const struct pretty_nlmsg_desc *desc,
+ unsigned int ndesc, unsigned int err_offset);
+int pretty_print_rtnlmsg(const struct nlmsghdr *nlhdr, unsigned int err_offset);
+
+/* message descriptions */
+
+extern const struct pretty_nlmsg_desc ethnl_umsg_desc[];
+extern const unsigned int ethnl_umsg_n_desc;
+extern const struct pretty_nlmsg_desc ethnl_kmsg_desc[];
+extern const unsigned int ethnl_kmsg_n_desc;
+
+extern const struct pretty_nlmsg_desc genlctrl_msg_desc[];
+extern const unsigned int genlctrl_msg_n_desc;
+
+extern const struct pretty_nlmsg_desc rtnl_msg_desc[];
+extern const unsigned int rtnl_msg_n_desc;
+extern const unsigned short rtnl_msghdr_lengths[];
+extern const unsigned int rtnl_msghdr_n_len;
+
+#endif /* ETHTOOL_NETLINK_PRETTYMSG_H__ */
diff --git a/netlink/privflags.c b/netlink/privflags.c
new file mode 100644
index 0000000..299ccdc
--- /dev/null
+++ b/netlink/privflags.c
@@ -0,0 +1,158 @@
+/*
+ * privflags.c - netlink implementation of private flags commands
+ *
+ * Implementation of "ethtool --show-priv-flags <dev>" and
+ * "ethtool --set-priv-flags <dev> ..."
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "strset.h"
+#include "bitset.h"
+#include "parser.h"
+
+/* PRIVFLAGS_GET */
+
+static void privflags_maxlen_walk_cb(unsigned int idx, const char *name,
+ bool val __maybe_unused, void *data)
+{
+ unsigned int *maxlen = data;
+ unsigned int len, n;
+
+ if (name)
+ len = strlen(name);
+ else {
+ len = 3; /* strlen("bit") */
+ for (n = idx ?: 1; n; n /= 10)
+ len++; /* plus number of ditigs */
+ }
+ if (len > *maxlen)
+ *maxlen = len;
+}
+
+static void privflags_dump_walk_cb(unsigned int idx, const char *name, bool val,
+ void *data)
+{
+ unsigned int *maxlen = data;
+ char buff[16];
+
+ if (!name) {
+ snprintf(buff, sizeof(buff) - 1, "bit%u", idx);
+ name = buff;
+ }
+ printf("%-*s: %s\n", *maxlen, name, val ? "on" : "off");
+}
+
+int privflags_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_PRIVFLAGS_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ const struct stringset *flag_names = NULL;
+ struct nl_context *nlctx = data;
+ unsigned int maxlen = 0;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0 || !tb[ETHTOOL_A_PRIVFLAGS_FLAGS])
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_PRIVFLAGS_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ if (bitset_is_compact(tb[ETHTOOL_A_PRIVFLAGS_FLAGS])) {
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return err_ret;
+ flag_names = perdev_stringset(nlctx->devname, ETH_SS_PRIV_FLAGS,
+ nlctx->ethnl2_socket);
+ }
+
+ ret = walk_bitset(tb[ETHTOOL_A_PRIVFLAGS_FLAGS], flag_names,
+ privflags_maxlen_walk_cb, &maxlen);
+ if (ret < 0)
+ return err_ret;
+ if (silent)
+ putchar('\n');
+ printf("Private flags for %s:\n", nlctx->devname);
+ ret = walk_bitset(tb[ETHTOOL_A_PRIVFLAGS_FLAGS], flag_names,
+ privflags_dump_walk_cb, &maxlen);
+ return (ret < 0) ? err_ret : MNL_CB_OK;
+}
+
+int nl_gprivflags(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_PRIVFLAGS_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_PRIVFLAGS_GET,
+ ETHTOOL_A_PRIVFLAGS_HEADER, 0);
+ if (ret < 0)
+ return ret;
+ return nlsock_send_get_request(nlsk, privflags_reply_cb);
+}
+
+/* PRIVFLAGS_SET */
+
+static const struct bitset_parser_data privflags_parser_data = {
+ .force_hex = false,
+ .no_mask = false,
+};
+
+int nl_sprivflags(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_PRIVFLAGS_SET, false))
+ return -EOPNOTSUPP;
+
+ nlctx->cmd = "--set-priv-flags";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_PRIVFLAGS_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_PRIVFLAGS_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parse_bitset(nlctx, ETHTOOL_A_PRIVFLAGS_FLAGS,
+ &privflags_parser_data, msgbuff, NULL);
+ if (ret < 0)
+ return -EINVAL;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return 2;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret == 0)
+ return 0;
+ else
+ return nlctx->exit_code ?: 1;
+}
diff --git a/netlink/pse-pd.c b/netlink/pse-pd.c
new file mode 100644
index 0000000..d6faff8
--- /dev/null
+++ b/netlink/pse-pd.c
@@ -0,0 +1,193 @@
+/*
+ * pse.c - netlink implementation of pse commands
+ *
+ * Implementation of "ethtool --show-pse <dev>" and
+ * "ethtool --set-pse <dev> ..."
+ */
+
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+/* PSE_GET */
+
+static const char *podl_pse_admin_state_name(u32 val)
+{
+ switch (val) {
+ case ETHTOOL_PODL_PSE_ADMIN_STATE_UNKNOWN:
+ return "unknown";
+ case ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED:
+ return "disabled";
+ case ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED:
+ return "enabled";
+ default:
+ return "unsupported";
+ }
+}
+
+static const char *podl_pse_pw_d_status_name(u32 val)
+{
+ switch (val) {
+ case ETHTOOL_PODL_PSE_PW_D_STATUS_UNKNOWN:
+ return "unknown";
+ case ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED:
+ return "disabled";
+ case ETHTOOL_PODL_PSE_PW_D_STATUS_SEARCHING:
+ return "searching";
+ case ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING:
+ return "delivering power";
+ case ETHTOOL_PODL_PSE_PW_D_STATUS_SLEEP:
+ return "sleep";
+ case ETHTOOL_PODL_PSE_PW_D_STATUS_IDLE:
+ return "idle";
+ case ETHTOOL_PODL_PSE_PW_D_STATUS_ERROR:
+ return "error";
+ default:
+ return "unsupported";
+ }
+}
+
+int pse_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_PSE_MAX + 1] = {};
+ struct nl_context *nlctx = data;
+ DECLARE_ATTR_TB_INFO(tb);
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_PSE_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (silent)
+ print_nl();
+
+ open_json_object(NULL);
+
+ print_string(PRINT_ANY, "ifname", "PSE attributes for %s:\n",
+ nlctx->devname);
+
+ if (tb[ETHTOOL_A_PODL_PSE_ADMIN_STATE]) {
+ u32 val;
+
+ val = mnl_attr_get_u32(tb[ETHTOOL_A_PODL_PSE_ADMIN_STATE]);
+ print_string(PRINT_ANY, "podl-pse-admin-state",
+ "PoDL PSE Admin State: %s\n",
+ podl_pse_admin_state_name(val));
+ }
+
+ if (tb[ETHTOOL_A_PODL_PSE_PW_D_STATUS]) {
+ u32 val;
+
+ val = mnl_attr_get_u32(tb[ETHTOOL_A_PODL_PSE_PW_D_STATUS]);
+ print_string(PRINT_ANY, "podl-pse-power-detection-status",
+ "PoDL PSE Power Detection Status: %s\n",
+ podl_pse_pw_d_status_name(val));
+ }
+
+ close_json_object();
+
+ return MNL_CB_OK;
+}
+
+int nl_gpse(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_PSE_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ nlsk = nlctx->ethnl_socket;
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_PSE_GET,
+ ETHTOOL_A_PSE_HEADER, 0);
+ if (ret < 0)
+ return ret;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, pse_reply_cb);
+ delete_json_obj();
+
+ return ret;
+}
+
+/* PSE_SET */
+
+static const struct lookup_entry_u32 podl_pse_admin_control_values[] = {
+ { .arg = "enable", .val = ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED },
+ { .arg = "disable", .val = ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED },
+ {}
+};
+
+static const struct param_parser spse_params[] = {
+ {
+ .arg = "podl-pse-admin-control",
+ .type = ETHTOOL_A_PODL_PSE_ADMIN_CONTROL,
+ .handler = nl_parse_lookup_u32,
+ .handler_data = podl_pse_admin_control_values,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_spse(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_PSE_SET, false))
+ return -EOPNOTSUPP;
+ if (!ctx->argc) {
+ fprintf(stderr, "ethtool (--set-pse): parameters missing\n");
+ return 1;
+ }
+
+ nlctx->cmd = "--set-pse";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_PSE_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_PSE_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, spse_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return 83;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret == 0)
+ return 0;
+ else
+ return nlctx->exit_code ?: 83;
+}
diff --git a/netlink/rings.c b/netlink/rings.c
new file mode 100644
index 0000000..f9eb67a
--- /dev/null
+++ b/netlink/rings.c
@@ -0,0 +1,237 @@
+/*
+ * rings.c - netlink implementation of ring commands
+ *
+ * Implementation of "ethtool -g <dev>" and "ethtool -G <dev> ..."
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+/* RINGS_GET */
+
+int rings_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_RINGS_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ unsigned char tcp_hds;
+ char *tcp_hds_fmt;
+ char *tcp_hds_key;
+ char tcp_hds_buf[256];
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_RINGS_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ open_json_object(NULL);
+
+ if (silent)
+ show_cr();
+ print_string(PRINT_ANY, "ifname", "Ring parameters for %s:\n",
+ nlctx->devname);
+ print_string(PRINT_FP, NULL, "Pre-set maximums:\n", NULL);
+ show_u32("rx-max", "RX:\t\t\t", tb[ETHTOOL_A_RINGS_RX_MAX]);
+ show_u32("rx-mini-max", "RX Mini:\t\t", tb[ETHTOOL_A_RINGS_RX_MINI_MAX]);
+ show_u32("rx-jumbo-max", "RX Jumbo:\t\t",
+ tb[ETHTOOL_A_RINGS_RX_JUMBO_MAX]);
+ show_u32("tx-max", "TX:\t\t\t", tb[ETHTOOL_A_RINGS_TX_MAX]);
+ show_u32("tx-push-buff-max-len", "TX push buff len:\t",
+ tb[ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX]);
+ print_string(PRINT_FP, NULL, "Current hardware settings:\n", NULL);
+ show_u32("rx", "RX:\t\t\t", tb[ETHTOOL_A_RINGS_RX]);
+ show_u32("rx-mini", "RX Mini:\t\t", tb[ETHTOOL_A_RINGS_RX_MINI]);
+ show_u32("rx-jumbo", "RX Jumbo:\t\t", tb[ETHTOOL_A_RINGS_RX_JUMBO]);
+ show_u32("tx", "TX:\t\t\t", tb[ETHTOOL_A_RINGS_TX]);
+ show_u32("rx-buf-len", "RX Buf Len:\t\t", tb[ETHTOOL_A_RINGS_RX_BUF_LEN]);
+ show_u32("cqe-size", "CQE Size:\t\t", tb[ETHTOOL_A_RINGS_CQE_SIZE]);
+ show_bool("tx-push", "TX Push:\t\t%s\n", tb[ETHTOOL_A_RINGS_TX_PUSH]);
+ show_bool("rx-push", "RX Push:\t\t%s\n", tb[ETHTOOL_A_RINGS_RX_PUSH]);
+ show_u32("tx-push-buf-len", "TX push buff len:\t",
+ tb[ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN]);
+
+ tcp_hds_fmt = "TCP data split:\t\t%s\n";
+ tcp_hds_key = "tcp-data-split";
+ tcp_hds = tb[ETHTOOL_A_RINGS_TCP_DATA_SPLIT] ?
+ mnl_attr_get_u8(tb[ETHTOOL_A_RINGS_TCP_DATA_SPLIT]) : 0;
+ switch (tcp_hds) {
+ case ETHTOOL_TCP_DATA_SPLIT_UNKNOWN:
+ print_string(PRINT_FP, tcp_hds_key, tcp_hds_fmt, "n/a");
+ break;
+ case ETHTOOL_TCP_DATA_SPLIT_DISABLED:
+ print_string(PRINT_ANY, tcp_hds_key, tcp_hds_fmt, "off");
+ break;
+ case ETHTOOL_TCP_DATA_SPLIT_ENABLED:
+ print_string(PRINT_ANY, tcp_hds_key, tcp_hds_fmt, "on");
+ break;
+ default:
+ snprintf(tcp_hds_buf, sizeof(tcp_hds_buf),
+ "unknown(%d)\n", tcp_hds);
+ print_string(PRINT_ANY, tcp_hds_key, tcp_hds_fmt, tcp_hds_buf);
+ break;
+ }
+
+ close_json_object();
+
+ return MNL_CB_OK;
+}
+
+int nl_gring(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_RINGS_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_RINGS_GET,
+ ETHTOOL_A_RINGS_HEADER, 0);
+ if (ret < 0)
+ return ret;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, rings_reply_cb);
+ delete_json_obj();
+ return ret;
+}
+
+/* RINGS_SET */
+
+static const struct lookup_entry_u8 tcp_data_split_values[] = {
+ {
+ .arg = "auto",
+ .val = ETHTOOL_TCP_DATA_SPLIT_UNKNOWN,
+ },
+ {
+ .arg = "off",
+ .val = ETHTOOL_TCP_DATA_SPLIT_DISABLED,
+ },
+ {
+ .arg = "on",
+ .val = ETHTOOL_TCP_DATA_SPLIT_ENABLED,
+ },
+ {}
+};
+
+static const struct param_parser sring_params[] = {
+ {
+ .arg = "rx",
+ .type = ETHTOOL_A_RINGS_RX,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-mini",
+ .type = ETHTOOL_A_RINGS_RX_MINI,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-jumbo",
+ .type = ETHTOOL_A_RINGS_RX_JUMBO,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx",
+ .type = ETHTOOL_A_RINGS_TX,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-push-buf-len",
+ .type = ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-buf-len",
+ .type = ETHTOOL_A_RINGS_RX_BUF_LEN,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tcp-data-split",
+ .type = ETHTOOL_A_RINGS_TCP_DATA_SPLIT,
+ .handler = nl_parse_lookup_u8,
+ .handler_data = tcp_data_split_values,
+ .min_argc = 1,
+ },
+ {
+ .arg = "cqe-size",
+ .type = ETHTOOL_A_RINGS_CQE_SIZE,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "tx-push",
+ .type = ETHTOOL_A_RINGS_TX_PUSH,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {
+ .arg = "rx-push",
+ .type = ETHTOOL_A_RINGS_RX_PUSH,
+ .handler = nl_parse_u8bool,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_sring(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_RINGS_SET, false))
+ return -EOPNOTSUPP;
+
+ nlctx->cmd = "-G";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_RINGS_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_RINGS_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, sring_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return 81;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+ if (ret == 0)
+ return 0;
+ else
+ return nlctx->exit_code ?: 81;
+}
diff --git a/netlink/rss.c b/netlink/rss.c
new file mode 100644
index 0000000..4ad6065
--- /dev/null
+++ b/netlink/rss.c
@@ -0,0 +1,230 @@
+/*
+ * rss.c - netlink implementation of RSS context commands
+ *
+ * Implementation of "ethtool -x <dev>"
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "strset.h"
+#include "parser.h"
+
+struct cb_args {
+ struct nl_context *nlctx;
+ u32 num_rings;
+};
+
+void dump_json_rss_info(struct cmd_context *ctx, u32 *indir_table,
+ u32 indir_size, u8 *hkey, u32 hkey_size,
+ const struct stringset *hash_funcs, u8 hfunc)
+{
+ unsigned int i;
+
+ open_json_object(NULL);
+ print_string(PRINT_JSON, "ifname", NULL, ctx->devname);
+ if (indir_size) {
+ open_json_array("rss-indirection-table", NULL);
+ for (i = 0; i < indir_size; i++)
+ print_uint(PRINT_JSON, NULL, NULL, indir_table[i]);
+ close_json_array("\n");
+ }
+
+ if (hkey_size) {
+ open_json_array("rss-hash-key", NULL);
+ for (i = 0; i < hkey_size; i++)
+ print_uint(PRINT_JSON, NULL, NULL, (u8)hkey[i]);
+ close_json_array("\n");
+ }
+
+ if (hfunc) {
+ for (i = 0; i < get_count(hash_funcs); i++) {
+ if (hfunc & (1 << i)) {
+ print_string(PRINT_JSON, "rss-hash-function",
+ NULL, get_string(hash_funcs, i));
+ break;
+ }
+ }
+ }
+
+ close_json_object();
+}
+
+int get_channels_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_CHANNELS_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct cb_args *args = data;
+ struct nl_context *nlctx = args->nlctx;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_CHANNELS_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+ if (tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT])
+ args->num_rings = mnl_attr_get_u32(tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT]);
+ if (tb[ETHTOOL_A_CHANNELS_RX_COUNT])
+ args->num_rings += mnl_attr_get_u32(tb[ETHTOOL_A_CHANNELS_RX_COUNT]);
+ return MNL_CB_OK;
+}
+
+int rss_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_RSS_MAX + 1] = {};
+ unsigned int indir_bytes = 0, hkey_bytes = 0;
+ DECLARE_ATTR_TB_INFO(tb);
+ struct cb_args *args = data;
+ struct nl_context *nlctx = args->nlctx;
+ const struct stringset *hash_funcs;
+ u32 rss_hfunc = 0, indir_size;
+ u32 *indir_table = NULL;
+ u8 *hkey = NULL;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_RSS_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ show_cr();
+
+ if (tb[ETHTOOL_A_RSS_HFUNC])
+ rss_hfunc = mnl_attr_get_u32(tb[ETHTOOL_A_RSS_HFUNC]);
+
+ if (tb[ETHTOOL_A_RSS_INDIR]) {
+ indir_bytes = mnl_attr_get_payload_len(tb[ETHTOOL_A_RSS_INDIR]);
+ indir_table = mnl_attr_get_payload(tb[ETHTOOL_A_RSS_INDIR]);
+ }
+
+ if (tb[ETHTOOL_A_RSS_HKEY]) {
+ hkey_bytes = mnl_attr_get_payload_len(tb[ETHTOOL_A_RSS_HKEY]);
+ hkey = mnl_attr_get_payload(tb[ETHTOOL_A_RSS_HKEY]);
+ }
+
+ /* Fetch RSS hash functions and their status and print */
+ if (!nlctx->is_monitor) {
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+ }
+ hash_funcs = global_stringset(ETH_SS_RSS_HASH_FUNCS,
+ nlctx->ethnl2_socket);
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return silent ? MNL_CB_OK : MNL_CB_ERROR;
+
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_RSS_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ /* Fetch ring count info into args->num_rings */
+ ret = nlsock_prep_get_request(nlctx->ethnl2_socket,
+ ETHTOOL_MSG_CHANNELS_GET,
+ ETHTOOL_A_CHANNELS_HEADER, 0);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+
+ ret = nlsock_sendmsg(nlctx->ethnl2_socket, NULL);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+
+ ret = nlsock_process_reply(nlctx->ethnl2_socket, get_channels_cb, args);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+
+ indir_size = indir_bytes / sizeof(u32);
+ if (is_json_context()) {
+ dump_json_rss_info(nlctx->ctx, (u32 *)indir_table, indir_size,
+ hkey, hkey_bytes, hash_funcs, rss_hfunc);
+ } else {
+ print_indir_table(nlctx->ctx, args->num_rings,
+ indir_size, (u32 *)indir_table);
+ print_rss_hkey(hkey, hkey_bytes);
+ printf("RSS hash function:\n");
+ if (!rss_hfunc) {
+ printf(" Operation not supported\n");
+ return 0;
+ }
+ for (unsigned int i = 0; i < get_count(hash_funcs); i++) {
+ printf(" %s: %s\n", get_string(hash_funcs, i),
+ (rss_hfunc & (1 << i)) ? "on" : "off");
+ }
+ }
+
+ return MNL_CB_OK;
+}
+
+/* RSS_GET */
+static const struct param_parser grss_params[] = {
+ {
+ .arg = "context",
+ .type = ETHTOOL_A_RSS_CONTEXT,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_grss(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ struct nl_msg_buff *msgbuff;
+ struct cb_args args = {};
+ int ret;
+
+ nlctx->cmd = "-x";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_RSS_GET, true))
+ return -EOPNOTSUPP;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_RSS_GET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 1;
+
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_RSS_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, grss_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ goto err;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ goto err;
+
+ args.nlctx = nlctx;
+ new_json_obj(ctx->json);
+ ret = nlsock_process_reply(nlsk, rss_reply_cb, &args);
+ delete_json_obj();
+
+ if (ret == 0)
+ return 0;
+err:
+ return nlctx->exit_code ?: 1;
+}
diff --git a/netlink/settings.c b/netlink/settings.c
new file mode 100644
index 0000000..a506618
--- /dev/null
+++ b/netlink/settings.c
@@ -0,0 +1,1377 @@
+/*
+ * settings.c - netlink implementation of settings commands
+ *
+ * Implementation of "ethtool <dev>" and "ethtool -s <dev> ...".
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "strset.h"
+#include "bitset.h"
+#include "parser.h"
+
+/* GET_SETTINGS */
+
+struct link_mode_info {
+ enum link_mode_class class;
+ u32 speed;
+ u8 duplex;
+};
+
+static const char *const names_duplex[] = {
+ [DUPLEX_HALF] = "Half",
+ [DUPLEX_FULL] = "Full",
+};
+
+static const char *const names_master_slave_state[] = {
+ [MASTER_SLAVE_STATE_UNKNOWN] = "unknown",
+ [MASTER_SLAVE_STATE_MASTER] = "master",
+ [MASTER_SLAVE_STATE_SLAVE] = "slave",
+ [MASTER_SLAVE_STATE_ERR] = "resolution error",
+};
+
+static const char *const names_master_slave_cfg[] = {
+ [MASTER_SLAVE_CFG_UNKNOWN] = "unknown",
+ [MASTER_SLAVE_CFG_MASTER_PREFERRED] = "preferred master",
+ [MASTER_SLAVE_CFG_SLAVE_PREFERRED] = "preferred slave",
+ [MASTER_SLAVE_CFG_MASTER_FORCE] = "forced master",
+ [MASTER_SLAVE_CFG_SLAVE_FORCE] = "forced slave",
+};
+
+static const char *const names_port[] = {
+ [PORT_TP] = "Twisted Pair",
+ [PORT_AUI] = "AUI",
+ [PORT_BNC] = "BNC",
+ [PORT_MII] = "MII",
+ [PORT_FIBRE] = "FIBRE",
+ [PORT_DA] = "Direct Attach Copper",
+ [PORT_NONE] = "None",
+ [PORT_OTHER] = "Other",
+};
+
+static const char *const names_transceiver[] = {
+ [XCVR_INTERNAL] = "internal",
+ [XCVR_EXTERNAL] = "external",
+};
+
+/* the practice of putting completely unrelated flags into link mode bitmaps
+ * is rather unfortunate but as even ethtool_link_ksettings preserved that,
+ * there is little chance of getting them separated any time soon so let's
+ * sort them out ourselves
+ */
+#define __REAL(_speed) \
+ { .class = LM_CLASS_REAL, .speed = _speed, .duplex = DUPLEX_FULL }
+#define __HALF_DUPLEX(_speed) \
+ { .class = LM_CLASS_REAL, .speed = _speed, .duplex = DUPLEX_HALF }
+#define __SPECIAL(_class) \
+ { .class = LM_CLASS_ ## _class }
+
+static const struct link_mode_info link_modes[] = {
+ [ETHTOOL_LINK_MODE_10baseT_Half_BIT] = __HALF_DUPLEX(10),
+ [ETHTOOL_LINK_MODE_10baseT_Full_BIT] = __REAL(10),
+ [ETHTOOL_LINK_MODE_100baseT_Half_BIT] = __HALF_DUPLEX(100),
+ [ETHTOOL_LINK_MODE_100baseT_Full_BIT] = __REAL(100),
+ [ETHTOOL_LINK_MODE_1000baseT_Half_BIT] = __HALF_DUPLEX(1000),
+ [ETHTOOL_LINK_MODE_1000baseT_Full_BIT] = __REAL(1000),
+ [ETHTOOL_LINK_MODE_Autoneg_BIT] = __SPECIAL(AUTONEG),
+ [ETHTOOL_LINK_MODE_TP_BIT] = __SPECIAL(PORT),
+ [ETHTOOL_LINK_MODE_AUI_BIT] = __SPECIAL(PORT),
+ [ETHTOOL_LINK_MODE_MII_BIT] = __SPECIAL(PORT),
+ [ETHTOOL_LINK_MODE_FIBRE_BIT] = __SPECIAL(PORT),
+ [ETHTOOL_LINK_MODE_BNC_BIT] = __SPECIAL(PORT),
+ [ETHTOOL_LINK_MODE_10000baseT_Full_BIT] = __REAL(10000),
+ [ETHTOOL_LINK_MODE_Pause_BIT] = __SPECIAL(PAUSE),
+ [ETHTOOL_LINK_MODE_Asym_Pause_BIT] = __SPECIAL(PAUSE),
+ [ETHTOOL_LINK_MODE_2500baseX_Full_BIT] = __REAL(2500),
+ [ETHTOOL_LINK_MODE_Backplane_BIT] = __SPECIAL(PORT),
+ [ETHTOOL_LINK_MODE_1000baseKX_Full_BIT] = __REAL(1000),
+ [ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT] = __REAL(10000),
+ [ETHTOOL_LINK_MODE_10000baseKR_Full_BIT] = __REAL(10000),
+ [ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = __REAL(10000),
+ [ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT] = __REAL(20000),
+ [ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT] = __REAL(20000),
+ [ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT] = __REAL(40000),
+ [ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT] = __REAL(40000),
+ [ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT] = __REAL(40000),
+ [ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT] = __REAL(40000),
+ [ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT] = __REAL(56000),
+ [ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT] = __REAL(56000),
+ [ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT] = __REAL(56000),
+ [ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT] = __REAL(56000),
+ [ETHTOOL_LINK_MODE_25000baseCR_Full_BIT] = __REAL(25000),
+ [ETHTOOL_LINK_MODE_25000baseKR_Full_BIT] = __REAL(25000),
+ [ETHTOOL_LINK_MODE_25000baseSR_Full_BIT] = __REAL(25000),
+ [ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT] = __REAL(50000),
+ [ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT] = __REAL(50000),
+ [ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT] = __REAL(50000),
+ [ETHTOOL_LINK_MODE_1000baseX_Full_BIT] = __REAL(1000),
+ [ETHTOOL_LINK_MODE_10000baseCR_Full_BIT] = __REAL(10000),
+ [ETHTOOL_LINK_MODE_10000baseSR_Full_BIT] = __REAL(10000),
+ [ETHTOOL_LINK_MODE_10000baseLR_Full_BIT] = __REAL(10000),
+ [ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT] = __REAL(10000),
+ [ETHTOOL_LINK_MODE_10000baseER_Full_BIT] = __REAL(10000),
+ [ETHTOOL_LINK_MODE_2500baseT_Full_BIT] = __REAL(2500),
+ [ETHTOOL_LINK_MODE_5000baseT_Full_BIT] = __REAL(5000),
+ [ETHTOOL_LINK_MODE_FEC_NONE_BIT] = __SPECIAL(FEC),
+ [ETHTOOL_LINK_MODE_FEC_RS_BIT] = __SPECIAL(FEC),
+ [ETHTOOL_LINK_MODE_FEC_BASER_BIT] = __SPECIAL(FEC),
+ [ETHTOOL_LINK_MODE_50000baseKR_Full_BIT] = __REAL(50000),
+ [ETHTOOL_LINK_MODE_50000baseSR_Full_BIT] = __REAL(50000),
+ [ETHTOOL_LINK_MODE_50000baseCR_Full_BIT] = __REAL(50000),
+ [ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT] = __REAL(50000),
+ [ETHTOOL_LINK_MODE_50000baseDR_Full_BIT] = __REAL(50000),
+ [ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT] = __REAL(200000),
+ [ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT] = __REAL(200000),
+ [ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT] = __REAL(200000),
+ [ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT] = __REAL(200000),
+ [ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT] = __REAL(200000),
+ [ETHTOOL_LINK_MODE_100baseT1_Full_BIT] = __REAL(100),
+ [ETHTOOL_LINK_MODE_1000baseT1_Full_BIT] = __REAL(1000),
+ [ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT] = __REAL(400000),
+ [ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT] = __REAL(400000),
+ [ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT] = __REAL(400000),
+ [ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT] = __REAL(400000),
+ [ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT] = __REAL(400000),
+ [ETHTOOL_LINK_MODE_FEC_LLRS_BIT] = __SPECIAL(FEC),
+ [ETHTOOL_LINK_MODE_100000baseKR_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseSR_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseCR_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_100000baseDR_Full_BIT] = __REAL(100000),
+ [ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT] = __REAL(200000),
+ [ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT] = __REAL(200000),
+ [ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT] = __REAL(200000),
+ [ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT] = __REAL(200000),
+ [ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT] = __REAL(200000),
+ [ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT] = __REAL(400000),
+ [ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT] = __REAL(400000),
+ [ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT] = __REAL(400000),
+ [ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT] = __REAL(400000),
+ [ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT] = __REAL(400000),
+ [ETHTOOL_LINK_MODE_100baseFX_Half_BIT] = __HALF_DUPLEX(100),
+ [ETHTOOL_LINK_MODE_100baseFX_Full_BIT] = __REAL(100),
+ [ETHTOOL_LINK_MODE_10baseT1L_Full_BIT] = __REAL(10),
+ [ETHTOOL_LINK_MODE_800000baseCR8_Full_BIT] = __REAL(800000),
+ [ETHTOOL_LINK_MODE_800000baseKR8_Full_BIT] = __REAL(800000),
+ [ETHTOOL_LINK_MODE_800000baseDR8_Full_BIT] = __REAL(800000),
+ [ETHTOOL_LINK_MODE_800000baseDR8_2_Full_BIT] = __REAL(800000),
+ [ETHTOOL_LINK_MODE_800000baseSR8_Full_BIT] = __REAL(800000),
+ [ETHTOOL_LINK_MODE_800000baseVR8_Full_BIT] = __REAL(800000),
+ [ETHTOOL_LINK_MODE_10baseT1S_Full_BIT] = __REAL(10),
+ [ETHTOOL_LINK_MODE_10baseT1S_Half_BIT] = __HALF_DUPLEX(10),
+ [ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT] = __HALF_DUPLEX(10),
+};
+const unsigned int link_modes_count = ARRAY_SIZE(link_modes);
+
+#undef __REAL
+#undef __HALF_DUPLEX
+#undef __SPECIAL
+
+static bool lm_class_match(unsigned int mode, enum link_mode_class class)
+{
+ unsigned int mode_class = (mode < link_modes_count) ?
+ link_modes[mode].class : LM_CLASS_UNKNOWN;
+
+ return mode_class == class ||
+ (class == LM_CLASS_REAL && mode_class == LM_CLASS_UNKNOWN);
+}
+
+static void print_enum(const char *const *info, unsigned int n_info,
+ unsigned int val, const char *label)
+{
+ if (val >= n_info || !info[val])
+ printf("\t%s: Unknown! (%d)\n", label, val);
+ else
+ printf("\t%s: %s\n", label, info[val]);
+}
+
+static int dump_pause(const struct nlattr *attr, bool mask, const char *label)
+{
+ bool pause, asym;
+ int ret = 0;
+
+ pause = bitset_get_bit(attr, mask, ETHTOOL_LINK_MODE_Pause_BIT, &ret);
+ if (ret < 0)
+ goto err;
+ asym = bitset_get_bit(attr, mask, ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ &ret);
+ if (ret < 0)
+ goto err;
+
+ printf("\t%s", label);
+ if (pause)
+ printf("%s\n", asym ? "Symmetric Receive-only" : "Symmetric");
+ else
+ printf("%s\n", asym ? "Transmit-only" : "No");
+
+ return 0;
+err:
+ fprintf(stderr, "malformed netlink message (pause modes)\n");
+ return ret;
+}
+
+static void print_banner(struct nl_context *nlctx)
+{
+ if (nlctx->no_banner)
+ return;
+ printf("Settings for %s:\n", nlctx->devname);
+ nlctx->no_banner = true;
+}
+
+int dump_link_modes(struct nl_context *nlctx, const struct nlattr *bitset,
+ bool mask, unsigned int class, const char *before,
+ const char *between, const char *after, const char *if_none)
+{
+ const struct nlattr *bitset_tb[ETHTOOL_A_BITSET_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(bitset_tb);
+ const unsigned int before_len = strlen(before);
+ unsigned int prev = UINT_MAX - 1;
+ const struct nlattr *bits;
+ const struct nlattr *bit;
+ bool first = true;
+ bool nomask;
+ int ret;
+
+ ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+ if (ret < 0)
+ goto err_nonl;
+
+ nomask = bitset_tb[ETHTOOL_A_BITSET_NOMASK];
+ /* Trying to print the mask of a "no mask" bitset doesn't make sense */
+ if (mask && nomask) {
+ ret = -EFAULT;
+ goto err_nonl;
+ }
+
+ bits = bitset_tb[ETHTOOL_A_BITSET_BITS];
+
+ if (!bits) {
+ const struct stringset *lm_strings;
+ unsigned int count;
+ unsigned int idx;
+ const char *name;
+
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ goto err_nonl;
+ lm_strings = global_stringset(ETH_SS_LINK_MODES,
+ nlctx->ethnl2_socket);
+ bits = mask ? bitset_tb[ETHTOOL_A_BITSET_MASK] :
+ bitset_tb[ETHTOOL_A_BITSET_VALUE];
+ ret = -EFAULT;
+ if (!bits || !bitset_tb[ETHTOOL_A_BITSET_SIZE])
+ goto err_nonl;
+ count = mnl_attr_get_u32(bitset_tb[ETHTOOL_A_BITSET_SIZE]);
+ if (mnl_attr_get_payload_len(bits) / 4 < (count + 31) / 32)
+ goto err_nonl;
+
+ printf("\t%s", before);
+ for (idx = 0; idx < count; idx++) {
+ const uint32_t *raw_data = mnl_attr_get_payload(bits);
+ char buff[14];
+
+ if (!(raw_data[idx / 32] & (1U << (idx % 32))))
+ continue;
+ if (!lm_class_match(idx, class))
+ continue;
+ name = get_string(lm_strings, idx);
+ if (!name) {
+ snprintf(buff, sizeof(buff), "BIT%u", idx);
+ name = buff;
+ }
+ if (first)
+ first = false;
+ /* ugly hack to preserve old output format */
+ if (class == LM_CLASS_REAL && (idx == prev + 1) &&
+ prev < link_modes_count &&
+ link_modes[prev].class == LM_CLASS_REAL &&
+ link_modes[prev].duplex == DUPLEX_HALF)
+ putchar(' ');
+ else if (between)
+ printf("\t%s", between);
+ else
+ printf("\n\t%*s", before_len, "");
+ printf("%s", name);
+ prev = idx;
+ }
+ goto after;
+ }
+
+ printf("\t%s", before);
+ mnl_attr_for_each_nested(bit, bits) {
+ const struct nlattr *tb[ETHTOOL_A_BITSET_BIT_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ unsigned int idx;
+ const char *name;
+
+ if (mnl_attr_get_type(bit) != ETHTOOL_A_BITSET_BITS_BIT)
+ continue;
+ ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+ if (ret < 0)
+ goto err;
+ ret = -EFAULT;
+ if (!tb[ETHTOOL_A_BITSET_BIT_INDEX] ||
+ !tb[ETHTOOL_A_BITSET_BIT_NAME])
+ goto err;
+ if (!mask && !nomask && !tb[ETHTOOL_A_BITSET_BIT_VALUE])
+ continue;
+
+ idx = mnl_attr_get_u32(tb[ETHTOOL_A_BITSET_BIT_INDEX]);
+ name = mnl_attr_get_str(tb[ETHTOOL_A_BITSET_BIT_NAME]);
+ if (!lm_class_match(idx, class))
+ continue;
+ if (first) {
+ first = false;
+ } else {
+ /* ugly hack to preserve old output format */
+ if ((class == LM_CLASS_REAL) && (idx == prev + 1) &&
+ (prev < link_modes_count) &&
+ (link_modes[prev].class == LM_CLASS_REAL) &&
+ (link_modes[prev].duplex == DUPLEX_HALF))
+ putchar(' ');
+ else if (between)
+ printf("\t%s", between);
+ else
+ printf("\n\t%*s", before_len, "");
+ }
+ printf("%s", name);
+ prev = idx;
+ }
+after:
+ if (first && if_none)
+ printf("%s", if_none);
+ printf("%s", after);
+
+ return 0;
+err:
+ putchar('\n');
+err_nonl:
+ fflush(stdout);
+ fprintf(stderr, "malformed netlink message (link_modes)\n");
+ return ret;
+}
+
+static int dump_our_modes(struct nl_context *nlctx, const struct nlattr *attr)
+{
+ bool autoneg;
+ int ret;
+
+ print_banner(nlctx);
+ ret = dump_link_modes(nlctx, attr, true, LM_CLASS_PORT,
+ "Supported ports: [ ", " ", " ]\n", NULL);
+ if (ret < 0)
+ return ret;
+
+ ret = dump_link_modes(nlctx, attr, true, LM_CLASS_REAL,
+ "Supported link modes: ", NULL, "\n",
+ "Not reported");
+ if (ret < 0)
+ return ret;
+ ret = dump_pause(attr, true, "Supported pause frame use: ");
+ if (ret < 0)
+ return ret;
+
+ autoneg = bitset_get_bit(attr, true, ETHTOOL_LINK_MODE_Autoneg_BIT,
+ &ret);
+ if (ret < 0)
+ return ret;
+ printf("\tSupports auto-negotiation: %s\n", autoneg ? "Yes" : "No");
+
+ ret = dump_link_modes(nlctx, attr, true, LM_CLASS_FEC,
+ "Supported FEC modes: ", " ", "\n",
+ "Not reported");
+ if (ret < 0)
+ return ret;
+
+ ret = dump_link_modes(nlctx, attr, false, LM_CLASS_REAL,
+ "Advertised link modes: ", NULL, "\n",
+ "Not reported");
+ if (ret < 0)
+ return ret;
+
+ ret = dump_pause(attr, false, "Advertised pause frame use: ");
+ if (ret < 0)
+ return ret;
+ autoneg = bitset_get_bit(attr, false, ETHTOOL_LINK_MODE_Autoneg_BIT,
+ &ret);
+ if (ret < 0)
+ return ret;
+ printf("\tAdvertised auto-negotiation: %s\n", autoneg ? "Yes" : "No");
+
+ ret = dump_link_modes(nlctx, attr, false, LM_CLASS_FEC,
+ "Advertised FEC modes: ", " ", "\n",
+ "Not reported");
+ return ret;
+}
+
+static int dump_peer_modes(struct nl_context *nlctx, const struct nlattr *attr)
+{
+ bool autoneg;
+ int ret;
+
+ print_banner(nlctx);
+ ret = dump_link_modes(nlctx, attr, false, LM_CLASS_REAL,
+ "Link partner advertised link modes: ",
+ NULL, "\n", "Not reported");
+ if (ret < 0)
+ return ret;
+
+ ret = dump_pause(attr, false,
+ "Link partner advertised pause frame use: ");
+ if (ret < 0)
+ return ret;
+
+ autoneg = bitset_get_bit(attr, false,
+ ETHTOOL_LINK_MODE_Autoneg_BIT, &ret);
+ if (ret < 0)
+ return ret;
+ printf("\tLink partner advertised auto-negotiation: %s\n",
+ autoneg ? "Yes" : "No");
+
+ ret = dump_link_modes(nlctx, attr, false, LM_CLASS_FEC,
+ "Link partner advertised FEC modes: ",
+ " ", "\n", "Not reported");
+ return ret;
+}
+
+int linkmodes_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ int ret;
+
+ if (nlctx->is_dump || nlctx->is_monitor)
+ nlctx->no_banner = false;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_LINKMODES_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ if (tb[ETHTOOL_A_LINKMODES_OURS]) {
+ ret = dump_our_modes(nlctx, tb[ETHTOOL_A_LINKMODES_OURS]);
+ if (ret < 0)
+ goto err;
+ }
+ if (tb[ETHTOOL_A_LINKMODES_PEER]) {
+ ret = dump_peer_modes(nlctx, tb[ETHTOOL_A_LINKMODES_PEER]);
+ if (ret < 0)
+ goto err;
+ }
+ if (tb[ETHTOOL_A_LINKMODES_SPEED]) {
+ uint32_t val = mnl_attr_get_u32(tb[ETHTOOL_A_LINKMODES_SPEED]);
+
+ print_banner(nlctx);
+ if (val == 0 || val == (uint16_t)(-1) || val == (uint32_t)(-1))
+ printf("\tSpeed: Unknown!\n");
+ else
+ printf("\tSpeed: %uMb/s\n", val);
+ }
+ if (tb[ETHTOOL_A_LINKMODES_LANES]) {
+ uint32_t val = mnl_attr_get_u32(tb[ETHTOOL_A_LINKMODES_LANES]);
+
+ print_banner(nlctx);
+ printf("\tLanes: %u\n", val);
+ }
+ if (tb[ETHTOOL_A_LINKMODES_DUPLEX]) {
+ uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_DUPLEX]);
+
+ print_banner(nlctx);
+ print_enum(names_duplex, ARRAY_SIZE(names_duplex), val,
+ "Duplex");
+ }
+ if (tb[ETHTOOL_A_LINKMODES_AUTONEG]) {
+ int autoneg = mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_AUTONEG]);
+
+ print_banner(nlctx);
+ printf("\tAuto-negotiation: %s\n",
+ (autoneg == AUTONEG_DISABLE) ? "off" : "on");
+ }
+ if (tb[ETHTOOL_A_LINKMODES_MASTER_SLAVE_CFG]) {
+ uint8_t val;
+
+ val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_MASTER_SLAVE_CFG]);
+
+ print_banner(nlctx);
+ print_enum(names_master_slave_cfg,
+ ARRAY_SIZE(names_master_slave_cfg), val,
+ "master-slave cfg");
+ }
+ if (tb[ETHTOOL_A_LINKMODES_MASTER_SLAVE_STATE]) {
+ uint8_t val;
+
+ val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_MASTER_SLAVE_STATE]);
+ print_banner(nlctx);
+ print_enum(names_master_slave_state,
+ ARRAY_SIZE(names_master_slave_state), val,
+ "master-slave status");
+ }
+
+ return MNL_CB_OK;
+err:
+ if (nlctx->is_monitor || nlctx->is_dump)
+ return MNL_CB_OK;
+ fputs("No data available\n", stdout);
+ nlctx->exit_code = 75;
+ return MNL_CB_ERROR;
+}
+
+int linkinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_LINKINFO_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ int port = -1;
+ int ret;
+
+ if (nlctx->is_dump || nlctx->is_monitor)
+ nlctx->no_banner = false;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_LINKINFO_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ if (tb[ETHTOOL_A_LINKINFO_PORT]) {
+ uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_PORT]);
+
+ print_banner(nlctx);
+ print_enum(names_port, ARRAY_SIZE(names_port), val, "Port");
+ port = val;
+ }
+ if (tb[ETHTOOL_A_LINKINFO_PHYADDR]) {
+ uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_PHYADDR]);
+
+ print_banner(nlctx);
+ printf("\tPHYAD: %u\n", val);
+ }
+ if (tb[ETHTOOL_A_LINKINFO_TRANSCEIVER]) {
+ uint8_t val;
+
+ val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_TRANSCEIVER]);
+ print_banner(nlctx);
+ print_enum(names_transceiver, ARRAY_SIZE(names_transceiver),
+ val, "Transceiver");
+ }
+ if (tb[ETHTOOL_A_LINKINFO_TP_MDIX] && tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL] &&
+ port == PORT_TP) {
+ uint8_t mdix = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_TP_MDIX]);
+ uint8_t mdix_ctrl =
+ mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL]);
+
+ print_banner(nlctx);
+ dump_mdix(mdix, mdix_ctrl);
+ }
+
+ return MNL_CB_OK;
+}
+
+static const char *get_enum_string(const char *const *string_table, unsigned int n_string_table,
+ unsigned int val)
+{
+ if (val >= n_string_table || !string_table[val])
+ return NULL;
+ else
+ return string_table[val];
+}
+
+static const char *const names_link_ext_state[] = {
+ [ETHTOOL_LINK_EXT_STATE_AUTONEG] = "Autoneg",
+ [ETHTOOL_LINK_EXT_STATE_LINK_TRAINING_FAILURE] = "Link training failure",
+ [ETHTOOL_LINK_EXT_STATE_LINK_LOGICAL_MISMATCH] = "Logical mismatch",
+ [ETHTOOL_LINK_EXT_STATE_BAD_SIGNAL_INTEGRITY] = "Bad signal integrity",
+ [ETHTOOL_LINK_EXT_STATE_NO_CABLE] = "No cable",
+ [ETHTOOL_LINK_EXT_STATE_CABLE_ISSUE] = "Cable issue",
+ [ETHTOOL_LINK_EXT_STATE_EEPROM_ISSUE] = "EEPROM issue",
+ [ETHTOOL_LINK_EXT_STATE_CALIBRATION_FAILURE] = "Calibration failure",
+ [ETHTOOL_LINK_EXT_STATE_POWER_BUDGET_EXCEEDED] = "Power budget exceeded",
+ [ETHTOOL_LINK_EXT_STATE_OVERHEAT] = "Overheat",
+ [ETHTOOL_LINK_EXT_STATE_MODULE] = "Module",
+};
+
+static const char *const names_autoneg_link_ext_substate[] = {
+ [ETHTOOL_LINK_EXT_SUBSTATE_AN_NO_PARTNER_DETECTED] =
+ "No partner detected",
+ [ETHTOOL_LINK_EXT_SUBSTATE_AN_ACK_NOT_RECEIVED] =
+ "Ack not received",
+ [ETHTOOL_LINK_EXT_SUBSTATE_AN_NEXT_PAGE_EXCHANGE_FAILED] =
+ "Next page exchange failed",
+ [ETHTOOL_LINK_EXT_SUBSTATE_AN_NO_PARTNER_DETECTED_FORCE_MODE] =
+ "No partner detected during force mode",
+ [ETHTOOL_LINK_EXT_SUBSTATE_AN_FEC_MISMATCH_DURING_OVERRIDE] =
+ "FEC mismatch during override",
+ [ETHTOOL_LINK_EXT_SUBSTATE_AN_NO_HCD] =
+ "No HCD",
+};
+
+static const char *const names_link_training_link_ext_substate[] = {
+ [ETHTOOL_LINK_EXT_SUBSTATE_LT_KR_FRAME_LOCK_NOT_ACQUIRED] =
+ "KR frame lock not acquired",
+ [ETHTOOL_LINK_EXT_SUBSTATE_LT_KR_LINK_INHIBIT_TIMEOUT] =
+ "KR link inhibit timeout",
+ [ETHTOOL_LINK_EXT_SUBSTATE_LT_KR_LINK_PARTNER_DID_NOT_SET_RECEIVER_READY] =
+ "KR Link partner did not set receiver ready",
+ [ETHTOOL_LINK_EXT_SUBSTATE_LT_REMOTE_FAULT] =
+ "Remote side is not ready yet",
+};
+
+static const char *const names_link_logical_mismatch_link_ext_substate[] = {
+ [ETHTOOL_LINK_EXT_SUBSTATE_LLM_PCS_DID_NOT_ACQUIRE_BLOCK_LOCK] =
+ "PCS did not acquire block lock",
+ [ETHTOOL_LINK_EXT_SUBSTATE_LLM_PCS_DID_NOT_ACQUIRE_AM_LOCK] =
+ "PCS did not acquire AM lock",
+ [ETHTOOL_LINK_EXT_SUBSTATE_LLM_PCS_DID_NOT_GET_ALIGN_STATUS] =
+ "PCS did not get align_status",
+ [ETHTOOL_LINK_EXT_SUBSTATE_LLM_FC_FEC_IS_NOT_LOCKED] =
+ "FC FEC is not locked",
+ [ETHTOOL_LINK_EXT_SUBSTATE_LLM_RS_FEC_IS_NOT_LOCKED] =
+ "RS FEC is not locked",
+};
+
+static const char *const names_bad_signal_integrity_link_ext_substate[] = {
+ [ETHTOOL_LINK_EXT_SUBSTATE_BSI_LARGE_NUMBER_OF_PHYSICAL_ERRORS] =
+ "Large number of physical errors",
+ [ETHTOOL_LINK_EXT_SUBSTATE_BSI_UNSUPPORTED_RATE] =
+ "Unsupported rate",
+ [ETHTOOL_LINK_EXT_SUBSTATE_BSI_SERDES_REFERENCE_CLOCK_LOST] =
+ "Serdes reference clock lost",
+ [ETHTOOL_LINK_EXT_SUBSTATE_BSI_SERDES_ALOS] =
+ "Serdes ALOS",
+};
+
+static const char *const names_cable_issue_link_ext_substate[] = {
+ [ETHTOOL_LINK_EXT_SUBSTATE_CI_UNSUPPORTED_CABLE] =
+ "Unsupported cable",
+ [ETHTOOL_LINK_EXT_SUBSTATE_CI_CABLE_TEST_FAILURE] =
+ "Cable test failure",
+};
+
+static const char *const names_module_link_ext_substate[] = {
+ [ETHTOOL_LINK_EXT_SUBSTATE_MODULE_CMIS_NOT_READY] =
+ "CMIS module is not in ModuleReady state",
+};
+
+static const char *link_ext_substate_get(uint8_t link_ext_state_val, uint8_t link_ext_substate_val)
+{
+ switch (link_ext_state_val) {
+ case ETHTOOL_LINK_EXT_STATE_AUTONEG:
+ return get_enum_string(names_autoneg_link_ext_substate,
+ ARRAY_SIZE(names_autoneg_link_ext_substate),
+ link_ext_substate_val);
+ case ETHTOOL_LINK_EXT_STATE_LINK_TRAINING_FAILURE:
+ return get_enum_string(names_link_training_link_ext_substate,
+ ARRAY_SIZE(names_link_training_link_ext_substate),
+ link_ext_substate_val);
+ case ETHTOOL_LINK_EXT_STATE_LINK_LOGICAL_MISMATCH:
+ return get_enum_string(names_link_logical_mismatch_link_ext_substate,
+ ARRAY_SIZE(names_link_logical_mismatch_link_ext_substate),
+ link_ext_substate_val);
+ case ETHTOOL_LINK_EXT_STATE_BAD_SIGNAL_INTEGRITY:
+ return get_enum_string(names_bad_signal_integrity_link_ext_substate,
+ ARRAY_SIZE(names_bad_signal_integrity_link_ext_substate),
+ link_ext_substate_val);
+ case ETHTOOL_LINK_EXT_STATE_CABLE_ISSUE:
+ return get_enum_string(names_cable_issue_link_ext_substate,
+ ARRAY_SIZE(names_cable_issue_link_ext_substate),
+ link_ext_substate_val);
+ case ETHTOOL_LINK_EXT_STATE_MODULE:
+ return get_enum_string(names_module_link_ext_substate,
+ ARRAY_SIZE(names_module_link_ext_substate),
+ link_ext_substate_val);
+ default:
+ return NULL;
+ }
+}
+
+static void linkstate_link_ext_substate_print(const struct nlattr *tb[],
+ uint8_t link_ext_state_val)
+{
+ uint8_t link_ext_substate_val;
+ const char *link_ext_substate_str;
+
+ if (!tb[ETHTOOL_A_LINKSTATE_EXT_SUBSTATE])
+ return;
+
+ link_ext_substate_val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKSTATE_EXT_SUBSTATE]);
+
+ link_ext_substate_str = link_ext_substate_get(link_ext_state_val, link_ext_substate_val);
+ if (!link_ext_substate_str)
+ printf(", %u", link_ext_substate_val);
+ else
+ printf(", %s", link_ext_substate_str);
+}
+
+static void linkstate_link_ext_state_print(const struct nlattr *tb[])
+{
+ uint8_t link_ext_state_val;
+ const char *link_ext_state_str;
+
+ if (!tb[ETHTOOL_A_LINKSTATE_EXT_STATE])
+ return;
+
+ link_ext_state_val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKSTATE_EXT_STATE]);
+
+ link_ext_state_str = get_enum_string(names_link_ext_state,
+ ARRAY_SIZE(names_link_ext_state),
+ link_ext_state_val);
+ if (!link_ext_state_str)
+ printf(" (%u", link_ext_state_val);
+ else
+ printf(" (%s", link_ext_state_str);
+
+ linkstate_link_ext_substate_print(tb, link_ext_state_val);
+ printf(")");
+}
+
+int linkstate_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_LINKSTATE_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ int ret;
+
+ if (nlctx->is_dump || nlctx->is_monitor)
+ nlctx->no_banner = false;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_LINKSTATE_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ if (tb[ETHTOOL_A_LINKSTATE_LINK]) {
+ uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKSTATE_LINK]);
+
+ print_banner(nlctx);
+ printf("\tLink detected: %s", val ? "yes" : "no");
+ linkstate_link_ext_state_print(tb);
+ printf("\n");
+ }
+
+ if (tb[ETHTOOL_A_LINKSTATE_SQI]) {
+ uint32_t val = mnl_attr_get_u32(tb[ETHTOOL_A_LINKSTATE_SQI]);
+
+ print_banner(nlctx);
+ printf("\tSQI: %u", val);
+
+ if (tb[ETHTOOL_A_LINKSTATE_SQI_MAX]) {
+ uint32_t max;
+
+ max = mnl_attr_get_u32(tb[ETHTOOL_A_LINKSTATE_SQI_MAX]);
+ printf("/%u\n", max);
+ } else {
+ printf("\n");
+ }
+ }
+
+ if (tb[ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT]) {
+ uint32_t val;
+
+ val = mnl_attr_get_u32(tb[ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT]);
+ printf("\tLink Down Events: %u\n", val);
+ }
+
+ return MNL_CB_OK;
+}
+
+void wol_modes_cb(unsigned int idx, const char *name __maybe_unused, bool val,
+ void *data)
+{
+ struct ethtool_wolinfo *wol = data;
+
+ if (idx >= 32)
+ return;
+ wol->supported |= (1U << idx);
+ if (val)
+ wol->wolopts |= (1U << idx);
+}
+
+int wol_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_WOL_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ struct ethtool_wolinfo wol = {};
+ int ret;
+
+ if (nlctx->is_dump || nlctx->is_monitor)
+ nlctx->no_banner = false;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_WOL_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ if (tb[ETHTOOL_A_WOL_MODES])
+ walk_bitset(tb[ETHTOOL_A_WOL_MODES], NULL, wol_modes_cb, &wol);
+ if (tb[ETHTOOL_A_WOL_SOPASS]) {
+ unsigned int len;
+
+ len = mnl_attr_get_payload_len(tb[ETHTOOL_A_WOL_SOPASS]);
+ if (len != SOPASS_MAX)
+ fprintf(stderr, "invalid SecureOn password length %u (should be %u)\n",
+ len, SOPASS_MAX);
+ else
+ memcpy(wol.sopass,
+ mnl_attr_get_payload(tb[ETHTOOL_A_WOL_SOPASS]),
+ SOPASS_MAX);
+ }
+ print_banner(nlctx);
+ dump_wol(&wol);
+
+ return MNL_CB_OK;
+}
+
+void msgmask_cb(unsigned int idx, const char *name __maybe_unused, bool val,
+ void *data)
+{
+ u32 *msg_mask = data;
+
+ if (idx >= 32)
+ return;
+ if (val)
+ *msg_mask |= (1U << idx);
+}
+
+void msgmask_cb2(unsigned int idx __maybe_unused, const char *name,
+ bool val, void *data __maybe_unused)
+{
+ if (val)
+ printf(" %s", name);
+}
+
+int debug_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_DEBUG_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ const struct stringset *msgmask_strings = NULL;
+ struct nl_context *nlctx = data;
+ u32 msg_mask = 0;
+ int ret;
+
+ if (nlctx->is_dump || nlctx->is_monitor)
+ nlctx->no_banner = false;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_DEBUG_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ if (!tb[ETHTOOL_A_DEBUG_MSGMASK])
+ return MNL_CB_OK;
+ if (bitset_is_compact(tb[ETHTOOL_A_DEBUG_MSGMASK])) {
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return MNL_CB_OK;
+ msgmask_strings = global_stringset(ETH_SS_MSG_CLASSES,
+ nlctx->ethnl2_socket);
+ }
+
+ print_banner(nlctx);
+ walk_bitset(tb[ETHTOOL_A_DEBUG_MSGMASK], NULL, msgmask_cb, &msg_mask);
+ printf(" Current message level: 0x%08x (%u)\n"
+ " ",
+ msg_mask, msg_mask);
+ walk_bitset(tb[ETHTOOL_A_DEBUG_MSGMASK], msgmask_strings, msgmask_cb2,
+ NULL);
+ fputc('\n', stdout);
+
+ return MNL_CB_OK;
+}
+
+int plca_cfg_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_PLCA_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ int ret;
+
+ if (nlctx->is_dump || nlctx->is_monitor)
+ nlctx->no_banner = false;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_PLCA_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ print_banner(nlctx);
+ printf("\tPLCA support: ");
+
+ if (tb[ETHTOOL_A_PLCA_VERSION]) {
+ uint16_t val = mnl_attr_get_u16(tb[ETHTOOL_A_PLCA_VERSION]);
+
+ printf("OPEN Alliance v%u.%u",
+ (unsigned int)((val >> 4) & 0xF),
+ (unsigned int)(val & 0xF));
+ } else
+ printf("non-standard");
+
+ printf("\n");
+
+ return MNL_CB_OK;
+}
+
+int plca_status_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_PLCA_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ int ret;
+
+ if (nlctx->is_dump || nlctx->is_monitor)
+ nlctx->no_banner = false;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_PLCA_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ print_banner(nlctx);
+ printf("\tPLCA status: ");
+
+ if (tb[ETHTOOL_A_PLCA_STATUS]) {
+ uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_PLCA_STATUS]);
+
+ printf(val ? "up" : "down");
+ } else
+ printf("unknown");
+
+ printf("\n");
+
+ return MNL_CB_OK;
+}
+
+static int gset_request(struct cmd_context *ctx, uint8_t msg_type,
+ uint16_t hdr_attr, mnl_cb_t cb)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ u32 flags;
+ int ret;
+
+ if (netlink_cmd_check(ctx, msg_type, true))
+ return 0;
+
+ flags = get_stats_flag(nlctx, msg_type, hdr_attr);
+
+ ret = nlsock_prep_get_request(nlsk, msg_type, hdr_attr, flags);
+ if (ret < 0)
+ return ret;
+ return nlsock_send_get_request(nlsk, cb);
+}
+
+int nl_gset(struct cmd_context *ctx)
+{
+ int ret;
+
+ /* Check for the base set of commands */
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_LINKMODES_GET, true) ||
+ netlink_cmd_check(ctx, ETHTOOL_MSG_LINKINFO_GET, true) ||
+ netlink_cmd_check(ctx, ETHTOOL_MSG_WOL_GET, true) ||
+ netlink_cmd_check(ctx, ETHTOOL_MSG_DEBUG_GET, true) ||
+ netlink_cmd_check(ctx, ETHTOOL_MSG_LINKSTATE_GET, true))
+ return -EOPNOTSUPP;
+
+ ctx->nlctx->suppress_nlerr = 1;
+
+ ret = gset_request(ctx, ETHTOOL_MSG_LINKMODES_GET,
+ ETHTOOL_A_LINKMODES_HEADER, linkmodes_reply_cb);
+ if (ret == -ENODEV)
+ return ret;
+
+ ret = gset_request(ctx, ETHTOOL_MSG_LINKINFO_GET,
+ ETHTOOL_A_LINKINFO_HEADER, linkinfo_reply_cb);
+ if (ret == -ENODEV)
+ return ret;
+
+ ret = gset_request(ctx, ETHTOOL_MSG_WOL_GET, ETHTOOL_A_WOL_HEADER,
+ wol_reply_cb);
+ if (ret == -ENODEV)
+ return ret;
+
+ ret = gset_request(ctx, ETHTOOL_MSG_PLCA_GET_CFG,
+ ETHTOOL_A_PLCA_HEADER, plca_cfg_reply_cb);
+ if (ret == -ENODEV)
+ return ret;
+
+ ret = gset_request(ctx, ETHTOOL_MSG_DEBUG_GET, ETHTOOL_A_DEBUG_HEADER,
+ debug_reply_cb);
+ if (ret == -ENODEV)
+ return ret;
+
+ ret = gset_request(ctx, ETHTOOL_MSG_LINKSTATE_GET,
+ ETHTOOL_A_LINKSTATE_HEADER, linkstate_reply_cb);
+ if (ret == -ENODEV)
+ return ret;
+
+ ret = gset_request(ctx, ETHTOOL_MSG_PLCA_GET_STATUS,
+ ETHTOOL_A_PLCA_HEADER, plca_status_reply_cb);
+ if (ret == -ENODEV)
+ return ret;
+
+ if (!ctx->nlctx->no_banner) {
+ printf("No data available\n");
+ return 75;
+ }
+
+ return 0;
+}
+
+/* SET_SETTINGS */
+
+enum {
+ WAKE_PHY_BIT = 0,
+ WAKE_UCAST_BIT = 1,
+ WAKE_MCAST_BIT = 2,
+ WAKE_BCAST_BIT = 3,
+ WAKE_ARP_BIT = 4,
+ WAKE_MAGIC_BIT = 5,
+ WAKE_MAGICSECURE_BIT = 6,
+ WAKE_FILTER_BIT = 7,
+};
+
+#define WAKE_ALL (WAKE_PHY | WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_ARP | \
+ WAKE_MAGIC | WAKE_MAGICSECURE)
+
+static const struct lookup_entry_u8 port_values[] = {
+ { .arg = "tp", .val = PORT_TP },
+ { .arg = "aui", .val = PORT_AUI },
+ { .arg = "mii", .val = PORT_MII },
+ { .arg = "fibre", .val = PORT_FIBRE },
+ { .arg = "bnc", .val = PORT_BNC },
+ { .arg = "da", .val = PORT_DA },
+ {}
+};
+
+static const struct lookup_entry_u8 mdix_values[] = {
+ { .arg = "auto", .val = ETH_TP_MDI_AUTO },
+ { .arg = "on", .val = ETH_TP_MDI_X },
+ { .arg = "off", .val = ETH_TP_MDI },
+ {}
+};
+
+static const struct error_parser_data xcvr_parser_data = {
+ .err_msg = "deprecated parameter '%s' not supported by kernel\n",
+ .ret_val = -EINVAL,
+ .extra_args = 1,
+};
+
+static const struct lookup_entry_u8 autoneg_values[] = {
+ { .arg = "off", .val = AUTONEG_DISABLE },
+ { .arg = "on", .val = AUTONEG_ENABLE },
+ {}
+};
+
+static const struct bitset_parser_data advertise_parser_data = {
+ .no_mask = false,
+ .force_hex = true,
+};
+
+static const struct lookup_entry_u8 duplex_values[] = {
+ { .arg = "half", .val = DUPLEX_HALF },
+ { .arg = "full", .val = DUPLEX_FULL },
+ {}
+};
+
+static const struct lookup_entry_u8 master_slave_values[] = {
+ { .arg = "preferred-master", .val = MASTER_SLAVE_CFG_MASTER_PREFERRED },
+ { .arg = "preferred-slave", .val = MASTER_SLAVE_CFG_SLAVE_PREFERRED },
+ { .arg = "forced-master", .val = MASTER_SLAVE_CFG_MASTER_FORCE },
+ { .arg = "forced-slave", .val = MASTER_SLAVE_CFG_SLAVE_FORCE },
+ {}
+};
+
+char wol_bit_chars[WOL_MODE_COUNT] = {
+ [WAKE_PHY_BIT] = 'p',
+ [WAKE_UCAST_BIT] = 'u',
+ [WAKE_MCAST_BIT] = 'm',
+ [WAKE_BCAST_BIT] = 'b',
+ [WAKE_ARP_BIT] = 'a',
+ [WAKE_MAGIC_BIT] = 'g',
+ [WAKE_MAGICSECURE_BIT] = 's',
+ [WAKE_FILTER_BIT] = 'f',
+};
+
+const struct char_bitset_parser_data wol_parser_data = {
+ .bit_chars = wol_bit_chars,
+ .nbits = WOL_MODE_COUNT,
+ .reset_char = 'd',
+};
+
+const struct byte_str_parser_data sopass_parser_data = {
+ .min_len = 6,
+ .max_len = 6,
+ .delim = ':',
+};
+
+static const struct bitset_parser_data msglvl_parser_data = {
+ .no_mask = false,
+ .force_hex = false,
+};
+
+static const struct param_parser sset_params[] = {
+ {
+ .arg = "port",
+ .group = ETHTOOL_MSG_LINKINFO_SET,
+ .type = ETHTOOL_A_LINKINFO_PORT,
+ .handler = nl_parse_lookup_u8,
+ .handler_data = port_values,
+ .min_argc = 1,
+ },
+ {
+ .arg = "mdix",
+ .group = ETHTOOL_MSG_LINKINFO_SET,
+ .type = ETHTOOL_A_LINKINFO_TP_MDIX_CTRL,
+ .handler = nl_parse_lookup_u8,
+ .handler_data = mdix_values,
+ .min_argc = 1,
+ },
+ {
+ .arg = "phyad",
+ .group = ETHTOOL_MSG_LINKINFO_SET,
+ .type = ETHTOOL_A_LINKINFO_PHYADDR,
+ .handler = nl_parse_direct_u8,
+ .min_argc = 1,
+ },
+ {
+ .arg = "xcvr",
+ .group = ETHTOOL_MSG_LINKINFO_SET,
+ .handler = nl_parse_error,
+ .handler_data = &xcvr_parser_data,
+ .min_argc = 1,
+ },
+ {
+ .arg = "autoneg",
+ .group = ETHTOOL_MSG_LINKMODES_SET,
+ .type = ETHTOOL_A_LINKMODES_AUTONEG,
+ .handler = nl_parse_lookup_u8,
+ .handler_data = autoneg_values,
+ .min_argc = 1,
+ },
+ {
+ .arg = "advertise",
+ .group = ETHTOOL_MSG_LINKMODES_SET,
+ .type = ETHTOOL_A_LINKMODES_OURS,
+ .handler = nl_parse_bitset,
+ .handler_data = &advertise_parser_data,
+ .min_argc = 1,
+ },
+ {
+ .arg = "speed",
+ .group = ETHTOOL_MSG_LINKMODES_SET,
+ .type = ETHTOOL_A_LINKMODES_SPEED,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "lanes",
+ .group = ETHTOOL_MSG_LINKMODES_SET,
+ .type = ETHTOOL_A_LINKMODES_LANES,
+ .handler = nl_parse_direct_u32,
+ .min_argc = 1,
+ },
+ {
+ .arg = "duplex",
+ .group = ETHTOOL_MSG_LINKMODES_SET,
+ .type = ETHTOOL_A_LINKMODES_DUPLEX,
+ .handler = nl_parse_lookup_u8,
+ .handler_data = duplex_values,
+ .min_argc = 1,
+ },
+ {
+ .arg = "master-slave",
+ .group = ETHTOOL_MSG_LINKMODES_SET,
+ .type = ETHTOOL_A_LINKMODES_MASTER_SLAVE_CFG,
+ .handler = nl_parse_lookup_u8,
+ .handler_data = master_slave_values,
+ .min_argc = 1,
+ },
+ {
+ .arg = "wol",
+ .group = ETHTOOL_MSG_WOL_SET,
+ .type = ETHTOOL_A_WOL_MODES,
+ .handler = nl_parse_char_bitset,
+ .handler_data = &wol_parser_data,
+ .min_argc = 1,
+ },
+ {
+ .arg = "sopass",
+ .group = ETHTOOL_MSG_WOL_SET,
+ .type = ETHTOOL_A_WOL_SOPASS,
+ .handler = nl_parse_byte_str,
+ .handler_data = &sopass_parser_data,
+ .min_argc = 1,
+ },
+ {
+ .arg = "msglvl",
+ .group = ETHTOOL_MSG_DEBUG_SET,
+ .type = ETHTOOL_A_DEBUG_MSGMASK,
+ .handler = nl_parse_bitset,
+ .handler_data = &msglvl_parser_data,
+ .min_argc = 1,
+ },
+ {}
+};
+
+/* Maximum number of request messages sent to kernel; must be equal to the
+ * number of different .group values in sset_params[] array.
+ */
+#define SSET_MAX_MSGS 4
+
+static int linkmodes_reply_advert_all_cb(const struct nlmsghdr *nlhdr,
+ void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_msg_buff *req_msgbuff = data;
+ const struct nlattr *ours_attr;
+ struct nlattr *req_bitset;
+ uint32_t *supported_modes;
+ unsigned int modes_count;
+ unsigned int i;
+ int ret;
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+ ours_attr = tb[ETHTOOL_A_LINKMODES_OURS];
+ if (!ours_attr)
+ return MNL_CB_ERROR;
+ modes_count = bitset_get_count(tb[ETHTOOL_A_LINKMODES_OURS], &ret);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+ supported_modes = get_compact_bitset_mask(tb[ETHTOOL_A_LINKMODES_OURS]);
+ if (!supported_modes)
+ return MNL_CB_ERROR;
+
+ /* keep only "real" link modes */
+ for (i = 0; i < modes_count; i++)
+ if (!lm_class_match(i, LM_CLASS_REAL))
+ supported_modes[i / 32] &= ~((uint32_t)1 << (i % 32));
+
+ req_bitset = ethnla_nest_start(req_msgbuff, ETHTOOL_A_LINKMODES_OURS);
+ if (!req_bitset)
+ return MNL_CB_ERROR;
+
+ if (ethnla_put_u32(req_msgbuff, ETHTOOL_A_BITSET_SIZE, modes_count) ||
+ ethnla_put(req_msgbuff, ETHTOOL_A_BITSET_VALUE,
+ DIV_ROUND_UP(modes_count, 32) * sizeof(uint32_t),
+ supported_modes) ||
+ ethnla_put(req_msgbuff, ETHTOOL_A_BITSET_MASK,
+ DIV_ROUND_UP(modes_count, 32) * sizeof(uint32_t),
+ supported_modes)) {
+ ethnla_nest_cancel(req_msgbuff, req_bitset);
+ return MNL_CB_ERROR;
+ }
+
+ ethnla_nest_end(req_msgbuff, req_bitset);
+ return MNL_CB_OK;
+}
+
+/* For compatibility reasons with ioctl-based ethtool, when "autoneg on" is
+ * specified without "advertise", "speed", "duplex" and "lanes", we need to
+ * query the supported link modes from the kernel and advertise all the "real"
+ * ones.
+ */
+static int nl_sset_compat_linkmodes(struct nl_context *nlctx,
+ struct nl_msg_buff *msgbuff)
+{
+ const struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ ret = mnl_attr_parse(msgbuff->nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ if (!tb[ETHTOOL_A_LINKMODES_AUTONEG] || tb[ETHTOOL_A_LINKMODES_OURS] ||
+ tb[ETHTOOL_A_LINKMODES_SPEED] || tb[ETHTOOL_A_LINKMODES_DUPLEX] ||
+ tb[ETHTOOL_A_LINKMODES_LANES])
+ return 0;
+ if (!mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_AUTONEG]))
+ return 0;
+
+ /* all conditions satisfied, create ETHTOOL_A_LINKMODES_OURS */
+ if (netlink_cmd_check(nlctx->ctx, ETHTOOL_MSG_LINKMODES_GET, false) ||
+ netlink_cmd_check(nlctx->ctx, ETHTOOL_MSG_LINKMODES_SET, false))
+ return -EOPNOTSUPP;
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_LINKMODES_GET,
+ ETHTOOL_A_LINKMODES_HEADER,
+ ETHTOOL_FLAG_COMPACT_BITSETS);
+ if (ret < 0)
+ return ret;
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return ret;
+ return nlsock_process_reply(nlsk, linkmodes_reply_advert_all_cb,
+ msgbuff);
+}
+
+int nl_sset(struct cmd_context *ctx)
+{
+ struct nl_msg_buff *msgbuffs[SSET_MAX_MSGS] = {};
+ struct nl_context *nlctx = ctx->nlctx;
+ unsigned int i;
+ int ret;
+
+ nlctx->cmd = "-s";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+
+ ret = nl_parser(nlctx, sset_params, NULL, PARSER_GROUP_MSG, msgbuffs);
+ if (ret == -EOPNOTSUPP)
+ return ret;
+
+ if (ret < 0) {
+ ret = 1;
+ goto out_free;
+ }
+
+ for (i = 0; i < SSET_MAX_MSGS && msgbuffs[i]; i++) {
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+
+ if (msgbuffs[i]->genlhdr->cmd == ETHTOOL_MSG_LINKMODES_SET) {
+ ret = nl_sset_compat_linkmodes(nlctx, msgbuffs[i]);
+ if (ret < 0)
+ goto out_free;
+ }
+ ret = nlsock_sendmsg(nlsk, msgbuffs[i]);
+ if (ret < 0)
+ goto out_free;
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, NULL);
+ if (ret < 0)
+ goto out_free;
+ }
+
+out_free:
+ for (i = 0; i < SSET_MAX_MSGS && msgbuffs[i]; i++) {
+ msgbuff_done(msgbuffs[i]);
+ free(msgbuffs[i]);
+ }
+ if (ret >= 0)
+ return ret;
+ return nlctx->exit_code ?: 75;
+}
diff --git a/netlink/stats.c b/netlink/stats.c
new file mode 100644
index 0000000..8620d8d
--- /dev/null
+++ b/netlink/stats.c
@@ -0,0 +1,333 @@
+/*
+ * stats.c - netlink implementation of stats
+ *
+ * Implementation of "ethtool -S <dev> [--groups <types>] etc."
+ */
+
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+#include "strset.h"
+
+static int parse_rmon_hist_one(const char *grp_name, const struct nlattr *hist,
+ const char *dir)
+{
+ const struct nlattr *tb[ETHTOOL_A_STATS_GRP_HIST_VAL + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ unsigned long long val;
+ unsigned int low, hi;
+ int ret;
+
+ ret = mnl_attr_parse_nested(hist, attr_cb, &tb_info);
+ if (ret < 0) {
+ fprintf(stderr, "invalid kernel response - malformed histogram entry\n");
+ return 1;
+ }
+
+ if (!tb[ETHTOOL_A_STATS_GRP_HIST_BKT_LOW] ||
+ !tb[ETHTOOL_A_STATS_GRP_HIST_BKT_HI] ||
+ !tb[ETHTOOL_A_STATS_GRP_HIST_VAL]) {
+ fprintf(stderr, "invalid kernel response - histogram entry missing attributes\n");
+ return 1;
+ }
+
+ low = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_HIST_BKT_LOW]);
+ hi = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_HIST_BKT_HI]);
+ val = mnl_attr_get_u64(tb[ETHTOOL_A_STATS_GRP_HIST_VAL]);
+
+ if (!is_json_context()) {
+ fprintf(stdout, "%s-%s-etherStatsPkts", dir, grp_name);
+
+ if (low && hi) {
+ fprintf(stdout, "%uto%uOctets: %llu\n", low, hi, val);
+ } else if (hi) {
+ fprintf(stdout, "%uOctets: %llu\n", hi, val);
+ } else if (low) {
+ fprintf(stdout, "%utoMaxOctets: %llu\n", low, val);
+ } else {
+ fprintf(stderr, "invalid kernel response - bad histogram entry bounds\n");
+ return 1;
+ }
+ } else {
+ open_json_object(NULL);
+ print_uint(PRINT_JSON, "low", NULL, low);
+ print_uint(PRINT_JSON, "high", NULL, hi);
+ print_u64(PRINT_JSON, "val", NULL, val);
+ close_json_object();
+ }
+
+ return 0;
+}
+
+static int parse_rmon_hist(const struct nlattr *grp, const char *grp_name,
+ const char *name, const char *dir, unsigned int type)
+{
+ const struct nlattr *attr;
+
+ open_json_array(name, "");
+
+ mnl_attr_for_each_nested(attr, grp) {
+ if (mnl_attr_get_type(attr) == type &&
+ parse_rmon_hist_one(grp_name, attr, dir))
+ goto err_close_rmon;
+ }
+ close_json_array("");
+
+ return 0;
+
+err_close_rmon:
+ close_json_array("");
+ return 1;
+}
+
+static int parse_grp(struct nl_context *nlctx, const struct nlattr *grp,
+ const struct stringset *std_str)
+{
+ const struct nlattr *tb[ETHTOOL_A_STATS_GRP_SS_ID + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ bool hist_rx = false, hist_tx = false;
+ const struct stringset *stat_str;
+ const struct nlattr *attr, *stat;
+ const char *std_name, *name;
+ unsigned int ss_id, id, s;
+ unsigned long long val;
+ int ret;
+
+ ret = mnl_attr_parse_nested(grp, attr_cb, &tb_info);
+ if (ret < 0)
+ return 1;
+
+ if (!tb[ETHTOOL_A_STATS_GRP_ID])
+ return 1;
+ if (!tb[ETHTOOL_A_STATS_GRP_SS_ID])
+ return 0;
+
+ id = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_ID]);
+ ss_id = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_SS_ID]);
+
+ stat_str = global_stringset(ss_id, nlctx->ethnl2_socket);
+
+ std_name = get_string(std_str, id);
+ open_json_object(std_name);
+
+ mnl_attr_for_each_nested(attr, grp) {
+ switch (mnl_attr_get_type(attr)) {
+ case ETHTOOL_A_STATS_GRP_STAT:
+ break;
+ case ETHTOOL_A_STATS_GRP_HIST_RX:
+ hist_rx = true;
+ continue;
+ case ETHTOOL_A_STATS_GRP_HIST_TX:
+ hist_tx = true;
+ continue;
+ default:
+ continue;
+ }
+
+ stat = mnl_attr_get_payload(attr);
+ ret = mnl_attr_validate(stat, MNL_TYPE_U64);
+ if (ret) {
+ fprintf(stderr, "invalid kernel response - bad statistic entry\n");
+ goto err_close_grp;
+ }
+ s = mnl_attr_get_type(stat);
+ name = get_string(stat_str, s);
+ if (!name || !name[0])
+ continue;
+
+ if (!is_json_context())
+ fprintf(stdout, "%s-%s: ", std_name, name);
+
+ val = mnl_attr_get_u64(stat);
+ print_u64(PRINT_ANY, name, "%llu\n", val);
+ }
+
+ if (hist_rx)
+ parse_rmon_hist(grp, std_name, "rx-pktsNtoM", "rx",
+ ETHTOOL_A_STATS_GRP_HIST_RX);
+ if (hist_tx)
+ parse_rmon_hist(grp, std_name, "tx-pktsNtoM", "tx",
+ ETHTOOL_A_STATS_GRP_HIST_TX);
+
+ close_json_object();
+
+ return 0;
+
+err_close_grp:
+ close_json_object();
+ return 1;
+}
+
+static int stats_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_STATS_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ const struct stringset *std_str;
+ const struct nlattr *attr;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_STATS_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return err_ret;
+ std_str = global_stringset(ETH_SS_STATS_STD, nlctx->ethnl2_socket);
+
+ if (silent)
+ print_nl();
+
+ open_json_object(NULL);
+
+ print_string(PRINT_ANY, "ifname", "Standard stats for %s:\n",
+ nlctx->devname);
+
+ mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
+ if (mnl_attr_get_type(attr) == ETHTOOL_A_STATS_GRP) {
+ ret = parse_grp(nlctx, attr, std_str);
+ if (ret)
+ goto err_close_dev;
+ }
+ }
+
+ close_json_object();
+
+ return MNL_CB_OK;
+
+err_close_dev:
+ close_json_object();
+ return err_ret;
+}
+
+static const struct bitset_parser_data stats_parser_data = {
+ .no_mask = true,
+ .force_hex = false,
+};
+
+static int stats_parse_all_groups(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest)
+{
+ const struct stringset *std_str;
+ struct nlattr *nest;
+ int i, ret, nbits;
+ uint32_t *bits;
+
+ if (data || dest)
+ return -EFAULT;
+
+ /* ethnl2 and strset code already does caching */
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return ret;
+ std_str = global_stringset(ETH_SS_STATS_STD, nlctx->ethnl2_socket);
+
+ nbits = get_count(std_str);
+ bits = calloc(DIV_ROUND_UP(nbits, 32), sizeof(uint32_t));
+ if (!bits)
+ return -ENOMEM;
+
+ for (i = 0; i < nbits; i++)
+ bits[i / 32] |= 1U << (i % 32);
+
+ ret = -EMSGSIZE;
+ nest = ethnla_nest_start(msgbuff, type);
+ if (!nest)
+ goto err_free;
+
+ if (ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, true) ||
+ ethnla_put_u32(msgbuff, ETHTOOL_A_BITSET_SIZE, nbits) ||
+ ethnla_put(msgbuff, ETHTOOL_A_BITSET_VALUE,
+ DIV_ROUND_UP(nbits, 32) * sizeof(uint32_t), bits))
+ goto err_cancel;
+
+ ethnla_nest_end(msgbuff, nest);
+ free(bits);
+ return 0;
+
+err_cancel:
+ ethnla_nest_cancel(msgbuff, nest);
+err_free:
+ free(bits);
+ return ret;
+}
+
+static const struct lookup_entry_u32 stats_src_values[] = {
+ { .arg = "aggregate", .val = ETHTOOL_MAC_STATS_SRC_AGGREGATE },
+ { .arg = "emac", .val = ETHTOOL_MAC_STATS_SRC_EMAC },
+ { .arg = "pmac", .val = ETHTOOL_MAC_STATS_SRC_PMAC },
+ {}
+};
+
+static const struct param_parser stats_params[] = {
+ {
+ .arg = "--groups",
+ .type = ETHTOOL_A_STATS_GROUPS,
+ .handler = nl_parse_bitset,
+ .handler_data = &stats_parser_data,
+ .min_argc = 1,
+ .alt_group = 1,
+ },
+ {
+ .arg = "--all-groups",
+ .type = ETHTOOL_A_STATS_GROUPS,
+ .handler = stats_parse_all_groups,
+ .alt_group = 1,
+ },
+ {
+ .arg = "--src",
+ .type = ETHTOOL_A_STATS_SRC,
+ .handler = nl_parse_lookup_u32,
+ .handler_data = stats_src_values,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_gstats(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_STATS_GET,
+ ETHTOOL_A_STATS_HEADER, 0);
+ if (ret < 0)
+ return ret;
+
+ nlctx->cmd = "-S";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+
+ ret = nl_parser(nlctx, stats_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, stats_reply_cb);
+ delete_json_obj();
+ return ret;
+}
+
+bool nl_gstats_chk(struct cmd_context *ctx)
+{
+ return ctx->argc;
+}
diff --git a/netlink/strset.c b/netlink/strset.c
new file mode 100644
index 0000000..949d597
--- /dev/null
+++ b/netlink/strset.c
@@ -0,0 +1,297 @@
+/*
+ * strset.c - string set handling
+ *
+ * Implementation of local cache of ethtool string sets.
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "nlsock.h"
+#include "msgbuff.h"
+
+struct stringset {
+ const char **strings;
+ void *raw_data;
+ unsigned int count;
+ bool loaded;
+};
+
+struct perdev_strings {
+ int ifindex;
+ char devname[ALTIFNAMSIZ];
+ struct stringset strings[ETH_SS_COUNT];
+ struct perdev_strings *next;
+};
+
+/* universal string sets */
+static struct stringset global_strings[ETH_SS_COUNT];
+/* linked list of string sets related to network devices */
+static struct perdev_strings *device_strings;
+
+static void drop_stringset(struct stringset *set)
+{
+ if (!set)
+ return;
+
+ free(set->strings);
+ free(set->raw_data);
+ memset(set, 0, sizeof(*set));
+}
+
+static int import_stringset(struct stringset *dest, const struct nlattr *nest)
+{
+ const struct nlattr *tb_stringset[ETHTOOL_A_STRINGSET_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb_stringset);
+ const struct nlattr *string;
+ unsigned int size;
+ unsigned int count;
+ unsigned int idx;
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_stringset_info);
+ if (ret < 0)
+ return ret;
+ if (!tb_stringset[ETHTOOL_A_STRINGSET_ID] ||
+ !tb_stringset[ETHTOOL_A_STRINGSET_COUNT] ||
+ !tb_stringset[ETHTOOL_A_STRINGSET_STRINGS])
+ return -EFAULT;
+ idx = mnl_attr_get_u32(tb_stringset[ETHTOOL_A_STRINGSET_ID]);
+ if (idx >= ETH_SS_COUNT)
+ return 0;
+ if (dest[idx].loaded)
+ drop_stringset(&dest[idx]);
+ dest[idx].loaded = true;
+ count = mnl_attr_get_u32(tb_stringset[ETHTOOL_A_STRINGSET_COUNT]);
+ if (count == 0)
+ return 0;
+
+ size = mnl_attr_get_len(tb_stringset[ETHTOOL_A_STRINGSET_STRINGS]);
+ ret = -ENOMEM;
+ dest[idx].raw_data = malloc(size);
+ if (!dest[idx].raw_data)
+ goto err;
+ memcpy(dest[idx].raw_data, tb_stringset[ETHTOOL_A_STRINGSET_STRINGS],
+ size);
+ dest[idx].strings = calloc(count, sizeof(dest[idx].strings[0]));
+ if (!dest[idx].strings)
+ goto err;
+ dest[idx].count = count;
+
+ nest = dest[idx].raw_data;
+ mnl_attr_for_each_nested(string, nest) {
+ const struct nlattr *tb[ETHTOOL_A_STRING_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ unsigned int i;
+
+ if (mnl_attr_get_type(string) != ETHTOOL_A_STRINGS_STRING)
+ continue;
+ ret = mnl_attr_parse_nested(string, attr_cb, &tb_info);
+ if (ret < 0)
+ goto err;
+ ret = -EFAULT;
+ if (!tb[ETHTOOL_A_STRING_INDEX] || !tb[ETHTOOL_A_STRING_VALUE])
+ goto err;
+
+ i = mnl_attr_get_u32(tb[ETHTOOL_A_STRING_INDEX]);
+ if (i >= count)
+ goto err;
+ dest[idx].strings[i] =
+ mnl_attr_get_payload(tb[ETHTOOL_A_STRING_VALUE]);
+ }
+
+ return 0;
+err:
+ drop_stringset(&dest[idx]);
+ return ret;
+}
+
+static struct perdev_strings *get_perdev_by_ifindex(int ifindex)
+{
+ struct perdev_strings *perdev = device_strings;
+
+ while (perdev && perdev->ifindex != ifindex)
+ perdev = perdev->next;
+ if (perdev)
+ return perdev;
+
+ /* not found, allocate and insert into list */
+ perdev = calloc(1, sizeof(*perdev));
+ if (!perdev)
+ return NULL;
+ perdev->ifindex = ifindex;
+ perdev->next = device_strings;
+ device_strings = perdev;
+
+ return perdev;
+}
+
+static int strset_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_STRSET_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ char devname[ALTIFNAMSIZ] = "";
+ struct stringset *dest;
+ struct nlattr *attr;
+ int ifindex = 0;
+ int ret;
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ if (tb[ETHTOOL_A_STRSET_HEADER]) {
+ ret = get_dev_info(tb[ETHTOOL_A_STRSET_HEADER], &ifindex,
+ devname);
+ if (ret < 0)
+ return MNL_CB_OK;
+ if (ifindex && nlctx->filter_devname &&
+ strncmp(devname, nlctx->filter_devname, ALTIFNAMSIZ))
+ return MNL_CB_OK;
+ }
+
+ if (ifindex) {
+ struct perdev_strings *perdev;
+
+ perdev = get_perdev_by_ifindex(ifindex);
+ if (!perdev)
+ return MNL_CB_OK;
+ copy_devname(perdev->devname, devname);
+ dest = perdev->strings;
+ } else {
+ dest = global_strings;
+ }
+
+ if (!tb[ETHTOOL_A_STRSET_STRINGSETS])
+ return MNL_CB_OK;
+ mnl_attr_for_each_nested(attr, tb[ETHTOOL_A_STRSET_STRINGSETS]) {
+ if (mnl_attr_get_type(attr) ==
+ ETHTOOL_A_STRINGSETS_STRINGSET)
+ import_stringset(dest, attr);
+ }
+
+ return MNL_CB_OK;
+}
+
+static int fill_stringset_id(struct nl_msg_buff *msgbuff, unsigned int type)
+{
+ struct nlattr *nest_sets;
+ struct nlattr *nest_set;
+
+ nest_sets = ethnla_nest_start(msgbuff, ETHTOOL_A_STRSET_STRINGSETS);
+ if (!nest_sets)
+ return -EMSGSIZE;
+ nest_set = ethnla_nest_start(msgbuff, ETHTOOL_A_STRINGSETS_STRINGSET);
+ if (!nest_set)
+ goto err;
+ if (ethnla_put_u32(msgbuff, ETHTOOL_A_STRINGSET_ID, type))
+ goto err;
+ ethnla_nest_end(msgbuff, nest_set);
+ ethnla_nest_end(msgbuff, nest_sets);
+ return 0;
+
+err:
+ ethnla_nest_cancel(msgbuff, nest_sets);
+ return -EMSGSIZE;
+}
+
+static int stringset_load_request(struct nl_socket *nlsk, const char *devname,
+ int type, bool is_dump)
+{
+ struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+ int ret;
+
+ ret = msg_init(nlsk->nlctx, msgbuff, ETHTOOL_MSG_STRSET_GET,
+ NLM_F_REQUEST | NLM_F_ACK | (is_dump ? NLM_F_DUMP : 0));
+ if (ret < 0)
+ return ret;
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_STRSET_HEADER, devname, 0))
+ return -EMSGSIZE;
+ if (type >= 0) {
+ ret = fill_stringset_id(msgbuff, type);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = nlsock_send_get_request(nlsk, strset_reply_cb);
+ return ret;
+}
+
+/* interface */
+
+const struct stringset *global_stringset(unsigned int type,
+ struct nl_socket *nlsk)
+{
+ int ret;
+
+ if (type >= ETH_SS_COUNT)
+ return NULL;
+ if (global_strings[type].loaded)
+ return &global_strings[type];
+ ret = stringset_load_request(nlsk, NULL, type, false);
+ return ret < 0 ? NULL : &global_strings[type];
+}
+
+const struct stringset *perdev_stringset(const char *devname, unsigned int type,
+ struct nl_socket *nlsk)
+{
+ const struct perdev_strings *p;
+ int ret;
+
+ if (type >= ETH_SS_COUNT)
+ return NULL;
+ for (p = device_strings; p; p = p->next)
+ if (!strcmp(p->devname, devname))
+ return &p->strings[type];
+
+ ret = stringset_load_request(nlsk, devname, type, false);
+ if (ret < 0)
+ return NULL;
+ for (p = device_strings; p; p = p->next)
+ if (!strcmp(p->devname, devname))
+ return &p->strings[type];
+
+ return NULL;
+}
+
+unsigned int get_count(const struct stringset *set)
+{
+ return set->count;
+}
+
+const char *get_string(const struct stringset *set, unsigned int idx)
+{
+ if (!set || idx >= set->count)
+ return NULL;
+ return set->strings[idx];
+}
+
+int preload_global_strings(struct nl_socket *nlsk)
+{
+ return stringset_load_request(nlsk, NULL, -1, false);
+}
+
+int preload_perdev_strings(struct nl_socket *nlsk, const char *dev)
+{
+ return stringset_load_request(nlsk, dev, -1, !dev);
+}
+
+void cleanup_all_strings(void)
+{
+ struct perdev_strings *perdev;
+ unsigned int i;
+
+ for (i = 0; i < ETH_SS_COUNT; i++)
+ drop_stringset(&global_strings[i]);
+
+ perdev = device_strings;
+ while (perdev) {
+ device_strings = perdev->next;
+ for (i = 0; i < ETH_SS_COUNT; i++)
+ drop_stringset(&perdev->strings[i]);
+ free(perdev);
+ perdev = device_strings;
+ }
+}
diff --git a/netlink/strset.h b/netlink/strset.h
new file mode 100644
index 0000000..72a4a39
--- /dev/null
+++ b/netlink/strset.h
@@ -0,0 +1,25 @@
+/*
+ * strset.h - string set handling
+ *
+ * Interface for local cache of ethtool string sets.
+ */
+
+#ifndef ETHTOOL_NETLINK_STRSET_H__
+#define ETHTOOL_NETLINK_STRSET_H__
+
+struct nl_socket;
+struct stringset;
+
+const struct stringset *global_stringset(unsigned int type,
+ struct nl_socket *nlsk);
+const struct stringset *perdev_stringset(const char *dev, unsigned int type,
+ struct nl_socket *nlsk);
+
+unsigned int get_count(const struct stringset *set);
+const char *get_string(const struct stringset *set, unsigned int idx);
+
+int preload_global_strings(struct nl_socket *nlsk);
+int preload_perdev_strings(struct nl_socket *nlsk, const char *dev);
+void cleanup_all_strings(void);
+
+#endif /* ETHTOOL_NETLINK_STRSET_H__ */
diff --git a/netlink/tsinfo.c b/netlink/tsinfo.c
new file mode 100644
index 0000000..c6571ff
--- /dev/null
+++ b/netlink/tsinfo.c
@@ -0,0 +1,124 @@
+/*
+ * tsinfo.c - netlink implementation of timestamping commands
+ *
+ * Implementation of "ethtool -T <dev>"
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "bitset.h"
+
+/* TSINFO_GET */
+
+static void tsinfo_dump_cb(unsigned int idx, const char *name, bool val,
+ void *data __maybe_unused)
+{
+ if (!val)
+ return;
+
+ if (name)
+ printf("\t%s\n", name);
+ else
+ printf("\tbit%u\n", idx);
+}
+
+static int tsinfo_dump_list(struct nl_context *nlctx, const struct nlattr *attr,
+ const char *label, const char *if_empty,
+ unsigned int stringset_id)
+{
+ const struct stringset *strings = NULL;
+ int ret;
+
+ printf("%s:", label);
+ ret = 0;
+ if (!attr || bitset_is_empty(attr, false, &ret)) {
+ printf("%s\n", if_empty);
+ return ret;
+ }
+ putchar('\n');
+ if (ret < 0)
+ return ret;
+
+ if (bitset_is_compact(attr)) {
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return ret;
+ strings = global_stringset(stringset_id, nlctx->ethnl2_socket);
+ }
+ return walk_bitset(attr, strings, tsinfo_dump_cb, NULL);
+}
+
+int tsinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_TSINFO_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_TSINFO_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (silent)
+ putchar('\n');
+ printf("Time stamping parameters for %s:\n", nlctx->devname);
+
+ ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSINFO_TIMESTAMPING],
+ "Capabilities", "", ETH_SS_SOF_TIMESTAMPING);
+ if (ret < 0)
+ return err_ret;
+
+ printf("PTP Hardware Clock: ");
+ if (tb[ETHTOOL_A_TSINFO_PHC_INDEX])
+ printf("%d\n",
+ mnl_attr_get_u32(tb[ETHTOOL_A_TSINFO_PHC_INDEX]));
+ else
+ printf("none\n");
+
+ ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSINFO_TX_TYPES],
+ "Hardware Transmit Timestamp Modes", " none",
+ ETH_SS_TS_TX_TYPES);
+ if (ret < 0)
+ return err_ret;
+
+ ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSINFO_RX_FILTERS],
+ "Hardware Receive Filter Modes", " none",
+ ETH_SS_TS_RX_FILTERS);
+ if (ret < 0)
+ return err_ret;
+
+ return MNL_CB_OK;
+}
+
+int nl_tsinfo(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_TSINFO_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_TSINFO_GET,
+ ETHTOOL_A_TSINFO_HEADER, 0);
+ if (ret < 0)
+ return ret;
+ return nlsock_send_get_request(nlsk, tsinfo_reply_cb);
+}
diff --git a/netlink/tunnels.c b/netlink/tunnels.c
new file mode 100644
index 0000000..b464046
--- /dev/null
+++ b/netlink/tunnels.c
@@ -0,0 +1,236 @@
+/*
+ * tunnel.c - device tunnel information
+ *
+ * Implementation of "ethtool --show-tunnels <dev>"
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+
+#include "../common.h"
+#include "../internal.h"
+
+#include "bitset.h"
+#include "netlink.h"
+#include "strset.h"
+
+/* TUNNEL_INFO_GET */
+
+static int
+tunnel_info_strings_load(struct nl_context *nlctx,
+ const struct stringset **strings)
+{
+ int ret;
+
+ if (*strings)
+ return 0;
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return ret;
+ *strings = global_stringset(ETH_SS_UDP_TUNNEL_TYPES,
+ nlctx->ethnl2_socket);
+ return 0;
+}
+
+static int
+tunnel_info_dump_udp_entry(struct nl_context *nlctx, const struct nlattr *entry,
+ const struct stringset **strings)
+{
+ const struct nlattr *entry_tb[ETHTOOL_A_TUNNEL_UDP_ENTRY_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(entry_tb);
+ const struct nlattr *attr;
+ unsigned int port, type;
+ int ret;
+
+ if (tunnel_info_strings_load(nlctx, strings))
+ return 1;
+
+ ret = mnl_attr_parse_nested(entry, attr_cb, &entry_tb_info);
+ if (ret < 0) {
+ fprintf(stderr, "malformed netlink message (udp entry)\n");
+ return 1;
+ }
+
+ attr = entry_tb[ETHTOOL_A_TUNNEL_UDP_ENTRY_PORT];
+ if (!attr || mnl_attr_validate(attr, MNL_TYPE_U16) < 0) {
+ fprintf(stderr, "malformed netlink message (port)\n");
+ return 1;
+ }
+ port = ntohs(mnl_attr_get_u16(attr));
+
+ attr = entry_tb[ETHTOOL_A_TUNNEL_UDP_ENTRY_TYPE];
+ if (!attr || mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
+ fprintf(stderr, "malformed netlink message (tunnel type)\n");
+ return 1;
+ }
+ type = mnl_attr_get_u32(attr);
+
+ printf(" port %d, %s\n", port, get_string(*strings, type));
+
+ return 0;
+}
+
+static void tunnel_info_dump_cb(unsigned int idx, const char *name, bool val,
+ void *data)
+{
+ bool *first = data;
+
+ if (!val)
+ return;
+
+ if (!*first)
+ putchar(',');
+ *first = false;
+
+ if (name)
+ printf(" %s", name);
+ else
+ printf(" bit%u", idx);
+}
+
+static int
+tunnel_info_dump_list(struct nl_context *nlctx, const struct nlattr *attr,
+ const struct stringset **strings)
+{
+ bool first = true;
+ int ret;
+
+ if (bitset_is_empty(attr, false, &ret) || ret) {
+ if (ret)
+ return 1;
+
+ printf(" Types: none (static entries)\n");
+ return 0;
+ }
+
+ if (bitset_is_compact(attr) &&
+ tunnel_info_strings_load(nlctx, strings))
+ return 1;
+
+ printf(" Types:");
+ ret = walk_bitset(attr, *strings, tunnel_info_dump_cb, &first);
+ putchar('\n');
+ return ret;
+}
+
+static int
+tunnel_info_dump_udp_table(const struct nlattr *table, struct nl_context *nlctx,
+ const struct stringset **strings)
+{
+ const struct nlattr *table_tb[ETHTOOL_A_TUNNEL_UDP_TABLE_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(table_tb);
+ const struct nlattr *attr;
+ int i, ret;
+
+ ret = mnl_attr_parse_nested(table, attr_cb, &table_tb_info);
+ if (ret < 0) {
+ fprintf(stderr, "malformed netlink message (udp table)\n");
+ return 1;
+ }
+
+ attr = table_tb[ETHTOOL_A_TUNNEL_UDP_TABLE_SIZE];
+ if (!attr || mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
+ fprintf(stderr, "malformed netlink message (table size)\n");
+ return 1;
+ }
+ printf(" Size: %d\n", mnl_attr_get_u32(attr));
+
+ attr = table_tb[ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES];
+ if (!attr || tunnel_info_dump_list(nlctx, attr, strings)) {
+ fprintf(stderr, "malformed netlink message (types)\n");
+ return 1;
+ }
+
+ if (!table_tb[ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY]) {
+ printf(" No entries\n");
+ return 0;
+ }
+
+ i = 0;
+ mnl_attr_for_each_nested(attr, table) {
+ if (mnl_attr_get_type(attr) == ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY)
+ i++;
+ }
+
+ printf(" Entries (%d):\n", i++);
+ mnl_attr_for_each_nested(attr, table) {
+ if (mnl_attr_get_type(attr) == ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY)
+ if (tunnel_info_dump_udp_entry(nlctx, attr, strings))
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+tunnel_info_dump_udp(const struct nlattr *udp_ports, struct nl_context *nlctx)
+{
+ const struct stringset *strings = NULL;
+ const struct nlattr *table;
+ int i, err;
+
+ i = 0;
+ mnl_attr_for_each_nested(table, udp_ports) {
+ if (mnl_attr_get_type(table) != ETHTOOL_A_TUNNEL_UDP_TABLE)
+ continue;
+
+ printf(" UDP port table %d: \n", i++);
+ err = tunnel_info_dump_udp_table(table, nlctx, &strings);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int tunnel_info_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_TUNNEL_INFO_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ const struct nlattr *udp_ports;
+ struct nl_context *nlctx = data;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_TUNNEL_INFO_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ printf("Tunnel information for %s:\n", nlctx->devname);
+
+ udp_ports = tb[ETHTOOL_A_TUNNEL_INFO_UDP_PORTS];
+ if (udp_ports)
+ if (tunnel_info_dump_udp(udp_ports, nlctx))
+ return err_ret;
+
+ return MNL_CB_OK;
+}
+
+int nl_gtunnels(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_TUNNEL_INFO_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_TUNNEL_INFO_GET,
+ ETHTOOL_A_TUNNEL_INFO_HEADER, 0);
+ if (ret < 0)
+ return ret;
+ return nlsock_send_get_request(nlsk, tunnel_info_reply_cb);
+}