nl80211/cfg80211: support for mesh, sta dumping

Added support for mesh id and mesh path operation as well as
station structure dumping.

Signed-off-by: Luis Carlos Cobo <luisca@cozybit.com>
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 5b347479..64a7460 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -81,8 +81,12 @@
 	[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 */
@@ -369,11 +373,14 @@
 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]);
 		if (type > NL80211_IFTYPE_MAX)
@@ -392,12 +399,18 @@
 		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->change_virtual_intf(&drv->wiphy, ifindex,
-					    type, err ? NULL : &flags);
+					    type, err ? NULL : &flags, &params);
 	rtnl_unlock();
 
  unlock:
@@ -408,10 +421,13 @@
 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;
 
@@ -430,15 +446,22 @@
 		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, err ? NULL : &flags);
+		type, err ? NULL : &flags, &params);
 	rtnl_unlock();
 
+
  unlock:
 	cfg80211_put_dev(drv);
 	return err;
@@ -866,10 +889,10 @@
 
 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)
@@ -878,20 +901,29 @@
 	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);
+	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, statsattr);
+	nla_nest_end(msg, sinfoattr);
 
 	return genlmsg_end(msg, hdr);
 
@@ -899,17 +931,80 @@
 	return genlmsg_cancel(msg, hdr);
 }
 
+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;
@@ -926,15 +1021,18 @@
 	}
 
 	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);
@@ -1005,6 +1103,10 @@
 				&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;
@@ -1119,6 +1221,273 @@
 	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:
+	return genlmsg_cancel(msg, hdr);
+}
+
+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,
@@ -1203,7 +1572,7 @@
 	{
 		.cmd = NL80211_CMD_GET_STATION,
 		.doit = nl80211_get_station,
-		/* TODO: implement dumpit */
+		.dumpit = nl80211_dump_station,
 		.policy = nl80211_policy,
 		.flags = GENL_ADMIN_PERM,
 	},
@@ -1225,6 +1594,31 @@
 		.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 */