]> err.no Git - linux-2.6/blobdiff - net/wireless/nl80211.c
Merge git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-2.6
[linux-2.6] / net / wireless / nl80211.c
index 306ae019ea816e8b622e287dcf786fff1bd882e5..e3a214f63f9178e6689c2749ba7b275795718652 100644 (file)
@@ -76,6 +76,12 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
                                       .len = IEEE80211_MAX_DATA_LEN },
        [NL80211_ATTR_BEACON_TAIL] = { .type = NLA_BINARY,
                                       .len = IEEE80211_MAX_DATA_LEN },
+       [NL80211_ATTR_STA_AID] = { .type = NLA_U16 },
+       [NL80211_ATTR_STA_FLAGS] = { .type = NLA_NESTED },
+       [NL80211_ATTR_STA_LISTEN_INTERVAL] = { .type = NLA_U16 },
+       [NL80211_ATTR_STA_SUPPORTED_RATES] = { .type = NLA_BINARY,
+                                              .len = NL80211_MAX_SUPP_RATES },
+       [NL80211_ATTR_STA_VLAN] = { .type = NLA_U32 },
 };
 
 /* message building helper */
@@ -715,6 +721,290 @@ static int nl80211_del_beacon(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
+static const struct nla_policy sta_flags_policy[NL80211_STA_FLAG_MAX + 1] = {
+       [NL80211_STA_FLAG_AUTHORIZED] = { .type = NLA_FLAG },
+       [NL80211_STA_FLAG_SHORT_PREAMBLE] = { .type = NLA_FLAG },
+       [NL80211_STA_FLAG_WME] = { .type = NLA_FLAG },
+};
+
+static int parse_station_flags(struct nlattr *nla, u32 *staflags)
+{
+       struct nlattr *flags[NL80211_STA_FLAG_MAX + 1];
+       int flag;
+
+       *staflags = 0;
+
+       if (!nla)
+               return 0;
+
+       if (nla_parse_nested(flags, NL80211_STA_FLAG_MAX,
+                            nla, sta_flags_policy))
+               return -EINVAL;
+
+       *staflags = STATION_FLAG_CHANGED;
+
+       for (flag = 1; flag <= NL80211_STA_FLAG_MAX; flag++)
+               if (flags[flag])
+                       *staflags |= (1<<flag);
+
+       return 0;
+}
+
+static int nl80211_send_station(struct sk_buff *msg, u32 pid, u32 seq,
+                               int flags, struct net_device *dev,
+                               u8 *mac_addr, struct station_stats *stats)
+{
+       void *hdr;
+       struct nlattr *statsattr;
+
+       hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_STATION);
+       if (!hdr)
+               return -1;
+
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
+       NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr);
+
+       statsattr = nla_nest_start(msg, NL80211_ATTR_STA_STATS);
+       if (!statsattr)
+               goto nla_put_failure;
+       if (stats->filled & STATION_STAT_INACTIVE_TIME)
+               NLA_PUT_U32(msg, NL80211_STA_STAT_INACTIVE_TIME,
+                           stats->inactive_time);
+       if (stats->filled & STATION_STAT_RX_BYTES)
+               NLA_PUT_U32(msg, NL80211_STA_STAT_RX_BYTES,
+                           stats->rx_bytes);
+       if (stats->filled & STATION_STAT_TX_BYTES)
+               NLA_PUT_U32(msg, NL80211_STA_STAT_TX_BYTES,
+                           stats->tx_bytes);
+
+       nla_nest_end(msg, statsattr);
+
+       return genlmsg_end(msg, hdr);
+
+ nla_put_failure:
+       return genlmsg_cancel(msg, hdr);
+}
+
+
+static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       int err;
+       struct net_device *dev;
+       struct station_stats stats;
+       struct sk_buff *msg;
+       u8 *mac_addr = NULL;
+
+       memset(&stats, 0, sizeof(stats));
+
+       if (!info->attrs[NL80211_ATTR_MAC])
+               return -EINVAL;
+
+       mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
+
+       err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+       if (err)
+               return err;
+
+       if (!drv->ops->get_station) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       rtnl_lock();
+       err = drv->ops->get_station(&drv->wiphy, dev, mac_addr, &stats);
+       rtnl_unlock();
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               goto out;
+
+       if (nl80211_send_station(msg, info->snd_pid, info->snd_seq, 0,
+                                dev, mac_addr, &stats) < 0)
+               goto out_free;
+
+       err = genlmsg_unicast(msg, info->snd_pid);
+       goto out;
+
+ out_free:
+       nlmsg_free(msg);
+
+ out:
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+       return err;
+}
+
+/*
+ * Get vlan interface making sure it is on the right wiphy.
+ */
+static int get_vlan(struct nlattr *vlanattr,
+                   struct cfg80211_registered_device *rdev,
+                   struct net_device **vlan)
+{
+       *vlan = NULL;
+
+       if (vlanattr) {
+               *vlan = dev_get_by_index(&init_net, nla_get_u32(vlanattr));
+               if (!*vlan)
+                       return -ENODEV;
+               if (!(*vlan)->ieee80211_ptr)
+                       return -EINVAL;
+               if ((*vlan)->ieee80211_ptr->wiphy != &rdev->wiphy)
+                       return -EINVAL;
+       }
+       return 0;
+}
+
+static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       int err;
+       struct net_device *dev;
+       struct station_parameters params;
+       u8 *mac_addr = NULL;
+
+       memset(&params, 0, sizeof(params));
+
+       params.listen_interval = -1;
+
+       if (info->attrs[NL80211_ATTR_STA_AID])
+               return -EINVAL;
+
+       if (!info->attrs[NL80211_ATTR_MAC])
+               return -EINVAL;
+
+       mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
+
+       if (info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]) {
+               params.supported_rates =
+                       nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
+               params.supported_rates_len =
+                       nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
+       }
+
+       if (info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
+               params.listen_interval =
+                   nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
+
+       if (parse_station_flags(info->attrs[NL80211_ATTR_STA_FLAGS],
+                               &params.station_flags))
+               return -EINVAL;
+
+       err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+       if (err)
+               return err;
+
+       err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, &params.vlan);
+       if (err)
+               goto out;
+
+       if (!drv->ops->change_station) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       rtnl_lock();
+       err = drv->ops->change_station(&drv->wiphy, dev, mac_addr, &params);
+       rtnl_unlock();
+
+ out:
+       if (params.vlan)
+               dev_put(params.vlan);
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+       return err;
+}
+
+static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       int err;
+       struct net_device *dev;
+       struct station_parameters params;
+       u8 *mac_addr = NULL;
+
+       memset(&params, 0, sizeof(params));
+
+       if (!info->attrs[NL80211_ATTR_MAC])
+               return -EINVAL;
+
+       if (!info->attrs[NL80211_ATTR_STA_AID])
+               return -EINVAL;
+
+       if (!info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
+               return -EINVAL;
+
+       if (!info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES])
+               return -EINVAL;
+
+       mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
+       params.supported_rates =
+               nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
+       params.supported_rates_len =
+               nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
+       params.listen_interval =
+               nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
+       params.listen_interval = nla_get_u16(info->attrs[NL80211_ATTR_STA_AID]);
+
+       if (parse_station_flags(info->attrs[NL80211_ATTR_STA_FLAGS],
+                               &params.station_flags))
+               return -EINVAL;
+
+       err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+       if (err)
+               return err;
+
+       err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, &params.vlan);
+       if (err)
+               goto out;
+
+       if (!drv->ops->add_station) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       rtnl_lock();
+       err = drv->ops->add_station(&drv->wiphy, dev, mac_addr, &params);
+       rtnl_unlock();
+
+ out:
+       if (params.vlan)
+               dev_put(params.vlan);
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+       return err;
+}
+
+static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       int err;
+       struct net_device *dev;
+       u8 *mac_addr = NULL;
+
+       if (info->attrs[NL80211_ATTR_MAC])
+               mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
+
+       err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+       if (err)
+               return err;
+
+       if (!drv->ops->del_station) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       rtnl_lock();
+       err = drv->ops->del_station(&drv->wiphy, dev, mac_addr);
+       rtnl_unlock();
+
+ out:
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+       return err;
+}
+
 static struct genl_ops nl80211_ops[] = {
        {
                .cmd = NL80211_CMD_GET_WIPHY,
@@ -796,6 +1086,31 @@ static struct genl_ops nl80211_ops[] = {
                .flags = GENL_ADMIN_PERM,
                .doit = nl80211_del_beacon,
        },
+       {
+               .cmd = NL80211_CMD_GET_STATION,
+               .doit = nl80211_get_station,
+               /* TODO: implement dumpit */
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_SET_STATION,
+               .doit = nl80211_set_station,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_NEW_STATION,
+               .doit = nl80211_new_station,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_DEL_STATION,
+               .doit = nl80211_del_station,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
 };
 
 /* multicast groups */