mac80211/cfg80211: add station events
[safe/jmp/linux-2.6] / net / wireless / nl80211.c
index 5c8b3bf..e3bee3c 100644 (file)
@@ -139,6 +139,10 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
        [NL80211_ATTR_WPA_VERSIONS] = { .type = NLA_U32 },
        [NL80211_ATTR_PID] = { .type = NLA_U32 },
        [NL80211_ATTR_4ADDR] = { .type = NLA_U8 },
+       [NL80211_ATTR_PMKID] = { .type = NLA_BINARY,
+                                .len = WLAN_PMKID_LEN },
+       [NL80211_ATTR_DURATION] = { .type = NLA_U32 },
+       [NL80211_ATTR_COOKIE] = { .type = NLA_U64 },
 };
 
 /* policy for the attributes */
@@ -450,6 +454,9 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
                sizeof(u32) * dev->wiphy.n_cipher_suites,
                dev->wiphy.cipher_suites);
 
+       NLA_PUT_U8(msg, NL80211_ATTR_MAX_NUM_PMKIDS,
+                  dev->wiphy.max_num_pmkids);
+
        nl_modes = nla_nest_start(msg, NL80211_ATTR_SUPPORTED_IFTYPES);
        if (!nl_modes)
                goto nla_put_failure;
@@ -561,7 +568,11 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
        CMD(deauth, DEAUTHENTICATE);
        CMD(disassoc, DISASSOCIATE);
        CMD(join_ibss, JOIN_IBSS);
-       if (dev->wiphy.netnsok) {
+       CMD(set_pmksa, SET_PMKSA);
+       CMD(del_pmksa, DEL_PMKSA);
+       CMD(flush_pmksa, FLUSH_PMKSA);
+       CMD(remain_on_channel, REMAIN_ON_CHANNEL);
+       if (dev->wiphy.flags & WIPHY_FLAG_NETNS_OK) {
                i++;
                NLA_PUT_U32(msg, i, NL80211_CMD_SET_WIPHY_NETNS);
        }
@@ -968,6 +979,32 @@ static int parse_monitor_flags(struct nlattr *nla, u32 *mntrflags)
        return 0;
 }
 
+static int nl80211_valid_4addr(struct cfg80211_registered_device *rdev,
+                              struct net_device *netdev, u8 use_4addr,
+                              enum nl80211_iftype iftype)
+{
+       if (!use_4addr) {
+               if (netdev && netdev->br_port)
+                       return -EBUSY;
+               return 0;
+       }
+
+       switch (iftype) {
+       case NL80211_IFTYPE_AP_VLAN:
+               if (rdev->wiphy.flags & WIPHY_FLAG_4ADDR_AP)
+                       return 0;
+               break;
+       case NL80211_IFTYPE_STATION:
+               if (rdev->wiphy.flags & WIPHY_FLAG_4ADDR_STATION)
+                       return 0;
+               break;
+       default:
+               break;
+       }
+
+       return -EOPNOTSUPP;
+}
+
 static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev;
@@ -1011,6 +1048,9 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
        if (info->attrs[NL80211_ATTR_4ADDR]) {
                params.use_4addr = !!nla_get_u8(info->attrs[NL80211_ATTR_4ADDR]);
                change = true;
+               err = nl80211_valid_4addr(rdev, dev, params.use_4addr, ntype);
+               if (err)
+                       goto unlock;
        } else {
                params.use_4addr = -1;
        }
@@ -1034,6 +1074,9 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
        else
                err = 0;
 
+       if (!err && params.use_4addr != -1)
+               dev->ieee80211_ptr->use_4addr = params.use_4addr;
+
  unlock:
        dev_put(dev);
        cfg80211_unlock_rdev(rdev);
@@ -1081,8 +1124,12 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
                params.mesh_id_len = nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
        }
 
-       if (info->attrs[NL80211_ATTR_4ADDR])
+       if (info->attrs[NL80211_ATTR_4ADDR]) {
                params.use_4addr = !!nla_get_u8(info->attrs[NL80211_ATTR_4ADDR]);
+               err = nl80211_valid_4addr(rdev, NULL, params.use_4addr, type);
+               if (err)
+                       goto unlock;
+       }
 
        err = parse_monitor_flags(type == NL80211_IFTYPE_MONITOR ?
                                  info->attrs[NL80211_ATTR_MNTR_FLAGS] : NULL,
@@ -1593,42 +1640,9 @@ static int parse_station_flags(struct genl_info *info,
        return 0;
 }
 
-static u16 nl80211_calculate_bitrate(struct rate_info *rate)
-{
-       int modulation, streams, bitrate;
-
-       if (!(rate->flags & RATE_INFO_FLAGS_MCS))
-               return rate->legacy;
-
-       /* the formula below does only work for MCS values smaller than 32 */
-       if (rate->mcs >= 32)
-               return 0;
-
-       modulation = rate->mcs & 7;
-       streams = (rate->mcs >> 3) + 1;
-
-       bitrate = (rate->flags & RATE_INFO_FLAGS_40_MHZ_WIDTH) ?
-                       13500000 : 6500000;
-
-       if (modulation < 4)
-               bitrate *= (modulation + 1);
-       else if (modulation == 4)
-               bitrate *= (modulation + 2);
-       else
-               bitrate *= (modulation + 3);
-
-       bitrate *= streams;
-
-       if (rate->flags & RATE_INFO_FLAGS_SHORT_GI)
-               bitrate = (bitrate / 9) * 10;
-
-       /* do NOT round down here */
-       return (bitrate + 50000) / 100000;
-}
-
 static int nl80211_send_station(struct sk_buff *msg, u32 pid, u32 seq,
                                int flags, struct net_device *dev,
-                               u8 *mac_addr, struct station_info *sinfo)
+                               const u8 *mac_addr, struct station_info *sinfo)
 {
        void *hdr;
        struct nlattr *sinfoattr, *txrate;
@@ -1672,8 +1686,8 @@ static int nl80211_send_station(struct sk_buff *msg, u32 pid, u32 seq,
                if (!txrate)
                        goto nla_put_failure;
 
-               /* nl80211_calculate_bitrate will return 0 for mcs >= 32 */
-               bitrate = nl80211_calculate_bitrate(&sinfo->txrate);
+               /* cfg80211_calculate_bitrate will return 0 for mcs >= 32 */
+               bitrate = cfg80211_calculate_bitrate(&sinfo->txrate);
                if (bitrate > 0)
                        NLA_PUT_U16(msg, NL80211_RATE_INFO_BITRATE, bitrate);
 
@@ -1821,7 +1835,7 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
 }
 
 /*
- * Get vlan interface making sure it is on the right wiphy.
+ * Get vlan interface making sure it is running and on the right wiphy.
  */
 static int get_vlan(struct genl_info *info,
                    struct cfg80211_registered_device *rdev,
@@ -1839,6 +1853,8 @@ static int get_vlan(struct genl_info *info,
                        return -EINVAL;
                if ((*vlan)->ieee80211_ptr->wiphy != &rdev->wiphy)
                        return -EINVAL;
+               if (!netif_running(*vlan))
+                       return -ENETDOWN;
        }
        return 0;
 }
@@ -2537,12 +2553,6 @@ static int nl80211_req_set_reg(struct sk_buff *skb, struct genl_info *info)
 
        data = nla_data(info->attrs[NL80211_ATTR_REG_ALPHA2]);
 
-#ifdef CONFIG_WIRELESS_OLD_REGULATORY
-       /* We ignore world regdom requests with the old regdom setup */
-       if (is_world_regdom(data))
-               return -EINVAL;
-#endif
-
        r = regulatory_hint_user(data);
 
        return r;
@@ -4183,6 +4193,236 @@ static int nl80211_wiphy_netns(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
+static int nl80211_setdel_pmksa(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev;
+       int (*rdev_ops)(struct wiphy *wiphy, struct net_device *dev,
+                       struct cfg80211_pmksa *pmksa) = NULL;
+       int err;
+       struct net_device *dev;
+       struct cfg80211_pmksa pmksa;
+
+       memset(&pmksa, 0, sizeof(struct cfg80211_pmksa));
+
+       if (!info->attrs[NL80211_ATTR_MAC])
+               return -EINVAL;
+
+       if (!info->attrs[NL80211_ATTR_PMKID])
+               return -EINVAL;
+
+       rtnl_lock();
+
+       err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev);
+       if (err)
+               goto out_rtnl;
+
+       pmksa.pmkid = nla_data(info->attrs[NL80211_ATTR_PMKID]);
+       pmksa.bssid = nla_data(info->attrs[NL80211_ATTR_MAC]);
+
+       if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_STATION) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       switch (info->genlhdr->cmd) {
+       case NL80211_CMD_SET_PMKSA:
+               rdev_ops = rdev->ops->set_pmksa;
+               break;
+       case NL80211_CMD_DEL_PMKSA:
+               rdev_ops = rdev->ops->del_pmksa;
+               break;
+       default:
+               WARN_ON(1);
+               break;
+       }
+
+       if (!rdev_ops) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       err = rdev_ops(&rdev->wiphy, dev, &pmksa);
+
+ out:
+       cfg80211_unlock_rdev(rdev);
+       dev_put(dev);
+ out_rtnl:
+       rtnl_unlock();
+
+       return err;
+}
+
+static int nl80211_flush_pmksa(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev;
+       int err;
+       struct net_device *dev;
+
+       rtnl_lock();
+
+       err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev);
+       if (err)
+               goto out_rtnl;
+
+       if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_STATION) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (!rdev->ops->flush_pmksa) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       err = rdev->ops->flush_pmksa(&rdev->wiphy, dev);
+
+ out:
+       cfg80211_unlock_rdev(rdev);
+       dev_put(dev);
+ out_rtnl:
+       rtnl_unlock();
+
+       return err;
+
+}
+
+static int nl80211_remain_on_channel(struct sk_buff *skb,
+                                    struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev;
+       struct net_device *dev;
+       struct ieee80211_channel *chan;
+       struct sk_buff *msg;
+       void *hdr;
+       u64 cookie;
+       enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
+       u32 freq, duration;
+       int err;
+
+       if (!info->attrs[NL80211_ATTR_WIPHY_FREQ] ||
+           !info->attrs[NL80211_ATTR_DURATION])
+               return -EINVAL;
+
+       duration = nla_get_u32(info->attrs[NL80211_ATTR_DURATION]);
+
+       /*
+        * We should be on that channel for at least one jiffie,
+        * and more than 5 seconds seems excessive.
+        */
+       if (!duration || !msecs_to_jiffies(duration) || duration > 5000)
+               return -EINVAL;
+
+       rtnl_lock();
+
+       err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev);
+       if (err)
+               goto unlock_rtnl;
+
+       if (!rdev->ops->remain_on_channel) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (!netif_running(dev)) {
+               err = -ENETDOWN;
+               goto out;
+       }
+
+       if (info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
+               channel_type = nla_get_u32(
+                       info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]);
+               if (channel_type != NL80211_CHAN_NO_HT &&
+                   channel_type != NL80211_CHAN_HT20 &&
+                   channel_type != NL80211_CHAN_HT40PLUS &&
+                   channel_type != NL80211_CHAN_HT40MINUS)
+                       err = -EINVAL;
+                       goto out;
+       }
+
+       freq = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]);
+       chan = rdev_freq_to_chan(rdev, freq, channel_type);
+       if (chan == NULL) {
+               err = -EINVAL;
+               goto out;
+       }
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg) {
+               err = -ENOMEM;
+               goto out;
+       }
+
+       hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0,
+                            NL80211_CMD_REMAIN_ON_CHANNEL);
+
+       if (IS_ERR(hdr)) {
+               err = PTR_ERR(hdr);
+               goto free_msg;
+       }
+
+       err = rdev->ops->remain_on_channel(&rdev->wiphy, dev, chan,
+                                          channel_type, duration, &cookie);
+
+       if (err)
+               goto free_msg;
+
+       NLA_PUT_U64(msg, NL80211_ATTR_COOKIE, cookie);
+
+       genlmsg_end(msg, hdr);
+       err = genlmsg_reply(msg, info);
+       goto out;
+
+ nla_put_failure:
+       err = -ENOBUFS;
+ free_msg:
+       nlmsg_free(msg);
+ out:
+       cfg80211_unlock_rdev(rdev);
+       dev_put(dev);
+ unlock_rtnl:
+       rtnl_unlock();
+       return err;
+}
+
+static int nl80211_cancel_remain_on_channel(struct sk_buff *skb,
+                                           struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev;
+       struct net_device *dev;
+       u64 cookie;
+       int err;
+
+       if (!info->attrs[NL80211_ATTR_COOKIE])
+               return -EINVAL;
+
+       rtnl_lock();
+
+       err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev);
+       if (err)
+               goto unlock_rtnl;
+
+       if (!rdev->ops->cancel_remain_on_channel) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (!netif_running(dev)) {
+               err = -ENETDOWN;
+               goto out;
+       }
+
+       cookie = nla_get_u64(info->attrs[NL80211_ATTR_COOKIE]);
+
+       err = rdev->ops->cancel_remain_on_channel(&rdev->wiphy, dev, cookie);
+
+ out:
+       cfg80211_unlock_rdev(rdev);
+       dev_put(dev);
+ unlock_rtnl:
+       rtnl_unlock();
+       return err;
+}
+
 static struct genl_ops nl80211_ops[] = {
        {
                .cmd = NL80211_CMD_GET_WIPHY,
@@ -4427,7 +4667,38 @@ static struct genl_ops nl80211_ops[] = {
                .policy = nl80211_policy,
                .dumpit = nl80211_dump_survey,
        },
+       {
+               .cmd = NL80211_CMD_SET_PMKSA,
+               .doit = nl80211_setdel_pmksa,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_DEL_PMKSA,
+               .doit = nl80211_setdel_pmksa,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_FLUSH_PMKSA,
+               .doit = nl80211_flush_pmksa,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_REMAIN_ON_CHANNEL,
+               .doit = nl80211_remain_on_channel,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL,
+               .doit = nl80211_cancel_remain_on_channel,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
 };
+
 static struct genl_multicast_group nl80211_mlme_mcgrp = {
        .name = "mlme",
 };
@@ -5015,6 +5286,89 @@ nla_put_failure:
        nlmsg_free(msg);
 }
 
+static void nl80211_send_remain_on_chan_event(
+       int cmd, struct cfg80211_registered_device *rdev,
+       struct net_device *netdev, u64 cookie,
+       struct ieee80211_channel *chan,
+       enum nl80211_channel_type channel_type,
+       unsigned int duration, gfp_t gfp)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+       if (!msg)
+               return;
+
+       hdr = nl80211hdr_put(msg, 0, 0, 0, cmd);
+       if (!hdr) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx);
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, chan->center_freq);
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE, channel_type);
+       NLA_PUT_U64(msg, NL80211_ATTR_COOKIE, cookie);
+
+       if (cmd == NL80211_CMD_REMAIN_ON_CHANNEL)
+               NLA_PUT_U32(msg, NL80211_ATTR_DURATION, duration);
+
+       if (genlmsg_end(msg, hdr) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0,
+                               nl80211_mlme_mcgrp.id, gfp);
+       return;
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       nlmsg_free(msg);
+}
+
+void nl80211_send_remain_on_channel(struct cfg80211_registered_device *rdev,
+                                   struct net_device *netdev, u64 cookie,
+                                   struct ieee80211_channel *chan,
+                                   enum nl80211_channel_type channel_type,
+                                   unsigned int duration, gfp_t gfp)
+{
+       nl80211_send_remain_on_chan_event(NL80211_CMD_REMAIN_ON_CHANNEL,
+                                         rdev, netdev, cookie, chan,
+                                         channel_type, duration, gfp);
+}
+
+void nl80211_send_remain_on_channel_cancel(
+       struct cfg80211_registered_device *rdev, struct net_device *netdev,
+       u64 cookie, struct ieee80211_channel *chan,
+       enum nl80211_channel_type channel_type, gfp_t gfp)
+{
+       nl80211_send_remain_on_chan_event(NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL,
+                                         rdev, netdev, cookie, chan,
+                                         channel_type, 0, gfp);
+}
+
+void nl80211_send_sta_event(struct cfg80211_registered_device *rdev,
+                           struct net_device *dev, const u8 *mac_addr,
+                           struct station_info *sinfo, gfp_t gfp)
+{
+       struct sk_buff *msg;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, gfp);
+       if (!msg)
+               return;
+
+       if (nl80211_send_station(msg, 0, 0, 0, dev, mac_addr, sinfo) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0,
+                               nl80211_mlme_mcgrp.id, gfp);
+}
+
 /* initialisation/exit functions */
 
 int nl80211_init(void)