]>
Commit | Line | Data |
---|---|---|
55682965 JB |
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 | } |