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 <eliadx.peller@intel.com>
This commit is contained in:
Eliad Peller 2014-04-24 08:45:39 +03:00 committed by Jouni Malinen
parent 959214b260
commit e4fa8b120b
7 changed files with 205 additions and 0 deletions

View file

@ -682,6 +682,16 @@ enum hide_ssid {
HIDDEN_SSID_ZERO_CONTENTS 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 { struct wpa_driver_ap_params {
/** /**
* head - Beacon head from IEEE 802.11 header to IEs before TIM IE * 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; const u8 *extended_capa, *extended_capa_mask;
unsigned int extended_capa_len; 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, int (*set_qos_map)(void *priv, const u8 *qos_map_set,
u8 qos_map_set_len); 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 * signal_poll - Get current connection information
* @priv: Private driver interface data * @priv: Private driver interface data

View file

@ -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) static int wiphy_info_handler(struct nl_msg *msg, void *arg)
{ {
struct nlattr *tb[NL80211_ATTR_MAX + 1]; 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; 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 = { const struct wpa_driver_ops wpa_driver_nl80211_ops = {
.name = "nl80211", .name = "nl80211",
.desc = "Linux nl80211/cfg80211", .desc = "Linux nl80211/cfg80211",
@ -12227,4 +12310,5 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
#endif /* ANDROID */ #endif /* ANDROID */
.vendor_cmd = nl80211_vendor_cmd, .vendor_cmd = nl80211_vendor_cmd,
.set_qos_map = nl80211_set_qos_map, .set_qos_map = nl80211_set_qos_map,
.set_wowlan = nl80211_set_wowlan,
}; };

View file

@ -2022,6 +2022,7 @@ void wpa_config_free(struct wpa_config *config)
os_free(config->sae_groups); os_free(config->sae_groups);
wpabuf_free(config->ap_vendor_elements); wpabuf_free(config->ap_vendor_elements);
os_free(config->osu_dir); os_free(config->osu_dir);
os_free(config->wowlan_triggers);
os_free(config); os_free(config);
} }
@ -3875,6 +3876,7 @@ static const struct global_parse_data global_fields[] = {
{ INT(sched_scan_interval), 0 }, { INT(sched_scan_interval), 0 },
{ INT(tdls_external_control), 0}, { INT(tdls_external_control), 0},
{ STR(osu_dir), 0 }, { STR(osu_dir), 0 },
{ STR(wowlan_triggers), 0 },
}; };
#undef FUNC #undef FUNC

View file

@ -1022,6 +1022,13 @@ struct wpa_config {
* directory. * directory.
*/ */
char *osu_dir; char *osu_dir;
/**
* wowlan_triggers - Wake-on-WLAN triggers
*
* If set, these wowlan triggers will be configured.
*/
char *wowlan_triggers;
}; };

View file

@ -1155,6 +1155,10 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config)
fprintf(f, "tdls_external_control=%d\n", fprintf(f, "tdls_external_control=%d\n",
config->tdls_external_control); config->tdls_external_control);
if (config->wowlan_triggers)
fprintf(f, "wowlan_triggers=\"%s\"\n",
config->wowlan_triggers);
if (config->bgscan) if (config->bgscan)
fprintf(f, "bgscan=\"%s\"\n", config->bgscan); fprintf(f, "bgscan=\"%s\"\n", config->bgscan);
} }

View file

@ -614,6 +614,14 @@ static inline int wpa_drv_set_qos_map(struct wpa_supplicant *wpa_s,
qos_map_set_len); 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, static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s,
int vendor_id, int subcmd, const u8 *data, int vendor_id, int subcmd, const u8 *data,
size_t data_len, struct wpabuf *buf) size_t data_len, struct wpabuf *buf)

View file

@ -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, static struct wpa_radio * radio_add_interface(struct wpa_supplicant *wpa_s,
const char *rn) 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) if (wpa_bss_init(wpa_s) < 0)
return -1; 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 #ifdef CONFIG_EAP_PROXY
{ {
size_t len; size_t len;