aboutsummaryrefslogtreecommitdiffstats
path: root/net/wireless
diff options
context:
space:
mode:
authorJouni Malinen <jouni.malinen@atheros.com>2010-02-15 05:53:10 -0500
committerJohn W. Linville <linville@tuxdriver.com>2010-02-15 16:14:15 -0500
commit026331c4d9b526561ea96f95fac4bfc52b69e316 (patch)
treea82b0a92a7f03a1d151a9db123320689c73d98c7 /net/wireless
parent8404080568613d93ad7cf0a16dfb68459b42a264 (diff)
cfg80211/mac80211: allow registering for and sending action frames
This implements a new command to register for action frames that userspace wants to handle instead of the in-kernel rejection. It is then responsible for rejecting ones that it decided not to handle. There is no unregistration, but the socket can be closed for that. Frames that are not registered for will not be forwarded to userspace and will be rejected by the kernel, the cfg80211 API helps implementing that. Additionally, this patch adds a new command that allows doing action frame transmission from userspace. It can be used either to exchange action frames on the current operational channel (e.g., with the AP with which we are currently associated) or to exchange off-channel Public Action frames with the remain-on-channel command. Signed-off-by: Jouni Malinen <jouni.malinen@atheros.com> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net/wireless')
-rw-r--r--net/wireless/core.c4
-rw-r--r--net/wireless/core.h9
-rw-r--r--net/wireless/mlme.c166
-rw-r--r--net/wireless/nl80211.c260
-rw-r--r--net/wireless/nl80211.h8
5 files changed, 446 insertions, 1 deletions
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 71b6b3a9cf1f..51908dc2ea00 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -677,6 +677,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
677 INIT_WORK(&wdev->cleanup_work, wdev_cleanup_work); 677 INIT_WORK(&wdev->cleanup_work, wdev_cleanup_work);
678 INIT_LIST_HEAD(&wdev->event_list); 678 INIT_LIST_HEAD(&wdev->event_list);
679 spin_lock_init(&wdev->event_lock); 679 spin_lock_init(&wdev->event_lock);
680 INIT_LIST_HEAD(&wdev->action_registrations);
681 spin_lock_init(&wdev->action_registrations_lock);
682
680 mutex_lock(&rdev->devlist_mtx); 683 mutex_lock(&rdev->devlist_mtx);
681 list_add_rcu(&wdev->list, &rdev->netdev_list); 684 list_add_rcu(&wdev->list, &rdev->netdev_list);
682 rdev->devlist_generation++; 685 rdev->devlist_generation++;
@@ -792,6 +795,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
792 sysfs_remove_link(&dev->dev.kobj, "phy80211"); 795 sysfs_remove_link(&dev->dev.kobj, "phy80211");
793 list_del_rcu(&wdev->list); 796 list_del_rcu(&wdev->list);
794 rdev->devlist_generation++; 797 rdev->devlist_generation++;
798 cfg80211_mlme_purge_actions(wdev);
795#ifdef CONFIG_CFG80211_WEXT 799#ifdef CONFIG_CFG80211_WEXT
796 kfree(wdev->wext.keys); 800 kfree(wdev->wext.keys);
797#endif 801#endif
diff --git a/net/wireless/core.h b/net/wireless/core.h
index c326a667022a..d52da913145a 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -329,6 +329,15 @@ void __cfg80211_connect_result(struct net_device *dev, const u8 *bssid,
329 const u8 *resp_ie, size_t resp_ie_len, 329 const u8 *resp_ie, size_t resp_ie_len,
330 u16 status, bool wextev, 330 u16 status, bool wextev,
331 struct cfg80211_bss *bss); 331 struct cfg80211_bss *bss);
332int cfg80211_mlme_register_action(struct wireless_dev *wdev, u32 snd_pid,
333 const u8 *match_data, int match_len);
334void cfg80211_mlme_unregister_actions(struct wireless_dev *wdev, u32 nlpid);
335void cfg80211_mlme_purge_actions(struct wireless_dev *wdev);
336int cfg80211_mlme_action(struct cfg80211_registered_device *rdev,
337 struct net_device *dev,
338 struct ieee80211_channel *chan,
339 enum nl80211_channel_type channel_type,
340 const u8 *buf, size_t len, u64 *cookie);
332 341
333/* SME */ 342/* SME */
334int __cfg80211_connect(struct cfg80211_registered_device *rdev, 343int __cfg80211_connect(struct cfg80211_registered_device *rdev,
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index 94d151f6f73e..62bc8855e123 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -728,3 +728,169 @@ void cfg80211_new_sta(struct net_device *dev, const u8 *mac_addr,
728 nl80211_send_sta_event(rdev, dev, mac_addr, sinfo, gfp); 728 nl80211_send_sta_event(rdev, dev, mac_addr, sinfo, gfp);
729} 729}
730EXPORT_SYMBOL(cfg80211_new_sta); 730EXPORT_SYMBOL(cfg80211_new_sta);
731
732struct cfg80211_action_registration {
733 struct list_head list;
734
735 u32 nlpid;
736
737 int match_len;
738
739 u8 match[];
740};
741
742int cfg80211_mlme_register_action(struct wireless_dev *wdev, u32 snd_pid,
743 const u8 *match_data, int match_len)
744{
745 struct cfg80211_action_registration *reg, *nreg;
746 int err = 0;
747
748 nreg = kzalloc(sizeof(*reg) + match_len, GFP_KERNEL);
749 if (!nreg)
750 return -ENOMEM;
751
752 spin_lock_bh(&wdev->action_registrations_lock);
753
754 list_for_each_entry(reg, &wdev->action_registrations, list) {
755 int mlen = min(match_len, reg->match_len);
756
757 if (memcmp(reg->match, match_data, mlen) == 0) {
758 err = -EALREADY;
759 break;
760 }
761 }
762
763 if (err) {
764 kfree(nreg);
765 goto out;
766 }
767
768 memcpy(nreg->match, match_data, match_len);
769 nreg->match_len = match_len;
770 nreg->nlpid = snd_pid;
771 list_add(&nreg->list, &wdev->action_registrations);
772
773 out:
774 spin_unlock_bh(&wdev->action_registrations_lock);
775 return err;
776}
777
778void cfg80211_mlme_unregister_actions(struct wireless_dev *wdev, u32 nlpid)
779{
780 struct cfg80211_action_registration *reg, *tmp;
781
782 spin_lock_bh(&wdev->action_registrations_lock);
783
784 list_for_each_entry_safe(reg, tmp, &wdev->action_registrations, list) {
785 if (reg->nlpid == nlpid) {
786 list_del(&reg->list);
787 kfree(reg);
788 }
789 }
790
791 spin_unlock_bh(&wdev->action_registrations_lock);
792}
793
794void cfg80211_mlme_purge_actions(struct wireless_dev *wdev)
795{
796 struct cfg80211_action_registration *reg, *tmp;
797
798 spin_lock_bh(&wdev->action_registrations_lock);
799
800 list_for_each_entry_safe(reg, tmp, &wdev->action_registrations, list) {
801 list_del(&reg->list);
802 kfree(reg);
803 }
804
805 spin_unlock_bh(&wdev->action_registrations_lock);
806}
807
808int cfg80211_mlme_action(struct cfg80211_registered_device *rdev,
809 struct net_device *dev,
810 struct ieee80211_channel *chan,
811 enum nl80211_channel_type channel_type,
812 const u8 *buf, size_t len, u64 *cookie)
813{
814 struct wireless_dev *wdev = dev->ieee80211_ptr;
815 const struct ieee80211_mgmt *mgmt;
816
817 if (rdev->ops->action == NULL)
818 return -EOPNOTSUPP;
819 if (len < 24 + 1)
820 return -EINVAL;
821
822 mgmt = (const struct ieee80211_mgmt *) buf;
823 if (!ieee80211_is_action(mgmt->frame_control))
824 return -EINVAL;
825 if (mgmt->u.action.category != WLAN_CATEGORY_PUBLIC) {
826 /* Verify that we are associated with the destination AP */
827 if (!wdev->current_bss ||
828 memcmp(wdev->current_bss->pub.bssid, mgmt->bssid,
829 ETH_ALEN) != 0 ||
830 memcmp(wdev->current_bss->pub.bssid, mgmt->da,
831 ETH_ALEN) != 0)
832 return -ENOTCONN;
833 }
834
835 if (memcmp(mgmt->sa, dev->dev_addr, ETH_ALEN) != 0)
836 return -EINVAL;
837
838 /* Transmit the Action frame as requested by user space */
839 return rdev->ops->action(&rdev->wiphy, dev, chan, channel_type,
840 buf, len, cookie);
841}
842
843bool cfg80211_rx_action(struct net_device *dev, int freq, const u8 *buf,
844 size_t len, gfp_t gfp)
845{
846 struct wireless_dev *wdev = dev->ieee80211_ptr;
847 struct wiphy *wiphy = wdev->wiphy;
848 struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
849 struct cfg80211_action_registration *reg;
850 const u8 *action_data;
851 int action_data_len;
852 bool result = false;
853
854 /* frame length - min size excluding category */
855 action_data_len = len - (IEEE80211_MIN_ACTION_SIZE - 1);
856
857 /* action data starts with category */
858 action_data = buf + IEEE80211_MIN_ACTION_SIZE - 1;
859
860 spin_lock_bh(&wdev->action_registrations_lock);
861
862 list_for_each_entry(reg, &wdev->action_registrations, list) {
863 if (reg->match_len > action_data_len)
864 continue;
865
866 if (memcmp(reg->match, action_data, reg->match_len))
867 continue;
868
869 /* found match! */
870
871 /* Indicate the received Action frame to user space */
872 if (nl80211_send_action(rdev, dev, reg->nlpid, freq,
873 buf, len, gfp))
874 continue;
875
876 result = true;
877 break;
878 }
879
880 spin_unlock_bh(&wdev->action_registrations_lock);
881
882 return result;
883}
884EXPORT_SYMBOL(cfg80211_rx_action);
885
886void cfg80211_action_tx_status(struct net_device *dev, u64 cookie,
887 const u8 *buf, size_t len, bool ack, gfp_t gfp)
888{
889 struct wireless_dev *wdev = dev->ieee80211_ptr;
890 struct wiphy *wiphy = wdev->wiphy;
891 struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
892
893 /* Indicate TX status of the Action frame to user space */
894 nl80211_send_action_tx_status(rdev, dev, cookie, buf, len, ack, gfp);
895}
896EXPORT_SYMBOL(cfg80211_action_tx_status);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index a95ab9e4c19e..328112081358 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1,7 +1,7 @@
1/* 1/*
2 * This is the new netlink-based wireless configuration interface. 2 * This is the new netlink-based wireless configuration interface.
3 * 3 *
4 * Copyright 2006-2009 Johannes Berg <johannes@sipsolutions.net> 4 * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
5 */ 5 */
6 6
7#include <linux/if.h> 7#include <linux/if.h>
@@ -145,6 +145,9 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
145 [NL80211_ATTR_DURATION] = { .type = NLA_U32 }, 145 [NL80211_ATTR_DURATION] = { .type = NLA_U32 },
146 [NL80211_ATTR_COOKIE] = { .type = NLA_U64 }, 146 [NL80211_ATTR_COOKIE] = { .type = NLA_U64 },
147 [NL80211_ATTR_TX_RATES] = { .type = NLA_NESTED }, 147 [NL80211_ATTR_TX_RATES] = { .type = NLA_NESTED },
148 [NL80211_ATTR_FRAME] = { .type = NLA_BINARY,
149 .len = IEEE80211_MAX_DATA_LEN },
150 [NL80211_ATTR_FRAME_MATCH] = { .type = NLA_BINARY, },
148}; 151};
149 152
150/* policy for the attributes */ 153/* policy for the attributes */
@@ -577,6 +580,7 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
577 CMD(flush_pmksa, FLUSH_PMKSA); 580 CMD(flush_pmksa, FLUSH_PMKSA);
578 CMD(remain_on_channel, REMAIN_ON_CHANNEL); 581 CMD(remain_on_channel, REMAIN_ON_CHANNEL);
579 CMD(set_bitrate_mask, SET_TX_BITRATE_MASK); 582 CMD(set_bitrate_mask, SET_TX_BITRATE_MASK);
583 CMD(action, ACTION);
580 if (dev->wiphy.flags & WIPHY_FLAG_NETNS_OK) { 584 if (dev->wiphy.flags & WIPHY_FLAG_NETNS_OK) {
581 i++; 585 i++;
582 NLA_PUT_U32(msg, i, NL80211_CMD_SET_WIPHY_NETNS); 586 NLA_PUT_U32(msg, i, NL80211_CMD_SET_WIPHY_NETNS);
@@ -4526,6 +4530,139 @@ static int nl80211_set_tx_bitrate_mask(struct sk_buff *skb,
4526 return err; 4530 return err;
4527} 4531}
4528 4532
4533static int nl80211_register_action(struct sk_buff *skb, struct genl_info *info)
4534{
4535 struct cfg80211_registered_device *rdev;
4536 struct net_device *dev;
4537 int err;
4538
4539 if (!info->attrs[NL80211_ATTR_FRAME_MATCH])
4540 return -EINVAL;
4541
4542 if (nla_len(info->attrs[NL80211_ATTR_FRAME_MATCH]) < 1)
4543 return -EINVAL;
4544
4545 rtnl_lock();
4546
4547 err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev);
4548 if (err)
4549 goto unlock_rtnl;
4550
4551 if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_STATION) {
4552 err = -EOPNOTSUPP;
4553 goto out;
4554 }
4555
4556 /* not much point in registering if we can't reply */
4557 if (!rdev->ops->action) {
4558 err = -EOPNOTSUPP;
4559 goto out;
4560 }
4561
4562 err = cfg80211_mlme_register_action(dev->ieee80211_ptr, info->snd_pid,
4563 nla_data(info->attrs[NL80211_ATTR_FRAME_MATCH]),
4564 nla_len(info->attrs[NL80211_ATTR_FRAME_MATCH]));
4565 out:
4566 cfg80211_unlock_rdev(rdev);
4567 dev_put(dev);
4568 unlock_rtnl:
4569 rtnl_unlock();
4570 return err;
4571}
4572
4573static int nl80211_action(struct sk_buff *skb, struct genl_info *info)
4574{
4575 struct cfg80211_registered_device *rdev;
4576 struct net_device *dev;
4577 struct ieee80211_channel *chan;
4578 enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
4579 u32 freq;
4580 int err;
4581 void *hdr;
4582 u64 cookie;
4583 struct sk_buff *msg;
4584
4585 if (!info->attrs[NL80211_ATTR_FRAME] ||
4586 !info->attrs[NL80211_ATTR_WIPHY_FREQ])
4587 return -EINVAL;
4588
4589 rtnl_lock();
4590
4591 err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev);
4592 if (err)
4593 goto unlock_rtnl;
4594
4595 if (!rdev->ops->action) {
4596 err = -EOPNOTSUPP;
4597 goto out;
4598 }
4599
4600 if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_STATION) {
4601 err = -EOPNOTSUPP;
4602 goto out;
4603 }
4604
4605 if (!netif_running(dev)) {
4606 err = -ENETDOWN;
4607 goto out;
4608 }
4609
4610 if (info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
4611 channel_type = nla_get_u32(
4612 info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]);
4613 if (channel_type != NL80211_CHAN_NO_HT &&
4614 channel_type != NL80211_CHAN_HT20 &&
4615 channel_type != NL80211_CHAN_HT40PLUS &&
4616 channel_type != NL80211_CHAN_HT40MINUS)
4617 err = -EINVAL;
4618 goto out;
4619 }
4620
4621 freq = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]);
4622 chan = rdev_freq_to_chan(rdev, freq, channel_type);
4623 if (chan == NULL) {
4624 err = -EINVAL;
4625 goto out;
4626 }
4627
4628 msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
4629 if (!msg) {
4630 err = -ENOMEM;
4631 goto out;
4632 }
4633
4634 hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0,
4635 NL80211_CMD_ACTION);
4636
4637 if (IS_ERR(hdr)) {
4638 err = PTR_ERR(hdr);
4639 goto free_msg;
4640 }
4641 err = cfg80211_mlme_action(rdev, dev, chan, channel_type,
4642 nla_data(info->attrs[NL80211_ATTR_FRAME]),
4643 nla_len(info->attrs[NL80211_ATTR_FRAME]),
4644 &cookie);
4645 if (err)
4646 goto free_msg;
4647
4648 NLA_PUT_U64(msg, NL80211_ATTR_COOKIE, cookie);
4649
4650 genlmsg_end(msg, hdr);
4651 err = genlmsg_reply(msg, info);
4652 goto out;
4653
4654 nla_put_failure:
4655 err = -ENOBUFS;
4656 free_msg:
4657 nlmsg_free(msg);
4658 out:
4659 cfg80211_unlock_rdev(rdev);
4660 dev_put(dev);
4661unlock_rtnl:
4662 rtnl_unlock();
4663 return err;
4664}
4665
4529static struct genl_ops nl80211_ops[] = { 4666static struct genl_ops nl80211_ops[] = {
4530 { 4667 {
4531 .cmd = NL80211_CMD_GET_WIPHY, 4668 .cmd = NL80211_CMD_GET_WIPHY,
@@ -4806,6 +4943,18 @@ static struct genl_ops nl80211_ops[] = {
4806 .policy = nl80211_policy, 4943 .policy = nl80211_policy,
4807 .flags = GENL_ADMIN_PERM, 4944 .flags = GENL_ADMIN_PERM,
4808 }, 4945 },
4946 {
4947 .cmd = NL80211_CMD_REGISTER_ACTION,
4948 .doit = nl80211_register_action,
4949 .policy = nl80211_policy,
4950 .flags = GENL_ADMIN_PERM,
4951 },
4952 {
4953 .cmd = NL80211_CMD_ACTION,
4954 .doit = nl80211_action,
4955 .policy = nl80211_policy,
4956 .flags = GENL_ADMIN_PERM,
4957 },
4809}; 4958};
4810 4959
4811static struct genl_multicast_group nl80211_mlme_mcgrp = { 4960static struct genl_multicast_group nl80211_mlme_mcgrp = {
@@ -5478,6 +5627,110 @@ void nl80211_send_sta_event(struct cfg80211_registered_device *rdev,
5478 nl80211_mlme_mcgrp.id, gfp); 5627 nl80211_mlme_mcgrp.id, gfp);
5479} 5628}
5480 5629
5630int nl80211_send_action(struct cfg80211_registered_device *rdev,
5631 struct net_device *netdev, u32 nlpid,
5632 int freq, const u8 *buf, size_t len, gfp_t gfp)
5633{
5634 struct sk_buff *msg;
5635 void *hdr;
5636 int err;
5637
5638 msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
5639 if (!msg)
5640 return -ENOMEM;
5641
5642 hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_ACTION);
5643 if (!hdr) {
5644 nlmsg_free(msg);
5645 return -ENOMEM;
5646 }
5647
5648 NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx);
5649 NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
5650 NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, freq);
5651 NLA_PUT(msg, NL80211_ATTR_FRAME, len, buf);
5652
5653 err = genlmsg_end(msg, hdr);
5654 if (err < 0) {
5655 nlmsg_free(msg);
5656 return err;
5657 }
5658
5659 err = genlmsg_unicast(wiphy_net(&rdev->wiphy), msg, nlpid);
5660 if (err < 0)
5661 return err;
5662 return 0;
5663
5664 nla_put_failure:
5665 genlmsg_cancel(msg, hdr);
5666 nlmsg_free(msg);
5667 return -ENOBUFS;
5668}
5669
5670void nl80211_send_action_tx_status(struct cfg80211_registered_device *rdev,
5671 struct net_device *netdev, u64 cookie,
5672 const u8 *buf, size_t len, bool ack,
5673 gfp_t gfp)
5674{
5675 struct sk_buff *msg;
5676 void *hdr;
5677
5678 msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
5679 if (!msg)
5680 return;
5681
5682 hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_ACTION_TX_STATUS);
5683 if (!hdr) {
5684 nlmsg_free(msg);
5685 return;
5686 }
5687
5688 NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx);
5689 NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
5690 NLA_PUT(msg, NL80211_ATTR_FRAME, len, buf);
5691 NLA_PUT_U64(msg, NL80211_ATTR_COOKIE, cookie);
5692 if (ack)
5693 NLA_PUT_FLAG(msg, NL80211_ATTR_ACK);
5694
5695 if (genlmsg_end(msg, hdr) < 0) {
5696 nlmsg_free(msg);
5697 return;
5698 }
5699
5700 genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, gfp);
5701 return;
5702
5703 nla_put_failure:
5704 genlmsg_cancel(msg, hdr);
5705 nlmsg_free(msg);
5706}
5707
5708static int nl80211_netlink_notify(struct notifier_block * nb,
5709 unsigned long state,
5710 void *_notify)
5711{
5712 struct netlink_notify *notify = _notify;
5713 struct cfg80211_registered_device *rdev;
5714 struct wireless_dev *wdev;
5715
5716 if (state != NETLINK_URELEASE)
5717 return NOTIFY_DONE;
5718
5719 rcu_read_lock();
5720
5721 list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list)
5722 list_for_each_entry_rcu(wdev, &rdev->netdev_list, list)
5723 cfg80211_mlme_unregister_actions(wdev, notify->pid);
5724
5725 rcu_read_unlock();
5726
5727 return NOTIFY_DONE;
5728}
5729
5730static struct notifier_block nl80211_netlink_notifier = {
5731 .notifier_call = nl80211_netlink_notify,
5732};
5733
5481/* initialisation/exit functions */ 5734/* initialisation/exit functions */
5482 5735
5483int nl80211_init(void) 5736int nl80211_init(void)
@@ -5511,6 +5764,10 @@ int nl80211_init(void)
5511 goto err_out; 5764 goto err_out;
5512#endif 5765#endif
5513 5766
5767 err = netlink_register_notifier(&nl80211_netlink_notifier);
5768 if (err)
5769 goto err_out;
5770
5514 return 0; 5771 return 0;
5515 err_out: 5772 err_out:
5516 genl_unregister_family(&nl80211_fam); 5773 genl_unregister_family(&nl80211_fam);
@@ -5519,5 +5776,6 @@ int nl80211_init(void)
5519 5776
5520void nl80211_exit(void) 5777void nl80211_exit(void)
5521{ 5778{
5779 netlink_unregister_notifier(&nl80211_netlink_notifier);
5522 genl_unregister_family(&nl80211_fam); 5780 genl_unregister_family(&nl80211_fam);
5523} 5781}
diff --git a/net/wireless/nl80211.h b/net/wireless/nl80211.h
index 14855b8fb430..4ca511102c6c 100644
--- a/net/wireless/nl80211.h
+++ b/net/wireless/nl80211.h
@@ -74,4 +74,12 @@ void nl80211_send_sta_event(struct cfg80211_registered_device *rdev,
74 struct net_device *dev, const u8 *mac_addr, 74 struct net_device *dev, const u8 *mac_addr,
75 struct station_info *sinfo, gfp_t gfp); 75 struct station_info *sinfo, gfp_t gfp);
76 76
77int nl80211_send_action(struct cfg80211_registered_device *rdev,
78 struct net_device *netdev, u32 nlpid, int freq,
79 const u8 *buf, size_t len, gfp_t gfp);
80void nl80211_send_action_tx_status(struct cfg80211_registered_device *rdev,
81 struct net_device *netdev, u64 cookie,
82 const u8 *buf, size_t len, bool ack,
83 gfp_t gfp);
84
77#endif /* __NET_WIRELESS_NL80211_H */ 85#endif /* __NET_WIRELESS_NL80211_H */