1 /*
2 * monitor.c - netlink notification monitor
3 *
4 * Implementation of "ethtool --monitor" for watching netlink notifications.
5 */
6
7 #include <errno.h>
8
9 #include "../internal.h"
10 #include "netlink.h"
11 #include "nlsock.h"
12 #include "strset.h"
13
14 static struct {
15 uint8_t cmd;
16 mnl_cb_t cb;
17 } monitor_callbacks[] = {
18 {
19 .cmd = ETHTOOL_MSG_LINKMODES_NTF,
20 .cb = linkmodes_reply_cb,
21 },
22 {
23 .cmd = ETHTOOL_MSG_LINKINFO_NTF,
24 .cb = linkinfo_reply_cb,
25 },
26 {
27 .cmd = ETHTOOL_MSG_WOL_NTF,
28 .cb = wol_reply_cb,
29 },
30 {
31 .cmd = ETHTOOL_MSG_DEBUG_NTF,
32 .cb = debug_reply_cb,
33 },
34 {
35 .cmd = ETHTOOL_MSG_FEATURES_NTF,
36 .cb = features_reply_cb,
37 },
38 {
39 .cmd = ETHTOOL_MSG_PRIVFLAGS_NTF,
40 .cb = privflags_reply_cb,
41 },
42 {
43 .cmd = ETHTOOL_MSG_RINGS_NTF,
44 .cb = rings_reply_cb,
45 },
46 {
47 .cmd = ETHTOOL_MSG_CHANNELS_NTF,
48 .cb = channels_reply_cb,
49 },
50 {
51 .cmd = ETHTOOL_MSG_COALESCE_NTF,
52 .cb = coalesce_reply_cb,
53 },
54 {
55 .cmd = ETHTOOL_MSG_PAUSE_NTF,
56 .cb = pause_reply_cb,
57 },
58 {
59 .cmd = ETHTOOL_MSG_EEE_NTF,
60 .cb = eee_reply_cb,
61 },
62 {
63 .cmd = ETHTOOL_MSG_CABLE_TEST_NTF,
64 .cb = cable_test_ntf_cb,
65 },
66 {
67 .cmd = ETHTOOL_MSG_CABLE_TEST_TDR_NTF,
68 .cb = cable_test_tdr_ntf_cb,
69 },
70 {
71 .cmd = ETHTOOL_MSG_FEC_NTF,
72 .cb = fec_reply_cb,
73 },
74 {
75 .cmd = ETHTOOL_MSG_MODULE_NTF,
76 .cb = module_reply_cb,
77 },
78 };
79
clear_filter(struct nl_context * nlctx)80 static void clear_filter(struct nl_context *nlctx)
81 {
82 unsigned int i;
83
84 for (i = 0; i < CMDMASK_WORDS; i++)
85 nlctx->filter_cmds[i] = 0;
86 }
87
test_filter_cmd(const struct nl_context * nlctx,unsigned int cmd)88 static bool test_filter_cmd(const struct nl_context *nlctx, unsigned int cmd)
89 {
90 return nlctx->filter_cmds[cmd / 32] & (1U << (cmd % 32));
91 }
92
set_filter_cmd(struct nl_context * nlctx,unsigned int cmd)93 static void set_filter_cmd(struct nl_context *nlctx, unsigned int cmd)
94 {
95 nlctx->filter_cmds[cmd / 32] |= (1U << (cmd % 32));
96 }
97
set_filter_all(struct nl_context * nlctx)98 static void set_filter_all(struct nl_context *nlctx)
99 {
100 unsigned int i;
101
102 for (i = 0; i < ARRAY_SIZE(monitor_callbacks); i++)
103 set_filter_cmd(nlctx, monitor_callbacks[i].cmd);
104 }
105
monitor_any_cb(const struct nlmsghdr * nlhdr,void * data)106 static int monitor_any_cb(const struct nlmsghdr *nlhdr, void *data)
107 {
108 const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
109 struct nl_context *nlctx = data;
110 unsigned int i;
111
112 if (!test_filter_cmd(nlctx, ghdr->cmd))
113 return MNL_CB_OK;
114
115 for (i = 0; i < MNL_ARRAY_SIZE(monitor_callbacks); i++)
116 if (monitor_callbacks[i].cmd == ghdr->cmd)
117 return monitor_callbacks[i].cb(nlhdr, data);
118
119 return MNL_CB_OK;
120 }
121
122 struct monitor_option {
123 const char *pattern;
124 uint8_t cmd;
125 uint32_t info_mask;
126 };
127
128 static struct monitor_option monitor_opts[] = {
129 {
130 .pattern = "|--all",
131 .cmd = 0,
132 },
133 {
134 .pattern = "-s|--change",
135 .cmd = ETHTOOL_MSG_LINKINFO_NTF,
136 },
137 {
138 .pattern = "-s|--change",
139 .cmd = ETHTOOL_MSG_LINKMODES_NTF,
140 },
141 {
142 .pattern = "-s|--change",
143 .cmd = ETHTOOL_MSG_WOL_NTF,
144 },
145 {
146 .pattern = "-s|--change",
147 .cmd = ETHTOOL_MSG_DEBUG_NTF,
148 },
149 {
150 .pattern = "-k|--show-features|--show-offload|-K|--features|--offload",
151 .cmd = ETHTOOL_MSG_FEATURES_NTF,
152 },
153 {
154 .pattern = "--show-priv-flags|--set-priv-flags",
155 .cmd = ETHTOOL_MSG_PRIVFLAGS_NTF,
156 },
157 {
158 .pattern = "-g|--show-ring|-G|--set-ring",
159 .cmd = ETHTOOL_MSG_RINGS_NTF,
160 },
161 {
162 .pattern = "-l|--show-channels|-L|--set-channels",
163 .cmd = ETHTOOL_MSG_CHANNELS_NTF,
164 },
165 {
166 .pattern = "-c|--show-coalesce|-C|--coalesce",
167 .cmd = ETHTOOL_MSG_COALESCE_NTF,
168 },
169 {
170 .pattern = "-a|--show-pause|-A|--pause",
171 .cmd = ETHTOOL_MSG_PAUSE_NTF,
172 },
173 {
174 .pattern = "--show-eee|--set-eee",
175 .cmd = ETHTOOL_MSG_EEE_NTF,
176 },
177 {
178 .pattern = "--cable-test",
179 .cmd = ETHTOOL_MSG_CABLE_TEST_NTF,
180 },
181 {
182 .pattern = "--cable-test-tdr",
183 .cmd = ETHTOOL_MSG_CABLE_TEST_TDR_NTF,
184 },
185 {
186 .pattern = "--show-module|--set-module",
187 .cmd = ETHTOOL_MSG_MODULE_NTF,
188 },
189 };
190
pattern_match(const char * s,const char * pattern)191 static bool pattern_match(const char *s, const char *pattern)
192 {
193 const char *opt = pattern;
194 const char *next;
195 int slen = strlen(s);
196 int optlen;
197
198 do {
199 next = opt;
200 while (*next && *next != '|')
201 next++;
202 optlen = next - opt;
203 if (slen == optlen && !strncmp(s, opt, optlen))
204 return true;
205
206 opt = next;
207 if (*opt == '|')
208 opt++;
209 } while (*opt);
210
211 return false;
212 }
213
parse_monitor(struct cmd_context * ctx)214 static int parse_monitor(struct cmd_context *ctx)
215 {
216 struct nl_context *nlctx = ctx->nlctx;
217 char **argp = ctx->argp;
218 int argc = ctx->argc;
219 const char *opt = "";
220 bool opt_found;
221 unsigned int i;
222
223 if (*argp && argp[0][0] == '-') {
224 opt = *argp;
225 argp++;
226 argc--;
227 }
228 opt_found = false;
229 clear_filter(nlctx);
230 for (i = 0; i < MNL_ARRAY_SIZE(monitor_opts); i++) {
231 if (pattern_match(opt, monitor_opts[i].pattern)) {
232 unsigned int cmd = monitor_opts[i].cmd;
233
234 if (!cmd)
235 set_filter_all(nlctx);
236 else
237 set_filter_cmd(nlctx, cmd);
238 opt_found = true;
239 }
240 }
241 if (!opt_found) {
242 fprintf(stderr, "monitoring for option '%s' not supported\n",
243 *argp);
244 return -1;
245 }
246
247 if (*argp && strcmp(*argp, WILDCARD_DEVNAME))
248 ctx->devname = *argp;
249 return 0;
250 }
251
nl_monitor(struct cmd_context * ctx)252 int nl_monitor(struct cmd_context *ctx)
253 {
254 struct nl_context *nlctx;
255 struct nl_socket *nlsk;
256 uint32_t grpid;
257 bool is_dev;
258 int ret;
259
260 ret = netlink_init(ctx);
261 if (ret < 0) {
262 fprintf(stderr, "Netlink interface initialization failed, option --monitor not supported.\n");
263 return ret;
264 }
265 nlctx = ctx->nlctx;
266 nlsk = nlctx->ethnl_socket;
267 grpid = nlctx->ethnl_mongrp;
268 if (!grpid) {
269 fprintf(stderr, "multicast group 'monitor' not found\n");
270 return -EOPNOTSUPP;
271 }
272
273 if (parse_monitor(ctx) < 0)
274 return 1;
275 is_dev = ctx->devname && strcmp(ctx->devname, WILDCARD_DEVNAME);
276
277 ret = preload_global_strings(nlsk);
278 if (ret < 0)
279 return ret;
280 ret = mnl_socket_setsockopt(nlsk->sk, NETLINK_ADD_MEMBERSHIP,
281 &grpid, sizeof(grpid));
282 if (ret < 0)
283 return ret;
284 if (is_dev) {
285 ret = preload_perdev_strings(nlsk, ctx->devname);
286 if (ret < 0)
287 goto out_strings;
288 }
289
290 nlctx->filter_devname = ctx->devname;
291 nlctx->is_monitor = true;
292 nlsk->port = 0;
293 nlsk->seq = 0;
294
295 fputs("listening...\n", stdout);
296 fflush(stdout);
297 ret = nlsock_process_reply(nlsk, monitor_any_cb, nlctx);
298
299 out_strings:
300 cleanup_all_strings();
301 return ret;
302 }
303
nl_monitor_usage(void)304 void nl_monitor_usage(void)
305 {
306 unsigned int i;
307 const char *p;
308
309 fputs(" ethtool --monitor Show kernel notifications\n",
310 stdout);
311 fputs(" ( [ --all ]", stdout);
312 for (i = 1; i < MNL_ARRAY_SIZE(monitor_opts); i++) {
313 if (!strcmp(monitor_opts[i].pattern, monitor_opts[i - 1].pattern))
314 continue;
315 fputs("\n | ", stdout);
316 for (p = monitor_opts[i].pattern; *p; p++)
317 if (*p == '|')
318 fputs(" | ", stdout);
319 else
320 fputc(*p, stdout);
321 }
322 fputs(" )\n", stdout);
323 fputs(" [ DEVNAME | * ]\n", stdout);
324 }
325