diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2007-09-20 13:09:35 -0400 |
---|---|---|
committer | David S. Miller <davem@sunset.davemloft.net> | 2007-10-10 19:52:14 -0400 |
commit | 556829657397b9b05baec6691ead4e22ee8d1567 (patch) | |
tree | 44242431553e5e22c0bceaab7a06d9d7bf0dd2f6 /net/wireless/nl80211.c | |
parent | 0800f170263d19b882e519441156c5f6ed190fc1 (diff) |
[NL80211]: add netlink interface to cfg80211
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/wireless/nl80211.c')
-rw-r--r-- | net/wireless/nl80211.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c new file mode 100644 index 000000000000..48b0d453e4e1 --- /dev/null +++ b/net/wireless/nl80211.c | |||
@@ -0,0 +1,431 @@ | |||
1 | /* | ||
2 | * This is the new netlink-based wireless configuration interface. | ||
3 | * | ||
4 | * Copyright 2006, 2007 Johannes Berg <johannes@sipsolutions.net> | ||
5 | */ | ||
6 | |||
7 | #include <linux/if.h> | ||
8 | #include <linux/module.h> | ||
9 | #include <linux/err.h> | ||
10 | #include <linux/mutex.h> | ||
11 | #include <linux/list.h> | ||
12 | #include <linux/if_ether.h> | ||
13 | #include <linux/ieee80211.h> | ||
14 | #include <linux/nl80211.h> | ||
15 | #include <linux/rtnetlink.h> | ||
16 | #include <linux/netlink.h> | ||
17 | #include <net/genetlink.h> | ||
18 | #include <net/cfg80211.h> | ||
19 | #include "core.h" | ||
20 | #include "nl80211.h" | ||
21 | |||
22 | /* the netlink family */ | ||
23 | static struct genl_family nl80211_fam = { | ||
24 | .id = GENL_ID_GENERATE, /* don't bother with a hardcoded ID */ | ||
25 | .name = "nl80211", /* have users key off the name instead */ | ||
26 | .hdrsize = 0, /* no private header */ | ||
27 | .version = 1, /* no particular meaning now */ | ||
28 | .maxattr = NL80211_ATTR_MAX, | ||
29 | }; | ||
30 | |||
31 | /* internal helper: get drv and dev */ | ||
32 | static int get_drv_dev_by_info_ifindex(struct genl_info *info, | ||
33 | struct cfg80211_registered_device **drv, | ||
34 | struct net_device **dev) | ||
35 | { | ||
36 | int ifindex; | ||
37 | |||
38 | if (!info->attrs[NL80211_ATTR_IFINDEX]) | ||
39 | return -EINVAL; | ||
40 | |||
41 | ifindex = nla_get_u32(info->attrs[NL80211_ATTR_IFINDEX]); | ||
42 | *dev = dev_get_by_index(&init_net, ifindex); | ||
43 | if (!*dev) | ||
44 | return -ENODEV; | ||
45 | |||
46 | *drv = cfg80211_get_dev_from_ifindex(ifindex); | ||
47 | if (IS_ERR(*drv)) { | ||
48 | dev_put(*dev); | ||
49 | return PTR_ERR(*drv); | ||
50 | } | ||
51 | |||
52 | return 0; | ||
53 | } | ||
54 | |||
55 | /* policy for the attributes */ | ||
56 | static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = { | ||
57 | [NL80211_ATTR_WIPHY] = { .type = NLA_U32 }, | ||
58 | [NL80211_ATTR_WIPHY_NAME] = { .type = NLA_NUL_STRING, | ||
59 | .len = BUS_ID_SIZE-1 }, | ||
60 | |||
61 | [NL80211_ATTR_IFTYPE] = { .type = NLA_U32 }, | ||
62 | [NL80211_ATTR_IFINDEX] = { .type = NLA_U32 }, | ||
63 | [NL80211_ATTR_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ-1 }, | ||
64 | }; | ||
65 | |||
66 | /* message building helper */ | ||
67 | static inline void *nl80211hdr_put(struct sk_buff *skb, u32 pid, u32 seq, | ||
68 | int flags, u8 cmd) | ||
69 | { | ||
70 | /* since there is no private header just add the generic one */ | ||
71 | return genlmsg_put(skb, pid, seq, &nl80211_fam, flags, cmd); | ||
72 | } | ||
73 | |||
74 | /* netlink command implementations */ | ||
75 | |||
76 | static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, | ||
77 | struct cfg80211_registered_device *dev) | ||
78 | { | ||
79 | void *hdr; | ||
80 | |||
81 | hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_WIPHY); | ||
82 | if (!hdr) | ||
83 | return -1; | ||
84 | |||
85 | NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->idx); | ||
86 | NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy)); | ||
87 | return genlmsg_end(msg, hdr); | ||
88 | |||
89 | nla_put_failure: | ||
90 | return genlmsg_cancel(msg, hdr); | ||
91 | } | ||
92 | |||
93 | static int nl80211_dump_wiphy(struct sk_buff *skb, struct netlink_callback *cb) | ||
94 | { | ||
95 | int idx = 0; | ||
96 | int start = cb->args[0]; | ||
97 | struct cfg80211_registered_device *dev; | ||
98 | |||
99 | mutex_lock(&cfg80211_drv_mutex); | ||
100 | list_for_each_entry(dev, &cfg80211_drv_list, list) { | ||
101 | if (++idx < start) | ||
102 | continue; | ||
103 | if (nl80211_send_wiphy(skb, NETLINK_CB(cb->skb).pid, | ||
104 | cb->nlh->nlmsg_seq, NLM_F_MULTI, | ||
105 | dev) < 0) | ||
106 | break; | ||
107 | } | ||
108 | mutex_unlock(&cfg80211_drv_mutex); | ||
109 | |||
110 | cb->args[0] = idx; | ||
111 | |||
112 | return skb->len; | ||
113 | } | ||
114 | |||
115 | static int nl80211_get_wiphy(struct sk_buff *skb, struct genl_info *info) | ||
116 | { | ||
117 | struct sk_buff *msg; | ||
118 | struct cfg80211_registered_device *dev; | ||
119 | |||
120 | dev = cfg80211_get_dev_from_info(info); | ||
121 | if (IS_ERR(dev)) | ||
122 | return PTR_ERR(dev); | ||
123 | |||
124 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
125 | if (!msg) | ||
126 | goto out_err; | ||
127 | |||
128 | if (nl80211_send_wiphy(msg, info->snd_pid, info->snd_seq, 0, dev) < 0) | ||
129 | goto out_free; | ||
130 | |||
131 | cfg80211_put_dev(dev); | ||
132 | |||
133 | return genlmsg_unicast(msg, info->snd_pid); | ||
134 | |||
135 | out_free: | ||
136 | nlmsg_free(msg); | ||
137 | out_err: | ||
138 | cfg80211_put_dev(dev); | ||
139 | return -ENOBUFS; | ||
140 | } | ||
141 | |||
142 | static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) | ||
143 | { | ||
144 | struct cfg80211_registered_device *rdev; | ||
145 | int result; | ||
146 | |||
147 | if (!info->attrs[NL80211_ATTR_WIPHY_NAME]) | ||
148 | return -EINVAL; | ||
149 | |||
150 | rdev = cfg80211_get_dev_from_info(info); | ||
151 | if (IS_ERR(rdev)) | ||
152 | return PTR_ERR(rdev); | ||
153 | |||
154 | result = cfg80211_dev_rename(rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME])); | ||
155 | |||
156 | cfg80211_put_dev(rdev); | ||
157 | return result; | ||
158 | } | ||
159 | |||
160 | |||
161 | static int nl80211_send_iface(struct sk_buff *msg, u32 pid, u32 seq, int flags, | ||
162 | struct net_device *dev) | ||
163 | { | ||
164 | void *hdr; | ||
165 | |||
166 | hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_INTERFACE); | ||
167 | if (!hdr) | ||
168 | return -1; | ||
169 | |||
170 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex); | ||
171 | NLA_PUT_STRING(msg, NL80211_ATTR_IFNAME, dev->name); | ||
172 | /* TODO: interface type */ | ||
173 | return genlmsg_end(msg, hdr); | ||
174 | |||
175 | nla_put_failure: | ||
176 | return genlmsg_cancel(msg, hdr); | ||
177 | } | ||
178 | |||
179 | static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *cb) | ||
180 | { | ||
181 | int wp_idx = 0; | ||
182 | int if_idx = 0; | ||
183 | int wp_start = cb->args[0]; | ||
184 | int if_start = cb->args[1]; | ||
185 | struct cfg80211_registered_device *dev; | ||
186 | struct wireless_dev *wdev; | ||
187 | |||
188 | mutex_lock(&cfg80211_drv_mutex); | ||
189 | list_for_each_entry(dev, &cfg80211_drv_list, list) { | ||
190 | if (++wp_idx < wp_start) | ||
191 | continue; | ||
192 | if_idx = 0; | ||
193 | |||
194 | mutex_lock(&dev->devlist_mtx); | ||
195 | list_for_each_entry(wdev, &dev->netdev_list, list) { | ||
196 | if (++if_idx < if_start) | ||
197 | continue; | ||
198 | if (nl80211_send_iface(skb, NETLINK_CB(cb->skb).pid, | ||
199 | cb->nlh->nlmsg_seq, NLM_F_MULTI, | ||
200 | wdev->netdev) < 0) | ||
201 | break; | ||
202 | } | ||
203 | mutex_unlock(&dev->devlist_mtx); | ||
204 | } | ||
205 | mutex_unlock(&cfg80211_drv_mutex); | ||
206 | |||
207 | cb->args[0] = wp_idx; | ||
208 | cb->args[1] = if_idx; | ||
209 | |||
210 | return skb->len; | ||
211 | } | ||
212 | |||
213 | static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info) | ||
214 | { | ||
215 | struct sk_buff *msg; | ||
216 | struct cfg80211_registered_device *dev; | ||
217 | struct net_device *netdev; | ||
218 | int err; | ||
219 | |||
220 | err = get_drv_dev_by_info_ifindex(info, &dev, &netdev); | ||
221 | if (err) | ||
222 | return err; | ||
223 | |||
224 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
225 | if (!msg) | ||
226 | goto out_err; | ||
227 | |||
228 | if (nl80211_send_iface(msg, info->snd_pid, info->snd_seq, 0, netdev) < 0) | ||
229 | goto out_free; | ||
230 | |||
231 | dev_put(netdev); | ||
232 | cfg80211_put_dev(dev); | ||
233 | |||
234 | return genlmsg_unicast(msg, info->snd_pid); | ||
235 | |||
236 | out_free: | ||
237 | nlmsg_free(msg); | ||
238 | out_err: | ||
239 | dev_put(netdev); | ||
240 | cfg80211_put_dev(dev); | ||
241 | return -ENOBUFS; | ||
242 | } | ||
243 | |||
244 | static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info) | ||
245 | { | ||
246 | struct cfg80211_registered_device *drv; | ||
247 | int err, ifindex; | ||
248 | enum nl80211_iftype type; | ||
249 | struct net_device *dev; | ||
250 | |||
251 | if (info->attrs[NL80211_ATTR_IFTYPE]) { | ||
252 | type = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]); | ||
253 | if (type > NL80211_IFTYPE_MAX) | ||
254 | return -EINVAL; | ||
255 | } else | ||
256 | return -EINVAL; | ||
257 | |||
258 | err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | ||
259 | if (err) | ||
260 | return err; | ||
261 | ifindex = dev->ifindex; | ||
262 | dev_put(dev); | ||
263 | |||
264 | if (!drv->ops->change_virtual_intf) { | ||
265 | err = -EOPNOTSUPP; | ||
266 | goto unlock; | ||
267 | } | ||
268 | |||
269 | rtnl_lock(); | ||
270 | err = drv->ops->change_virtual_intf(&drv->wiphy, ifindex, type); | ||
271 | rtnl_unlock(); | ||
272 | |||
273 | unlock: | ||
274 | cfg80211_put_dev(drv); | ||
275 | return err; | ||
276 | } | ||
277 | |||
278 | static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info) | ||
279 | { | ||
280 | struct cfg80211_registered_device *drv; | ||
281 | int err; | ||
282 | enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED; | ||
283 | |||
284 | if (!info->attrs[NL80211_ATTR_IFNAME]) | ||
285 | return -EINVAL; | ||
286 | |||
287 | if (info->attrs[NL80211_ATTR_IFTYPE]) { | ||
288 | type = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]); | ||
289 | if (type > NL80211_IFTYPE_MAX) | ||
290 | return -EINVAL; | ||
291 | } | ||
292 | |||
293 | drv = cfg80211_get_dev_from_info(info); | ||
294 | if (IS_ERR(drv)) | ||
295 | return PTR_ERR(drv); | ||
296 | |||
297 | if (!drv->ops->add_virtual_intf) { | ||
298 | err = -EOPNOTSUPP; | ||
299 | goto unlock; | ||
300 | } | ||
301 | |||
302 | rtnl_lock(); | ||
303 | err = drv->ops->add_virtual_intf(&drv->wiphy, | ||
304 | nla_data(info->attrs[NL80211_ATTR_IFNAME]), type); | ||
305 | rtnl_unlock(); | ||
306 | |||
307 | unlock: | ||
308 | cfg80211_put_dev(drv); | ||
309 | return err; | ||
310 | } | ||
311 | |||
312 | static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info) | ||
313 | { | ||
314 | struct cfg80211_registered_device *drv; | ||
315 | int ifindex, err; | ||
316 | struct net_device *dev; | ||
317 | |||
318 | err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | ||
319 | if (err) | ||
320 | return err; | ||
321 | ifindex = dev->ifindex; | ||
322 | dev_put(dev); | ||
323 | |||
324 | if (!drv->ops->del_virtual_intf) { | ||
325 | err = -EOPNOTSUPP; | ||
326 | goto out; | ||
327 | } | ||
328 | |||
329 | rtnl_lock(); | ||
330 | err = drv->ops->del_virtual_intf(&drv->wiphy, ifindex); | ||
331 | rtnl_unlock(); | ||
332 | |||
333 | out: | ||
334 | cfg80211_put_dev(drv); | ||
335 | return err; | ||
336 | } | ||
337 | |||
338 | static struct genl_ops nl80211_ops[] = { | ||
339 | { | ||
340 | .cmd = NL80211_CMD_GET_WIPHY, | ||
341 | .doit = nl80211_get_wiphy, | ||
342 | .dumpit = nl80211_dump_wiphy, | ||
343 | .policy = nl80211_policy, | ||
344 | /* can be retrieved by unprivileged users */ | ||
345 | }, | ||
346 | { | ||
347 | .cmd = NL80211_CMD_SET_WIPHY, | ||
348 | .doit = nl80211_set_wiphy, | ||
349 | .policy = nl80211_policy, | ||
350 | .flags = GENL_ADMIN_PERM, | ||
351 | }, | ||
352 | { | ||
353 | .cmd = NL80211_CMD_GET_INTERFACE, | ||
354 | .doit = nl80211_get_interface, | ||
355 | .dumpit = nl80211_dump_interface, | ||
356 | .policy = nl80211_policy, | ||
357 | /* can be retrieved by unprivileged users */ | ||
358 | }, | ||
359 | { | ||
360 | .cmd = NL80211_CMD_SET_INTERFACE, | ||
361 | .doit = nl80211_set_interface, | ||
362 | .policy = nl80211_policy, | ||
363 | .flags = GENL_ADMIN_PERM, | ||
364 | }, | ||
365 | { | ||
366 | .cmd = NL80211_CMD_NEW_INTERFACE, | ||
367 | .doit = nl80211_new_interface, | ||
368 | .policy = nl80211_policy, | ||
369 | .flags = GENL_ADMIN_PERM, | ||
370 | }, | ||
371 | { | ||
372 | .cmd = NL80211_CMD_DEL_INTERFACE, | ||
373 | .doit = nl80211_del_interface, | ||
374 | .policy = nl80211_policy, | ||
375 | .flags = GENL_ADMIN_PERM, | ||
376 | }, | ||
377 | }; | ||
378 | |||
379 | /* multicast groups */ | ||
380 | static struct genl_multicast_group nl80211_config_mcgrp = { | ||
381 | .name = "config", | ||
382 | }; | ||
383 | |||
384 | /* notification functions */ | ||
385 | |||
386 | void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev) | ||
387 | { | ||
388 | struct sk_buff *msg; | ||
389 | |||
390 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
391 | if (!msg) | ||
392 | return; | ||
393 | |||
394 | if (nl80211_send_wiphy(msg, 0, 0, 0, rdev) < 0) { | ||
395 | nlmsg_free(msg); | ||
396 | return; | ||
397 | } | ||
398 | |||
399 | genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL); | ||
400 | } | ||
401 | |||
402 | /* initialisation/exit functions */ | ||
403 | |||
404 | int nl80211_init(void) | ||
405 | { | ||
406 | int err, i; | ||
407 | |||
408 | err = genl_register_family(&nl80211_fam); | ||
409 | if (err) | ||
410 | return err; | ||
411 | |||
412 | for (i = 0; i < ARRAY_SIZE(nl80211_ops); i++) { | ||
413 | err = genl_register_ops(&nl80211_fam, &nl80211_ops[i]); | ||
414 | if (err) | ||
415 | goto err_out; | ||
416 | } | ||
417 | |||
418 | err = genl_register_mc_group(&nl80211_fam, &nl80211_config_mcgrp); | ||
419 | if (err) | ||
420 | goto err_out; | ||
421 | |||
422 | return 0; | ||
423 | err_out: | ||
424 | genl_unregister_family(&nl80211_fam); | ||
425 | return err; | ||
426 | } | ||
427 | |||
428 | void nl80211_exit(void) | ||
429 | { | ||
430 | genl_unregister_family(&nl80211_fam); | ||
431 | } | ||