#include <net/if.h> #include <errno.h> #include <string.h> #include <stdio.h> #include <netlink/genl/genl.h> #include <netlink/genl/family.h> #include <netlink/genl/ctrl.h> #include <netlink/msg.h> #include <netlink/attr.h> #include <arpa/inet.h> #include "nl80211.h" #include "iw.h" SECTION(wowlan); static int wowlan_parse_tcp_file(struct nl_msg *msg, const char *fn) { char buf[16768]; int err = 1; FILE *f = fopen(fn, "r"); struct nlattr *tcp; if (!f) return 1; tcp = nla_nest_start(msg, NL80211_WOWLAN_TRIG_TCP_CONNECTION); if (!tcp) goto nla_put_failure; while (!feof(f)) { char *eol; if (!fgets(buf, sizeof(buf), f)) break; eol = strchr(buf + 5, '\r'); if (eol) *eol = 0; eol = strchr(buf + 5, '\n'); if (eol) *eol = 0; if (strncmp(buf, "source=", 7) == 0) { struct in_addr in_addr; char *addr = buf + 7; char *port = strchr(buf + 7, ':'); if (port) { *port = 0; port++; } if (inet_aton(addr, &in_addr) == 0) goto close; NLA_PUT_U32(msg, NL80211_WOWLAN_TCP_SRC_IPV4, in_addr.s_addr); if (port) NLA_PUT_U16(msg, NL80211_WOWLAN_TCP_SRC_PORT, atoi(port)); } else if (strncmp(buf, "dest=", 5) == 0) { struct in_addr in_addr; char *addr = buf + 5; char *port = strchr(buf + 5, ':'); char *mac; unsigned char macbuf[6]; if (!port) goto close; *port = 0; port++; mac = strchr(port, '@'); if (!mac) goto close; *mac = 0; mac++; if (inet_aton(addr, &in_addr) == 0) goto close; NLA_PUT_U32(msg, NL80211_WOWLAN_TCP_DST_IPV4, in_addr.s_addr); NLA_PUT_U16(msg, NL80211_WOWLAN_TCP_DST_PORT, atoi(port)); if (mac_addr_a2n(macbuf, mac)) goto close; NLA_PUT(msg, NL80211_WOWLAN_TCP_DST_MAC, 6, macbuf); } else if (strncmp(buf, "data=", 5) == 0) { size_t len; unsigned char *pkt = parse_hex(buf + 5, &len); if (!pkt) goto close; NLA_PUT(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD, len, pkt); free(pkt); } else if (strncmp(buf, "data.interval=", 14) == 0) { NLA_PUT_U32(msg, NL80211_WOWLAN_TCP_DATA_INTERVAL, atoi(buf + 14)); } else if (strncmp(buf, "wake=", 5) == 0) { unsigned char *pat, *mask; size_t patlen; if (parse_hex_mask(buf + 5, &pat, &patlen, &mask)) goto close; NLA_PUT(msg, NL80211_WOWLAN_TCP_WAKE_MASK, DIV_ROUND_UP(patlen, 8), mask); NLA_PUT(msg, NL80211_WOWLAN_TCP_WAKE_PAYLOAD, patlen, pat); free(mask); free(pat); } else if (strncmp(buf, "data.seq=", 9) == 0) { struct nl80211_wowlan_tcp_data_seq seq = {}; char *len, *offs, *start; len = buf + 9; offs = strchr(len, ','); if (!offs) goto close; *offs = 0; offs++; start = strchr(offs, ','); if (start) { *start = 0; start++; seq.start = atoi(start); } seq.len = atoi(len); seq.offset = atoi(offs); NLA_PUT(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ, sizeof(seq), &seq); } else if (strncmp(buf, "data.tok=", 9) == 0) { struct nl80211_wowlan_tcp_data_token *tok; size_t stream_len; char *len, *offs, *toks; unsigned char *stream; len = buf + 9; offs = strchr(len, ','); if (!offs) goto close; *offs = 0; offs++; toks = strchr(offs, ','); if (!toks) goto close; *toks = 0; toks++; stream = parse_hex(toks, &stream_len); if (!stream) goto close; tok = malloc(sizeof(*tok) + stream_len); if (!tok) { free(stream); err = -ENOMEM; goto close; } tok->len = atoi(len); tok->offset = atoi(offs); memcpy(tok->token_stream, stream, stream_len); NLA_PUT(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN, sizeof(*tok) + stream_len, tok); free(stream); free(tok); } else { if (buf[0] == '#') continue; goto close; } } err = 0; goto close; nla_put_failure: err = -ENOBUFS; close: fclose(f); nla_nest_end(msg, tcp); return err; } static int wowlan_parse_net_detect(struct nl_msg *msg, int *argc, char ***argv) { struct nlattr *nd; int err = 0; nd = nla_nest_start(msg, NL80211_WOWLAN_TRIG_NET_DETECT); if (!nd) return -ENOBUFS; err = parse_sched_scan(msg, argc, argv); nla_nest_end(msg, nd); return err; } static int handle_wowlan_enable(struct nl80211_state *state, struct nl_cb *cb, struct nl_msg *msg, int argc, char **argv, enum id_input id) { struct nlattr *wowlan, *pattern; struct nl_msg *patterns = NULL; enum { PS_REG, PS_PAT, } parse_state = PS_REG; int err = -ENOBUFS; unsigned char *pat, *mask; size_t patlen; int patnum = 0, pkt_offset; char *eptr, *value1, *value2, *sptr = NULL; wowlan = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS); if (!wowlan) return -ENOBUFS; while (argc) { switch (parse_state) { case PS_REG: if (strcmp(argv[0], "any") == 0) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY); else if (strcmp(argv[0], "disconnect") == 0) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT); else if (strcmp(argv[0], "magic-packet") == 0) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT); else if (strcmp(argv[0], "gtk-rekey-failure") == 0) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE); else if (strcmp(argv[0], "eap-identity-request") == 0) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST); else if (strcmp(argv[0], "4way-handshake") == 0) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE); else if (strcmp(argv[0], "rfkill-release") == 0) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE); else if (strcmp(argv[0], "tcp") == 0) { argv++; argc--; if (!argc) { err = 1; goto nla_put_failure; } err = wowlan_parse_tcp_file(msg, argv[0]); if (err) goto nla_put_failure; } else if (strcmp(argv[0], "patterns") == 0) { parse_state = PS_PAT; patterns = nlmsg_alloc(); if (!patterns) { err = -ENOMEM; goto nla_put_failure; } } else if (strcmp(argv[0], "net-detect") == 0) { argv++; argc--; if (!argc) { err = 1; goto nla_put_failure; } err = wowlan_parse_net_detect(msg, &argc, &argv); if (err) goto nla_put_failure; continue; } else { err = 1; goto nla_put_failure; } break; case PS_PAT: value1 = strtok_r(argv[0], "+", &sptr); value2 = strtok_r(NULL, "+", &sptr); if (!value2) { pkt_offset = 0; value2 = value1; } else { pkt_offset = strtoul(value1, &eptr, 10); if (eptr != value1 + strlen(value1)) { err = 1; goto nla_put_failure; } } if (parse_hex_mask(value2, &pat, &patlen, &mask)) { err = 1; goto nla_put_failure; } pattern = nla_nest_start(patterns, ++patnum); NLA_PUT(patterns, NL80211_PKTPAT_MASK, DIV_ROUND_UP(patlen, 8), mask); NLA_PUT(patterns, NL80211_PKTPAT_PATTERN, patlen, pat); NLA_PUT_U32(patterns, NL80211_PKTPAT_OFFSET, pkt_offset); nla_nest_end(patterns, pattern); free(mask); free(pat); break; } argv++; argc--; } if (patterns) nla_put_nested(msg, NL80211_WOWLAN_TRIG_PKT_PATTERN, patterns); nla_nest_end(msg, wowlan); err = 0; nla_put_failure: nlmsg_free(patterns); return err; } COMMAND(wowlan, enable, "[any] [disconnect] [magic-packet] [gtk-rekey-failure] [eap-identity-request]" " [4way-handshake] [rfkill-release] [net-detect " SCHED_SCAN_OPTIONS "]" " [tcp <config-file>] [patterns [offset1+]<pattern1> ...]", NL80211_CMD_SET_WOWLAN, 0, CIB_PHY, handle_wowlan_enable, "Enable WoWLAN with the given triggers.\n" "Each pattern is given as a bytestring with '-' in places where any byte\n" "may be present, e.g. 00:11:22:-:44 will match 00:11:22:33:44 and\n" "00:11:22:33:ff:44 etc.\n" "Offset and pattern should be separated by '+', e.g. 18+43:34:00:12 will match " "'43:34:00:12' after 18 bytes of offset in Rx packet.\n\n" "The TCP configuration file contains:\n" " source=ip[:port]\n" " dest=ip:port@mac\n" " data=<hex data packet>\n" " data.interval=seconds\n" " [wake=<hex packet with masked out bytes indicated by '-'>]\n" " [data.seq=len,offset[,start]]\n" " [data.tok=len,offset,<token stream>]\n\n" "Net-detect configuration example:\n" " iw phy0 wowlan enable net-detect interval 5000 delay 30 freqs 2412 2422 matches ssid foo ssid bar"); static int handle_wowlan_disable(struct nl80211_state *state, struct nl_cb *cb, struct nl_msg *msg, int argc, char **argv, enum id_input id) { /* just a set w/o wowlan attribute */ return 0; } COMMAND(wowlan, disable, "", NL80211_CMD_SET_WOWLAN, 0, CIB_PHY, handle_wowlan_disable, "Disable WoWLAN."); static int print_wowlan_handler(struct nl_msg *msg, void *arg) { struct nlattr *attrs[NL80211_ATTR_MAX + 1]; struct nlattr *trig[NUM_NL80211_WOWLAN_TRIG]; struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); struct nlattr *pattern; int rem_pattern; nla_parse(attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS]) { printf("WoWLAN is disabled.\n"); return NL_SKIP; } /* XXX: use policy */ nla_parse(trig, MAX_NL80211_WOWLAN_TRIG, nla_data(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), nla_len(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), NULL); printf("WoWLAN is enabled:\n"); if (trig[NL80211_WOWLAN_TRIG_ANY]) printf(" * wake up on special any trigger\n"); if (trig[NL80211_WOWLAN_TRIG_DISCONNECT]) printf(" * wake up on disconnect\n"); if (trig[NL80211_WOWLAN_TRIG_MAGIC_PKT]) printf(" * wake up on magic packet\n"); if (trig[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE]) printf(" * wake up on GTK rekeying failure\n"); if (trig[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST]) printf(" * wake up on EAP identity request\n"); if (trig[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE]) printf(" * wake up on 4-way handshake\n"); if (trig[NL80211_WOWLAN_TRIG_RFKILL_RELEASE]) printf(" * wake up on RF-kill release\n"); if (trig[NL80211_WOWLAN_TRIG_NET_DETECT]) { struct nlattr *match, *freq, *nd[NUM_NL80211_ATTR], *tb[NUM_NL80211_ATTR]; int rem_match; printf(" * wake up on network detection\n"); nla_parse(nd, NUM_NL80211_ATTR, nla_data(trig[NL80211_WOWLAN_TRIG_NET_DETECT]), nla_len(trig[NL80211_WOWLAN_TRIG_NET_DETECT]), NULL); if (nd[NL80211_ATTR_SCHED_SCAN_INTERVAL]) printf("\tscan interval: %u msecs\n", nla_get_u32(nd[NL80211_ATTR_SCHED_SCAN_INTERVAL])); if (nd[NL80211_ATTR_SCHED_SCAN_DELAY]) printf("\tinitial scan delay: %u secs\n", nla_get_u32(nd[NL80211_ATTR_SCHED_SCAN_DELAY])); if (nd[NL80211_ATTR_SCHED_SCAN_MATCH]) { printf("\tmatches:\n"); nla_for_each_nested(match, nd[NL80211_ATTR_SCHED_SCAN_MATCH], rem_match) { nla_parse(tb, NUM_NL80211_ATTR, nla_data(match), nla_len(match), NULL); printf("\t\tSSID: "); print_ssid_escaped( nla_len(tb[NL80211_SCHED_SCAN_MATCH_ATTR_SSID]), nla_data(tb[NL80211_SCHED_SCAN_MATCH_ATTR_SSID])); printf("\n"); } } if (nd[NL80211_ATTR_SCAN_FREQUENCIES]) { printf("\tfrequencies:"); nla_for_each_nested(freq, nd[NL80211_ATTR_SCAN_FREQUENCIES], rem_match) { printf(" %d", nla_get_u32(freq)); } printf("\n"); } } if (trig[NL80211_WOWLAN_TRIG_PKT_PATTERN]) { nla_for_each_nested(pattern, trig[NL80211_WOWLAN_TRIG_PKT_PATTERN], rem_pattern) { struct nlattr *patattr[NUM_NL80211_PKTPAT]; int i, patlen, masklen; uint8_t *mask, *pat; nla_parse(patattr, MAX_NL80211_PKTPAT, nla_data(pattern), nla_len(pattern), NULL); if (!patattr[NL80211_PKTPAT_MASK] || !patattr[NL80211_PKTPAT_PATTERN]) { printf(" * (invalid pattern specification)\n"); continue; } masklen = nla_len(patattr[NL80211_PKTPAT_MASK]); patlen = nla_len(patattr[NL80211_PKTPAT_PATTERN]); if (DIV_ROUND_UP(patlen, 8) != masklen) { printf(" * (invalid pattern specification)\n"); continue; } if (patattr[NL80211_PKTPAT_OFFSET]) { int pkt_offset = nla_get_u32(patattr[NL80211_PKTPAT_OFFSET]); printf(" * wake up on packet offset: %d", pkt_offset); } printf(" pattern: "); pat = nla_data(patattr[NL80211_PKTPAT_PATTERN]); mask = nla_data(patattr[NL80211_PKTPAT_MASK]); for (i = 0; i < patlen; i++) { if (mask[i / 8] & (1 << (i % 8))) printf("%.2x", pat[i]); else printf("--"); if (i != patlen - 1) printf(":"); } printf("\n"); } } if (trig[NL80211_WOWLAN_TRIG_TCP_CONNECTION]) printf(" * wake up on TCP connection\n"); return NL_SKIP; } static int handle_wowlan_show(struct nl80211_state *state, struct nl_cb *cb, struct nl_msg *msg, int argc, char **argv, enum id_input id) { nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, print_wowlan_handler, NULL); return 0; } COMMAND(wowlan, show, "", NL80211_CMD_GET_WOWLAN, 0, CIB_PHY, handle_wowlan_show, "Show WoWLAN status.");