diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2009-02-10 15:25:55 -0500 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2009-02-13 13:45:49 -0500 |
commit | 2a5193119269062608582418deba7af82844159a (patch) | |
tree | 1f2fe8cffbeb7530dce7fa708310f6fb29ab0dd8 /net/wireless/nl80211.c | |
parent | 849b7967818995a32c3017542e33eb3155944368 (diff) |
cfg80211/nl80211: scanning (and mac80211 update to use it)
This patch adds basic scan capability to cfg80211/nl80211 and
changes mac80211 to use it. The BSS list that cfg80211 maintains
is made driver-accessible with a private area in each BSS struct,
but mac80211 doesn't yet use it. That's another large project.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net/wireless/nl80211.c')
-rw-r--r-- | net/wireless/nl80211.c | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index d452396006ee..298a4de59948 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c | |||
@@ -14,6 +14,7 @@ | |||
14 | #include <linux/nl80211.h> | 14 | #include <linux/nl80211.h> |
15 | #include <linux/rtnetlink.h> | 15 | #include <linux/rtnetlink.h> |
16 | #include <linux/netlink.h> | 16 | #include <linux/netlink.h> |
17 | #include <linux/etherdevice.h> | ||
17 | #include <net/genetlink.h> | 18 | #include <net/genetlink.h> |
18 | #include <net/cfg80211.h> | 19 | #include <net/cfg80211.h> |
19 | #include "core.h" | 20 | #include "core.h" |
@@ -109,6 +110,8 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = { | |||
109 | [NL80211_ATTR_MGMT_SUBTYPE] = { .type = NLA_U8 }, | 110 | [NL80211_ATTR_MGMT_SUBTYPE] = { .type = NLA_U8 }, |
110 | [NL80211_ATTR_IE] = { .type = NLA_BINARY, | 111 | [NL80211_ATTR_IE] = { .type = NLA_BINARY, |
111 | .len = IEEE80211_MAX_DATA_LEN }, | 112 | .len = IEEE80211_MAX_DATA_LEN }, |
113 | [NL80211_ATTR_SCAN_FREQUENCIES] = { .type = NLA_NESTED }, | ||
114 | [NL80211_ATTR_SCAN_SSIDS] = { .type = NLA_NESTED }, | ||
112 | }; | 115 | }; |
113 | 116 | ||
114 | /* message building helper */ | 117 | /* message building helper */ |
@@ -141,6 +144,8 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, | |||
141 | 144 | ||
142 | NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->idx); | 145 | NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->idx); |
143 | NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy)); | 146 | NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy)); |
147 | NLA_PUT_U8(msg, NL80211_ATTR_MAX_NUM_SCAN_SSIDS, | ||
148 | dev->wiphy.max_scan_ssids); | ||
144 | 149 | ||
145 | nl_modes = nla_nest_start(msg, NL80211_ATTR_SUPPORTED_IFTYPES); | 150 | nl_modes = nla_nest_start(msg, NL80211_ATTR_SUPPORTED_IFTYPES); |
146 | if (!nl_modes) | 151 | if (!nl_modes) |
@@ -2270,6 +2275,246 @@ static int nl80211_set_mgmt_extra_ie(struct sk_buff *skb, | |||
2270 | return err; | 2275 | return err; |
2271 | } | 2276 | } |
2272 | 2277 | ||
2278 | static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) | ||
2279 | { | ||
2280 | struct cfg80211_registered_device *drv; | ||
2281 | struct net_device *dev; | ||
2282 | struct cfg80211_scan_request *request; | ||
2283 | struct cfg80211_ssid *ssid; | ||
2284 | struct ieee80211_channel *channel; | ||
2285 | struct nlattr *attr; | ||
2286 | struct wiphy *wiphy; | ||
2287 | int err, tmp, n_ssids = 0, n_channels = 0, i; | ||
2288 | enum ieee80211_band band; | ||
2289 | |||
2290 | err = get_drv_dev_by_info_ifindex(info->attrs, &drv, &dev); | ||
2291 | if (err) | ||
2292 | return err; | ||
2293 | |||
2294 | wiphy = &drv->wiphy; | ||
2295 | |||
2296 | if (!drv->ops->scan) { | ||
2297 | err = -EOPNOTSUPP; | ||
2298 | goto out; | ||
2299 | } | ||
2300 | |||
2301 | rtnl_lock(); | ||
2302 | |||
2303 | if (drv->scan_req) { | ||
2304 | err = -EBUSY; | ||
2305 | goto out_unlock; | ||
2306 | } | ||
2307 | |||
2308 | if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { | ||
2309 | nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], tmp) | ||
2310 | n_channels++; | ||
2311 | if (!n_channels) { | ||
2312 | err = -EINVAL; | ||
2313 | goto out_unlock; | ||
2314 | } | ||
2315 | } else { | ||
2316 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) | ||
2317 | if (wiphy->bands[band]) | ||
2318 | n_channels += wiphy->bands[band]->n_channels; | ||
2319 | } | ||
2320 | |||
2321 | if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) | ||
2322 | nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], tmp) | ||
2323 | n_ssids++; | ||
2324 | |||
2325 | if (n_ssids > wiphy->max_scan_ssids) { | ||
2326 | err = -EINVAL; | ||
2327 | goto out_unlock; | ||
2328 | } | ||
2329 | |||
2330 | request = kzalloc(sizeof(*request) | ||
2331 | + sizeof(*ssid) * n_ssids | ||
2332 | + sizeof(channel) * n_channels, GFP_KERNEL); | ||
2333 | if (!request) { | ||
2334 | err = -ENOMEM; | ||
2335 | goto out_unlock; | ||
2336 | } | ||
2337 | |||
2338 | request->channels = (void *)((char *)request + sizeof(*request)); | ||
2339 | request->n_channels = n_channels; | ||
2340 | if (n_ssids) | ||
2341 | request->ssids = (void *)(request->channels + n_channels); | ||
2342 | request->n_ssids = n_ssids; | ||
2343 | |||
2344 | if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { | ||
2345 | /* user specified, bail out if channel not found */ | ||
2346 | request->n_channels = n_channels; | ||
2347 | i = 0; | ||
2348 | nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], tmp) { | ||
2349 | request->channels[i] = ieee80211_get_channel(wiphy, nla_get_u32(attr)); | ||
2350 | if (!request->channels[i]) { | ||
2351 | err = -EINVAL; | ||
2352 | goto out_free; | ||
2353 | } | ||
2354 | i++; | ||
2355 | } | ||
2356 | } else { | ||
2357 | /* all channels */ | ||
2358 | i = 0; | ||
2359 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) { | ||
2360 | int j; | ||
2361 | if (!wiphy->bands[band]) | ||
2362 | continue; | ||
2363 | for (j = 0; j < wiphy->bands[band]->n_channels; j++) { | ||
2364 | request->channels[i] = &wiphy->bands[band]->channels[j]; | ||
2365 | i++; | ||
2366 | } | ||
2367 | } | ||
2368 | } | ||
2369 | |||
2370 | i = 0; | ||
2371 | if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) { | ||
2372 | nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], tmp) { | ||
2373 | if (request->ssids[i].ssid_len > IEEE80211_MAX_SSID_LEN) { | ||
2374 | err = -EINVAL; | ||
2375 | goto out_free; | ||
2376 | } | ||
2377 | memcpy(request->ssids[i].ssid, nla_data(attr), nla_len(attr)); | ||
2378 | request->ssids[i].ssid_len = nla_len(attr); | ||
2379 | i++; | ||
2380 | } | ||
2381 | } | ||
2382 | |||
2383 | request->ifidx = dev->ifindex; | ||
2384 | request->wiphy = &drv->wiphy; | ||
2385 | |||
2386 | drv->scan_req = request; | ||
2387 | err = drv->ops->scan(&drv->wiphy, dev, request); | ||
2388 | |||
2389 | out_free: | ||
2390 | if (err) { | ||
2391 | drv->scan_req = NULL; | ||
2392 | kfree(request); | ||
2393 | } | ||
2394 | out_unlock: | ||
2395 | rtnl_unlock(); | ||
2396 | out: | ||
2397 | cfg80211_put_dev(drv); | ||
2398 | dev_put(dev); | ||
2399 | return err; | ||
2400 | } | ||
2401 | |||
2402 | static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags, | ||
2403 | struct cfg80211_registered_device *rdev, | ||
2404 | struct net_device *dev, | ||
2405 | struct cfg80211_bss *res) | ||
2406 | { | ||
2407 | void *hdr; | ||
2408 | struct nlattr *bss; | ||
2409 | |||
2410 | hdr = nl80211hdr_put(msg, pid, seq, flags, | ||
2411 | NL80211_CMD_NEW_SCAN_RESULTS); | ||
2412 | if (!hdr) | ||
2413 | return -1; | ||
2414 | |||
2415 | NLA_PUT_U32(msg, NL80211_ATTR_SCAN_GENERATION, | ||
2416 | rdev->bss_generation); | ||
2417 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex); | ||
2418 | |||
2419 | bss = nla_nest_start(msg, NL80211_ATTR_BSS); | ||
2420 | if (!bss) | ||
2421 | goto nla_put_failure; | ||
2422 | if (!is_zero_ether_addr(res->bssid)) | ||
2423 | NLA_PUT(msg, NL80211_BSS_BSSID, ETH_ALEN, res->bssid); | ||
2424 | if (res->information_elements && res->len_information_elements) | ||
2425 | NLA_PUT(msg, NL80211_BSS_INFORMATION_ELEMENTS, | ||
2426 | res->len_information_elements, | ||
2427 | res->information_elements); | ||
2428 | if (res->tsf) | ||
2429 | NLA_PUT_U64(msg, NL80211_BSS_TSF, res->tsf); | ||
2430 | if (res->beacon_interval) | ||
2431 | NLA_PUT_U16(msg, NL80211_BSS_BEACON_INTERVAL, res->beacon_interval); | ||
2432 | NLA_PUT_U16(msg, NL80211_BSS_CAPABILITY, res->capability); | ||
2433 | NLA_PUT_U32(msg, NL80211_BSS_FREQUENCY, res->channel->center_freq); | ||
2434 | |||
2435 | switch (res->signal_type) { | ||
2436 | case CFG80211_SIGNAL_TYPE_MBM: | ||
2437 | NLA_PUT_U32(msg, NL80211_BSS_SIGNAL_MBM, res->signal); | ||
2438 | break; | ||
2439 | case CFG80211_SIGNAL_TYPE_UNSPEC: | ||
2440 | NLA_PUT_U8(msg, NL80211_BSS_SIGNAL_UNSPEC, res->signal); | ||
2441 | break; | ||
2442 | default: | ||
2443 | break; | ||
2444 | } | ||
2445 | |||
2446 | nla_nest_end(msg, bss); | ||
2447 | |||
2448 | return genlmsg_end(msg, hdr); | ||
2449 | |||
2450 | nla_put_failure: | ||
2451 | genlmsg_cancel(msg, hdr); | ||
2452 | return -EMSGSIZE; | ||
2453 | } | ||
2454 | |||
2455 | static int nl80211_dump_scan(struct sk_buff *skb, | ||
2456 | struct netlink_callback *cb) | ||
2457 | { | ||
2458 | struct cfg80211_registered_device *dev; | ||
2459 | struct net_device *netdev; | ||
2460 | struct cfg80211_internal_bss *scan; | ||
2461 | int ifidx = cb->args[0]; | ||
2462 | int start = cb->args[1], idx = 0; | ||
2463 | int err; | ||
2464 | |||
2465 | if (!ifidx) { | ||
2466 | err = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl80211_fam.hdrsize, | ||
2467 | nl80211_fam.attrbuf, nl80211_fam.maxattr, | ||
2468 | nl80211_policy); | ||
2469 | if (err) | ||
2470 | return err; | ||
2471 | |||
2472 | if (!nl80211_fam.attrbuf[NL80211_ATTR_IFINDEX]) | ||
2473 | return -EINVAL; | ||
2474 | |||
2475 | ifidx = nla_get_u32(nl80211_fam.attrbuf[NL80211_ATTR_IFINDEX]); | ||
2476 | if (!ifidx) | ||
2477 | return -EINVAL; | ||
2478 | cb->args[0] = ifidx; | ||
2479 | } | ||
2480 | |||
2481 | netdev = dev_get_by_index(&init_net, ifidx); | ||
2482 | if (!netdev) | ||
2483 | return -ENODEV; | ||
2484 | |||
2485 | dev = cfg80211_get_dev_from_ifindex(ifidx); | ||
2486 | if (IS_ERR(dev)) { | ||
2487 | err = PTR_ERR(dev); | ||
2488 | goto out_put_netdev; | ||
2489 | } | ||
2490 | |||
2491 | spin_lock_bh(&dev->bss_lock); | ||
2492 | cfg80211_bss_expire(dev); | ||
2493 | |||
2494 | list_for_each_entry(scan, &dev->bss_list, list) { | ||
2495 | if (++idx <= start) | ||
2496 | continue; | ||
2497 | if (nl80211_send_bss(skb, | ||
2498 | NETLINK_CB(cb->skb).pid, | ||
2499 | cb->nlh->nlmsg_seq, NLM_F_MULTI, | ||
2500 | dev, netdev, &scan->pub) < 0) { | ||
2501 | idx--; | ||
2502 | goto out; | ||
2503 | } | ||
2504 | } | ||
2505 | |||
2506 | out: | ||
2507 | spin_unlock_bh(&dev->bss_lock); | ||
2508 | |||
2509 | cb->args[1] = idx; | ||
2510 | err = skb->len; | ||
2511 | cfg80211_put_dev(dev); | ||
2512 | out_put_netdev: | ||
2513 | dev_put(netdev); | ||
2514 | |||
2515 | return err; | ||
2516 | } | ||
2517 | |||
2273 | static struct genl_ops nl80211_ops[] = { | 2518 | static struct genl_ops nl80211_ops[] = { |
2274 | { | 2519 | { |
2275 | .cmd = NL80211_CMD_GET_WIPHY, | 2520 | .cmd = NL80211_CMD_GET_WIPHY, |
@@ -2443,12 +2688,26 @@ static struct genl_ops nl80211_ops[] = { | |||
2443 | .policy = nl80211_policy, | 2688 | .policy = nl80211_policy, |
2444 | .flags = GENL_ADMIN_PERM, | 2689 | .flags = GENL_ADMIN_PERM, |
2445 | }, | 2690 | }, |
2691 | { | ||
2692 | .cmd = NL80211_CMD_TRIGGER_SCAN, | ||
2693 | .doit = nl80211_trigger_scan, | ||
2694 | .policy = nl80211_policy, | ||
2695 | .flags = GENL_ADMIN_PERM, | ||
2696 | }, | ||
2697 | { | ||
2698 | .cmd = NL80211_CMD_GET_SCAN, | ||
2699 | .policy = nl80211_policy, | ||
2700 | .dumpit = nl80211_dump_scan, | ||
2701 | }, | ||
2446 | }; | 2702 | }; |
2447 | 2703 | ||
2448 | /* multicast groups */ | 2704 | /* multicast groups */ |
2449 | static struct genl_multicast_group nl80211_config_mcgrp = { | 2705 | static struct genl_multicast_group nl80211_config_mcgrp = { |
2450 | .name = "config", | 2706 | .name = "config", |
2451 | }; | 2707 | }; |
2708 | static struct genl_multicast_group nl80211_scan_mcgrp = { | ||
2709 | .name = "scan", | ||
2710 | }; | ||
2452 | 2711 | ||
2453 | /* notification functions */ | 2712 | /* notification functions */ |
2454 | 2713 | ||
@@ -2468,6 +2727,66 @@ void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev) | |||
2468 | genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL); | 2727 | genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL); |
2469 | } | 2728 | } |
2470 | 2729 | ||
2730 | static int nl80211_send_scan_donemsg(struct sk_buff *msg, | ||
2731 | struct cfg80211_registered_device *rdev, | ||
2732 | struct net_device *netdev, | ||
2733 | u32 pid, u32 seq, int flags, | ||
2734 | u32 cmd) | ||
2735 | { | ||
2736 | void *hdr; | ||
2737 | |||
2738 | hdr = nl80211hdr_put(msg, pid, seq, flags, cmd); | ||
2739 | if (!hdr) | ||
2740 | return -1; | ||
2741 | |||
2742 | NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->idx); | ||
2743 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex); | ||
2744 | |||
2745 | /* XXX: we should probably bounce back the request? */ | ||
2746 | |||
2747 | return genlmsg_end(msg, hdr); | ||
2748 | |||
2749 | nla_put_failure: | ||
2750 | genlmsg_cancel(msg, hdr); | ||
2751 | return -EMSGSIZE; | ||
2752 | } | ||
2753 | |||
2754 | void nl80211_send_scan_done(struct cfg80211_registered_device *rdev, | ||
2755 | struct net_device *netdev) | ||
2756 | { | ||
2757 | struct sk_buff *msg; | ||
2758 | |||
2759 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
2760 | if (!msg) | ||
2761 | return; | ||
2762 | |||
2763 | if (nl80211_send_scan_donemsg(msg, rdev, netdev, 0, 0, 0, | ||
2764 | NL80211_CMD_NEW_SCAN_RESULTS) < 0) { | ||
2765 | nlmsg_free(msg); | ||
2766 | return; | ||
2767 | } | ||
2768 | |||
2769 | genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL); | ||
2770 | } | ||
2771 | |||
2772 | void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev, | ||
2773 | struct net_device *netdev) | ||
2774 | { | ||
2775 | struct sk_buff *msg; | ||
2776 | |||
2777 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
2778 | if (!msg) | ||
2779 | return; | ||
2780 | |||
2781 | if (nl80211_send_scan_donemsg(msg, rdev, netdev, 0, 0, 0, | ||
2782 | NL80211_CMD_SCAN_ABORTED) < 0) { | ||
2783 | nlmsg_free(msg); | ||
2784 | return; | ||
2785 | } | ||
2786 | |||
2787 | genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL); | ||
2788 | } | ||
2789 | |||
2471 | /* initialisation/exit functions */ | 2790 | /* initialisation/exit functions */ |
2472 | 2791 | ||
2473 | int nl80211_init(void) | 2792 | int nl80211_init(void) |
@@ -2488,6 +2807,10 @@ int nl80211_init(void) | |||
2488 | if (err) | 2807 | if (err) |
2489 | goto err_out; | 2808 | goto err_out; |
2490 | 2809 | ||
2810 | err = genl_register_mc_group(&nl80211_fam, &nl80211_scan_mcgrp); | ||
2811 | if (err) | ||
2812 | goto err_out; | ||
2813 | |||
2491 | return 0; | 2814 | return 0; |
2492 | err_out: | 2815 | err_out: |
2493 | genl_unregister_family(&nl80211_fam); | 2816 | genl_unregister_family(&nl80211_fam); |