Commit 04a773ad authored by Johannes Berg's avatar Johannes Berg Committed by John W. Linville

cfg80211/nl80211: add IBSS API

This adds IBSS API along with (preliminary) wext handlers.
The wext handlers can only do IBSS so you need to call them
from your own wext handlers if the mode is IBSS.

The nl80211 API requires
 * an SSID
 * a channel (frequency) for the case that a new IBSS
   has to be created

It optionally supports
 * a flag to fix the channel
 * a fixed BSSID

The cfg80211 code also takes care to leave the IBSS before
the netdev is set down. If wireless extensions are used, it
also caches values when the interface is down and instructs
the driver to join when the interface is set up.
Signed-off-by: default avatarJohannes Berg <johannes@sipsolutions.net>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 691597cb
......@@ -223,6 +223,15 @@
* %NL80211_ATTR_KEY_SEQ to indicate the TSC value of the frame; this
* event matches with MLME-MICHAELMICFAILURE.indication() primitive
*
* @NL80211_CMD_JOIN_IBSS: Join a new IBSS -- given at least an SSID and a
* FREQ attribute (for the initial frequency if no peer can be found)
* and optionally a MAC (as BSSID) and FREQ_FIXED attribute if those
* should be fixed rather than automatically determined. Can only be
* executed on a network interface that is UP, and fixed BSSID/FREQ
* may be rejected.
* @NL80211_CMD_LEAVE_IBSS: Leave the IBSS -- no special arguments, the IBSS is
* determined by the network interface.
*
* @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use
*/
......@@ -288,6 +297,9 @@ enum nl80211_commands {
NL80211_CMD_REG_BEACON_HINT,
NL80211_CMD_JOIN_IBSS,
NL80211_CMD_LEAVE_IBSS,
/* add new commands above here */
/* used to define NL80211_CMD_MAX below */
......@@ -456,6 +468,9 @@ enum nl80211_commands {
* @NL80211_ATTR_CIPHER_SUITES: a set of u32 values indicating the supported
* cipher suites
*
* @NL80211_ATTR_FREQ_FIXED: a flag indicating the IBSS should not try to look
* for other networks on different channels
*
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
*/
......@@ -547,6 +562,9 @@ enum nl80211_attrs {
NL80211_ATTR_FREQ_BEFORE,
NL80211_ATTR_FREQ_AFTER,
NL80211_ATTR_FREQ_FIXED,
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
......
......@@ -657,6 +657,31 @@ struct cfg80211_disassoc_request {
size_t ie_len;
};
/**
* struct cfg80211_ibss_params - IBSS parameters
*
* This structure defines the IBSS parameters for the join_ibss()
* method.
*
* @ssid: The SSID, will always be non-null.
* @ssid_len: The length of the SSID, will always be non-zero.
* @bssid: Fixed BSSID requested, maybe be %NULL, if set do not
* search for IBSSs with a different BSSID.
* @channel: The channel to use if no IBSS can be found to join.
* @channel_fixed: The channel should be fixed -- do not search for
* IBSSs to join on other channels.
* @ie: information element(s) to include in the beacon
* @ie_len: length of that
*/
struct cfg80211_ibss_params {
u8 *ssid;
u8 *bssid;
struct ieee80211_channel *channel;
u8 *ie;
u8 ssid_len, ie_len;
bool channel_fixed;
};
/**
* struct cfg80211_ops - backend description for wireless configuration
*
......@@ -732,6 +757,11 @@ struct cfg80211_disassoc_request {
* @assoc: Request to (re)associate with the specified peer
* @deauth: Request to deauthenticate from the specified peer
* @disassoc: Request to disassociate from the specified peer
*
* @join_ibss: Join the specified IBSS (or create if necessary). Once done, call
* cfg80211_ibss_joined(), also call that function when changing BSSID due
* to a merge.
* @leave_ibss: Leave the IBSS.
*/
struct cfg80211_ops {
int (*suspend)(struct wiphy *wiphy);
......@@ -817,6 +847,10 @@ struct cfg80211_ops {
struct cfg80211_deauth_request *req);
int (*disassoc)(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_disassoc_request *req);
int (*join_ibss)(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_ibss_params *params);
int (*leave_ibss)(struct wiphy *wiphy, struct net_device *dev);
};
/* temporary wext handlers */
......@@ -839,6 +873,28 @@ int cfg80211_wext_siwmlme(struct net_device *dev,
int cfg80211_wext_giwrange(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *data, char *extra);
int cfg80211_ibss_wext_siwfreq(struct net_device *dev,
struct iw_request_info *info,
struct iw_freq *freq, char *extra);
int cfg80211_ibss_wext_giwfreq(struct net_device *dev,
struct iw_request_info *info,
struct iw_freq *freq, char *extra);
int cfg80211_ibss_wext_siwessid(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *data, char *ssid);
int cfg80211_ibss_wext_giwessid(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *data, char *ssid);
int cfg80211_ibss_wext_siwap(struct net_device *dev,
struct iw_request_info *info,
struct sockaddr *ap_addr, char *extra);
int cfg80211_ibss_wext_giwap(struct net_device *dev,
struct iw_request_info *info,
struct sockaddr *ap_addr, char *extra);
/* wext helper for now (to be removed) */
struct ieee80211_channel *cfg80211_wext_freq(struct wiphy *wiphy,
struct iw_freq *freq);
/**
* cfg80211_scan_done - notify that scan finished
......@@ -984,4 +1040,20 @@ void cfg80211_michael_mic_failure(struct net_device *dev, const u8 *addr,
enum nl80211_key_type key_type, int key_id,
const u8 *tsc);
/**
* cfg80211_ibss_joined - notify cfg80211 that device joined an IBSS
*
* @dev: network device
* @bssid: the BSSID of the IBSS joined
* @gfp: allocation flags
*
* This function notifies cfg80211 that the device joined an IBSS or
* switched to a different BSSID. Before this function can be called,
* either a beacon has to have been received from the IBSS, or one of
* the cfg80211_inform_bss{,_frame} functions must have been called
* with the locally generated beacon -- this guarantees that there is
* always a scan result for this IBSS. cfg80211 will handle the rest.
*/
void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, gfp_t gfp);
#endif /* __NET_CFG80211_H */
......@@ -265,6 +265,8 @@ struct wiphy {
*
* @wiphy: pointer to hardware description
* @iftype: interface type
* @list: (private)
* @netdev (private)
*/
struct wireless_dev {
struct wiphy *wiphy;
......@@ -273,6 +275,18 @@ struct wireless_dev {
/* private to the generic wireless code */
struct list_head list;
struct net_device *netdev;
/* currently used for IBSS - might be rearranged in the future */
struct cfg80211_bss *current_bss;
u8 bssid[ETH_ALEN];
u8 ssid[IEEE80211_MAX_SSID_LEN];
u8 ssid_len;
#ifdef CONFIG_WIRELESS_EXT
/* wext data */
struct cfg80211_ibss_params wext;
u8 wext_bssid[ETH_ALEN];
#endif
};
/**
......
......@@ -5,7 +5,7 @@ obj-$(CONFIG_LIB80211_CRYPT_WEP) += lib80211_crypt_wep.o
obj-$(CONFIG_LIB80211_CRYPT_CCMP) += lib80211_crypt_ccmp.o
obj-$(CONFIG_LIB80211_CRYPT_TKIP) += lib80211_crypt_tkip.o
cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o scan.o nl80211.o mlme.o
cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o scan.o nl80211.o mlme.o ibss.o
cfg80211-$(CONFIG_WIRELESS_EXT) += wext-compat.o
ccflags-y += -D__CHECK_ENDIAN__
......@@ -450,6 +450,22 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
dev->ieee80211_ptr->netdev = dev;
mutex_unlock(&rdev->devlist_mtx);
break;
case NETDEV_GOING_DOWN:
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_ADHOC)
break;
if (!dev->ieee80211_ptr->ssid_len)
break;
cfg80211_leave_ibss(rdev, dev);
break;
case NETDEV_UP:
#ifdef CONFIG_WIRELESS_EXT
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_ADHOC)
break;
if (!dev->ieee80211_ptr->wext.ssid_len)
break;
cfg80211_join_ibss(rdev, dev, &dev->ieee80211_ptr->wext);
break;
#endif
case NETDEV_UNREGISTER:
mutex_lock(&rdev->devlist_mtx);
if (!list_empty(&dev->ieee80211_ptr->list)) {
......
......@@ -144,4 +144,12 @@ void cfg80211_bss_expire(struct cfg80211_registered_device *dev);
void cfg80211_bss_age(struct cfg80211_registered_device *dev,
unsigned long age_secs);
/* IBSS */
int cfg80211_join_ibss(struct cfg80211_registered_device *rdev,
struct net_device *dev,
struct cfg80211_ibss_params *params);
void cfg80211_clear_ibss(struct net_device *dev);
int cfg80211_leave_ibss(struct cfg80211_registered_device *rdev,
struct net_device *dev);
#endif /* __NET_WIRELESS_CORE_H */
/*
* Some IBSS support code for cfg80211.
*
* Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
*/
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <net/cfg80211.h>
#include <net/wireless.h>
#include "nl80211.h"
void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, gfp_t gfp)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_bss *bss;
#ifdef CONFIG_WIRELESS_EXT
union iwreq_data wrqu;
#endif
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
return;
if (WARN_ON(!wdev->ssid_len))
return;
if (memcmp(bssid, wdev->bssid, ETH_ALEN) == 0)
return;
bss = cfg80211_get_bss(wdev->wiphy, NULL, bssid,
wdev->ssid, wdev->ssid_len,
WLAN_CAPABILITY_IBSS, WLAN_CAPABILITY_IBSS);
if (WARN_ON(!bss))
return;
if (wdev->current_bss) {
cfg80211_unhold_bss(wdev->current_bss);
cfg80211_put_bss(wdev->current_bss);
}
cfg80211_hold_bss(bss);
wdev->current_bss = bss;
memcpy(wdev->bssid, bssid, ETH_ALEN);
nl80211_send_ibss_bssid(wiphy_to_dev(wdev->wiphy), dev, bssid, gfp);
#ifdef CONFIG_WIRELESS_EXT
memset(&wrqu, 0, sizeof(wrqu));
memcpy(wrqu.ap_addr.sa_data, bssid, ETH_ALEN);
wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL);
#endif
}
EXPORT_SYMBOL(cfg80211_ibss_joined);
int cfg80211_join_ibss(struct cfg80211_registered_device *rdev,
struct net_device *dev,
struct cfg80211_ibss_params *params)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
int err;
if (wdev->ssid_len)
return -EALREADY;
#ifdef CONFIG_WIRELESS_EXT
wdev->wext.channel = params->channel;
#endif
err = rdev->ops->join_ibss(&rdev->wiphy, dev, params);
if (err)
return err;
memcpy(wdev->ssid, params->ssid, params->ssid_len);
wdev->ssid_len = params->ssid_len;
return 0;
}
void cfg80211_clear_ibss(struct net_device *dev)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
if (wdev->current_bss) {
cfg80211_unhold_bss(wdev->current_bss);
cfg80211_put_bss(wdev->current_bss);
}
wdev->current_bss = NULL;
wdev->ssid_len = 0;
memset(wdev->bssid, 0, ETH_ALEN);
}
int cfg80211_leave_ibss(struct cfg80211_registered_device *rdev,
struct net_device *dev)
{
int err;
err = rdev->ops->leave_ibss(&rdev->wiphy, dev);
if (err)
return err;
cfg80211_clear_ibss(dev);
return 0;
}
#ifdef CONFIG_WIRELESS_EXT
static int cfg80211_ibss_wext_join(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev)
{
enum ieee80211_band band;
int i;
/* try to find an IBSS channel if none requested ... */
if (!wdev->wext.channel) {
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
struct ieee80211_supported_band *sband;
struct ieee80211_channel *chan;
sband = rdev->wiphy.bands[band];
if (!sband)
continue;
for (i = 0; i < sband->n_channels; i++) {
chan = &sband->channels[i];
if (chan->flags & IEEE80211_CHAN_NO_IBSS)
continue;
if (chan->flags & IEEE80211_CHAN_DISABLED)
continue;
wdev->wext.channel = chan;
break;
}
if (wdev->wext.channel)
break;
}
if (!wdev->wext.channel)
return -EINVAL;
}
/* don't join -- SSID is not there */
if (!wdev->wext.ssid_len)
return 0;
if (!netif_running(wdev->netdev))
return 0;
return cfg80211_join_ibss(wiphy_to_dev(wdev->wiphy),
wdev->netdev, &wdev->wext);
}
int cfg80211_ibss_wext_siwfreq(struct net_device *dev,
struct iw_request_info *info,
struct iw_freq *freq, char *extra)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct ieee80211_channel *chan;
int err;
/* call only for ibss! */
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
return -EINVAL;
if (!wiphy_to_dev(wdev->wiphy)->ops->join_ibss)
return -EOPNOTSUPP;
chan = cfg80211_wext_freq(wdev->wiphy, freq);
if (chan && IS_ERR(chan))
return PTR_ERR(chan);
if (chan &&
(chan->flags & IEEE80211_CHAN_NO_IBSS ||
chan->flags & IEEE80211_CHAN_DISABLED))
return -EINVAL;
if (wdev->wext.channel == chan)
return 0;
if (wdev->ssid_len) {
err = cfg80211_leave_ibss(wiphy_to_dev(wdev->wiphy), dev);
if (err)
return err;
}
if (chan) {
wdev->wext.channel = chan;
wdev->wext.channel_fixed = true;
} else {
/* cfg80211_ibss_wext_join will pick one if needed */
wdev->wext.channel_fixed = false;
}
return cfg80211_ibss_wext_join(wiphy_to_dev(wdev->wiphy), wdev);
}
/* temporary symbol - mark GPL - in the future the handler won't be */
EXPORT_SYMBOL_GPL(cfg80211_ibss_wext_siwfreq);
int cfg80211_ibss_wext_giwfreq(struct net_device *dev,
struct iw_request_info *info,
struct iw_freq *freq, char *extra)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct ieee80211_channel *chan = NULL;
/* call only for ibss! */
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
return -EINVAL;
if (wdev->current_bss)
chan = wdev->current_bss->channel;
else if (wdev->wext.channel)
chan = wdev->wext.channel;
if (chan) {
freq->m = chan->center_freq;
freq->e = 6;
return 0;
}
/* no channel if not joining */
return -EINVAL;
}
/* temporary symbol - mark GPL - in the future the handler won't be */
EXPORT_SYMBOL_GPL(cfg80211_ibss_wext_giwfreq);
int cfg80211_ibss_wext_siwessid(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *data, char *ssid)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
size_t len = data->length;
int err;
/* call only for ibss! */
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
return -EINVAL;
if (!wiphy_to_dev(wdev->wiphy)->ops->join_ibss)
return -EOPNOTSUPP;
if (wdev->ssid_len) {
err = cfg80211_leave_ibss(wiphy_to_dev(wdev->wiphy), dev);
if (err)
return err;
}
/* iwconfig uses nul termination in SSID.. */
if (len > 0 && ssid[len - 1] == '\0')
len--;
wdev->wext.ssid = wdev->ssid;
memcpy(wdev->wext.ssid, ssid, len);
wdev->wext.ssid_len = len;
return cfg80211_ibss_wext_join(wiphy_to_dev(wdev->wiphy), wdev);
}
/* temporary symbol - mark GPL - in the future the handler won't be */
EXPORT_SYMBOL_GPL(cfg80211_ibss_wext_siwessid);
int cfg80211_ibss_wext_giwessid(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *data, char *ssid)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
/* call only for ibss! */
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
return -EINVAL;
data->flags = 0;
if (wdev->ssid_len) {
data->flags = 1;
data->length = wdev->ssid_len;
memcpy(ssid, wdev->ssid, data->length);
} else if (wdev->wext.ssid) {
data->flags = 1;
data->length = wdev->wext.ssid_len;
memcpy(ssid, wdev->wext.ssid, data->length);
}
return 0;
}
/* temporary symbol - mark GPL - in the future the handler won't be */
EXPORT_SYMBOL_GPL(cfg80211_ibss_wext_giwessid);
int cfg80211_ibss_wext_siwap(struct net_device *dev,
struct iw_request_info *info,
struct sockaddr *ap_addr, char *extra)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
u8 *bssid = ap_addr->sa_data;
int err;
/* call only for ibss! */
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
return -EINVAL;
if (!wiphy_to_dev(wdev->wiphy)->ops->join_ibss)
return -EOPNOTSUPP;
if (ap_addr->sa_family != ARPHRD_ETHER)
return -EINVAL;
/* automatic mode */
if (is_zero_ether_addr(bssid) || is_broadcast_ether_addr(bssid))
bssid = NULL;
/* both automatic */
if (!bssid && !wdev->wext.bssid)
return 0;
/* fixed already - and no change */
if (wdev->wext.bssid && bssid &&
compare_ether_addr(bssid, wdev->wext.bssid) == 0)
return 0;
if (wdev->ssid_len) {
err = cfg80211_leave_ibss(wiphy_to_dev(wdev->wiphy), dev);
if (err)
return err;
}
if (bssid) {
memcpy(wdev->wext_bssid, bssid, ETH_ALEN);
wdev->wext.bssid = wdev->wext_bssid;
} else
wdev->wext.bssid = NULL;
return cfg80211_ibss_wext_join(wiphy_to_dev(wdev->wiphy), wdev);
}
/* temporary symbol - mark GPL - in the future the handler won't be */
EXPORT_SYMBOL_GPL(cfg80211_ibss_wext_siwap);
int cfg80211_ibss_wext_giwap(struct net_device *dev,
struct iw_request_info *info,
struct sockaddr *ap_addr, char *extra)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
/* call only for ibss! */
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
return -EINVAL;
ap_addr->sa_family = ARPHRD_ETHER;
if (wdev->wext.bssid) {
memcpy(ap_addr->sa_data, wdev->wext.bssid, ETH_ALEN);
return 0;
}
memcpy(ap_addr->sa_data, wdev->bssid, ETH_ALEN);
return 0;
}
/* temporary symbol - mark GPL - in the future the handler won't be */
EXPORT_SYMBOL_GPL(cfg80211_ibss_wext_giwap);
#endif
......@@ -116,6 +116,7 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
.len = IEEE80211_MAX_SSID_LEN },
[NL80211_ATTR_AUTH_TYPE] = { .type = NLA_U32 },
[NL80211_ATTR_REASON_CODE] = { .type = NLA_U16 },
[NL80211_ATTR_FREQ_FIXED] = { .type = NLA_FLAG },
};
/* IE validation */
......@@ -322,6 +323,7 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
CMD(assoc, ASSOCIATE);
CMD(deauth, DEAUTHENTICATE);
CMD(disassoc, DISASSOCIATE);
CMD(join_ibss, JOIN_IBSS);
#undef CMD
nla_nest_end(msg, nl_cmds);
......@@ -668,7 +670,7 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
struct cfg80211_registered_device *drv;
struct vif_params params;
int err, ifindex;
enum nl80211_iftype type;
enum nl80211_iftype otype, ntype;
struct net_device *dev;
u32 _flags, *flags = NULL;
bool change = false;
......@@ -682,30 +684,27 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
goto unlock_rtnl;
ifindex = dev->ifindex;
type = dev->ieee80211_ptr->iftype;
otype = ntype = dev->ieee80211_ptr->iftype;
dev_put(dev);
if (info->attrs[NL80211_ATTR_IFTYPE]) {
enum nl80211_iftype ntype;
ntype = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]);
if (type != ntype)
if (otype != ntype)
change = true;
type = ntype;
if (type > NL80211_IFTYPE_MAX) {
if (ntype > NL80211_IFTYPE_MAX) {
err = -EINVAL;
goto unlock;
}
}
if (!drv->ops->change_virtual_intf ||
!(drv->wiphy.interface_modes & (1 << type))) {
!(drv->wiphy.interface_modes & (1 << ntype))) {
err = -EOPNOTSUPP;
goto unlock;
}
if (info->attrs[NL80211_ATTR_MESH_ID]) {
if (type != NL80211_IFTYPE_MESH_POINT) {
if (ntype != NL80211_IFTYPE_MESH_POINT) {
err = -EINVAL;
goto unlock;
}
......@@ -715,7 +714,7 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
}
if (info->attrs[NL80211_ATTR_MNTR_FLAGS]) {
if (type != NL80211_IFTYPE_MONITOR) {
if (ntype != NL80211_IFTYPE_MONITOR) {