]> git.ipfire.org Git - thirdparty/iw.git/blob - wowlan.c
iw: support multiple regdom print
[thirdparty/iw.git] / wowlan.c
1 #include <net/if.h>
2 #include <errno.h>
3 #include <string.h>
4 #include <stdio.h>
5
6 #include <netlink/genl/genl.h>
7 #include <netlink/genl/family.h>
8 #include <netlink/genl/ctrl.h>
9 #include <netlink/msg.h>
10 #include <netlink/attr.h>
11
12 #include <arpa/inet.h>
13
14 #include "nl80211.h"
15 #include "iw.h"
16
17 SECTION(wowlan);
18
19 static int wowlan_parse_tcp_file(struct nl_msg *msg, const char *fn)
20 {
21 char buf[16768];
22 int err = 1;
23 FILE *f = fopen(fn, "r");
24 struct nlattr *tcp;
25
26 if (!f)
27 return 1;
28 tcp = nla_nest_start(msg, NL80211_WOWLAN_TRIG_TCP_CONNECTION);
29 if (!tcp)
30 goto nla_put_failure;
31
32 while (!feof(f)) {
33 char *eol;
34
35 if (!fgets(buf, sizeof(buf), f))
36 break;
37
38 eol = strchr(buf + 5, '\r');
39 if (eol)
40 *eol = 0;
41 eol = strchr(buf + 5, '\n');
42 if (eol)
43 *eol = 0;
44
45 if (strncmp(buf, "source=", 7) == 0) {
46 struct in_addr in_addr;
47 char *addr = buf + 7;
48 char *port = strchr(buf + 7, ':');
49
50 if (port) {
51 *port = 0;
52 port++;
53 }
54 if (inet_aton(addr, &in_addr) == 0)
55 goto close;
56 NLA_PUT_U32(msg, NL80211_WOWLAN_TCP_SRC_IPV4,
57 in_addr.s_addr);
58 if (port)
59 NLA_PUT_U16(msg, NL80211_WOWLAN_TCP_SRC_PORT,
60 atoi(port));
61 } else if (strncmp(buf, "dest=", 5) == 0) {
62 struct in_addr in_addr;
63 char *addr = buf + 5;
64 char *port = strchr(buf + 5, ':');
65 char *mac;
66 unsigned char macbuf[6];
67
68 if (!port)
69 goto close;
70 *port = 0;
71 port++;
72 mac = strchr(port, '@');
73 if (!mac)
74 goto close;
75 *mac = 0;
76 mac++;
77 if (inet_aton(addr, &in_addr) == 0)
78 goto close;
79 NLA_PUT_U32(msg, NL80211_WOWLAN_TCP_DST_IPV4,
80 in_addr.s_addr);
81 NLA_PUT_U16(msg, NL80211_WOWLAN_TCP_DST_PORT,
82 atoi(port));
83 if (mac_addr_a2n(macbuf, mac))
84 goto close;
85 NLA_PUT(msg, NL80211_WOWLAN_TCP_DST_MAC,
86 6, macbuf);
87 } else if (strncmp(buf, "data=", 5) == 0) {
88 size_t len;
89 unsigned char *pkt = parse_hex(buf + 5, &len);
90
91 if (!pkt)
92 goto close;
93 NLA_PUT(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD, len, pkt);
94 free(pkt);
95 } else if (strncmp(buf, "data.interval=", 14) == 0) {
96 NLA_PUT_U32(msg, NL80211_WOWLAN_TCP_DATA_INTERVAL,
97 atoi(buf + 14));
98 } else if (strncmp(buf, "wake=", 5) == 0) {
99 unsigned char *pat, *mask;
100 size_t patlen;
101
102 if (parse_hex_mask(buf + 5, &pat, &patlen, &mask))
103 goto close;
104 NLA_PUT(msg, NL80211_WOWLAN_TCP_WAKE_MASK,
105 DIV_ROUND_UP(patlen, 8), mask);
106 NLA_PUT(msg, NL80211_WOWLAN_TCP_WAKE_PAYLOAD,
107 patlen, pat);
108 free(mask);
109 free(pat);
110 } else if (strncmp(buf, "data.seq=", 9) == 0) {
111 struct nl80211_wowlan_tcp_data_seq seq = {};
112 char *len, *offs, *start;
113
114 len = buf + 9;
115 offs = strchr(len, ',');
116 if (!offs)
117 goto close;
118 *offs = 0;
119 offs++;
120 start = strchr(offs, ',');
121 if (start) {
122 *start = 0;
123 start++;
124 seq.start = atoi(start);
125 }
126 seq.len = atoi(len);
127 seq.offset = atoi(offs);
128
129 NLA_PUT(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ,
130 sizeof(seq), &seq);
131 } else if (strncmp(buf, "data.tok=", 9) == 0) {
132 struct nl80211_wowlan_tcp_data_token *tok;
133 size_t stream_len;
134 char *len, *offs, *toks;
135 unsigned char *stream;
136
137 len = buf + 9;
138 offs = strchr(len, ',');
139 if (!offs)
140 goto close;
141 *offs = 0;
142 offs++;
143 toks = strchr(offs, ',');
144 if (!toks)
145 goto close;
146 *toks = 0;
147 toks++;
148
149 stream = parse_hex(toks, &stream_len);
150 if (!stream)
151 goto close;
152 tok = malloc(sizeof(*tok) + stream_len);
153 if (!tok) {
154 free(stream);
155 err = -ENOMEM;
156 goto close;
157 }
158
159 tok->len = atoi(len);
160 tok->offset = atoi(offs);
161 memcpy(tok->token_stream, stream, stream_len);
162
163 NLA_PUT(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN,
164 sizeof(*tok) + stream_len, tok);
165 free(stream);
166 free(tok);
167 } else {
168 if (buf[0] == '#')
169 continue;
170 goto close;
171 }
172 }
173
174 err = 0;
175 goto close;
176 nla_put_failure:
177 err = -ENOBUFS;
178 close:
179 fclose(f);
180 nla_nest_end(msg, tcp);
181 return err;
182 }
183
184 static int wowlan_parse_net_detect(struct nl_msg *msg, int *argc, char ***argv)
185 {
186 struct nl_msg *matchset = NULL, *freqs = NULL;
187 struct nlattr *nd, *match = NULL;
188 enum {
189 ND_TOPLEVEL,
190 ND_MATCH,
191 ND_FREQS,
192 } parse_state = ND_TOPLEVEL;
193 int c = *argc;
194 char *end, **v = *argv;
195 int err = 0, i = 0;
196 unsigned int freq, interval = 0;
197 bool have_matchset = false, have_freqs = false;
198
199 nd = nla_nest_start(msg, NL80211_WOWLAN_TRIG_NET_DETECT);
200 if (!nd) {
201 err = -ENOBUFS;
202 goto out;
203 }
204
205 matchset = nlmsg_alloc();
206 if (!matchset) {
207 err = -ENOBUFS;
208 goto out;
209 }
210
211 freqs = nlmsg_alloc();
212 if (!freqs) {
213 err = -ENOBUFS;
214 goto out;
215 }
216
217 while (c) {
218 switch (parse_state) {
219 case ND_TOPLEVEL:
220 if (!strcmp(v[0], "interval")) {
221 c--; v++;
222 if (c == 0) {
223 err = -EINVAL;
224 goto nla_put_failure;
225 }
226
227 if (interval) {
228 err = -EINVAL;
229 goto nla_put_failure;
230 }
231 interval = strtoul(v[0], &end, 10);
232 if (*end || !interval) {
233 err = -EINVAL;
234 goto nla_put_failure;
235 }
236 NLA_PUT_U32(msg,
237 NL80211_ATTR_SCHED_SCAN_INTERVAL,
238 interval);
239 } else if (!strcmp(v[0], "matches")) {
240 parse_state = ND_MATCH;
241 if (have_matchset) {
242 err = -EINVAL;
243 goto nla_put_failure;
244 }
245
246 i = 0;
247 } else if (!strcmp(v[0], "freqs")) {
248 parse_state = ND_FREQS;
249 if (have_freqs) {
250 err = -EINVAL;
251 goto nla_put_failure;
252 }
253
254 have_freqs = true;
255 i = 0;
256 } else {
257 /* this element is not for us, so
258 * return to continue parsing.
259 */
260 goto nla_put_failure;
261 }
262 c--; v++;
263
264 break;
265 case ND_MATCH:
266 if (!strcmp(v[0], "ssid")) {
267 c--; v++;
268 if (c == 0) {
269 err = -EINVAL;
270 goto nla_put_failure;
271 }
272
273 /* TODO: for now we can only have an
274 * SSID in the match, so we can start
275 * the match nest here.
276 */
277 match = nla_nest_start(matchset, i);
278 if (!match) {
279 err = -ENOBUFS;
280 goto nla_put_failure;
281 }
282
283 NLA_PUT(matchset,
284 NL80211_SCHED_SCAN_MATCH_ATTR_SSID,
285 strlen(v[0]), v[0]);
286 nla_nest_end(matchset, match);
287 match = NULL;
288
289 have_matchset = true;
290 i++;
291 c--; v++;
292 } else {
293 /* other element that cannot be part
294 * of a match indicates the end of the
295 * match. */
296 /* need at least one match in the matchset */
297 if (i == 0) {
298 err = -EINVAL;
299 goto nla_put_failure;
300 }
301
302 parse_state = ND_TOPLEVEL;
303 }
304
305 break;
306 case ND_FREQS:
307 freq = strtoul(v[0], &end, 10);
308 if (*end) {
309 if (i == 0) {
310 err = -EINVAL;
311 goto nla_put_failure;
312 }
313
314 parse_state = ND_TOPLEVEL;
315 } else {
316 NLA_PUT_U32(freqs, i, freq);
317 i++;
318 c--; v++;
319 }
320 break;
321 }
322 }
323
324 if (have_freqs)
325 nla_put_nested(msg, NL80211_ATTR_SCAN_FREQUENCIES, freqs);
326 if (have_matchset)
327 nla_put_nested(msg, NL80211_ATTR_SCHED_SCAN_MATCH, matchset);
328
329 nla_put_failure:
330 if (match)
331 nla_nest_end(msg, match);
332 nlmsg_free(freqs);
333 nlmsg_free(matchset);
334 nla_nest_end(msg, nd);
335 out:
336 *argc = c;
337 *argv = v;
338 return err;
339 }
340
341 static int handle_wowlan_enable(struct nl80211_state *state, struct nl_cb *cb,
342 struct nl_msg *msg, int argc, char **argv,
343 enum id_input id)
344 {
345 struct nlattr *wowlan, *pattern;
346 struct nl_msg *patterns = NULL;
347 enum {
348 PS_REG,
349 PS_PAT,
350 } parse_state = PS_REG;
351 int err = -ENOBUFS;
352 unsigned char *pat, *mask;
353 size_t patlen;
354 int patnum = 0, pkt_offset;
355 char *eptr, *value1, *value2, *sptr = NULL;
356
357 wowlan = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS);
358 if (!wowlan)
359 return -ENOBUFS;
360
361 while (argc) {
362 switch (parse_state) {
363 case PS_REG:
364 if (strcmp(argv[0], "any") == 0)
365 NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY);
366 else if (strcmp(argv[0], "disconnect") == 0)
367 NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT);
368 else if (strcmp(argv[0], "magic-packet") == 0)
369 NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT);
370 else if (strcmp(argv[0], "gtk-rekey-failure") == 0)
371 NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE);
372 else if (strcmp(argv[0], "eap-identity-request") == 0)
373 NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST);
374 else if (strcmp(argv[0], "4way-handshake") == 0)
375 NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE);
376 else if (strcmp(argv[0], "rfkill-release") == 0)
377 NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE);
378 else if (strcmp(argv[0], "tcp") == 0) {
379 argv++;
380 argc--;
381 if (!argc) {
382 err = 1;
383 goto nla_put_failure;
384 }
385 err = wowlan_parse_tcp_file(msg, argv[0]);
386 if (err)
387 goto nla_put_failure;
388 } else if (strcmp(argv[0], "patterns") == 0) {
389 parse_state = PS_PAT;
390 patterns = nlmsg_alloc();
391 if (!patterns) {
392 err = -ENOMEM;
393 goto nla_put_failure;
394 }
395 } else if (strcmp(argv[0], "net-detect") == 0) {
396 argv++;
397 argc--;
398 if (!argc) {
399 err = 1;
400 goto nla_put_failure;
401 }
402 err = wowlan_parse_net_detect(msg, &argc, &argv);
403 if (err)
404 goto nla_put_failure;
405 continue;
406 } else {
407 err = 1;
408 goto nla_put_failure;
409 }
410 break;
411 case PS_PAT:
412 value1 = strtok_r(argv[0], "+", &sptr);
413 value2 = strtok_r(NULL, "+", &sptr);
414
415 if (!value2) {
416 pkt_offset = 0;
417 value2 = value1;
418 } else {
419 pkt_offset = strtoul(value1, &eptr, 10);
420 if (eptr != value1 + strlen(value1)) {
421 err = 1;
422 goto nla_put_failure;
423 }
424 }
425
426 if (parse_hex_mask(value2, &pat, &patlen, &mask)) {
427 err = 1;
428 goto nla_put_failure;
429 }
430
431 pattern = nla_nest_start(patterns, ++patnum);
432 NLA_PUT(patterns, NL80211_PKTPAT_MASK,
433 DIV_ROUND_UP(patlen, 8), mask);
434 NLA_PUT(patterns, NL80211_PKTPAT_PATTERN, patlen, pat);
435 NLA_PUT_U32(patterns, NL80211_PKTPAT_OFFSET,
436 pkt_offset);
437 nla_nest_end(patterns, pattern);
438 free(mask);
439 free(pat);
440 break;
441 }
442 argv++;
443 argc--;
444 }
445
446 if (patterns)
447 nla_put_nested(msg, NL80211_WOWLAN_TRIG_PKT_PATTERN,
448 patterns);
449
450 nla_nest_end(msg, wowlan);
451 err = 0;
452 nla_put_failure:
453 nlmsg_free(patterns);
454 return err;
455 }
456 COMMAND(wowlan, enable, "[any] [disconnect] [magic-packet] [gtk-rekey-failure] [eap-identity-request]"
457 " [4way-handshake] [rfkill-release] [net-detect interval <in_msecs> [freqs <freq>+] [matches [ssid <ssid>]+]]"
458 " [tcp <config-file>] [patterns [offset1+]<pattern1> ...]",
459 NL80211_CMD_SET_WOWLAN, 0, CIB_PHY, handle_wowlan_enable,
460 "Enable WoWLAN with the given triggers.\n"
461 "Each pattern is given as a bytestring with '-' in places where any byte\n"
462 "may be present, e.g. 00:11:22:-:44 will match 00:11:22:33:44 and\n"
463 "00:11:22:33:ff:44 etc.\n"
464 "Offset and pattern should be separated by '+', e.g. 18+43:34:00:12 will match "
465 "'43:34:00:12' after 18 bytes of offset in Rx packet.\n\n"
466 "The TCP configuration file contains:\n"
467 " source=ip[:port]\n"
468 " dest=ip:port@mac\n"
469 " data=<hex data packet>\n"
470 " data.interval=seconds\n"
471 " [wake=<hex packet with masked out bytes indicated by '-'>]\n"
472 " [data.seq=len,offset[,start]]\n"
473 " [data.tok=len,offset,<token stream>]\n\n"
474 "Net-detect configuration example:\n"
475 " iw phy0 wowlan enable net-detect interval 5000 freqs 2412 2422 matches ssid foo ssid bar");
476
477
478 static int handle_wowlan_disable(struct nl80211_state *state, struct nl_cb *cb,
479 struct nl_msg *msg, int argc, char **argv,
480 enum id_input id)
481 {
482 /* just a set w/o wowlan attribute */
483 return 0;
484 }
485 COMMAND(wowlan, disable, "", NL80211_CMD_SET_WOWLAN, 0, CIB_PHY, handle_wowlan_disable,
486 "Disable WoWLAN.");
487
488
489 static int print_wowlan_handler(struct nl_msg *msg, void *arg)
490 {
491 struct nlattr *attrs[NL80211_ATTR_MAX + 1];
492 struct nlattr *trig[NUM_NL80211_WOWLAN_TRIG];
493 struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
494 struct nlattr *pattern;
495 int rem_pattern;
496
497 nla_parse(attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
498 genlmsg_attrlen(gnlh, 0), NULL);
499
500 if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS]) {
501 printf("WoWLAN is disabled.\n");
502 return NL_SKIP;
503 }
504
505 /* XXX: use policy */
506 nla_parse(trig, MAX_NL80211_WOWLAN_TRIG,
507 nla_data(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
508 nla_len(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
509 NULL);
510
511 printf("WoWLAN is enabled:\n");
512 if (trig[NL80211_WOWLAN_TRIG_ANY])
513 printf(" * wake up on special any trigger\n");
514 if (trig[NL80211_WOWLAN_TRIG_DISCONNECT])
515 printf(" * wake up on disconnect\n");
516 if (trig[NL80211_WOWLAN_TRIG_MAGIC_PKT])
517 printf(" * wake up on magic packet\n");
518 if (trig[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE])
519 printf(" * wake up on GTK rekeying failure\n");
520 if (trig[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST])
521 printf(" * wake up on EAP identity request\n");
522 if (trig[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE])
523 printf(" * wake up on 4-way handshake\n");
524 if (trig[NL80211_WOWLAN_TRIG_RFKILL_RELEASE])
525 printf(" * wake up on RF-kill release\n");
526 if (trig[NL80211_WOWLAN_TRIG_NET_DETECT]) {
527 struct nlattr *match, *freq,
528 *nd[NUM_NL80211_ATTR], *tb[NUM_NL80211_ATTR];
529 int rem_match;
530
531 printf(" * wake up on network detection\n");
532 nla_parse(nd, NUM_NL80211_ATTR,
533 nla_data(trig[NL80211_WOWLAN_TRIG_NET_DETECT]),
534 nla_len(trig[NL80211_WOWLAN_TRIG_NET_DETECT]), NULL);
535
536 if (nd[NL80211_ATTR_SCHED_SCAN_INTERVAL])
537 printf("\tscan interval: %d msecs\n",
538 nla_get_u32(nd[NL80211_ATTR_SCHED_SCAN_INTERVAL]));
539
540 if (nd[NL80211_ATTR_SCHED_SCAN_MATCH]) {
541 printf("\tmatches:\n");
542 nla_for_each_nested(match,
543 nd[NL80211_ATTR_SCHED_SCAN_MATCH],
544 rem_match) {
545 nla_parse(tb, NUM_NL80211_ATTR, nla_data(match),
546 nla_len(match),
547 NULL);
548 printf("\t\tSSID: ");
549 print_ssid_escaped(
550 nla_len(tb[NL80211_SCHED_SCAN_MATCH_ATTR_SSID]),
551 nla_data(tb[NL80211_SCHED_SCAN_MATCH_ATTR_SSID]));
552 printf("\n");
553 }
554 }
555 if (nd[NL80211_ATTR_SCAN_FREQUENCIES]) {
556 printf("\tfrequencies:");
557 nla_for_each_nested(freq,
558 nd[NL80211_ATTR_SCAN_FREQUENCIES],
559 rem_match) {
560 printf(" %d", nla_get_u32(freq));
561 }
562 printf("\n");
563 }
564 }
565 if (trig[NL80211_WOWLAN_TRIG_PKT_PATTERN]) {
566 nla_for_each_nested(pattern,
567 trig[NL80211_WOWLAN_TRIG_PKT_PATTERN],
568 rem_pattern) {
569 struct nlattr *patattr[NUM_NL80211_PKTPAT];
570 int i, patlen, masklen;
571 uint8_t *mask, *pat;
572 nla_parse(patattr, MAX_NL80211_PKTPAT,
573 nla_data(pattern), nla_len(pattern), NULL);
574 if (!patattr[NL80211_PKTPAT_MASK] ||
575 !patattr[NL80211_PKTPAT_PATTERN]) {
576 printf(" * (invalid pattern specification)\n");
577 continue;
578 }
579 masklen = nla_len(patattr[NL80211_PKTPAT_MASK]);
580 patlen = nla_len(patattr[NL80211_PKTPAT_PATTERN]);
581 if (DIV_ROUND_UP(patlen, 8) != masklen) {
582 printf(" * (invalid pattern specification)\n");
583 continue;
584 }
585 if (patattr[NL80211_PKTPAT_OFFSET]) {
586 int pkt_offset =
587 nla_get_u32(patattr[NL80211_PKTPAT_OFFSET]);
588 printf(" * wake up on packet offset: %d", pkt_offset);
589 }
590 printf(" pattern: ");
591 pat = nla_data(patattr[NL80211_PKTPAT_PATTERN]);
592 mask = nla_data(patattr[NL80211_PKTPAT_MASK]);
593 for (i = 0; i < patlen; i++) {
594 if (mask[i / 8] & (1 << (i % 8)))
595 printf("%.2x", pat[i]);
596 else
597 printf("--");
598 if (i != patlen - 1)
599 printf(":");
600 }
601 printf("\n");
602 }
603 }
604 if (trig[NL80211_WOWLAN_TRIG_TCP_CONNECTION])
605 printf(" * wake up on TCP connection\n");
606
607 return NL_SKIP;
608 }
609
610 static int handle_wowlan_show(struct nl80211_state *state, struct nl_cb *cb,
611 struct nl_msg *msg, int argc, char **argv,
612 enum id_input id)
613 {
614 nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM,
615 print_wowlan_handler, NULL);
616
617 return 0;
618 }
619 COMMAND(wowlan, show, "", NL80211_CMD_GET_WOWLAN, 0, CIB_PHY, handle_wowlan_show,
620 "Show WoWLAN status.");