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 <mkube...@suse.cz>
---
 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

Reply via email to