/* * monitor.c - netlink notification monitor * * Implementation of "ethtool --monitor" for watching netlink notifications. */ #include #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); }