[RFC PATCH ethtool v2 05/23] netlink: add notification monitor

From: Michal Kubecek
Date: Mon Jul 30 2018 - 08:56:16 EST


With 'ethtool --monitor [ --all | opt ] [dev]', let ethtool listen to
netlink notification and display them in a format similar to output of
related "get" commands. With --all, show all types of notifications. If
device name is specified, show only notifications for that device, if no
device name or "*" is passed, show notifications for all devices.

Signed-off-by: Michal Kubecek <mkubecek@xxxxxxx>
---
Makefile.am | 2 +-
ethtool.c | 17 ++++
netlink/extapi.h | 4 +
netlink/monitor.c | 218 ++++++++++++++++++++++++++++++++++++++++++++++
netlink/netlink.c | 32 ++++++-
netlink/netlink.h | 26 ++++++
6 files changed, 297 insertions(+), 2 deletions(-)
create mode 100644 netlink/monitor.c

diff --git a/Makefile.am b/Makefile.am
index 6a4d7083919e..a412734fffd1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,7 +22,7 @@ endif
if ETHTOOL_ENABLE_NETLINK
ethtool_SOURCES += \
netlink/netlink.c netlink/netlink.h netlink/extapi.h \
- netlink/strset.c netlink/strset.h \
+ netlink/strset.c netlink/strset.h netlink/monitor.c \
uapi/linux/ethtool_netlink.h \
uapi/linux/netlink.h uapi/linux/genetlink.h
ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/ethtool.c b/ethtool.c
index e8044a6af76c..b7f40d2f3826 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -5294,6 +5294,9 @@ static int show_usage(struct cmd_context *ctx)
if (args[i].opthelp)
fputs(args[i].opthelp, stdout);
}
+#ifdef ETHTOOL_ENABLE_NETLINK
+ monitor_usage();
+#endif

return 0;
}
@@ -5335,6 +5338,20 @@ int main(int argc, char **argp)
argp++;
argc--;

+#ifdef ETHTOOL_ENABLE_NETLINK
+ if (*argp && !strcmp(*argp, "--monitor")) {
+ if (netlink_init(&ctx)) {
+ fprintf(stderr,
+ "Option --monitor is only available with netlink.\n");
+ return 1;
+ } else {
+ ctx.argp = ++argp;
+ ctx.argc = --argc;
+ return nl_monitor(&ctx);
+ }
+ }
+#endif
+
/* First argument must be either a valid option or a device
* name to get settings for (which we don't expect to begin
* with '-').
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 05ab083d9b9c..827e3732888a 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -13,4 +13,8 @@ struct nl_context;
int netlink_init(struct cmd_context *ctx);
int netlink_done(struct cmd_context *ctx);

+int nl_monitor(struct cmd_context *ctx);
+
+void monitor_usage();
+
#endif /* ETHTOOL_EXTAPI_H__ */
diff --git a/netlink/monitor.c b/netlink/monitor.c
new file mode 100644
index 000000000000..d7ed562356f1
--- /dev/null
+++ b/netlink/monitor.c
@@ -0,0 +1,218 @@
+#include <errno.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "strset.h"
+
+static void monitor_newdev(struct nl_context *nlctx, struct nlattr *evattr)
+{
+ const struct nlattr *tb[ETHA_NEWDEV_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ const char *devname;
+ int ret;
+
+ ret = mnl_attr_parse_nested(evattr, attr_cb, &tb_info);
+ if (ret < 0)
+ return;
+ if (!tb[ETHA_NEWDEV_DEV])
+ return;
+ devname = get_dev_name(tb[ETHA_NEWDEV_DEV]);
+ if (!devname)
+ return;
+ printf("New device %s registered.\n", devname);
+
+ ret = init_aux_nlctx(nlctx);
+ if (ret < 0)
+ return;
+ load_perdev_strings(nlctx->aux_nlctx, devname);
+}
+
+static void monitor_deldev(struct nl_context *nlctx, struct nlattr *evattr)
+{
+ const struct nlattr *tb[ETHA_DELDEV_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ const char *devname;
+ int ret;
+
+ ret = mnl_attr_parse_nested(evattr, attr_cb, &tb_info);
+ if (ret < 0)
+ return;
+ if (!tb[ETHA_DELDEV_DEV])
+ return;
+ devname = get_dev_name(tb[ETHA_DELDEV_DEV]);
+ if (!devname)
+ return;
+ printf("Device %s unregistered.\n", devname);
+
+ free_perdev_strings(devname);
+}
+
+static int monitor_event_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ struct nl_context *nlctx = data;
+ struct nlattr *evattr;
+
+ mnl_attr_for_each(evattr, nlhdr, GENL_HDRLEN) {
+ switch(mnl_attr_get_type(evattr)) {
+ case ETHA_EVENT_NEWDEV:
+ monitor_newdev(nlctx, evattr);
+ break;
+ case ETHA_EVENT_DELDEV:
+ monitor_deldev(nlctx, evattr);
+ break;
+ }
+ }
+
+ return MNL_CB_OK;
+}
+
+static struct {
+ uint8_t cmd;
+ mnl_cb_t cb;
+} monitor_callbacks[] = {
+ {
+ .cmd = ETHNL_CMD_EVENT,
+ .cb = monitor_event_cb,
+ },
+};
+
+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 i;
+
+ if (nlctx->filter_cmd && ghdr->cmd != nlctx->filter_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,
+ },
+};
+
+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 = "";
+ unsigned int i;
+
+ if (*argp && argp[0][0] == '-') {
+ opt = *argp;
+ argp++;
+ argc--;
+ }
+ for (i = 0; i < MNL_ARRAY_SIZE(monitor_opts); i++) {
+ if (pattern_match(opt, monitor_opts[i].pattern)) {
+ nlctx->filter_cmd = monitor_opts[i].cmd;
+ nlctx->filter_mask = monitor_opts[i].info_mask;
+ goto opt_found;
+ }
+ }
+ fprintf(stderr, "monitoring for option '%s' not supported\n", *argp);
+ return -1;
+
+opt_found:
+ if (*argp && strcmp(*argp, WILDCARD_DEVNAME))
+ ctx->devname = *argp;
+ return 0;
+}
+
+int nl_monitor(struct cmd_context *ctx)
+{
+ bool is_dev;
+ struct nl_context *nlctx = ctx->nlctx;
+ uint32_t grpid = nlctx->mon_mcgrp_id;
+ int ret;
+
+ 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 = load_global_strings(nlctx);
+ if (ret < 0)
+ return ret;
+ ret = mnl_socket_setsockopt(nlctx->sk, NETLINK_ADD_MEMBERSHIP,
+ &grpid, sizeof(grpid));
+ if (ret < 0)
+ return ret;
+ ret = load_perdev_strings(nlctx, is_dev ? ctx->devname : NULL);
+ if (ret < 0)
+ return ret;
+
+ nlctx->filter_devname = ctx->devname;
+ nlctx->is_monitor = true;
+ nlctx->port = 0;
+ nlctx->seq = 0;
+
+ fputs("listening...\n", stdout);
+ fflush(stdout);
+ ret = ethnl_process_reply(nlctx, monitor_any_cb);
+ free_perdev_strings(NULL);
+ return ret;
+}
+
+void monitor_usage()
+{
+ const char *p;
+ unsigned i;
+
+ fputs(" ethtool --monitor Show kernel notifications\n",
+ stdout);
+ for (i = 0; i < MNL_ARRAY_SIZE(monitor_opts); i++) {
+ if (i > 0)
+ fputs("\n | ", stdout);
+ else
+ fputs(" ( ", 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/netlink.c b/netlink/netlink.c
index 0671c4589c72..69f692d2cf04 100644
--- a/netlink/netlink.c
+++ b/netlink/netlink.c
@@ -391,6 +391,31 @@ err:

/* get ethtool family id */

+static void ethnl_find_monitor_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;
+
+ nlctx->mon_mcgrp_id = 0;
+ 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->mon_mcgrp_id =
+ mnl_attr_get_u32(grp_tb[CTRL_ATTR_MCAST_GRP_ID]);
+ return;
+ }
+}
+
static int ethnl_family_cb(const struct nlmsghdr *nlhdr, void *data)
{
struct nl_context *nlctx = data;
@@ -398,9 +423,13 @@ static int ethnl_family_cb(const struct nlmsghdr *nlhdr, void *data)

nlctx->ethnl_fam = 0;
mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
- if (mnl_attr_get_type(attr) == CTRL_ATTR_FAMILY_ID) {
+ switch(mnl_attr_get_type(attr)) {
+ case CTRL_ATTR_FAMILY_ID:
nlctx->ethnl_fam = mnl_attr_get_u16(attr);
break;
+ case CTRL_ATTR_MCAST_GROUPS:
+ ethnl_find_monitor_group(nlctx, attr);
+ break;
}
}

@@ -478,6 +507,7 @@ int __init_aux_nlctx(struct nl_context *nlctx)
}

aux->ethnl_fam = nlctx->ethnl_fam;
+ aux->mon_mcgrp_id = nlctx->mon_mcgrp_id;
nlctx->aux_nlctx = aux;

return 0;
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 547c865ed535..15bf9f3873b0 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -17,6 +17,7 @@

struct nl_context {
int ethnl_fam;
+ uint32_t mon_mcgrp_id;
struct mnl_socket *sk;
struct nl_context *aux_nlctx;
void *cmd_private;
@@ -30,6 +31,10 @@ struct nl_context {
const char *devname;
bool is_dump;
int exit_code;
+ bool is_monitor;
+ uint8_t filter_cmd;
+ uint32_t filter_mask;
+ const char *filter_devname;
};

struct attr_tb_info {
@@ -144,6 +149,27 @@ static inline void show_u32_yn(const struct nlattr **tb, unsigned int idx,
mnl_attr_get_u32(tb[idx]) ? "yes" : "no");
}

+/* reply content filtering */
+
+static inline bool mask_ok(const struct nl_context *nlctx, uint32_t bits)
+{
+ return !nlctx->filter_mask || (nlctx->filter_mask & bits);
+}
+
+static inline bool dev_ok(const struct nl_context *nlctx)
+{
+ return !nlctx->filter_devname ||
+ (nlctx->devname &&
+ !strcmp(nlctx->devname, nlctx->filter_devname));
+}
+
+static inline bool show_only(const struct nl_context *nlctx, uint32_t bits)
+{
+ if (nlctx->is_monitor || !nlctx->filter_mask)
+ return false;
+ return nlctx->filter_mask & ~bits;
+}
+
/* misc */

static inline int init_aux_nlctx(struct nl_context *nlctx)
--
2.18.0