Add Linux rfkill support

Add a new wpa_supplicant state: interface disabled. This can be used
to allow wpa_supplicant to be running with the network interface even
when the driver does not actually allow any radio operations (e.g.,
due to rfkill).

Allow driver_nl80211.c and driver_wext.c to start while rfkill is in
blocked state (i.e., when ifconfig up fails) and process rfkill
events to block/unblock WLAN.
This commit is contained in:
Jouni Malinen 2010-05-23 10:27:32 +03:00
parent 6deb41e73f
commit 8401a6b028
12 changed files with 428 additions and 13 deletions

View file

@ -136,6 +136,15 @@ enum wpa_states {
*/
WPA_DISCONNECTED,
/**
* WPA_INTERFACE_DISABLED - Interface disabled
*
* This stat eis entered if the network interface is disabled, e.g.,
* due to rfkill. wpa_supplicant refuses any new operations that would
* use the radio until the interface has been enabled.
*/
WPA_INTERFACE_DISABLED,
/**
* WPA_INACTIVE - Inactive state (wpa_supplicant disabled)
*

View file

@ -2046,7 +2046,23 @@ enum wpa_event_type {
* observed in frames received from the current AP if signal strength
* monitoring has been enabled with signal_monitor().
*/
EVENT_SIGNAL_CHANGE
EVENT_SIGNAL_CHANGE,
/**
* EVENT_INTERFACE_ENABLED - Notify that interface was enabled
*
* This event is used to indicate that the interface was enabled after
* having been previously disabled, e.g., due to rfkill.
*/
EVENT_INTERFACE_ENABLED,
/**
* EVENT_INTERFACE_DISABLED - Notify that interface was disabled
*
* This event is used to indicate that the interface was disabled,
* e.g., due to rfkill.
*/
EVENT_INTERFACE_DISABLED
};

View file

@ -33,6 +33,7 @@
#include "linux_ioctl.h"
#include "radiotap.h"
#include "radiotap_iter.h"
#include "rfkill.h"
#include "driver.h"
#ifdef CONFIG_LIBNL20
@ -72,6 +73,7 @@ struct wpa_driver_nl80211_data {
char brname[IFNAMSIZ];
int ifindex;
int if_removed;
struct rfkill_data *rfkill;
struct wpa_driver_capa capa;
int has_capability;
@ -1347,6 +1349,27 @@ err1:
}
static void wpa_driver_nl80211_rfkill_blocked(void *ctx)
{
struct wpa_driver_nl80211_data *drv = ctx;
wpa_printf(MSG_DEBUG, "nl80211: RFKILL blocked");
wpa_supplicant_event(drv->ctx, EVENT_INTERFACE_DISABLED, NULL);
}
static void wpa_driver_nl80211_rfkill_unblocked(void *ctx)
{
struct wpa_driver_nl80211_data *drv = ctx;
wpa_printf(MSG_DEBUG, "nl80211: RFKILL unblocked");
if (linux_set_iface_flags(drv->ioctl_sock, drv->first_bss.ifname, 1)) {
wpa_printf(MSG_DEBUG, "nl80211: Could not set interface UP "
"after rfkill unblock");
return;
}
wpa_supplicant_event(drv->ctx, EVENT_INTERFACE_ENABLED, NULL);
}
/**
* wpa_driver_nl80211_init - Initialize nl80211 driver interface
* @ctx: context to be used when calling wpa_supplicant functions,
@ -1358,6 +1381,7 @@ static void * wpa_driver_nl80211_init(void *ctx, const char *ifname)
{
struct wpa_driver_nl80211_data *drv;
struct netlink_config *cfg;
struct rfkill_config *rcfg;
struct i802_bss *bss;
drv = os_zalloc(sizeof(*drv));
@ -1393,12 +1417,25 @@ static void * wpa_driver_nl80211_init(void *ctx, const char *ifname)
os_free(cfg);
goto failed;
}
rcfg = os_zalloc(sizeof(*rcfg));
if (rcfg == NULL)
goto failed;
rcfg->ctx = drv;
os_strlcpy(rcfg->ifname, ifname, sizeof(rcfg->ifname));
rcfg->blocked_cb = wpa_driver_nl80211_rfkill_blocked;
rcfg->unblocked_cb = wpa_driver_nl80211_rfkill_unblocked;
drv->rfkill = rfkill_init(rcfg);
if (drv->rfkill == NULL)
wpa_printf(MSG_DEBUG, "nl80211: RFKILL status not available");
if (wpa_driver_nl80211_finish_drv_init(drv))
goto failed;
return bss;
failed:
rfkill_deinit(drv->rfkill);
netlink_deinit(drv->netlink);
if (drv->ioctl_sock >= 0)
close(drv->ioctl_sock);
@ -1459,10 +1496,17 @@ static int nl80211_register_action_frames(struct wpa_driver_nl80211_data *drv)
}
static void wpa_driver_nl80211_send_rfkill(void *eloop_ctx, void *timeout_ctx)
{
wpa_supplicant_event(timeout_ctx, EVENT_INTERFACE_DISABLED, NULL);
}
static int
wpa_driver_nl80211_finish_drv_init(struct wpa_driver_nl80211_data *drv)
{
struct i802_bss *bss = &drv->first_bss;
int send_rfkill_event = 0;
drv->ifindex = if_nametoindex(bss->ifname);
drv->first_bss.ifindex = drv->ifindex;
@ -1474,9 +1518,16 @@ wpa_driver_nl80211_finish_drv_init(struct wpa_driver_nl80211_data *drv)
}
if (linux_set_iface_flags(drv->ioctl_sock, bss->ifname, 1)) {
wpa_printf(MSG_ERROR, "Could not set interface '%s' UP",
bss->ifname);
return -1;
if (rfkill_is_blocked(drv->rfkill)) {
wpa_printf(MSG_DEBUG, "nl80211: Could not yet enable "
"interface '%s' due to rfkill",
bss->ifname);
send_rfkill_event = 1;
} else {
wpa_printf(MSG_ERROR, "nl80211: Could not set "
"interface '%s' UP", bss->ifname);
return -1;
}
}
if (wpa_driver_nl80211_capa(drv))
@ -1496,6 +1547,11 @@ wpa_driver_nl80211_finish_drv_init(struct wpa_driver_nl80211_data *drv)
*/
}
if (send_rfkill_event) {
eloop_register_timeout(0, 0, wpa_driver_nl80211_send_rfkill,
drv, drv->ctx);
}
return 0;
}
@ -1572,6 +1628,7 @@ static void wpa_driver_nl80211_deinit(void *priv)
netlink_send_oper_ifla(drv->netlink, drv->ifindex, 0, IF_OPER_UP);
netlink_deinit(drv->netlink);
rfkill_deinit(drv->rfkill);
eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx);

View file

@ -31,6 +31,7 @@
#include "priv_netlink.h"
#include "netlink.h"
#include "linux_ioctl.h"
#include "rfkill.h"
#include "driver.h"
#include "driver_wext.h"
@ -687,6 +688,27 @@ static void wpa_driver_wext_event_rtm_dellink(void *ctx, struct ifinfomsg *ifi,
}
static void wpa_driver_wext_rfkill_blocked(void *ctx)
{
struct wpa_driver_wext_data *drv = ctx;
wpa_printf(MSG_DEBUG, "WEXT: RFKILL blocked");
wpa_supplicant_event(drv->ctx, EVENT_INTERFACE_DISABLED, NULL);
}
static void wpa_driver_wext_rfkill_unblocked(void *ctx)
{
struct wpa_driver_wext_data *drv = ctx;
wpa_printf(MSG_DEBUG, "WEXT: RFKILL unblocked");
if (linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 1)) {
wpa_printf(MSG_DEBUG, "WEXT: Could not set interface UP "
"after rfkill unblock");
return;
}
wpa_supplicant_event(drv->ctx, EVENT_INTERFACE_ENABLED, NULL);
}
/**
* wpa_driver_wext_init - Initialize WE driver interface
* @ctx: context to be used when calling wpa_supplicant functions,
@ -698,6 +720,7 @@ void * wpa_driver_wext_init(void *ctx, const char *ifname)
{
struct wpa_driver_wext_data *drv;
struct netlink_config *cfg;
struct rfkill_config *rcfg;
char path[128];
struct stat buf;
@ -731,6 +754,17 @@ void * wpa_driver_wext_init(void *ctx, const char *ifname)
goto err2;
}
rcfg = os_zalloc(sizeof(*rcfg));
if (rcfg == NULL)
goto err3;
rcfg->ctx = drv;
os_strlcpy(rcfg->ifname, ifname, sizeof(rcfg->ifname));
rcfg->blocked_cb = wpa_driver_wext_rfkill_blocked;
rcfg->unblocked_cb = wpa_driver_wext_rfkill_unblocked;
drv->rfkill = rfkill_init(rcfg);
if (drv->rfkill == NULL)
wpa_printf(MSG_DEBUG, "WEXT: RFKILL status not available");
drv->mlme_sock = -1;
if (wpa_driver_wext_finish_drv_init(drv) < 0)
@ -741,6 +775,7 @@ void * wpa_driver_wext_init(void *ctx, const char *ifname)
return drv;
err3:
rfkill_deinit(drv->rfkill);
netlink_deinit(drv->netlink);
err2:
close(drv->ioctl_sock);
@ -750,10 +785,28 @@ err1:
}
static void wpa_driver_wext_send_rfkill(void *eloop_ctx, void *timeout_ctx)
{
wpa_supplicant_event(timeout_ctx, EVENT_INTERFACE_DISABLED, NULL);
}
static int wpa_driver_wext_finish_drv_init(struct wpa_driver_wext_data *drv)
{
if (linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 1) < 0)
return -1;
int send_rfkill_event = 0;
if (linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 1) < 0) {
if (rfkill_is_blocked(drv->rfkill)) {
wpa_printf(MSG_DEBUG, "WEXT: Could not yet enable "
"interface '%s' due to rfkill",
drv->ifname);
send_rfkill_event = 1;
} else {
wpa_printf(MSG_ERROR, "WEXT: Could not set "
"interface '%s' UP", drv->ifname);
return -1;
}
}
/*
* Make sure that the driver does not have any obsolete PMKID entries.
@ -795,6 +848,11 @@ static int wpa_driver_wext_finish_drv_init(struct wpa_driver_wext_data *drv)
netlink_send_oper_ifla(drv->netlink, drv->ifindex,
1, IF_OPER_DORMANT);
if (send_rfkill_event) {
eloop_register_timeout(0, 0, wpa_driver_wext_send_rfkill,
drv, drv->ctx);
}
return 0;
}
@ -822,6 +880,7 @@ void wpa_driver_wext_deinit(void *priv)
netlink_send_oper_ifla(drv->netlink, drv->ifindex, 0, IF_OPER_UP);
netlink_deinit(drv->netlink);
rfkill_deinit(drv->rfkill);
if (drv->mlme_sock >= 0)
eloop_unregister_read_sock(drv->mlme_sock);

View file

@ -26,6 +26,7 @@ struct wpa_driver_wext_data {
int ifindex;
int ifindex2;
int if_removed;
struct rfkill_data *rfkill;
u8 *assoc_req_ies;
size_t assoc_req_ies_len;
u8 *assoc_resp_ies;

View file

@ -31,6 +31,7 @@ NEED_SME=y
NEED_AP_MLME=y
NEED_NETLINK=y
NEED_LINUX_IOCTL=y
NEED_RFKILL=y
DRV_LIBS += -lnl
ifdef CONFIG_LIBNL20
@ -77,6 +78,7 @@ DRV_WPA_CFLAGS += -DCONFIG_DRIVER_WEXT
CONFIG_WIRELESS_EXTENSION=y
NEED_NETLINK=y
NEED_LINUX_IOCTL=y
NEED_RFKILL=y
endif
ifdef CONFIG_DRIVER_HERMES
@ -162,6 +164,10 @@ ifdef NEED_LINUX_IOCTL
DRV_OBJS += ../src/drivers/linux_ioctl.o
endif
ifdef NEED_RFKILL
DRV_OBJS += ../src/drivers/rfkill.o
endif
##### COMMON VARS
DRV_BOTH_CFLAGS := $(DRV_CFLAGS) $(DRV_WPA_CFLAGS) $(DRV_AP_CFLAGS)

194
src/drivers/rfkill.c Normal file
View file

@ -0,0 +1,194 @@
/*
* Linux rfkill helper functions for driver wrappers
* Copyright (c) 2010, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Alternatively, this software may be distributed under the terms of BSD
* license.
*
* See README and COPYING for more details.
*/
#include "includes.h"
#include <fcntl.h>
#include "utils/common.h"
#include "utils/eloop.h"
#include "rfkill.h"
#define RFKILL_EVENT_SIZE_V1 8
struct rfkill_event {
u32 idx;
u8 type;
u8 op;
u8 soft;
u8 hard;
} STRUCT_PACKED;
enum rfkill_operation {
RFKILL_OP_ADD = 0,
RFKILL_OP_DEL,
RFKILL_OP_CHANGE,
RFKILL_OP_CHANGE_ALL,
};
enum rfkill_type {
RFKILL_TYPE_ALL = 0,
RFKILL_TYPE_WLAN,
RFKILL_TYPE_BLUETOOTH,
RFKILL_TYPE_UWB,
RFKILL_TYPE_WIMAX,
RFKILL_TYPE_WWAN,
RFKILL_TYPE_GPS,
RFKILL_TYPE_FM,
NUM_RFKILL_TYPES,
};
struct rfkill_data {
struct rfkill_config *cfg;
int fd;
int blocked;
};
static void rfkill_receive(int sock, void *eloop_ctx, void *sock_ctx)
{
struct rfkill_data *rfkill = eloop_ctx;
struct rfkill_event event;
ssize_t len;
int new_blocked;
len = read(rfkill->fd, &event, sizeof(event));
if (len < 0) {
wpa_printf(MSG_ERROR, "rfkill: Event read failed: %s",
strerror(errno));
return;
}
if (len != RFKILL_EVENT_SIZE_V1) {
wpa_printf(MSG_DEBUG, "rfkill: Unexpected event size "
"%d (expected %d)",
(int) len, RFKILL_EVENT_SIZE_V1);
return;
}
wpa_printf(MSG_DEBUG, "rfkill: event: idx=%u type=%d "
"op=%u soft=%u hard=%u",
event.idx, event.type, event.op, event.soft,
event.hard);
if (event.op != RFKILL_OP_CHANGE || event.type != RFKILL_TYPE_WLAN)
return;
if (event.hard) {
wpa_printf(MSG_INFO, "rfkill: WLAN hard blocked");
new_blocked = 1;
} else if (event.soft) {
wpa_printf(MSG_INFO, "rfkill: WLAN soft blocked");
new_blocked = 1;
} else {
wpa_printf(MSG_INFO, "rfkill: WLAN unblocked");
new_blocked = 0;
}
if (new_blocked != rfkill->blocked) {
rfkill->blocked = new_blocked;
if (new_blocked)
rfkill->cfg->blocked_cb(rfkill->cfg->ctx);
else
rfkill->cfg->unblocked_cb(rfkill->cfg->ctx);
}
}
struct rfkill_data * rfkill_init(struct rfkill_config *cfg)
{
struct rfkill_data *rfkill;
struct rfkill_event event;
ssize_t len;
rfkill = os_zalloc(sizeof(*rfkill));
if (rfkill == NULL)
return NULL;
rfkill->cfg = cfg;
rfkill->fd = open("/dev/rfkill", O_RDONLY);
if (rfkill->fd < 0) {
wpa_printf(MSG_INFO, "rfkill: Cannot open RFKILL control "
"device");
goto fail;
}
if (fcntl(rfkill->fd, F_SETFL, O_NONBLOCK) < 0) {
wpa_printf(MSG_ERROR, "rfkill: Cannot set non-blocking mode: "
"%s", strerror(errno));
goto fail2;
}
for (;;) {
len = read(rfkill->fd, &event, sizeof(event));
if (len < 0) {
if (errno == EAGAIN)
break; /* No more entries */
wpa_printf(MSG_ERROR, "rfkill: Event read failed: %s",
strerror(errno));
break;
}
if (len != RFKILL_EVENT_SIZE_V1) {
wpa_printf(MSG_DEBUG, "rfkill: Unexpected event size "
"%d (expected %d)",
(int) len, RFKILL_EVENT_SIZE_V1);
continue;
}
wpa_printf(MSG_DEBUG, "rfkill: initial event: idx=%u type=%d "
"op=%u soft=%u hard=%u",
event.idx, event.type, event.op, event.soft,
event.hard);
if (event.op != RFKILL_OP_ADD ||
event.type != RFKILL_TYPE_WLAN)
continue;
if (event.hard) {
wpa_printf(MSG_INFO, "rfkill: WLAN hard blocked");
rfkill->blocked = 1;
} else if (event.soft) {
wpa_printf(MSG_INFO, "rfkill: WLAN soft blocked");
rfkill->blocked = 1;
}
}
eloop_register_read_sock(rfkill->fd, rfkill_receive, rfkill, NULL);
return rfkill;
fail2:
close(rfkill->fd);
fail:
os_free(rfkill);
return NULL;
}
void rfkill_deinit(struct rfkill_data *rfkill)
{
if (rfkill == NULL)
return;
if (rfkill->fd >= 0) {
eloop_unregister_read_sock(rfkill->fd);
close(rfkill->fd);
}
os_free(rfkill->cfg);
os_free(rfkill);
}
int rfkill_is_blocked(struct rfkill_data *rfkill)
{
if (rfkill == NULL)
return 0;
return rfkill->blocked;
}

31
src/drivers/rfkill.h Normal file
View file

@ -0,0 +1,31 @@
/*
* Linux rfkill helper functions for driver wrappers
* Copyright (c) 2010, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Alternatively, this software may be distributed under the terms of BSD
* license.
*
* See README and COPYING for more details.
*/
#ifndef RFKILL_H
#define RFKILL_H
struct rfkill_data;
struct rfkill_config {
void *ctx;
char ifname[IFNAMSIZ];
void (*blocked_cb)(void *ctx);
void (*unblocked_cb)(void *ctx);
};
struct rfkill_data * rfkill_init(struct rfkill_config *cfg);
void rfkill_deinit(struct rfkill_data *rfkill);
int rfkill_is_blocked(struct rfkill_data *rfkill);
#endif /* RFKILL_H */

View file

@ -1742,11 +1742,17 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
} else if (os_strcmp(buf, "LOGOFF") == 0) {
eapol_sm_notify_logoff(wpa_s->eapol, TRUE);
} else if (os_strcmp(buf, "REASSOCIATE") == 0) {
wpa_s->disconnected = 0;
wpa_s->reassociate = 1;
wpa_supplicant_req_scan(wpa_s, 0, 0);
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED)
reply_len = -1;
else {
wpa_s->disconnected = 0;
wpa_s->reassociate = 1;
wpa_supplicant_req_scan(wpa_s, 0, 0);
}
} else if (os_strcmp(buf, "RECONNECT") == 0) {
if (wpa_s->disconnected) {
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED)
reply_len = -1;
else if (wpa_s->disconnected) {
wpa_s->disconnected = 0;
wpa_s->reassociate = 1;
wpa_supplicant_req_scan(wpa_s, 0, 0);
@ -1832,8 +1838,12 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
wpa_supplicant_deauthenticate(wpa_s,
WLAN_REASON_DEAUTH_LEAVING);
} else if (os_strcmp(buf, "SCAN") == 0) {
wpa_s->scan_req = 2;
wpa_supplicant_req_scan(wpa_s, 0, 0);
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED)
reply_len = -1;
else {
wpa_s->scan_req = 2;
wpa_supplicant_req_scan(wpa_s, 0, 0);
}
} else if (os_strcmp(buf, "SCAN_RESULTS") == 0) {
reply_len = wpa_supplicant_ctrl_iface_scan_results(
wpa_s, reply, reply_size);

View file

@ -109,6 +109,9 @@ void wpa_supplicant_mark_disassoc(struct wpa_supplicant *wpa_s)
{
int bssid_changed;
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED)
return;
wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
bssid_changed = !is_zero_ether_addr(wpa_s->bssid);
os_memset(wpa_s->bssid, 0, ETH_ALEN);
@ -1583,6 +1586,14 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
struct wpa_supplicant *wpa_s = ctx;
u16 reason_code = 0;
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED &&
event != EVENT_INTERFACE_ENABLED &&
event != EVENT_INTERFACE_STATUS) {
wpa_printf(MSG_DEBUG, "Ignore event %d while interface is "
"disabled", event);
return;
}
switch (event) {
case EVENT_AUTH:
sme_event_auth(wpa_s, data);
@ -1726,6 +1737,19 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
bgscan_notify_signal_change(
wpa_s, data->signal_change.above_threshold);
break;
case EVENT_INTERFACE_ENABLED:
wpa_printf(MSG_DEBUG, "Interface was enabled");
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) {
wpa_supplicant_set_state(wpa_s,
WPA_DISCONNECTED);
wpa_supplicant_req_scan(wpa_s, 0, 0);
}
break;
case EVENT_INTERFACE_DISABLED:
wpa_printf(MSG_DEBUG, "Interface was disabled");
wpa_supplicant_mark_disassoc(wpa_s);
wpa_supplicant_set_state(wpa_s, WPA_INTERFACE_DISABLED);
break;
default:
wpa_printf(MSG_INFO, "Unknown event %d", event);
break;

View file

@ -251,6 +251,11 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
size_t max_ssids;
enum wpa_states prev_state;
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) {
wpa_printf(MSG_DEBUG, "Skip scan - interface disabled");
return;
}
if (wpa_s->disconnected && !wpa_s->scan_req) {
wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
return;

View file

@ -489,6 +489,8 @@ const char * wpa_supplicant_state_txt(enum wpa_states state)
return "DISCONNECTED";
case WPA_INACTIVE:
return "INACTIVE";
case WPA_INTERFACE_DISABLED:
return "INTERFACE_DISABLED";
case WPA_SCANNING:
return "SCANNING";
case WPA_AUTHENTICATING:
@ -592,7 +594,8 @@ static void wpa_supplicant_clear_status(struct wpa_supplicant *wpa_s)
wpa_s->group_cipher = 0;
wpa_s->mgmt_group_cipher = 0;
wpa_s->key_mgmt = 0;
wpa_s->wpa_state = WPA_DISCONNECTED;
if (wpa_s->wpa_state != WPA_INTERFACE_DISABLED)
wpa_s->wpa_state = WPA_DISCONNECTED;
if (wpa_s->wpa_state != old_state)
wpas_notify_state_changed(wpa_s, wpa_s->wpa_state, old_state);