@@ -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@
@@ -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 '-').
@@ -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__ */
new file mode 100644
@@ -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);
+}
@@ -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;
@@ -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)
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@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