]> err.no Git - linux-2.6/blobdiff - net/wireless/nl80211.c
Merge branch 'master' of master.kernel.org:/pub/scm/linux/kernel/git/torvalds/linux-2.6
[linux-2.6] / net / wireless / nl80211.c
index f68a5c8f214733b386f715ecd1bd20745fd5dfaf..b7fefffd2d0d02e26e307ab6fa78c7120170763c 100644 (file)
@@ -81,7 +81,12 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
        [NL80211_ATTR_STA_LISTEN_INTERVAL] = { .type = NLA_U16 },
        [NL80211_ATTR_STA_SUPPORTED_RATES] = { .type = NLA_BINARY,
                                               .len = NL80211_MAX_SUPP_RATES },
+       [NL80211_ATTR_STA_PLINK_ACTION] = { .type = NLA_U8 },
        [NL80211_ATTR_STA_VLAN] = { .type = NLA_U32 },
+       [NL80211_ATTR_MNTR_FLAGS] = { .type = NLA_NESTED },
+       [NL80211_ATTR_MESH_ID] = { .type = NLA_BINARY,
+                               .len = IEEE80211_MAX_MESH_ID_LEN },
+       [NL80211_ATTR_MPATH_NEXT_HOP] = { .type = NLA_U32 },
 };
 
 /* message building helper */
@@ -98,6 +103,13 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
                              struct cfg80211_registered_device *dev)
 {
        void *hdr;
+       struct nlattr *nl_bands, *nl_band;
+       struct nlattr *nl_freqs, *nl_freq;
+       struct nlattr *nl_rates, *nl_rate;
+       enum ieee80211_band band;
+       struct ieee80211_channel *chan;
+       struct ieee80211_rate *rate;
+       int i;
 
        hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_WIPHY);
        if (!hdr)
@@ -105,10 +117,78 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
 
        NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->idx);
        NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy));
+
+       nl_bands = nla_nest_start(msg, NL80211_ATTR_WIPHY_BANDS);
+       if (!nl_bands)
+               goto nla_put_failure;
+
+       for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+               if (!dev->wiphy.bands[band])
+                       continue;
+
+               nl_band = nla_nest_start(msg, band);
+               if (!nl_band)
+                       goto nla_put_failure;
+
+               /* add frequencies */
+               nl_freqs = nla_nest_start(msg, NL80211_BAND_ATTR_FREQS);
+               if (!nl_freqs)
+                       goto nla_put_failure;
+
+               for (i = 0; i < dev->wiphy.bands[band]->n_channels; i++) {
+                       nl_freq = nla_nest_start(msg, i);
+                       if (!nl_freq)
+                               goto nla_put_failure;
+
+                       chan = &dev->wiphy.bands[band]->channels[i];
+                       NLA_PUT_U32(msg, NL80211_FREQUENCY_ATTR_FREQ,
+                                   chan->center_freq);
+
+                       if (chan->flags & IEEE80211_CHAN_DISABLED)
+                               NLA_PUT_FLAG(msg, NL80211_FREQUENCY_ATTR_DISABLED);
+                       if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN)
+                               NLA_PUT_FLAG(msg, NL80211_FREQUENCY_ATTR_PASSIVE_SCAN);
+                       if (chan->flags & IEEE80211_CHAN_NO_IBSS)
+                               NLA_PUT_FLAG(msg, NL80211_FREQUENCY_ATTR_NO_IBSS);
+                       if (chan->flags & IEEE80211_CHAN_RADAR)
+                               NLA_PUT_FLAG(msg, NL80211_FREQUENCY_ATTR_RADAR);
+
+                       nla_nest_end(msg, nl_freq);
+               }
+
+               nla_nest_end(msg, nl_freqs);
+
+               /* add bitrates */
+               nl_rates = nla_nest_start(msg, NL80211_BAND_ATTR_RATES);
+               if (!nl_rates)
+                       goto nla_put_failure;
+
+               for (i = 0; i < dev->wiphy.bands[band]->n_bitrates; i++) {
+                       nl_rate = nla_nest_start(msg, i);
+                       if (!nl_rate)
+                               goto nla_put_failure;
+
+                       rate = &dev->wiphy.bands[band]->bitrates[i];
+                       NLA_PUT_U32(msg, NL80211_BITRATE_ATTR_RATE,
+                                   rate->bitrate);
+                       if (rate->flags & IEEE80211_RATE_SHORT_PREAMBLE)
+                               NLA_PUT_FLAG(msg,
+                                       NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE);
+
+                       nla_nest_end(msg, nl_rate);
+               }
+
+               nla_nest_end(msg, nl_rates);
+
+               nla_nest_end(msg, nl_band);
+       }
+       nla_nest_end(msg, nl_bands);
+
        return genlmsg_end(msg, hdr);
 
  nla_put_failure:
-       return genlmsg_cancel(msg, hdr);
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
 }
 
 static int nl80211_dump_wiphy(struct sk_buff *skb, struct netlink_callback *cb)
@@ -119,12 +199,14 @@ static int nl80211_dump_wiphy(struct sk_buff *skb, struct netlink_callback *cb)
 
        mutex_lock(&cfg80211_drv_mutex);
        list_for_each_entry(dev, &cfg80211_drv_list, list) {
-               if (++idx < start)
+               if (++idx <= start)
                        continue;
                if (nl80211_send_wiphy(skb, NETLINK_CB(cb->skb).pid,
                                       cb->nlh->nlmsg_seq, NLM_F_MULTI,
-                                      dev) < 0)
+                                      dev) < 0) {
+                       idx--;
                        break;
+               }
        }
        mutex_unlock(&cfg80211_drv_mutex);
 
@@ -194,7 +276,8 @@ static int nl80211_send_iface(struct sk_buff *msg, u32 pid, u32 seq, int flags,
        return genlmsg_end(msg, hdr);
 
  nla_put_failure:
-       return genlmsg_cancel(msg, hdr);
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
 }
 
 static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *cb)
@@ -262,12 +345,45 @@ static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info)
        return -ENOBUFS;
 }
 
+static const struct nla_policy mntr_flags_policy[NL80211_MNTR_FLAG_MAX + 1] = {
+       [NL80211_MNTR_FLAG_FCSFAIL] = { .type = NLA_FLAG },
+       [NL80211_MNTR_FLAG_PLCPFAIL] = { .type = NLA_FLAG },
+       [NL80211_MNTR_FLAG_CONTROL] = { .type = NLA_FLAG },
+       [NL80211_MNTR_FLAG_OTHER_BSS] = { .type = NLA_FLAG },
+       [NL80211_MNTR_FLAG_COOK_FRAMES] = { .type = NLA_FLAG },
+};
+
+static int parse_monitor_flags(struct nlattr *nla, u32 *mntrflags)
+{
+       struct nlattr *flags[NL80211_MNTR_FLAG_MAX + 1];
+       int flag;
+
+       *mntrflags = 0;
+
+       if (!nla)
+               return -EINVAL;
+
+       if (nla_parse_nested(flags, NL80211_MNTR_FLAG_MAX,
+                            nla, mntr_flags_policy))
+               return -EINVAL;
+
+       for (flag = 1; flag <= NL80211_MNTR_FLAG_MAX; flag++)
+               if (flags[flag])
+                       *mntrflags |= (1<<flag);
+
+       return 0;
+}
+
 static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *drv;
+       struct vif_params params;
        int err, ifindex;
        enum nl80211_iftype type;
        struct net_device *dev;
+       u32 flags;
+
+       memset(&params, 0, sizeof(params));
 
        if (info->attrs[NL80211_ATTR_IFTYPE]) {
                type = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]);
@@ -287,8 +403,18 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
                goto unlock;
        }
 
+       if (type == NL80211_IFTYPE_MESH_POINT &&
+           info->attrs[NL80211_ATTR_MESH_ID]) {
+               params.mesh_id = nla_data(info->attrs[NL80211_ATTR_MESH_ID]);
+               params.mesh_id_len = nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
+       }
+
        rtnl_lock();
-       err = drv->ops->change_virtual_intf(&drv->wiphy, ifindex, type);
+       err = parse_monitor_flags(type == NL80211_IFTYPE_MONITOR ?
+                                 info->attrs[NL80211_ATTR_MNTR_FLAGS] : NULL,
+                                 &flags);
+       err = drv->ops->change_virtual_intf(&drv->wiphy, ifindex,
+                                           type, err ? NULL : &flags, &params);
        rtnl_unlock();
 
  unlock:
@@ -299,8 +425,12 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
 static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *drv;
+       struct vif_params params;
        int err;
        enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED;
+       u32 flags;
+
+       memset(&params, 0, sizeof(params));
 
        if (!info->attrs[NL80211_ATTR_IFNAME])
                return -EINVAL;
@@ -320,11 +450,22 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
                goto unlock;
        }
 
+       if (type == NL80211_IFTYPE_MESH_POINT &&
+           info->attrs[NL80211_ATTR_MESH_ID]) {
+               params.mesh_id = nla_data(info->attrs[NL80211_ATTR_MESH_ID]);
+               params.mesh_id_len = nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
+       }
+
        rtnl_lock();
+       err = parse_monitor_flags(type == NL80211_IFTYPE_MONITOR ?
+                                 info->attrs[NL80211_ATTR_MNTR_FLAGS] : NULL,
+                                 &flags);
        err = drv->ops->add_virtual_intf(&drv->wiphy,
-               nla_data(info->attrs[NL80211_ATTR_IFNAME]), type);
+               nla_data(info->attrs[NL80211_ATTR_IFNAME]),
+               type, err ? NULL : &flags, &params);
        rtnl_unlock();
 
+
  unlock:
        cfg80211_put_dev(drv);
        return err;
@@ -752,10 +893,10 @@ static int parse_station_flags(struct nlattr *nla, u32 *staflags)
 
 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)
+                               u8 *mac_addr, struct station_info *sinfo)
 {
        void *hdr;
-       struct nlattr *statsattr;
+       struct nlattr *sinfoattr;
 
        hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_STATION);
        if (!hdr)
@@ -764,38 +905,111 @@ static int nl80211_send_station(struct sk_buff *msg, u32 pid, u32 seq,
        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)
+       sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO);
+       if (!sinfoattr)
                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);
+       if (sinfo->filled & STATION_INFO_INACTIVE_TIME)
+               NLA_PUT_U32(msg, NL80211_STA_INFO_INACTIVE_TIME,
+                           sinfo->inactive_time);
+       if (sinfo->filled & STATION_INFO_RX_BYTES)
+               NLA_PUT_U32(msg, NL80211_STA_INFO_RX_BYTES,
+                           sinfo->rx_bytes);
+       if (sinfo->filled & STATION_INFO_TX_BYTES)
+               NLA_PUT_U32(msg, NL80211_STA_INFO_TX_BYTES,
+                           sinfo->tx_bytes);
+       if (sinfo->filled & STATION_INFO_LLID)
+               NLA_PUT_U16(msg, NL80211_STA_INFO_LLID,
+                           sinfo->llid);
+       if (sinfo->filled & STATION_INFO_PLID)
+               NLA_PUT_U16(msg, NL80211_STA_INFO_PLID,
+                           sinfo->plid);
+       if (sinfo->filled & STATION_INFO_PLINK_STATE)
+               NLA_PUT_U8(msg, NL80211_STA_INFO_PLINK_STATE,
+                           sinfo->plink_state);
+
+       nla_nest_end(msg, sinfoattr);
 
        return genlmsg_end(msg, hdr);
 
  nla_put_failure:
-       return genlmsg_cancel(msg, hdr);
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
 }
 
+static int nl80211_dump_station(struct sk_buff *skb,
+               struct netlink_callback *cb)
+{
+       int wp_idx = 0;
+       int if_idx = 0;
+       int sta_idx = cb->args[2];
+       int wp_start = cb->args[0];
+       int if_start = cb->args[1];
+       struct station_info sinfo;
+       struct cfg80211_registered_device *dev;
+       struct wireless_dev *wdev;
+       u8 mac_addr[ETH_ALEN];
+       int err;
+       int exit = 0;
+
+       /* TODO: filter by device */
+       mutex_lock(&cfg80211_drv_mutex);
+       list_for_each_entry(dev, &cfg80211_drv_list, list) {
+               if (exit)
+                       break;
+               if (++wp_idx < wp_start)
+                       continue;
+               if_idx = 0;
+
+               mutex_lock(&dev->devlist_mtx);
+               list_for_each_entry(wdev, &dev->netdev_list, list) {
+                       if (exit)
+                               break;
+                       if (++if_idx < if_start)
+                               continue;
+                       if (!dev->ops->dump_station)
+                               continue;
+
+                       for (;; ++sta_idx) {
+                               rtnl_lock();
+                               err = dev->ops->dump_station(&dev->wiphy,
+                                               wdev->netdev, sta_idx, mac_addr,
+                                               &sinfo);
+                               rtnl_unlock();
+                               if (err) {
+                                       sta_idx = 0;
+                                       break;
+                               }
+                               if (nl80211_send_station(skb,
+                                               NETLINK_CB(cb->skb).pid,
+                                               cb->nlh->nlmsg_seq, NLM_F_MULTI,
+                                               wdev->netdev, mac_addr,
+                                               &sinfo) < 0) {
+                                       exit = 1;
+                                       break;
+                               }
+                       }
+               }
+               mutex_unlock(&dev->devlist_mtx);
+       }
+       mutex_unlock(&cfg80211_drv_mutex);
+
+       cb->args[0] = wp_idx;
+       cb->args[1] = if_idx;
+       cb->args[2] = sta_idx;
+
+       return skb->len;
+}
 
 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 station_info sinfo;
        struct sk_buff *msg;
        u8 *mac_addr = NULL;
 
-       memset(&stats, 0, sizeof(stats));
+       memset(&sinfo, 0, sizeof(sinfo));
 
        if (!info->attrs[NL80211_ATTR_MAC])
                return -EINVAL;
@@ -812,15 +1026,18 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
        }
 
        rtnl_lock();
-       err = drv->ops->get_station(&drv->wiphy, dev, mac_addr, &stats);
+       err = drv->ops->get_station(&drv->wiphy, dev, mac_addr, &sinfo);
        rtnl_unlock();
 
+       if (err)
+               goto out;
+
        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)
+                                dev, mac_addr, &sinfo) < 0)
                goto out_free;
 
        err = genlmsg_unicast(msg, info->snd_pid);
@@ -891,6 +1108,10 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
                                &params.station_flags))
                return -EINVAL;
 
+       if (info->attrs[NL80211_ATTR_STA_PLINK_ACTION])
+               params.plink_action =
+                   nla_get_u8(info->attrs[NL80211_ATTR_STA_PLINK_ACTION]);
+
        err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
        if (err)
                return err;
@@ -1005,6 +1226,274 @@ static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
+static int nl80211_send_mpath(struct sk_buff *msg, u32 pid, u32 seq,
+                               int flags, struct net_device *dev,
+                               u8 *dst, u8 *next_hop,
+                               struct mpath_info *pinfo)
+{
+       void *hdr;
+       struct nlattr *pinfoattr;
+
+       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, dst);
+       NLA_PUT(msg, NL80211_ATTR_MPATH_NEXT_HOP, ETH_ALEN, next_hop);
+
+       pinfoattr = nla_nest_start(msg, NL80211_ATTR_MPATH_INFO);
+       if (!pinfoattr)
+               goto nla_put_failure;
+       if (pinfo->filled & MPATH_INFO_FRAME_QLEN)
+               NLA_PUT_U32(msg, NL80211_MPATH_INFO_FRAME_QLEN,
+                           pinfo->frame_qlen);
+       if (pinfo->filled & MPATH_INFO_DSN)
+               NLA_PUT_U32(msg, NL80211_MPATH_INFO_DSN,
+                           pinfo->dsn);
+       if (pinfo->filled & MPATH_INFO_METRIC)
+               NLA_PUT_U32(msg, NL80211_MPATH_INFO_METRIC,
+                           pinfo->metric);
+       if (pinfo->filled & MPATH_INFO_EXPTIME)
+               NLA_PUT_U32(msg, NL80211_MPATH_INFO_EXPTIME,
+                           pinfo->exptime);
+       if (pinfo->filled & MPATH_INFO_FLAGS)
+               NLA_PUT_U8(msg, NL80211_MPATH_INFO_FLAGS,
+                           pinfo->flags);
+       if (pinfo->filled & MPATH_INFO_DISCOVERY_TIMEOUT)
+               NLA_PUT_U32(msg, NL80211_MPATH_INFO_DISCOVERY_TIMEOUT,
+                           pinfo->discovery_timeout);
+       if (pinfo->filled & MPATH_INFO_DISCOVERY_RETRIES)
+               NLA_PUT_U8(msg, NL80211_MPATH_INFO_DISCOVERY_RETRIES,
+                           pinfo->discovery_retries);
+
+       nla_nest_end(msg, pinfoattr);
+
+       return genlmsg_end(msg, hdr);
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
+static int nl80211_dump_mpath(struct sk_buff *skb,
+               struct netlink_callback *cb)
+{
+       int wp_idx = 0;
+       int if_idx = 0;
+       int sta_idx = cb->args[2];
+       int wp_start = cb->args[0];
+       int if_start = cb->args[1];
+       struct mpath_info pinfo;
+       struct cfg80211_registered_device *dev;
+       struct wireless_dev *wdev;
+       u8 dst[ETH_ALEN];
+       u8 next_hop[ETH_ALEN];
+       int err;
+       int exit = 0;
+
+       /* TODO: filter by device */
+       mutex_lock(&cfg80211_drv_mutex);
+       list_for_each_entry(dev, &cfg80211_drv_list, list) {
+               if (exit)
+                       break;
+               if (++wp_idx < wp_start)
+                       continue;
+               if_idx = 0;
+
+               mutex_lock(&dev->devlist_mtx);
+               list_for_each_entry(wdev, &dev->netdev_list, list) {
+                       if (exit)
+                               break;
+                       if (++if_idx < if_start)
+                               continue;
+                       if (!dev->ops->dump_mpath)
+                               continue;
+
+                       for (;; ++sta_idx) {
+                               rtnl_lock();
+                               err = dev->ops->dump_mpath(&dev->wiphy,
+                                               wdev->netdev, sta_idx, dst,
+                                               next_hop, &pinfo);
+                               rtnl_unlock();
+                               if (err) {
+                                       sta_idx = 0;
+                                       break;
+                               }
+                               if (nl80211_send_mpath(skb,
+                                               NETLINK_CB(cb->skb).pid,
+                                               cb->nlh->nlmsg_seq, NLM_F_MULTI,
+                                               wdev->netdev, dst, next_hop,
+                                               &pinfo) < 0) {
+                                       exit = 1;
+                                       break;
+                               }
+                       }
+               }
+               mutex_unlock(&dev->devlist_mtx);
+       }
+       mutex_unlock(&cfg80211_drv_mutex);
+
+       cb->args[0] = wp_idx;
+       cb->args[1] = if_idx;
+       cb->args[2] = sta_idx;
+
+       return skb->len;
+}
+
+static int nl80211_get_mpath(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       int err;
+       struct net_device *dev;
+       struct mpath_info pinfo;
+       struct sk_buff *msg;
+       u8 *dst = NULL;
+       u8 next_hop[ETH_ALEN];
+
+       memset(&pinfo, 0, sizeof(pinfo));
+
+       if (!info->attrs[NL80211_ATTR_MAC])
+               return -EINVAL;
+
+       dst = 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_mpath) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       rtnl_lock();
+       err = drv->ops->get_mpath(&drv->wiphy, dev, dst, next_hop, &pinfo);
+       rtnl_unlock();
+
+       if (err)
+               goto out;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               goto out;
+
+       if (nl80211_send_mpath(msg, info->snd_pid, info->snd_seq, 0,
+                                dev, dst, next_hop, &pinfo) < 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;
+}
+
+static int nl80211_set_mpath(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       int err;
+       struct net_device *dev;
+       u8 *dst = NULL;
+       u8 *next_hop = NULL;
+
+       if (!info->attrs[NL80211_ATTR_MAC])
+               return -EINVAL;
+
+       if (!info->attrs[NL80211_ATTR_MPATH_NEXT_HOP])
+               return -EINVAL;
+
+       dst = nla_data(info->attrs[NL80211_ATTR_MAC]);
+       next_hop = nla_data(info->attrs[NL80211_ATTR_MPATH_NEXT_HOP]);
+
+       err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+       if (err)
+               return err;
+
+       if (!drv->ops->change_mpath) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       rtnl_lock();
+       err = drv->ops->change_mpath(&drv->wiphy, dev, dst, next_hop);
+       rtnl_unlock();
+
+ out:
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+       return err;
+}
+static int nl80211_new_mpath(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       int err;
+       struct net_device *dev;
+       u8 *dst = NULL;
+       u8 *next_hop = NULL;
+
+       if (!info->attrs[NL80211_ATTR_MAC])
+               return -EINVAL;
+
+       if (!info->attrs[NL80211_ATTR_MPATH_NEXT_HOP])
+               return -EINVAL;
+
+       dst = nla_data(info->attrs[NL80211_ATTR_MAC]);
+       next_hop = nla_data(info->attrs[NL80211_ATTR_MPATH_NEXT_HOP]);
+
+       err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+       if (err)
+               return err;
+
+       if (!drv->ops->add_mpath) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       rtnl_lock();
+       err = drv->ops->add_mpath(&drv->wiphy, dev, dst, next_hop);
+       rtnl_unlock();
+
+ out:
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+       return err;
+}
+
+static int nl80211_del_mpath(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       int err;
+       struct net_device *dev;
+       u8 *dst = NULL;
+
+       if (info->attrs[NL80211_ATTR_MAC])
+               dst = 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_mpath) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       rtnl_lock();
+       err = drv->ops->del_mpath(&drv->wiphy, dev, dst);
+       rtnl_unlock();
+
+ out:
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+       return err;
+}
+
 static struct genl_ops nl80211_ops[] = {
        {
                .cmd = NL80211_CMD_GET_WIPHY,
@@ -1089,7 +1578,7 @@ static struct genl_ops nl80211_ops[] = {
        {
                .cmd = NL80211_CMD_GET_STATION,
                .doit = nl80211_get_station,
-               /* TODO: implement dumpit */
+               .dumpit = nl80211_dump_station,
                .policy = nl80211_policy,
                .flags = GENL_ADMIN_PERM,
        },
@@ -1111,6 +1600,31 @@ static struct genl_ops nl80211_ops[] = {
                .policy = nl80211_policy,
                .flags = GENL_ADMIN_PERM,
        },
+       {
+               .cmd = NL80211_CMD_GET_MPATH,
+               .doit = nl80211_get_mpath,
+               .dumpit = nl80211_dump_mpath,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_SET_MPATH,
+               .doit = nl80211_set_mpath,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_NEW_MPATH,
+               .doit = nl80211_new_mpath,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_DEL_MPATH,
+               .doit = nl80211_del_mpath,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
 };
 
 /* multicast groups */