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