-#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/msg.h>
#include <netlink/attr.h>
+#include <arpa/inet.h>
+
#include "nl80211.h"
#include "iw.h"
SECTION(wowlan);
-static int handle_wowlan_enable(struct nl80211_state *state, struct nl_cb *cb,
- struct nl_msg *msg, int argc, char **argv)
+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);
+
+ if (nla_put(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN,
+ sizeof(*tok) + stream_len, tok) < 0) {
+ free(stream);
+ free(tok);
+ goto nla_put_failure;
+ }
+ free(stream);
+ free(tok);
+ } else {
+ if (buf[0] == '#')
+ continue;
+ goto close;
+ }
+ }
+
+ err = 0;
+ goto close;
+ nla_put_failure:
+ err = -ENOBUFS;
+ close:
+ fclose(f);
+ if (tcp)
+ 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_msg *msg, int argc, char **argv,
+ enum id_input id)
{
struct nlattr *wowlan, *pattern;
struct nl_msg *patterns = NULL;
int err = -ENOBUFS;
unsigned char *pat, *mask;
size_t patlen;
- int patnum = 0;
+ int patnum = 0, pkt_offset;
+ char *eptr, *value1, *value2, *sptr = NULL;
wowlan = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS);
if (!wowlan)
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], "patterns") == 0) {
+ 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:
- if (parse_hex_mask(argv[0], &pat, &patlen, &mask)) {
+ 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_WOWLAN_PKTPAT_MASK,
+ NLA_PUT(patterns, NL80211_PKTPAT_MASK,
DIV_ROUND_UP(patlen, 8), mask);
- NLA_PUT(patterns, NL80211_WOWLAN_PKTPAT_PATTERN,
- patlen, pat);
+ 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);
return err;
}
COMMAND(wowlan, enable, "[any] [disconnect] [magic-packet] [gtk-rekey-failure] [eap-identity-request]"
- " [4way-handshake] [rfkill-release] [patterns <pattern>*]",
+ " [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.");
+ "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)
+static int handle_wowlan_disable(struct nl80211_state *state,
+ struct nl_msg *msg, int argc, char **argv,
+ enum id_input id)
{
/* just a set w/o wowlan attribute */
return 0;
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_nested(nd, NL80211_ATTR_MAX,
+ 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_nested(tb, NL80211_ATTR_MAX, 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_WOWLAN_PKTPAT];
+ struct nlattr *patattr[NUM_NL80211_PKTPAT];
int i, patlen, masklen;
uint8_t *mask, *pat;
- nla_parse(patattr, MAX_NL80211_WOWLAN_PKTPAT,
- nla_data(pattern), nla_len(pattern),
- NULL);
- if (!patattr[NL80211_WOWLAN_PKTPAT_MASK] ||
- !patattr[NL80211_WOWLAN_PKTPAT_PATTERN]) {
+ 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_WOWLAN_PKTPAT_MASK]);
- patlen = nla_len(patattr[NL80211_WOWLAN_PKTPAT_PATTERN]);
+ 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;
}
- printf(" * wake up on pattern: ");
- pat = nla_data(patattr[NL80211_WOWLAN_PKTPAT_PATTERN]);
- mask = nla_data(patattr[NL80211_WOWLAN_PKTPAT_MASK]);
+ 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]);
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)
+static int handle_wowlan_show(struct nl80211_state *state,
+ 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);
+ register_handler(print_wowlan_handler, NULL);
return 0;
}