Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wirel...
[linux-2.6.git] / net / wireless / nl80211.c
index cea3381..6a82c89 100644 (file)
@@ -176,6 +176,7 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
        [NL80211_ATTR_WOWLAN_TRIGGERS] = { .type = NLA_NESTED },
        [NL80211_ATTR_STA_PLINK_STATE] = { .type = NLA_U8 },
        [NL80211_ATTR_SCHED_SCAN_INTERVAL] = { .type = NLA_U32 },
+       [NL80211_ATTR_REKEY_DATA] = { .type = NLA_NESTED },
 };
 
 /* policy for the key attributes */
@@ -206,6 +207,14 @@ nl80211_wowlan_policy[NUM_NL80211_WOWLAN_TRIG] = {
        [NL80211_WOWLAN_TRIG_PKT_PATTERN] = { .type = NLA_NESTED },
 };
 
+/* policy for GTK rekey offload attributes */
+static const struct nla_policy
+nl80211_rekey_policy[NUM_NL80211_REKEY_DATA] = {
+       [NL80211_REKEY_DATA_KEK] = { .len = NL80211_KEK_LEN },
+       [NL80211_REKEY_DATA_KCK] = { .len = NL80211_KCK_LEN },
+       [NL80211_REKEY_DATA_REPLAY_CTR] = { .len = NL80211_REPLAY_CTR_LEN },
+};
+
 /* ifidx get helper */
 static int nl80211_get_ifidx(struct netlink_callback *cb)
 {
@@ -3632,7 +3641,8 @@ static int nl80211_stop_sched_scan(struct sk_buff *skb,
        return err;
 }
 
-static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags,
+static int nl80211_send_bss(struct sk_buff *msg, struct netlink_callback *cb,
+                           u32 seq, int flags,
                            struct cfg80211_registered_device *rdev,
                            struct wireless_dev *wdev,
                            struct cfg80211_internal_bss *intbss)
@@ -3644,11 +3654,13 @@ static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags,
 
        ASSERT_WDEV_LOCK(wdev);
 
-       hdr = nl80211hdr_put(msg, pid, seq, flags,
+       hdr = nl80211hdr_put(msg, NETLINK_CB(cb->skb).pid, seq, flags,
                             NL80211_CMD_NEW_SCAN_RESULTS);
        if (!hdr)
                return -1;
 
+       genl_dump_check_consistent(cb, hdr, &nl80211_fam);
+
        NLA_PUT_U32(msg, NL80211_ATTR_GENERATION, rdev->bss_generation);
        NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex);
 
@@ -3737,11 +3749,12 @@ static int nl80211_dump_scan(struct sk_buff *skb,
        spin_lock_bh(&rdev->bss_lock);
        cfg80211_bss_expire(rdev);
 
+       cb->seq = rdev->bss_generation;
+
        list_for_each_entry(scan, &rdev->bss_list, list) {
                if (++idx <= start)
                        continue;
-               if (nl80211_send_bss(skb,
-                               NETLINK_CB(cb->skb).pid,
+               if (nl80211_send_bss(skb, cb,
                                cb->nlh->nlmsg_seq, NLM_F_MULTI,
                                rdev, wdev, scan) < 0) {
                        idx--;
@@ -3765,10 +3778,6 @@ static int nl80211_send_survey(struct sk_buff *msg, u32 pid, u32 seq,
        void *hdr;
        struct nlattr *infoattr;
 
-       /* Survey without a channel doesn't make sense */
-       if (!survey->channel)
-               return -EINVAL;
-
        hdr = nl80211hdr_put(msg, pid, seq, flags,
                             NL80211_CMD_NEW_SURVEY_RESULTS);
        if (!hdr)
@@ -3831,6 +3840,8 @@ static int nl80211_dump_survey(struct sk_buff *skb,
        }
 
        while (1) {
+               struct ieee80211_channel *chan;
+
                res = dev->ops->dump_survey(&dev->wiphy, netdev, survey_idx,
                                            &survey);
                if (res == -ENOENT)
@@ -3838,6 +3849,19 @@ static int nl80211_dump_survey(struct sk_buff *skb,
                if (res)
                        goto out_err;
 
+               /* Survey without a channel doesn't make sense */
+               if (!survey.channel) {
+                       res = -EINVAL;
+                       goto out;
+               }
+
+               chan = ieee80211_get_channel(&dev->wiphy,
+                                            survey.channel->center_freq);
+               if (!chan || chan->flags & IEEE80211_CHAN_DISABLED) {
+                       survey_idx++;
+                       continue;
+               }
+
                if (nl80211_send_survey(skb,
                                NETLINK_CB(cb->skb).pid,
                                cb->nlh->nlmsg_seq, NLM_F_MULTI,
@@ -4372,6 +4396,93 @@ static int nl80211_testmode_do(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
+static int nl80211_testmode_dump(struct sk_buff *skb,
+                                struct netlink_callback *cb)
+{
+       struct cfg80211_registered_device *dev;
+       int err;
+       long phy_idx;
+       void *data = NULL;
+       int data_len = 0;
+
+       if (cb->args[0]) {
+               /*
+                * 0 is a valid index, but not valid for args[0],
+                * so we need to offset by 1.
+                */
+               phy_idx = cb->args[0] - 1;
+       } else {
+               err = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl80211_fam.hdrsize,
+                                 nl80211_fam.attrbuf, nl80211_fam.maxattr,
+                                 nl80211_policy);
+               if (err)
+                       return err;
+               if (!nl80211_fam.attrbuf[NL80211_ATTR_WIPHY])
+                       return -EINVAL;
+               phy_idx = nla_get_u32(nl80211_fam.attrbuf[NL80211_ATTR_WIPHY]);
+               if (nl80211_fam.attrbuf[NL80211_ATTR_TESTDATA])
+                       cb->args[1] =
+                               (long)nl80211_fam.attrbuf[NL80211_ATTR_TESTDATA];
+       }
+
+       if (cb->args[1]) {
+               data = nla_data((void *)cb->args[1]);
+               data_len = nla_len((void *)cb->args[1]);
+       }
+
+       mutex_lock(&cfg80211_mutex);
+       dev = cfg80211_rdev_by_wiphy_idx(phy_idx);
+       if (!dev) {
+               mutex_unlock(&cfg80211_mutex);
+               return -ENOENT;
+       }
+       cfg80211_lock_rdev(dev);
+       mutex_unlock(&cfg80211_mutex);
+
+       if (!dev->ops->testmode_dump) {
+               err = -EOPNOTSUPP;
+               goto out_err;
+       }
+
+       while (1) {
+               void *hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).pid,
+                                          cb->nlh->nlmsg_seq, NLM_F_MULTI,
+                                          NL80211_CMD_TESTMODE);
+               struct nlattr *tmdata;
+
+               if (nla_put_u32(skb, NL80211_ATTR_WIPHY, dev->wiphy_idx) < 0) {
+                       genlmsg_cancel(skb, hdr);
+                       break;
+               }
+
+               tmdata = nla_nest_start(skb, NL80211_ATTR_TESTDATA);
+               if (!tmdata) {
+                       genlmsg_cancel(skb, hdr);
+                       break;
+               }
+               err = dev->ops->testmode_dump(&dev->wiphy, skb, cb,
+                                             data, data_len);
+               nla_nest_end(skb, tmdata);
+
+               if (err == -ENOBUFS || err == -ENOENT) {
+                       genlmsg_cancel(skb, hdr);
+                       break;
+               } else if (err) {
+                       genlmsg_cancel(skb, hdr);
+                       goto out_err;
+               }
+
+               genlmsg_end(skb, hdr);
+       }
+
+       err = skb->len;
+       /* see above */
+       cb->args[0] = phy_idx + 1;
+ out_err:
+       cfg80211_unlock_rdev(dev);
+       return err;
+}
+
 static struct sk_buff *
 __cfg80211_testmode_alloc_skb(struct cfg80211_registered_device *rdev,
                              int approxlen, u32 pid, u32 seq, gfp_t gfp)
@@ -5318,6 +5429,57 @@ static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
+static int nl80211_set_rekey_data(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct nlattr *tb[NUM_NL80211_REKEY_DATA];
+       struct cfg80211_gtk_rekey_data rekey_data;
+       int err;
+
+       if (!info->attrs[NL80211_ATTR_REKEY_DATA])
+               return -EINVAL;
+
+       err = nla_parse(tb, MAX_NL80211_REKEY_DATA,
+                       nla_data(info->attrs[NL80211_ATTR_REKEY_DATA]),
+                       nla_len(info->attrs[NL80211_ATTR_REKEY_DATA]),
+                       nl80211_rekey_policy);
+       if (err)
+               return err;
+
+       if (nla_len(tb[NL80211_REKEY_DATA_REPLAY_CTR]) != NL80211_REPLAY_CTR_LEN)
+               return -ERANGE;
+       if (nla_len(tb[NL80211_REKEY_DATA_KEK]) != NL80211_KEK_LEN)
+               return -ERANGE;
+       if (nla_len(tb[NL80211_REKEY_DATA_KCK]) != NL80211_KCK_LEN)
+               return -ERANGE;
+
+       memcpy(rekey_data.kek, nla_data(tb[NL80211_REKEY_DATA_KEK]),
+              NL80211_KEK_LEN);
+       memcpy(rekey_data.kck, nla_data(tb[NL80211_REKEY_DATA_KCK]),
+              NL80211_KCK_LEN);
+       memcpy(rekey_data.replay_ctr,
+              nla_data(tb[NL80211_REKEY_DATA_REPLAY_CTR]),
+              NL80211_REPLAY_CTR_LEN);
+
+       wdev_lock(wdev);
+       if (!wdev->current_bss) {
+               err = -ENOTCONN;
+               goto out;
+       }
+
+       if (!rdev->ops->set_rekey_data) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       err = rdev->ops->set_rekey_data(&rdev->wiphy, dev, &rekey_data);
+ out:
+       wdev_unlock(wdev);
+       return err;
+}
+
 #define NL80211_FLAG_NEED_WIPHY                0x01
 #define NL80211_FLAG_NEED_NETDEV       0x02
 #define NL80211_FLAG_NEED_RTNL         0x04
@@ -5669,6 +5831,7 @@ static struct genl_ops nl80211_ops[] = {
        {
                .cmd = NL80211_CMD_TESTMODE,
                .doit = nl80211_testmode_do,
+               .dumpit = nl80211_testmode_dump,
                .policy = nl80211_policy,
                .flags = GENL_ADMIN_PERM,
                .internal_flags = NL80211_FLAG_NEED_WIPHY |
@@ -5848,6 +6011,14 @@ static struct genl_ops nl80211_ops[] = {
                .internal_flags = NL80211_FLAG_NEED_WIPHY |
                                  NL80211_FLAG_NEED_RTNL,
        },
+       {
+               .cmd = NL80211_CMD_SET_REKEY_OFFLOAD,
+               .doit = nl80211_set_rekey_data,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+                                 NL80211_FLAG_NEED_RTNL,
+       },
 };
 
 static struct genl_multicast_group nl80211_mlme_mcgrp = {
@@ -6792,6 +6963,51 @@ nl80211_send_cqm_rssi_notify(struct cfg80211_registered_device *rdev,
        nlmsg_free(msg);
 }
 
+void nl80211_gtk_rekey_notify(struct cfg80211_registered_device *rdev,
+                             struct net_device *netdev, const u8 *bssid,
+                             const u8 *replay_ctr, gfp_t gfp)
+{
+       struct sk_buff *msg;
+       struct nlattr *rekey_attr;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, gfp);
+       if (!msg)
+               return;
+
+       hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_SET_REKEY_OFFLOAD);
+       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(msg, NL80211_ATTR_MAC, ETH_ALEN, bssid);
+
+       rekey_attr = nla_nest_start(msg, NL80211_ATTR_REKEY_DATA);
+       if (!rekey_attr)
+               goto nla_put_failure;
+
+       NLA_PUT(msg, NL80211_REKEY_DATA_REPLAY_CTR,
+               NL80211_REPLAY_CTR_LEN, replay_ctr);
+
+       nla_nest_end(msg, rekey_attr);
+
+       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_cqm_pktloss_notify(struct cfg80211_registered_device *rdev,
                                struct net_device *netdev, const u8 *peer,