diff options
Diffstat (limited to 'net/l2tp/l2tp_netlink.c')
-rw-r--r-- | net/l2tp/l2tp_netlink.c | 830 |
1 files changed, 830 insertions, 0 deletions
diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c new file mode 100644 index 000000000000..3d0f7f6f7488 --- /dev/null +++ b/net/l2tp/l2tp_netlink.c | |||
@@ -0,0 +1,830 @@ | |||
1 | /* | ||
2 | * L2TP netlink layer, for management | ||
3 | * | ||
4 | * Copyright (c) 2008,2009,2010 Katalix Systems Ltd | ||
5 | * | ||
6 | * Partly based on the IrDA nelink implementation | ||
7 | * (see net/irda/irnetlink.c) which is: | ||
8 | * Copyright (c) 2007 Samuel Ortiz <samuel@sortiz.org> | ||
9 | * which is in turn partly based on the wireless netlink code: | ||
10 | * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License version 2 as | ||
14 | * published by the Free Software Foundation. | ||
15 | */ | ||
16 | |||
17 | #include <net/sock.h> | ||
18 | #include <net/genetlink.h> | ||
19 | #include <net/udp.h> | ||
20 | #include <linux/in.h> | ||
21 | #include <linux/udp.h> | ||
22 | #include <linux/socket.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/list.h> | ||
25 | #include <net/net_namespace.h> | ||
26 | |||
27 | #include <linux/l2tp.h> | ||
28 | |||
29 | #include "l2tp_core.h" | ||
30 | |||
31 | |||
32 | static struct genl_family l2tp_nl_family = { | ||
33 | .id = GENL_ID_GENERATE, | ||
34 | .name = L2TP_GENL_NAME, | ||
35 | .version = L2TP_GENL_VERSION, | ||
36 | .hdrsize = 0, | ||
37 | .maxattr = L2TP_ATTR_MAX, | ||
38 | }; | ||
39 | |||
40 | /* Accessed under genl lock */ | ||
41 | static const struct l2tp_nl_cmd_ops *l2tp_nl_cmd_ops[__L2TP_PWTYPE_MAX]; | ||
42 | |||
43 | static struct l2tp_session *l2tp_nl_session_find(struct genl_info *info) | ||
44 | { | ||
45 | u32 tunnel_id; | ||
46 | u32 session_id; | ||
47 | char *ifname; | ||
48 | struct l2tp_tunnel *tunnel; | ||
49 | struct l2tp_session *session = NULL; | ||
50 | struct net *net = genl_info_net(info); | ||
51 | |||
52 | if (info->attrs[L2TP_ATTR_IFNAME]) { | ||
53 | ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]); | ||
54 | session = l2tp_session_find_by_ifname(net, ifname); | ||
55 | } else if ((info->attrs[L2TP_ATTR_SESSION_ID]) && | ||
56 | (info->attrs[L2TP_ATTR_CONN_ID])) { | ||
57 | tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); | ||
58 | session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]); | ||
59 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
60 | if (tunnel) | ||
61 | session = l2tp_session_find(net, tunnel, session_id); | ||
62 | } | ||
63 | |||
64 | return session; | ||
65 | } | ||
66 | |||
67 | static int l2tp_nl_cmd_noop(struct sk_buff *skb, struct genl_info *info) | ||
68 | { | ||
69 | struct sk_buff *msg; | ||
70 | void *hdr; | ||
71 | int ret = -ENOBUFS; | ||
72 | |||
73 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
74 | if (!msg) { | ||
75 | ret = -ENOMEM; | ||
76 | goto out; | ||
77 | } | ||
78 | |||
79 | hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, | ||
80 | &l2tp_nl_family, 0, L2TP_CMD_NOOP); | ||
81 | if (IS_ERR(hdr)) { | ||
82 | ret = PTR_ERR(hdr); | ||
83 | goto err_out; | ||
84 | } | ||
85 | |||
86 | genlmsg_end(msg, hdr); | ||
87 | |||
88 | return genlmsg_unicast(genl_info_net(info), msg, info->snd_pid); | ||
89 | |||
90 | err_out: | ||
91 | nlmsg_free(msg); | ||
92 | |||
93 | out: | ||
94 | return ret; | ||
95 | } | ||
96 | |||
97 | static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info) | ||
98 | { | ||
99 | u32 tunnel_id; | ||
100 | u32 peer_tunnel_id; | ||
101 | int proto_version; | ||
102 | int fd; | ||
103 | int ret = 0; | ||
104 | struct l2tp_tunnel_cfg cfg = { 0, }; | ||
105 | struct l2tp_tunnel *tunnel; | ||
106 | struct net *net = genl_info_net(info); | ||
107 | |||
108 | if (!info->attrs[L2TP_ATTR_CONN_ID]) { | ||
109 | ret = -EINVAL; | ||
110 | goto out; | ||
111 | } | ||
112 | tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); | ||
113 | |||
114 | if (!info->attrs[L2TP_ATTR_PEER_CONN_ID]) { | ||
115 | ret = -EINVAL; | ||
116 | goto out; | ||
117 | } | ||
118 | peer_tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_PEER_CONN_ID]); | ||
119 | |||
120 | if (!info->attrs[L2TP_ATTR_PROTO_VERSION]) { | ||
121 | ret = -EINVAL; | ||
122 | goto out; | ||
123 | } | ||
124 | proto_version = nla_get_u8(info->attrs[L2TP_ATTR_PROTO_VERSION]); | ||
125 | |||
126 | if (!info->attrs[L2TP_ATTR_ENCAP_TYPE]) { | ||
127 | ret = -EINVAL; | ||
128 | goto out; | ||
129 | } | ||
130 | cfg.encap = nla_get_u16(info->attrs[L2TP_ATTR_ENCAP_TYPE]); | ||
131 | |||
132 | if (!info->attrs[L2TP_ATTR_FD]) { | ||
133 | ret = -EINVAL; | ||
134 | goto out; | ||
135 | } | ||
136 | fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]); | ||
137 | |||
138 | if (info->attrs[L2TP_ATTR_DEBUG]) | ||
139 | cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); | ||
140 | |||
141 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
142 | if (tunnel != NULL) { | ||
143 | ret = -EEXIST; | ||
144 | goto out; | ||
145 | } | ||
146 | |||
147 | ret = -EINVAL; | ||
148 | switch (cfg.encap) { | ||
149 | case L2TP_ENCAPTYPE_UDP: | ||
150 | case L2TP_ENCAPTYPE_IP: | ||
151 | ret = l2tp_tunnel_create(net, fd, proto_version, tunnel_id, | ||
152 | peer_tunnel_id, &cfg, &tunnel); | ||
153 | break; | ||
154 | } | ||
155 | |||
156 | out: | ||
157 | return ret; | ||
158 | } | ||
159 | |||
160 | static int l2tp_nl_cmd_tunnel_delete(struct sk_buff *skb, struct genl_info *info) | ||
161 | { | ||
162 | struct l2tp_tunnel *tunnel; | ||
163 | u32 tunnel_id; | ||
164 | int ret = 0; | ||
165 | struct net *net = genl_info_net(info); | ||
166 | |||
167 | if (!info->attrs[L2TP_ATTR_CONN_ID]) { | ||
168 | ret = -EINVAL; | ||
169 | goto out; | ||
170 | } | ||
171 | tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); | ||
172 | |||
173 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
174 | if (tunnel == NULL) { | ||
175 | ret = -ENODEV; | ||
176 | goto out; | ||
177 | } | ||
178 | |||
179 | (void) l2tp_tunnel_delete(tunnel); | ||
180 | |||
181 | out: | ||
182 | return ret; | ||
183 | } | ||
184 | |||
185 | static int l2tp_nl_cmd_tunnel_modify(struct sk_buff *skb, struct genl_info *info) | ||
186 | { | ||
187 | struct l2tp_tunnel *tunnel; | ||
188 | u32 tunnel_id; | ||
189 | int ret = 0; | ||
190 | struct net *net = genl_info_net(info); | ||
191 | |||
192 | if (!info->attrs[L2TP_ATTR_CONN_ID]) { | ||
193 | ret = -EINVAL; | ||
194 | goto out; | ||
195 | } | ||
196 | tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); | ||
197 | |||
198 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
199 | if (tunnel == NULL) { | ||
200 | ret = -ENODEV; | ||
201 | goto out; | ||
202 | } | ||
203 | |||
204 | if (info->attrs[L2TP_ATTR_DEBUG]) | ||
205 | tunnel->debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); | ||
206 | |||
207 | out: | ||
208 | return ret; | ||
209 | } | ||
210 | |||
211 | static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 pid, u32 seq, int flags, | ||
212 | struct l2tp_tunnel *tunnel) | ||
213 | { | ||
214 | void *hdr; | ||
215 | struct nlattr *nest; | ||
216 | struct sock *sk = NULL; | ||
217 | struct inet_sock *inet; | ||
218 | |||
219 | hdr = genlmsg_put(skb, pid, seq, &l2tp_nl_family, flags, | ||
220 | L2TP_CMD_TUNNEL_GET); | ||
221 | if (IS_ERR(hdr)) | ||
222 | return PTR_ERR(hdr); | ||
223 | |||
224 | NLA_PUT_U8(skb, L2TP_ATTR_PROTO_VERSION, tunnel->version); | ||
225 | NLA_PUT_U32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id); | ||
226 | NLA_PUT_U32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id); | ||
227 | NLA_PUT_U32(skb, L2TP_ATTR_DEBUG, tunnel->debug); | ||
228 | NLA_PUT_U16(skb, L2TP_ATTR_ENCAP_TYPE, tunnel->encap); | ||
229 | |||
230 | nest = nla_nest_start(skb, L2TP_ATTR_STATS); | ||
231 | if (nest == NULL) | ||
232 | goto nla_put_failure; | ||
233 | |||
234 | NLA_PUT_U64(skb, L2TP_ATTR_TX_PACKETS, tunnel->stats.tx_packets); | ||
235 | NLA_PUT_U64(skb, L2TP_ATTR_TX_BYTES, tunnel->stats.tx_bytes); | ||
236 | NLA_PUT_U64(skb, L2TP_ATTR_TX_ERRORS, tunnel->stats.tx_errors); | ||
237 | NLA_PUT_U64(skb, L2TP_ATTR_RX_PACKETS, tunnel->stats.rx_packets); | ||
238 | NLA_PUT_U64(skb, L2TP_ATTR_RX_BYTES, tunnel->stats.rx_bytes); | ||
239 | NLA_PUT_U64(skb, L2TP_ATTR_RX_SEQ_DISCARDS, tunnel->stats.rx_seq_discards); | ||
240 | NLA_PUT_U64(skb, L2TP_ATTR_RX_OOS_PACKETS, tunnel->stats.rx_oos_packets); | ||
241 | NLA_PUT_U64(skb, L2TP_ATTR_RX_ERRORS, tunnel->stats.rx_errors); | ||
242 | nla_nest_end(skb, nest); | ||
243 | |||
244 | sk = tunnel->sock; | ||
245 | if (!sk) | ||
246 | goto out; | ||
247 | |||
248 | inet = inet_sk(sk); | ||
249 | |||
250 | switch (tunnel->encap) { | ||
251 | case L2TP_ENCAPTYPE_UDP: | ||
252 | NLA_PUT_U16(skb, L2TP_ATTR_UDP_SPORT, ntohs(inet->inet_sport)); | ||
253 | NLA_PUT_U16(skb, L2TP_ATTR_UDP_DPORT, ntohs(inet->inet_dport)); | ||
254 | NLA_PUT_U8(skb, L2TP_ATTR_UDP_CSUM, (sk->sk_no_check != UDP_CSUM_NOXMIT)); | ||
255 | /* NOBREAK */ | ||
256 | case L2TP_ENCAPTYPE_IP: | ||
257 | NLA_PUT_BE32(skb, L2TP_ATTR_IP_SADDR, inet->inet_saddr); | ||
258 | NLA_PUT_BE32(skb, L2TP_ATTR_IP_DADDR, inet->inet_daddr); | ||
259 | break; | ||
260 | } | ||
261 | |||
262 | out: | ||
263 | return genlmsg_end(skb, hdr); | ||
264 | |||
265 | nla_put_failure: | ||
266 | genlmsg_cancel(skb, hdr); | ||
267 | return -1; | ||
268 | } | ||
269 | |||
270 | static int l2tp_nl_cmd_tunnel_get(struct sk_buff *skb, struct genl_info *info) | ||
271 | { | ||
272 | struct l2tp_tunnel *tunnel; | ||
273 | struct sk_buff *msg; | ||
274 | u32 tunnel_id; | ||
275 | int ret = -ENOBUFS; | ||
276 | struct net *net = genl_info_net(info); | ||
277 | |||
278 | if (!info->attrs[L2TP_ATTR_CONN_ID]) { | ||
279 | ret = -EINVAL; | ||
280 | goto out; | ||
281 | } | ||
282 | |||
283 | tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); | ||
284 | |||
285 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
286 | if (tunnel == NULL) { | ||
287 | ret = -ENODEV; | ||
288 | goto out; | ||
289 | } | ||
290 | |||
291 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
292 | if (!msg) { | ||
293 | ret = -ENOMEM; | ||
294 | goto out; | ||
295 | } | ||
296 | |||
297 | ret = l2tp_nl_tunnel_send(msg, info->snd_pid, info->snd_seq, | ||
298 | NLM_F_ACK, tunnel); | ||
299 | if (ret < 0) | ||
300 | goto err_out; | ||
301 | |||
302 | return genlmsg_unicast(net, msg, info->snd_pid); | ||
303 | |||
304 | err_out: | ||
305 | nlmsg_free(msg); | ||
306 | |||
307 | out: | ||
308 | return ret; | ||
309 | } | ||
310 | |||
311 | static int l2tp_nl_cmd_tunnel_dump(struct sk_buff *skb, struct netlink_callback *cb) | ||
312 | { | ||
313 | int ti = cb->args[0]; | ||
314 | struct l2tp_tunnel *tunnel; | ||
315 | struct net *net = sock_net(skb->sk); | ||
316 | |||
317 | for (;;) { | ||
318 | tunnel = l2tp_tunnel_find_nth(net, ti); | ||
319 | if (tunnel == NULL) | ||
320 | goto out; | ||
321 | |||
322 | if (l2tp_nl_tunnel_send(skb, NETLINK_CB(cb->skb).pid, | ||
323 | cb->nlh->nlmsg_seq, NLM_F_MULTI, | ||
324 | tunnel) <= 0) | ||
325 | goto out; | ||
326 | |||
327 | ti++; | ||
328 | } | ||
329 | |||
330 | out: | ||
331 | cb->args[0] = ti; | ||
332 | |||
333 | return skb->len; | ||
334 | } | ||
335 | |||
336 | static int l2tp_nl_cmd_session_create(struct sk_buff *skb, struct genl_info *info) | ||
337 | { | ||
338 | u32 tunnel_id = 0; | ||
339 | u32 session_id; | ||
340 | u32 peer_session_id; | ||
341 | int ret = 0; | ||
342 | struct l2tp_tunnel *tunnel; | ||
343 | struct l2tp_session *session; | ||
344 | struct l2tp_session_cfg cfg = { 0, }; | ||
345 | struct net *net = genl_info_net(info); | ||
346 | |||
347 | if (!info->attrs[L2TP_ATTR_CONN_ID]) { | ||
348 | ret = -EINVAL; | ||
349 | goto out; | ||
350 | } | ||
351 | tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); | ||
352 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
353 | if (!tunnel) { | ||
354 | ret = -ENODEV; | ||
355 | goto out; | ||
356 | } | ||
357 | |||
358 | if (!info->attrs[L2TP_ATTR_SESSION_ID]) { | ||
359 | ret = -EINVAL; | ||
360 | goto out; | ||
361 | } | ||
362 | session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]); | ||
363 | session = l2tp_session_find(net, tunnel, session_id); | ||
364 | if (session) { | ||
365 | ret = -EEXIST; | ||
366 | goto out; | ||
367 | } | ||
368 | |||
369 | if (!info->attrs[L2TP_ATTR_PEER_SESSION_ID]) { | ||
370 | ret = -EINVAL; | ||
371 | goto out; | ||
372 | } | ||
373 | peer_session_id = nla_get_u32(info->attrs[L2TP_ATTR_PEER_SESSION_ID]); | ||
374 | |||
375 | if (!info->attrs[L2TP_ATTR_PW_TYPE]) { | ||
376 | ret = -EINVAL; | ||
377 | goto out; | ||
378 | } | ||
379 | cfg.pw_type = nla_get_u16(info->attrs[L2TP_ATTR_PW_TYPE]); | ||
380 | if (cfg.pw_type >= __L2TP_PWTYPE_MAX) { | ||
381 | ret = -EINVAL; | ||
382 | goto out; | ||
383 | } | ||
384 | |||
385 | if (tunnel->version > 2) { | ||
386 | if (info->attrs[L2TP_ATTR_OFFSET]) | ||
387 | cfg.offset = nla_get_u16(info->attrs[L2TP_ATTR_OFFSET]); | ||
388 | |||
389 | if (info->attrs[L2TP_ATTR_DATA_SEQ]) | ||
390 | cfg.data_seq = nla_get_u8(info->attrs[L2TP_ATTR_DATA_SEQ]); | ||
391 | |||
392 | cfg.l2specific_type = L2TP_L2SPECTYPE_DEFAULT; | ||
393 | if (info->attrs[L2TP_ATTR_L2SPEC_TYPE]) | ||
394 | cfg.l2specific_type = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_TYPE]); | ||
395 | |||
396 | cfg.l2specific_len = 4; | ||
397 | if (info->attrs[L2TP_ATTR_L2SPEC_LEN]) | ||
398 | cfg.l2specific_len = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_LEN]); | ||
399 | |||
400 | if (info->attrs[L2TP_ATTR_COOKIE]) { | ||
401 | u16 len = nla_len(info->attrs[L2TP_ATTR_COOKIE]); | ||
402 | if (len > 8) { | ||
403 | ret = -EINVAL; | ||
404 | goto out; | ||
405 | } | ||
406 | cfg.cookie_len = len; | ||
407 | memcpy(&cfg.cookie[0], nla_data(info->attrs[L2TP_ATTR_COOKIE]), len); | ||
408 | } | ||
409 | if (info->attrs[L2TP_ATTR_PEER_COOKIE]) { | ||
410 | u16 len = nla_len(info->attrs[L2TP_ATTR_PEER_COOKIE]); | ||
411 | if (len > 8) { | ||
412 | ret = -EINVAL; | ||
413 | goto out; | ||
414 | } | ||
415 | cfg.peer_cookie_len = len; | ||
416 | memcpy(&cfg.peer_cookie[0], nla_data(info->attrs[L2TP_ATTR_PEER_COOKIE]), len); | ||
417 | } | ||
418 | if (info->attrs[L2TP_ATTR_IFNAME]) | ||
419 | cfg.ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]); | ||
420 | |||
421 | if (info->attrs[L2TP_ATTR_VLAN_ID]) | ||
422 | cfg.vlan_id = nla_get_u16(info->attrs[L2TP_ATTR_VLAN_ID]); | ||
423 | } | ||
424 | |||
425 | if (info->attrs[L2TP_ATTR_DEBUG]) | ||
426 | cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); | ||
427 | |||
428 | if (info->attrs[L2TP_ATTR_RECV_SEQ]) | ||
429 | cfg.recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]); | ||
430 | |||
431 | if (info->attrs[L2TP_ATTR_SEND_SEQ]) | ||
432 | cfg.send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]); | ||
433 | |||
434 | if (info->attrs[L2TP_ATTR_LNS_MODE]) | ||
435 | cfg.lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]); | ||
436 | |||
437 | if (info->attrs[L2TP_ATTR_RECV_TIMEOUT]) | ||
438 | cfg.reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]); | ||
439 | |||
440 | if (info->attrs[L2TP_ATTR_MTU]) | ||
441 | cfg.mtu = nla_get_u16(info->attrs[L2TP_ATTR_MTU]); | ||
442 | |||
443 | if (info->attrs[L2TP_ATTR_MRU]) | ||
444 | cfg.mru = nla_get_u16(info->attrs[L2TP_ATTR_MRU]); | ||
445 | |||
446 | if ((l2tp_nl_cmd_ops[cfg.pw_type] == NULL) || | ||
447 | (l2tp_nl_cmd_ops[cfg.pw_type]->session_create == NULL)) { | ||
448 | ret = -EPROTONOSUPPORT; | ||
449 | goto out; | ||
450 | } | ||
451 | |||
452 | /* Check that pseudowire-specific params are present */ | ||
453 | switch (cfg.pw_type) { | ||
454 | case L2TP_PWTYPE_NONE: | ||
455 | break; | ||
456 | case L2TP_PWTYPE_ETH_VLAN: | ||
457 | if (!info->attrs[L2TP_ATTR_VLAN_ID]) { | ||
458 | ret = -EINVAL; | ||
459 | goto out; | ||
460 | } | ||
461 | break; | ||
462 | case L2TP_PWTYPE_ETH: | ||
463 | break; | ||
464 | case L2TP_PWTYPE_PPP: | ||
465 | case L2TP_PWTYPE_PPP_AC: | ||
466 | break; | ||
467 | case L2TP_PWTYPE_IP: | ||
468 | default: | ||
469 | ret = -EPROTONOSUPPORT; | ||
470 | break; | ||
471 | } | ||
472 | |||
473 | ret = -EPROTONOSUPPORT; | ||
474 | if (l2tp_nl_cmd_ops[cfg.pw_type]->session_create) | ||
475 | ret = (*l2tp_nl_cmd_ops[cfg.pw_type]->session_create)(net, tunnel_id, | ||
476 | session_id, peer_session_id, &cfg); | ||
477 | |||
478 | out: | ||
479 | return ret; | ||
480 | } | ||
481 | |||
482 | static int l2tp_nl_cmd_session_delete(struct sk_buff *skb, struct genl_info *info) | ||
483 | { | ||
484 | int ret = 0; | ||
485 | struct l2tp_session *session; | ||
486 | u16 pw_type; | ||
487 | |||
488 | session = l2tp_nl_session_find(info); | ||
489 | if (session == NULL) { | ||
490 | ret = -ENODEV; | ||
491 | goto out; | ||
492 | } | ||
493 | |||
494 | pw_type = session->pwtype; | ||
495 | if (pw_type < __L2TP_PWTYPE_MAX) | ||
496 | if (l2tp_nl_cmd_ops[pw_type] && l2tp_nl_cmd_ops[pw_type]->session_delete) | ||
497 | ret = (*l2tp_nl_cmd_ops[pw_type]->session_delete)(session); | ||
498 | |||
499 | out: | ||
500 | return ret; | ||
501 | } | ||
502 | |||
503 | static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *info) | ||
504 | { | ||
505 | int ret = 0; | ||
506 | struct l2tp_session *session; | ||
507 | |||
508 | session = l2tp_nl_session_find(info); | ||
509 | if (session == NULL) { | ||
510 | ret = -ENODEV; | ||
511 | goto out; | ||
512 | } | ||
513 | |||
514 | if (info->attrs[L2TP_ATTR_DEBUG]) | ||
515 | session->debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); | ||
516 | |||
517 | if (info->attrs[L2TP_ATTR_DATA_SEQ]) | ||
518 | session->data_seq = nla_get_u8(info->attrs[L2TP_ATTR_DATA_SEQ]); | ||
519 | |||
520 | if (info->attrs[L2TP_ATTR_RECV_SEQ]) | ||
521 | session->recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]); | ||
522 | |||
523 | if (info->attrs[L2TP_ATTR_SEND_SEQ]) | ||
524 | session->send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]); | ||
525 | |||
526 | if (info->attrs[L2TP_ATTR_LNS_MODE]) | ||
527 | session->lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]); | ||
528 | |||
529 | if (info->attrs[L2TP_ATTR_RECV_TIMEOUT]) | ||
530 | session->reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]); | ||
531 | |||
532 | if (info->attrs[L2TP_ATTR_MTU]) | ||
533 | session->mtu = nla_get_u16(info->attrs[L2TP_ATTR_MTU]); | ||
534 | |||
535 | if (info->attrs[L2TP_ATTR_MRU]) | ||
536 | session->mru = nla_get_u16(info->attrs[L2TP_ATTR_MRU]); | ||
537 | |||
538 | out: | ||
539 | return ret; | ||
540 | } | ||
541 | |||
542 | static int l2tp_nl_session_send(struct sk_buff *skb, u32 pid, u32 seq, int flags, | ||
543 | struct l2tp_session *session) | ||
544 | { | ||
545 | void *hdr; | ||
546 | struct nlattr *nest; | ||
547 | struct l2tp_tunnel *tunnel = session->tunnel; | ||
548 | struct sock *sk = NULL; | ||
549 | |||
550 | sk = tunnel->sock; | ||
551 | |||
552 | hdr = genlmsg_put(skb, pid, seq, &l2tp_nl_family, flags, L2TP_CMD_SESSION_GET); | ||
553 | if (IS_ERR(hdr)) | ||
554 | return PTR_ERR(hdr); | ||
555 | |||
556 | NLA_PUT_U32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id); | ||
557 | NLA_PUT_U32(skb, L2TP_ATTR_SESSION_ID, session->session_id); | ||
558 | NLA_PUT_U32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id); | ||
559 | NLA_PUT_U32(skb, L2TP_ATTR_PEER_SESSION_ID, session->peer_session_id); | ||
560 | NLA_PUT_U32(skb, L2TP_ATTR_DEBUG, session->debug); | ||
561 | NLA_PUT_U16(skb, L2TP_ATTR_PW_TYPE, session->pwtype); | ||
562 | NLA_PUT_U16(skb, L2TP_ATTR_MTU, session->mtu); | ||
563 | if (session->mru) | ||
564 | NLA_PUT_U16(skb, L2TP_ATTR_MRU, session->mru); | ||
565 | |||
566 | if (session->ifname && session->ifname[0]) | ||
567 | NLA_PUT_STRING(skb, L2TP_ATTR_IFNAME, session->ifname); | ||
568 | if (session->cookie_len) | ||
569 | NLA_PUT(skb, L2TP_ATTR_COOKIE, session->cookie_len, &session->cookie[0]); | ||
570 | if (session->peer_cookie_len) | ||
571 | NLA_PUT(skb, L2TP_ATTR_PEER_COOKIE, session->peer_cookie_len, &session->peer_cookie[0]); | ||
572 | NLA_PUT_U8(skb, L2TP_ATTR_RECV_SEQ, session->recv_seq); | ||
573 | NLA_PUT_U8(skb, L2TP_ATTR_SEND_SEQ, session->send_seq); | ||
574 | NLA_PUT_U8(skb, L2TP_ATTR_LNS_MODE, session->lns_mode); | ||
575 | #ifdef CONFIG_XFRM | ||
576 | if ((sk) && (sk->sk_policy[0] || sk->sk_policy[1])) | ||
577 | NLA_PUT_U8(skb, L2TP_ATTR_USING_IPSEC, 1); | ||
578 | #endif | ||
579 | if (session->reorder_timeout) | ||
580 | NLA_PUT_MSECS(skb, L2TP_ATTR_RECV_TIMEOUT, session->reorder_timeout); | ||
581 | |||
582 | nest = nla_nest_start(skb, L2TP_ATTR_STATS); | ||
583 | if (nest == NULL) | ||
584 | goto nla_put_failure; | ||
585 | NLA_PUT_U64(skb, L2TP_ATTR_TX_PACKETS, session->stats.tx_packets); | ||
586 | NLA_PUT_U64(skb, L2TP_ATTR_TX_BYTES, session->stats.tx_bytes); | ||
587 | NLA_PUT_U64(skb, L2TP_ATTR_TX_ERRORS, session->stats.tx_errors); | ||
588 | NLA_PUT_U64(skb, L2TP_ATTR_RX_PACKETS, session->stats.rx_packets); | ||
589 | NLA_PUT_U64(skb, L2TP_ATTR_RX_BYTES, session->stats.rx_bytes); | ||
590 | NLA_PUT_U64(skb, L2TP_ATTR_RX_SEQ_DISCARDS, session->stats.rx_seq_discards); | ||
591 | NLA_PUT_U64(skb, L2TP_ATTR_RX_OOS_PACKETS, session->stats.rx_oos_packets); | ||
592 | NLA_PUT_U64(skb, L2TP_ATTR_RX_ERRORS, session->stats.rx_errors); | ||
593 | nla_nest_end(skb, nest); | ||
594 | |||
595 | return genlmsg_end(skb, hdr); | ||
596 | |||
597 | nla_put_failure: | ||
598 | genlmsg_cancel(skb, hdr); | ||
599 | return -1; | ||
600 | } | ||
601 | |||
602 | static int l2tp_nl_cmd_session_get(struct sk_buff *skb, struct genl_info *info) | ||
603 | { | ||
604 | struct l2tp_session *session; | ||
605 | struct sk_buff *msg; | ||
606 | int ret; | ||
607 | |||
608 | session = l2tp_nl_session_find(info); | ||
609 | if (session == NULL) { | ||
610 | ret = -ENODEV; | ||
611 | goto out; | ||
612 | } | ||
613 | |||
614 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
615 | if (!msg) { | ||
616 | ret = -ENOMEM; | ||
617 | goto out; | ||
618 | } | ||
619 | |||
620 | ret = l2tp_nl_session_send(msg, info->snd_pid, info->snd_seq, | ||
621 | 0, session); | ||
622 | if (ret < 0) | ||
623 | goto err_out; | ||
624 | |||
625 | return genlmsg_unicast(genl_info_net(info), msg, info->snd_pid); | ||
626 | |||
627 | err_out: | ||
628 | nlmsg_free(msg); | ||
629 | |||
630 | out: | ||
631 | return ret; | ||
632 | } | ||
633 | |||
634 | static int l2tp_nl_cmd_session_dump(struct sk_buff *skb, struct netlink_callback *cb) | ||
635 | { | ||
636 | struct net *net = sock_net(skb->sk); | ||
637 | struct l2tp_session *session; | ||
638 | struct l2tp_tunnel *tunnel = NULL; | ||
639 | int ti = cb->args[0]; | ||
640 | int si = cb->args[1]; | ||
641 | |||
642 | for (;;) { | ||
643 | if (tunnel == NULL) { | ||
644 | tunnel = l2tp_tunnel_find_nth(net, ti); | ||
645 | if (tunnel == NULL) | ||
646 | goto out; | ||
647 | } | ||
648 | |||
649 | session = l2tp_session_find_nth(tunnel, si); | ||
650 | if (session == NULL) { | ||
651 | ti++; | ||
652 | tunnel = NULL; | ||
653 | si = 0; | ||
654 | continue; | ||
655 | } | ||
656 | |||
657 | if (l2tp_nl_session_send(skb, NETLINK_CB(cb->skb).pid, | ||
658 | cb->nlh->nlmsg_seq, NLM_F_MULTI, | ||
659 | session) <= 0) | ||
660 | break; | ||
661 | |||
662 | si++; | ||
663 | } | ||
664 | |||
665 | out: | ||
666 | cb->args[0] = ti; | ||
667 | cb->args[1] = si; | ||
668 | |||
669 | return skb->len; | ||
670 | } | ||
671 | |||
672 | static struct nla_policy l2tp_nl_policy[L2TP_ATTR_MAX + 1] = { | ||
673 | [L2TP_ATTR_NONE] = { .type = NLA_UNSPEC, }, | ||
674 | [L2TP_ATTR_PW_TYPE] = { .type = NLA_U16, }, | ||
675 | [L2TP_ATTR_ENCAP_TYPE] = { .type = NLA_U16, }, | ||
676 | [L2TP_ATTR_OFFSET] = { .type = NLA_U16, }, | ||
677 | [L2TP_ATTR_DATA_SEQ] = { .type = NLA_U8, }, | ||
678 | [L2TP_ATTR_L2SPEC_TYPE] = { .type = NLA_U8, }, | ||
679 | [L2TP_ATTR_L2SPEC_LEN] = { .type = NLA_U8, }, | ||
680 | [L2TP_ATTR_PROTO_VERSION] = { .type = NLA_U8, }, | ||
681 | [L2TP_ATTR_CONN_ID] = { .type = NLA_U32, }, | ||
682 | [L2TP_ATTR_PEER_CONN_ID] = { .type = NLA_U32, }, | ||
683 | [L2TP_ATTR_SESSION_ID] = { .type = NLA_U32, }, | ||
684 | [L2TP_ATTR_PEER_SESSION_ID] = { .type = NLA_U32, }, | ||
685 | [L2TP_ATTR_UDP_CSUM] = { .type = NLA_U8, }, | ||
686 | [L2TP_ATTR_VLAN_ID] = { .type = NLA_U16, }, | ||
687 | [L2TP_ATTR_DEBUG] = { .type = NLA_U32, }, | ||
688 | [L2TP_ATTR_RECV_SEQ] = { .type = NLA_U8, }, | ||
689 | [L2TP_ATTR_SEND_SEQ] = { .type = NLA_U8, }, | ||
690 | [L2TP_ATTR_LNS_MODE] = { .type = NLA_U8, }, | ||
691 | [L2TP_ATTR_USING_IPSEC] = { .type = NLA_U8, }, | ||
692 | [L2TP_ATTR_RECV_TIMEOUT] = { .type = NLA_MSECS, }, | ||
693 | [L2TP_ATTR_FD] = { .type = NLA_U32, }, | ||
694 | [L2TP_ATTR_IP_SADDR] = { .type = NLA_U32, }, | ||
695 | [L2TP_ATTR_IP_DADDR] = { .type = NLA_U32, }, | ||
696 | [L2TP_ATTR_UDP_SPORT] = { .type = NLA_U16, }, | ||
697 | [L2TP_ATTR_UDP_DPORT] = { .type = NLA_U16, }, | ||
698 | [L2TP_ATTR_MTU] = { .type = NLA_U16, }, | ||
699 | [L2TP_ATTR_MRU] = { .type = NLA_U16, }, | ||
700 | [L2TP_ATTR_STATS] = { .type = NLA_NESTED, }, | ||
701 | [L2TP_ATTR_IFNAME] = { | ||
702 | .type = NLA_NUL_STRING, | ||
703 | .len = IFNAMSIZ - 1, | ||
704 | }, | ||
705 | [L2TP_ATTR_COOKIE] = { | ||
706 | .type = NLA_BINARY, | ||
707 | .len = 8, | ||
708 | }, | ||
709 | [L2TP_ATTR_PEER_COOKIE] = { | ||
710 | .type = NLA_BINARY, | ||
711 | .len = 8, | ||
712 | }, | ||
713 | }; | ||
714 | |||
715 | static struct genl_ops l2tp_nl_ops[] = { | ||
716 | { | ||
717 | .cmd = L2TP_CMD_NOOP, | ||
718 | .doit = l2tp_nl_cmd_noop, | ||
719 | .policy = l2tp_nl_policy, | ||
720 | /* can be retrieved by unprivileged users */ | ||
721 | }, | ||
722 | { | ||
723 | .cmd = L2TP_CMD_TUNNEL_CREATE, | ||
724 | .doit = l2tp_nl_cmd_tunnel_create, | ||
725 | .policy = l2tp_nl_policy, | ||
726 | .flags = GENL_ADMIN_PERM, | ||
727 | }, | ||
728 | { | ||
729 | .cmd = L2TP_CMD_TUNNEL_DELETE, | ||
730 | .doit = l2tp_nl_cmd_tunnel_delete, | ||
731 | .policy = l2tp_nl_policy, | ||
732 | .flags = GENL_ADMIN_PERM, | ||
733 | }, | ||
734 | { | ||
735 | .cmd = L2TP_CMD_TUNNEL_MODIFY, | ||
736 | .doit = l2tp_nl_cmd_tunnel_modify, | ||
737 | .policy = l2tp_nl_policy, | ||
738 | .flags = GENL_ADMIN_PERM, | ||
739 | }, | ||
740 | { | ||
741 | .cmd = L2TP_CMD_TUNNEL_GET, | ||
742 | .doit = l2tp_nl_cmd_tunnel_get, | ||
743 | .dumpit = l2tp_nl_cmd_tunnel_dump, | ||
744 | .policy = l2tp_nl_policy, | ||
745 | .flags = GENL_ADMIN_PERM, | ||
746 | }, | ||
747 | { | ||
748 | .cmd = L2TP_CMD_SESSION_CREATE, | ||
749 | .doit = l2tp_nl_cmd_session_create, | ||
750 | .policy = l2tp_nl_policy, | ||
751 | .flags = GENL_ADMIN_PERM, | ||
752 | }, | ||
753 | { | ||
754 | .cmd = L2TP_CMD_SESSION_DELETE, | ||
755 | .doit = l2tp_nl_cmd_session_delete, | ||
756 | .policy = l2tp_nl_policy, | ||
757 | .flags = GENL_ADMIN_PERM, | ||
758 | }, | ||
759 | { | ||
760 | .cmd = L2TP_CMD_SESSION_MODIFY, | ||
761 | .doit = l2tp_nl_cmd_session_modify, | ||
762 | .policy = l2tp_nl_policy, | ||
763 | .flags = GENL_ADMIN_PERM, | ||
764 | }, | ||
765 | { | ||
766 | .cmd = L2TP_CMD_SESSION_GET, | ||
767 | .doit = l2tp_nl_cmd_session_get, | ||
768 | .dumpit = l2tp_nl_cmd_session_dump, | ||
769 | .policy = l2tp_nl_policy, | ||
770 | .flags = GENL_ADMIN_PERM, | ||
771 | }, | ||
772 | }; | ||
773 | |||
774 | int l2tp_nl_register_ops(enum l2tp_pwtype pw_type, const struct l2tp_nl_cmd_ops *ops) | ||
775 | { | ||
776 | int ret; | ||
777 | |||
778 | ret = -EINVAL; | ||
779 | if (pw_type >= __L2TP_PWTYPE_MAX) | ||
780 | goto err; | ||
781 | |||
782 | genl_lock(); | ||
783 | ret = -EBUSY; | ||
784 | if (l2tp_nl_cmd_ops[pw_type]) | ||
785 | goto out; | ||
786 | |||
787 | l2tp_nl_cmd_ops[pw_type] = ops; | ||
788 | |||
789 | out: | ||
790 | genl_unlock(); | ||
791 | err: | ||
792 | return 0; | ||
793 | } | ||
794 | EXPORT_SYMBOL_GPL(l2tp_nl_register_ops); | ||
795 | |||
796 | void l2tp_nl_unregister_ops(enum l2tp_pwtype pw_type) | ||
797 | { | ||
798 | if (pw_type < __L2TP_PWTYPE_MAX) { | ||
799 | genl_lock(); | ||
800 | l2tp_nl_cmd_ops[pw_type] = NULL; | ||
801 | genl_unlock(); | ||
802 | } | ||
803 | } | ||
804 | EXPORT_SYMBOL_GPL(l2tp_nl_unregister_ops); | ||
805 | |||
806 | static int l2tp_nl_init(void) | ||
807 | { | ||
808 | int err; | ||
809 | |||
810 | printk(KERN_INFO "L2TP netlink interface\n"); | ||
811 | err = genl_register_family_with_ops(&l2tp_nl_family, l2tp_nl_ops, | ||
812 | ARRAY_SIZE(l2tp_nl_ops)); | ||
813 | |||
814 | return err; | ||
815 | } | ||
816 | |||
817 | static void l2tp_nl_cleanup(void) | ||
818 | { | ||
819 | genl_unregister_family(&l2tp_nl_family); | ||
820 | } | ||
821 | |||
822 | module_init(l2tp_nl_init); | ||
823 | module_exit(l2tp_nl_cleanup); | ||
824 | |||
825 | MODULE_AUTHOR("James Chapman <jchapman@katalix.com>"); | ||
826 | MODULE_DESCRIPTION("L2TP netlink"); | ||
827 | MODULE_LICENSE("GPL"); | ||
828 | MODULE_VERSION("1.0"); | ||
829 | MODULE_ALIAS("net-pf-" __stringify(PF_NETLINK) "-proto-" \ | ||
830 | __stringify(NETLINK_GENERIC) "-type-" "l2tp") | ||