summaryrefslogtreecommitdiffstats
path: root/netlink/pause.c
diff options
context:
space:
mode:
Diffstat (limited to 'netlink/pause.c')
-rw-r--r--netlink/pause.c331
1 files changed, 331 insertions, 0 deletions
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;
+}