From e4fa8b120bf0014bc7f6f79f8d85923215833c9f Mon Sep 17 00:00:00 2001 From: Eliad Peller Date: Thu, 24 Apr 2014 08:45:39 +0300 Subject: [PATCH] wpa_supplicant: Add Wake-on-WLAN configuration support Add a new wowlan_triggers option to wpa_supplicant.conf. The triggers in this key will be used to configure the kernel wowlan configuration. For now, support only simple flags. More complex triggers can be added later on. Signed-off-by: Eliad Peller --- src/drivers/driver.h | 19 ++++++++ src/drivers/driver_nl80211.c | 84 +++++++++++++++++++++++++++++++++ wpa_supplicant/config.c | 2 + wpa_supplicant/config.h | 7 +++ wpa_supplicant/config_file.c | 4 ++ wpa_supplicant/driver_i.h | 8 ++++ wpa_supplicant/wpa_supplicant.c | 81 +++++++++++++++++++++++++++++++ 7 files changed, 205 insertions(+) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 00565a7b9..481ddd655 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -682,6 +682,16 @@ enum hide_ssid { HIDDEN_SSID_ZERO_CONTENTS }; +struct wowlan_triggers { + u8 any; + u8 disconnect; + u8 magic_pkt; + u8 gtk_rekey_failure; + u8 eap_identity_req; + u8 four_way_handshake; + u8 rfkill_release; +}; + struct wpa_driver_ap_params { /** * head - Beacon head from IEEE 802.11 header to IEs before TIM IE @@ -1032,6 +1042,8 @@ struct wpa_driver_capa { */ const u8 *extended_capa, *extended_capa_mask; unsigned int extended_capa_len; + + struct wowlan_triggers wowlan_triggers; }; @@ -2518,6 +2530,13 @@ struct wpa_driver_ops { int (*set_qos_map)(void *priv, const u8 *qos_map_set, u8 qos_map_set_len); + /** + * set_wowlan - Set wake-on-wireless triggers + * @priv: Private driver interface data + * @triggers: wowlan triggers + */ + int (*set_wowlan)(void *priv, const struct wowlan_triggers *triggers); + /** * signal_poll - Get current connection information * @priv: Private driver interface data diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index fe224cedc..9cbe6f35b 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -3697,6 +3697,35 @@ static void wiphy_info_probe_resp_offload(struct wpa_driver_capa *capa, } +static void wiphy_info_wowlan_triggers(struct wpa_driver_capa *capa, + struct nlattr *tb) +{ + struct nlattr *triggers[MAX_NL80211_WOWLAN_TRIG + 1]; + + if (tb == NULL) + return; + + if (nla_parse_nested(triggers, MAX_NL80211_WOWLAN_TRIG, + tb, NULL)) + return; + + if (triggers[NL80211_WOWLAN_TRIG_ANY]) + capa->wowlan_triggers.any = 1; + if (triggers[NL80211_WOWLAN_TRIG_DISCONNECT]) + capa->wowlan_triggers.disconnect = 1; + if (triggers[NL80211_WOWLAN_TRIG_MAGIC_PKT]) + capa->wowlan_triggers.magic_pkt = 1; + if (triggers[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE]) + capa->wowlan_triggers.gtk_rekey_failure = 1; + if (triggers[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST]) + capa->wowlan_triggers.eap_identity_req = 1; + if (triggers[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE]) + capa->wowlan_triggers.four_way_handshake = 1; + if (triggers[NL80211_WOWLAN_TRIG_RFKILL_RELEASE]) + capa->wowlan_triggers.rfkill_release = 1; +} + + static int wiphy_info_handler(struct nl_msg *msg, void *arg) { struct nlattr *tb[NL80211_ATTR_MAX + 1]; @@ -3820,6 +3849,9 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg) } } + wiphy_info_wowlan_triggers(capa, + tb[NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED]); + return NL_SKIP; } @@ -12137,6 +12169,57 @@ nla_put_failure: } +static int nl80211_set_wowlan(void *priv, + const struct wowlan_triggers *triggers) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + struct nl_msg *msg; + struct nlattr *wowlan_triggers; + int ret; + + msg = nlmsg_alloc(); + if (!msg) + return -ENOMEM; + + wpa_printf(MSG_DEBUG, "nl80211: Setting wowlan"); + + nl80211_cmd(drv, msg, 0, NL80211_CMD_SET_WOWLAN); + NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex); + + wowlan_triggers = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS); + if (!wowlan_triggers) + goto nla_put_failure; + + if (triggers->any) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY); + if (triggers->disconnect) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT); + if (triggers->magic_pkt) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT); + if (triggers->gtk_rekey_failure) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE); + if (triggers->eap_identity_req) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST); + if (triggers->four_way_handshake) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE); + if (triggers->rfkill_release) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE); + + nla_nest_end(msg, wowlan_triggers); + + ret = send_and_recv_msgs(drv, msg, NULL, NULL); + if (ret) + wpa_printf(MSG_DEBUG, "nl80211: Setting wowlan failed"); + + return ret; + +nla_put_failure: + nlmsg_free(msg); + return -ENOBUFS; +} + + const struct wpa_driver_ops wpa_driver_nl80211_ops = { .name = "nl80211", .desc = "Linux nl80211/cfg80211", @@ -12227,4 +12310,5 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { #endif /* ANDROID */ .vendor_cmd = nl80211_vendor_cmd, .set_qos_map = nl80211_set_qos_map, + .set_wowlan = nl80211_set_wowlan, }; diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 698b4592e..b5a5d7885 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -2022,6 +2022,7 @@ void wpa_config_free(struct wpa_config *config) os_free(config->sae_groups); wpabuf_free(config->ap_vendor_elements); os_free(config->osu_dir); + os_free(config->wowlan_triggers); os_free(config); } @@ -3875,6 +3876,7 @@ static const struct global_parse_data global_fields[] = { { INT(sched_scan_interval), 0 }, { INT(tdls_external_control), 0}, { STR(osu_dir), 0 }, + { STR(wowlan_triggers), 0 }, }; #undef FUNC diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h index 26b1233a6..bf3f3f79a 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -1022,6 +1022,13 @@ struct wpa_config { * directory. */ char *osu_dir; + + /** + * wowlan_triggers - Wake-on-WLAN triggers + * + * If set, these wowlan triggers will be configured. + */ + char *wowlan_triggers; }; diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 3a7adc2b6..3cfe5ba93 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -1155,6 +1155,10 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config) fprintf(f, "tdls_external_control=%d\n", config->tdls_external_control); + if (config->wowlan_triggers) + fprintf(f, "wowlan_triggers=\"%s\"\n", + config->wowlan_triggers); + if (config->bgscan) fprintf(f, "bgscan=\"%s\"\n", config->bgscan); } diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index 938ece699..beeb05909 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -614,6 +614,14 @@ static inline int wpa_drv_set_qos_map(struct wpa_supplicant *wpa_s, qos_map_set_len); } +static inline int wpa_drv_wowlan(struct wpa_supplicant *wpa_s, + const struct wowlan_triggers *triggers) +{ + if (!wpa_s->driver->set_wowlan) + return -1; + return wpa_s->driver->set_wowlan(wpa_s->drv_priv, triggers); +} + static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s, int vendor_id, int subcmd, const u8 *data, size_t data_len, struct wpabuf *buf) diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index af7b847b8..f56b198bd 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -3123,6 +3123,79 @@ int wpas_init_ext_pw(struct wpa_supplicant *wpa_s) } +static int wpas_check_wowlan_trigger(const char *start, const char *trigger, + int capa_trigger, u8 *param_trigger) +{ + if (os_strcmp(start, trigger) != 0) + return 0; + if (!capa_trigger) + return 0; + + *param_trigger = 1; + return 1; +} + + +int wpas_set_wowlan_triggers(struct wpa_supplicant *wpa_s, + struct wpa_driver_capa *capa) +{ + struct wowlan_triggers triggers; + char *start, *end, *buf; + int last, ret; + + if (!wpa_s->conf->wowlan_triggers) + return 0; + + buf = os_strdup(wpa_s->conf->wowlan_triggers); + if (buf == NULL) + return -1; + + os_memset(&triggers, 0, sizeof(triggers)); + +#define CHECK_TRIGGER(trigger) \ + wpas_check_wowlan_trigger(start, #trigger, \ + capa->wowlan_triggers.trigger, \ + &triggers.trigger) + + start = buf; + while (*start != '\0') { + while (isblank(*start)) + start++; + if (*start == '\0') + break; + end = start; + while (!isblank(*end) && *end != '\0') + end++; + last = *end == '\0'; + *end = '\0'; + + if (!CHECK_TRIGGER(any) && + !CHECK_TRIGGER(disconnect) && + !CHECK_TRIGGER(magic_pkt) && + !CHECK_TRIGGER(gtk_rekey_failure) && + !CHECK_TRIGGER(eap_identity_req) && + !CHECK_TRIGGER(four_way_handshake) && + !CHECK_TRIGGER(rfkill_release)) { + wpa_printf(MSG_DEBUG, + "Unknown/unsupported wowlan trigger '%s'", + start); + ret = -1; + goto out; + } + + if (last) + break; + start = end + 1; + } +#undef CHECK_TRIGGER + + ret = wpa_drv_wowlan(wpa_s, &triggers); +out: + os_free(buf); + return ret; +} + + static struct wpa_radio * radio_add_interface(struct wpa_supplicant *wpa_s, const char *rn) { @@ -3650,6 +3723,14 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, if (wpa_bss_init(wpa_s) < 0) return -1; + /* + * Set Wake-on-WLAN triggers, if configured. + * Note: We don't restore/remove the triggers on shutdown (it doesn't + * have effect anyway when the interface is down). + */ + if (wpas_set_wowlan_triggers(wpa_s, &capa) < 0) + return -1; + #ifdef CONFIG_EAP_PROXY { size_t len;