diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2010-05-21 00:04:44 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-05-21 00:04:44 -0400 |
commit | f8965467f366fd18f01feafb5db10512d7b4422c (patch) | |
tree | 3706a9cd779859271ca61b85c63a1bc3f82d626e /net/l2tp/l2tp_netlink.c | |
parent | a26272e5200765691e67d6780e52b32498fdb659 (diff) | |
parent | 2ec8c6bb5d8f3a62a79f463525054bae1e3d4487 (diff) |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next-2.6
* git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next-2.6: (1674 commits)
qlcnic: adding co maintainer
ixgbe: add support for active DA cables
ixgbe: dcb, do not tag tc_prio_control frames
ixgbe: fix ixgbe_tx_is_paused logic
ixgbe: always enable vlan strip/insert when DCB is enabled
ixgbe: remove some redundant code in setting FCoE FIP filter
ixgbe: fix wrong offset to fc_frame_header in ixgbe_fcoe_ddp
ixgbe: fix header len when unsplit packet overflows to data buffer
ipv6: Never schedule DAD timer on dead address
ipv6: Use POSTDAD state
ipv6: Use state_lock to protect ifa state
ipv6: Replace inet6_ifaddr->dead with state
cxgb4: notify upper drivers if the device is already up when they load
cxgb4: keep interrupts available when the ports are brought down
cxgb4: fix initial addition of MAC address
cnic: Return SPQ credit to bnx2x after ring setup and shutdown.
cnic: Convert cnic_local_flags to atomic ops.
can: Fix SJA1000 command register writes on SMP systems
bridge: fix build for CONFIG_SYSFS disabled
ARCNET: Limit com20020 PCI ID matches for SOHARD cards
...
Fix up various conflicts with pcmcia tree drivers/net/
{pcmcia/3c589_cs.c, wireless/orinoco/orinoco_cs.c and
wireless/orinoco/spectrum_cs.c} and feature removal
(Documentation/feature-removal-schedule.txt).
Also fix a non-content conflict due to pm_qos_requirement getting
renamed in the PM tree (now pm_qos_request) in net/mac80211/scan.c
Diffstat (limited to 'net/l2tp/l2tp_netlink.c')
-rw-r--r-- | net/l2tp/l2tp_netlink.c | 840 |
1 files changed, 840 insertions, 0 deletions
diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c new file mode 100644 index 000000000000..4c1e540732d7 --- /dev/null +++ b/net/l2tp/l2tp_netlink.c | |||
@@ -0,0 +1,840 @@ | |||
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 | fd = -1; | ||
133 | if (info->attrs[L2TP_ATTR_FD]) { | ||
134 | fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]); | ||
135 | } else { | ||
136 | if (info->attrs[L2TP_ATTR_IP_SADDR]) | ||
137 | cfg.local_ip.s_addr = nla_get_be32(info->attrs[L2TP_ATTR_IP_SADDR]); | ||
138 | if (info->attrs[L2TP_ATTR_IP_DADDR]) | ||
139 | cfg.peer_ip.s_addr = nla_get_be32(info->attrs[L2TP_ATTR_IP_DADDR]); | ||
140 | if (info->attrs[L2TP_ATTR_UDP_SPORT]) | ||
141 | cfg.local_udp_port = nla_get_u16(info->attrs[L2TP_ATTR_UDP_SPORT]); | ||
142 | if (info->attrs[L2TP_ATTR_UDP_DPORT]) | ||
143 | cfg.peer_udp_port = nla_get_u16(info->attrs[L2TP_ATTR_UDP_DPORT]); | ||
144 | if (info->attrs[L2TP_ATTR_UDP_CSUM]) | ||
145 | cfg.use_udp_checksums = nla_get_flag(info->attrs[L2TP_ATTR_UDP_CSUM]); | ||
146 | } | ||
147 | |||
148 | if (info->attrs[L2TP_ATTR_DEBUG]) | ||
149 | cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); | ||
150 | |||
151 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
152 | if (tunnel != NULL) { | ||
153 | ret = -EEXIST; | ||
154 | goto out; | ||
155 | } | ||
156 | |||
157 | ret = -EINVAL; | ||
158 | switch (cfg.encap) { | ||
159 | case L2TP_ENCAPTYPE_UDP: | ||
160 | case L2TP_ENCAPTYPE_IP: | ||
161 | ret = l2tp_tunnel_create(net, fd, proto_version, tunnel_id, | ||
162 | peer_tunnel_id, &cfg, &tunnel); | ||
163 | break; | ||
164 | } | ||
165 | |||
166 | out: | ||
167 | return ret; | ||
168 | } | ||
169 | |||
170 | static int l2tp_nl_cmd_tunnel_delete(struct sk_buff *skb, struct genl_info *info) | ||
171 | { | ||
172 | struct l2tp_tunnel *tunnel; | ||
173 | u32 tunnel_id; | ||
174 | int ret = 0; | ||
175 | struct net *net = genl_info_net(info); | ||
176 | |||
177 | if (!info->attrs[L2TP_ATTR_CONN_ID]) { | ||
178 | ret = -EINVAL; | ||
179 | goto out; | ||
180 | } | ||
181 | tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); | ||
182 | |||
183 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
184 | if (tunnel == NULL) { | ||
185 | ret = -ENODEV; | ||
186 | goto out; | ||
187 | } | ||
188 | |||
189 | (void) l2tp_tunnel_delete(tunnel); | ||
190 | |||
191 | out: | ||
192 | return ret; | ||
193 | } | ||
194 | |||
195 | static int l2tp_nl_cmd_tunnel_modify(struct sk_buff *skb, struct genl_info *info) | ||
196 | { | ||
197 | struct l2tp_tunnel *tunnel; | ||
198 | u32 tunnel_id; | ||
199 | int ret = 0; | ||
200 | struct net *net = genl_info_net(info); | ||
201 | |||
202 | if (!info->attrs[L2TP_ATTR_CONN_ID]) { | ||
203 | ret = -EINVAL; | ||
204 | goto out; | ||
205 | } | ||
206 | tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); | ||
207 | |||
208 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
209 | if (tunnel == NULL) { | ||
210 | ret = -ENODEV; | ||
211 | goto out; | ||
212 | } | ||
213 | |||
214 | if (info->attrs[L2TP_ATTR_DEBUG]) | ||
215 | tunnel->debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); | ||
216 | |||
217 | out: | ||
218 | return ret; | ||
219 | } | ||
220 | |||
221 | static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 pid, u32 seq, int flags, | ||
222 | struct l2tp_tunnel *tunnel) | ||
223 | { | ||
224 | void *hdr; | ||
225 | struct nlattr *nest; | ||
226 | struct sock *sk = NULL; | ||
227 | struct inet_sock *inet; | ||
228 | |||
229 | hdr = genlmsg_put(skb, pid, seq, &l2tp_nl_family, flags, | ||
230 | L2TP_CMD_TUNNEL_GET); | ||
231 | if (IS_ERR(hdr)) | ||
232 | return PTR_ERR(hdr); | ||
233 | |||
234 | NLA_PUT_U8(skb, L2TP_ATTR_PROTO_VERSION, tunnel->version); | ||
235 | NLA_PUT_U32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id); | ||
236 | NLA_PUT_U32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id); | ||
237 | NLA_PUT_U32(skb, L2TP_ATTR_DEBUG, tunnel->debug); | ||
238 | NLA_PUT_U16(skb, L2TP_ATTR_ENCAP_TYPE, tunnel->encap); | ||
239 | |||
240 | nest = nla_nest_start(skb, L2TP_ATTR_STATS); | ||
241 | if (nest == NULL) | ||
242 | goto nla_put_failure; | ||
243 | |||
244 | NLA_PUT_U64(skb, L2TP_ATTR_TX_PACKETS, tunnel->stats.tx_packets); | ||
245 | NLA_PUT_U64(skb, L2TP_ATTR_TX_BYTES, tunnel->stats.tx_bytes); | ||
246 | NLA_PUT_U64(skb, L2TP_ATTR_TX_ERRORS, tunnel->stats.tx_errors); | ||
247 | NLA_PUT_U64(skb, L2TP_ATTR_RX_PACKETS, tunnel->stats.rx_packets); | ||
248 | NLA_PUT_U64(skb, L2TP_ATTR_RX_BYTES, tunnel->stats.rx_bytes); | ||
249 | NLA_PUT_U64(skb, L2TP_ATTR_RX_SEQ_DISCARDS, tunnel->stats.rx_seq_discards); | ||
250 | NLA_PUT_U64(skb, L2TP_ATTR_RX_OOS_PACKETS, tunnel->stats.rx_oos_packets); | ||
251 | NLA_PUT_U64(skb, L2TP_ATTR_RX_ERRORS, tunnel->stats.rx_errors); | ||
252 | nla_nest_end(skb, nest); | ||
253 | |||
254 | sk = tunnel->sock; | ||
255 | if (!sk) | ||
256 | goto out; | ||
257 | |||
258 | inet = inet_sk(sk); | ||
259 | |||
260 | switch (tunnel->encap) { | ||
261 | case L2TP_ENCAPTYPE_UDP: | ||
262 | NLA_PUT_U16(skb, L2TP_ATTR_UDP_SPORT, ntohs(inet->inet_sport)); | ||
263 | NLA_PUT_U16(skb, L2TP_ATTR_UDP_DPORT, ntohs(inet->inet_dport)); | ||
264 | NLA_PUT_U8(skb, L2TP_ATTR_UDP_CSUM, (sk->sk_no_check != UDP_CSUM_NOXMIT)); | ||
265 | /* NOBREAK */ | ||
266 | case L2TP_ENCAPTYPE_IP: | ||
267 | NLA_PUT_BE32(skb, L2TP_ATTR_IP_SADDR, inet->inet_saddr); | ||
268 | NLA_PUT_BE32(skb, L2TP_ATTR_IP_DADDR, inet->inet_daddr); | ||
269 | break; | ||
270 | } | ||
271 | |||
272 | out: | ||
273 | return genlmsg_end(skb, hdr); | ||
274 | |||
275 | nla_put_failure: | ||
276 | genlmsg_cancel(skb, hdr); | ||
277 | return -1; | ||
278 | } | ||
279 | |||
280 | static int l2tp_nl_cmd_tunnel_get(struct sk_buff *skb, struct genl_info *info) | ||
281 | { | ||
282 | struct l2tp_tunnel *tunnel; | ||
283 | struct sk_buff *msg; | ||
284 | u32 tunnel_id; | ||
285 | int ret = -ENOBUFS; | ||
286 | struct net *net = genl_info_net(info); | ||
287 | |||
288 | if (!info->attrs[L2TP_ATTR_CONN_ID]) { | ||
289 | ret = -EINVAL; | ||
290 | goto out; | ||
291 | } | ||
292 | |||
293 | tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); | ||
294 | |||
295 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
296 | if (tunnel == NULL) { | ||
297 | ret = -ENODEV; | ||
298 | goto out; | ||
299 | } | ||
300 | |||
301 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
302 | if (!msg) { | ||
303 | ret = -ENOMEM; | ||
304 | goto out; | ||
305 | } | ||
306 | |||
307 | ret = l2tp_nl_tunnel_send(msg, info->snd_pid, info->snd_seq, | ||
308 | NLM_F_ACK, tunnel); | ||
309 | if (ret < 0) | ||
310 | goto err_out; | ||
311 | |||
312 | return genlmsg_unicast(net, msg, info->snd_pid); | ||
313 | |||
314 | err_out: | ||
315 | nlmsg_free(msg); | ||
316 | |||
317 | out: | ||
318 | return ret; | ||
319 | } | ||
320 | |||
321 | static int l2tp_nl_cmd_tunnel_dump(struct sk_buff *skb, struct netlink_callback *cb) | ||
322 | { | ||
323 | int ti = cb->args[0]; | ||
324 | struct l2tp_tunnel *tunnel; | ||
325 | struct net *net = sock_net(skb->sk); | ||
326 | |||
327 | for (;;) { | ||
328 | tunnel = l2tp_tunnel_find_nth(net, ti); | ||
329 | if (tunnel == NULL) | ||
330 | goto out; | ||
331 | |||
332 | if (l2tp_nl_tunnel_send(skb, NETLINK_CB(cb->skb).pid, | ||
333 | cb->nlh->nlmsg_seq, NLM_F_MULTI, | ||
334 | tunnel) <= 0) | ||
335 | goto out; | ||
336 | |||
337 | ti++; | ||
338 | } | ||
339 | |||
340 | out: | ||
341 | cb->args[0] = ti; | ||
342 | |||
343 | return skb->len; | ||
344 | } | ||
345 | |||
346 | static int l2tp_nl_cmd_session_create(struct sk_buff *skb, struct genl_info *info) | ||
347 | { | ||
348 | u32 tunnel_id = 0; | ||
349 | u32 session_id; | ||
350 | u32 peer_session_id; | ||
351 | int ret = 0; | ||
352 | struct l2tp_tunnel *tunnel; | ||
353 | struct l2tp_session *session; | ||
354 | struct l2tp_session_cfg cfg = { 0, }; | ||
355 | struct net *net = genl_info_net(info); | ||
356 | |||
357 | if (!info->attrs[L2TP_ATTR_CONN_ID]) { | ||
358 | ret = -EINVAL; | ||
359 | goto out; | ||
360 | } | ||
361 | tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); | ||
362 | tunnel = l2tp_tunnel_find(net, tunnel_id); | ||
363 | if (!tunnel) { | ||
364 | ret = -ENODEV; | ||
365 | goto out; | ||
366 | } | ||
367 | |||
368 | if (!info->attrs[L2TP_ATTR_SESSION_ID]) { | ||
369 | ret = -EINVAL; | ||
370 | goto out; | ||
371 | } | ||
372 | session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]); | ||
373 | session = l2tp_session_find(net, tunnel, session_id); | ||
374 | if (session) { | ||
375 | ret = -EEXIST; | ||
376 | goto out; | ||
377 | } | ||
378 | |||
379 | if (!info->attrs[L2TP_ATTR_PEER_SESSION_ID]) { | ||
380 | ret = -EINVAL; | ||
381 | goto out; | ||
382 | } | ||
383 | peer_session_id = nla_get_u32(info->attrs[L2TP_ATTR_PEER_SESSION_ID]); | ||
384 | |||
385 | if (!info->attrs[L2TP_ATTR_PW_TYPE]) { | ||
386 | ret = -EINVAL; | ||
387 | goto out; | ||
388 | } | ||
389 | cfg.pw_type = nla_get_u16(info->attrs[L2TP_ATTR_PW_TYPE]); | ||
390 | if (cfg.pw_type >= __L2TP_PWTYPE_MAX) { | ||
391 | ret = -EINVAL; | ||
392 | goto out; | ||
393 | } | ||
394 | |||
395 | if (tunnel->version > 2) { | ||
396 | if (info->attrs[L2TP_ATTR_OFFSET]) | ||
397 | cfg.offset = nla_get_u16(info->attrs[L2TP_ATTR_OFFSET]); | ||
398 | |||
399 | if (info->attrs[L2TP_ATTR_DATA_SEQ]) | ||
400 | cfg.data_seq = nla_get_u8(info->attrs[L2TP_ATTR_DATA_SEQ]); | ||
401 | |||
402 | cfg.l2specific_type = L2TP_L2SPECTYPE_DEFAULT; | ||
403 | if (info->attrs[L2TP_ATTR_L2SPEC_TYPE]) | ||
404 | cfg.l2specific_type = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_TYPE]); | ||
405 | |||
406 | cfg.l2specific_len = 4; | ||
407 | if (info->attrs[L2TP_ATTR_L2SPEC_LEN]) | ||
408 | cfg.l2specific_len = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_LEN]); | ||
409 | |||
410 | if (info->attrs[L2TP_ATTR_COOKIE]) { | ||
411 | u16 len = nla_len(info->attrs[L2TP_ATTR_COOKIE]); | ||
412 | if (len > 8) { | ||
413 | ret = -EINVAL; | ||
414 | goto out; | ||
415 | } | ||
416 | cfg.cookie_len = len; | ||
417 | memcpy(&cfg.cookie[0], nla_data(info->attrs[L2TP_ATTR_COOKIE]), len); | ||
418 | } | ||
419 | if (info->attrs[L2TP_ATTR_PEER_COOKIE]) { | ||
420 | u16 len = nla_len(info->attrs[L2TP_ATTR_PEER_COOKIE]); | ||
421 | if (len > 8) { | ||
422 | ret = -EINVAL; | ||
423 | goto out; | ||
424 | } | ||
425 | cfg.peer_cookie_len = len; | ||
426 | memcpy(&cfg.peer_cookie[0], nla_data(info->attrs[L2TP_ATTR_PEER_COOKIE]), len); | ||
427 | } | ||
428 | if (info->attrs[L2TP_ATTR_IFNAME]) | ||
429 | cfg.ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]); | ||
430 | |||
431 | if (info->attrs[L2TP_ATTR_VLAN_ID]) | ||
432 | cfg.vlan_id = nla_get_u16(info->attrs[L2TP_ATTR_VLAN_ID]); | ||
433 | } | ||
434 | |||
435 | if (info->attrs[L2TP_ATTR_DEBUG]) | ||
436 | cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); | ||
437 | |||
438 | if (info->attrs[L2TP_ATTR_RECV_SEQ]) | ||
439 | cfg.recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]); | ||
440 | |||
441 | if (info->attrs[L2TP_ATTR_SEND_SEQ]) | ||
442 | cfg.send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]); | ||
443 | |||
444 | if (info->attrs[L2TP_ATTR_LNS_MODE]) | ||
445 | cfg.lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]); | ||
446 | |||
447 | if (info->attrs[L2TP_ATTR_RECV_TIMEOUT]) | ||
448 | cfg.reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]); | ||
449 | |||
450 | if (info->attrs[L2TP_ATTR_MTU]) | ||
451 | cfg.mtu = nla_get_u16(info->attrs[L2TP_ATTR_MTU]); | ||
452 | |||
453 | if (info->attrs[L2TP_ATTR_MRU]) | ||
454 | cfg.mru = nla_get_u16(info->attrs[L2TP_ATTR_MRU]); | ||
455 | |||
456 | if ((l2tp_nl_cmd_ops[cfg.pw_type] == NULL) || | ||
457 | (l2tp_nl_cmd_ops[cfg.pw_type]->session_create == NULL)) { | ||
458 | ret = -EPROTONOSUPPORT; | ||
459 | goto out; | ||
460 | } | ||
461 | |||
462 | /* Check that pseudowire-specific params are present */ | ||
463 | switch (cfg.pw_type) { | ||
464 | case L2TP_PWTYPE_NONE: | ||
465 | break; | ||
466 | case L2TP_PWTYPE_ETH_VLAN: | ||
467 | if (!info->attrs[L2TP_ATTR_VLAN_ID]) { | ||
468 | ret = -EINVAL; | ||
469 | goto out; | ||
470 | } | ||
471 | break; | ||
472 | case L2TP_PWTYPE_ETH: | ||
473 | break; | ||
474 | case L2TP_PWTYPE_PPP: | ||
475 | case L2TP_PWTYPE_PPP_AC: | ||
476 | break; | ||
477 | case L2TP_PWTYPE_IP: | ||
478 | default: | ||
479 | ret = -EPROTONOSUPPORT; | ||
480 | break; | ||
481 | } | ||
482 | |||
483 | ret = -EPROTONOSUPPORT; | ||
484 | if (l2tp_nl_cmd_ops[cfg.pw_type]->session_create) | ||
485 | ret = (*l2tp_nl_cmd_ops[cfg.pw_type]->session_create)(net, tunnel_id, | ||
486 | session_id, peer_session_id, &cfg); | ||
487 | |||
488 | out: | ||
489 | return ret; | ||
490 | } | ||
491 | |||
492 | static int l2tp_nl_cmd_session_delete(struct sk_buff *skb, struct genl_info *info) | ||
493 | { | ||
494 | int ret = 0; | ||
495 | struct l2tp_session *session; | ||
496 | u16 pw_type; | ||
497 | |||
498 | session = l2tp_nl_session_find(info); | ||
499 | if (session == NULL) { | ||
500 | ret = -ENODEV; | ||
501 | goto out; | ||
502 | } | ||
503 | |||
504 | pw_type = session->pwtype; | ||
505 | if (pw_type < __L2TP_PWTYPE_MAX) | ||
506 | if (l2tp_nl_cmd_ops[pw_type] && l2tp_nl_cmd_ops[pw_type]->session_delete) | ||
507 | ret = (*l2tp_nl_cmd_ops[pw_type]->session_delete)(session); | ||
508 | |||
509 | out: | ||
510 | return ret; | ||
511 | } | ||
512 | |||
513 | static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *info) | ||
514 | { | ||
515 | int ret = 0; | ||
516 | struct l2tp_session *session; | ||
517 | |||
518 | session = l2tp_nl_session_find(info); | ||
519 | if (session == NULL) { | ||
520 | ret = -ENODEV; | ||
521 | goto out; | ||
522 | } | ||
523 | |||
524 | if (info->attrs[L2TP_ATTR_DEBUG]) | ||
525 | session->debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); | ||
526 | |||
527 | if (info->attrs[L2TP_ATTR_DATA_SEQ]) | ||
528 | session->data_seq = nla_get_u8(info->attrs[L2TP_ATTR_DATA_SEQ]); | ||
529 | |||
530 | if (info->attrs[L2TP_ATTR_RECV_SEQ]) | ||
531 | session->recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]); | ||
532 | |||
533 | if (info->attrs[L2TP_ATTR_SEND_SEQ]) | ||
534 | session->send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]); | ||
535 | |||
536 | if (info->attrs[L2TP_ATTR_LNS_MODE]) | ||
537 | session->lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]); | ||
538 | |||
539 | if (info->attrs[L2TP_ATTR_RECV_TIMEOUT]) | ||
540 | session->reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]); | ||
541 | |||
542 | if (info->attrs[L2TP_ATTR_MTU]) | ||
543 | session->mtu = nla_get_u16(info->attrs[L2TP_ATTR_MTU]); | ||
544 | |||
545 | if (info->attrs[L2TP_ATTR_MRU]) | ||
546 | session->mru = nla_get_u16(info->attrs[L2TP_ATTR_MRU]); | ||
547 | |||
548 | out: | ||
549 | return ret; | ||
550 | } | ||
551 | |||
552 | static int l2tp_nl_session_send(struct sk_buff *skb, u32 pid, u32 seq, int flags, | ||
553 | struct l2tp_session *session) | ||
554 | { | ||
555 | void *hdr; | ||
556 | struct nlattr *nest; | ||
557 | struct l2tp_tunnel *tunnel = session->tunnel; | ||
558 | struct sock *sk = NULL; | ||
559 | |||
560 | sk = tunnel->sock; | ||
561 | |||
562 | hdr = genlmsg_put(skb, pid, seq, &l2tp_nl_family, flags, L2TP_CMD_SESSION_GET); | ||
563 | if (IS_ERR(hdr)) | ||
564 | return PTR_ERR(hdr); | ||
565 | |||
566 | NLA_PUT_U32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id); | ||
567 | NLA_PUT_U32(skb, L2TP_ATTR_SESSION_ID, session->session_id); | ||
568 | NLA_PUT_U32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id); | ||
569 | NLA_PUT_U32(skb, L2TP_ATTR_PEER_SESSION_ID, session->peer_session_id); | ||
570 | NLA_PUT_U32(skb, L2TP_ATTR_DEBUG, session->debug); | ||
571 | NLA_PUT_U16(skb, L2TP_ATTR_PW_TYPE, session->pwtype); | ||
572 | NLA_PUT_U16(skb, L2TP_ATTR_MTU, session->mtu); | ||
573 | if (session->mru) | ||
574 | NLA_PUT_U16(skb, L2TP_ATTR_MRU, session->mru); | ||
575 | |||
576 | if (session->ifname && session->ifname[0]) | ||
577 | NLA_PUT_STRING(skb, L2TP_ATTR_IFNAME, session->ifname); | ||
578 | if (session->cookie_len) | ||
579 | NLA_PUT(skb, L2TP_ATTR_COOKIE, session->cookie_len, &session->cookie[0]); | ||
580 | if (session->peer_cookie_len) | ||
581 | NLA_PUT(skb, L2TP_ATTR_PEER_COOKIE, session->peer_cookie_len, &session->peer_cookie[0]); | ||
582 | NLA_PUT_U8(skb, L2TP_ATTR_RECV_SEQ, session->recv_seq); | ||
583 | NLA_PUT_U8(skb, L2TP_ATTR_SEND_SEQ, session->send_seq); | ||
584 | NLA_PUT_U8(skb, L2TP_ATTR_LNS_MODE, session->lns_mode); | ||
585 | #ifdef CONFIG_XFRM | ||
586 | if ((sk) && (sk->sk_policy[0] || sk->sk_policy[1])) | ||
587 | NLA_PUT_U8(skb, L2TP_ATTR_USING_IPSEC, 1); | ||
588 | #endif | ||
589 | if (session->reorder_timeout) | ||
590 | NLA_PUT_MSECS(skb, L2TP_ATTR_RECV_TIMEOUT, session->reorder_timeout); | ||
591 | |||
592 | nest = nla_nest_start(skb, L2TP_ATTR_STATS); | ||
593 | if (nest == NULL) | ||
594 | goto nla_put_failure; | ||
595 | NLA_PUT_U64(skb, L2TP_ATTR_TX_PACKETS, session->stats.tx_packets); | ||
596 | NLA_PUT_U64(skb, L2TP_ATTR_TX_BYTES, session->stats.tx_bytes); | ||
597 | NLA_PUT_U64(skb, L2TP_ATTR_TX_ERRORS, session->stats.tx_errors); | ||
598 | NLA_PUT_U64(skb, L2TP_ATTR_RX_PACKETS, session->stats.rx_packets); | ||
599 | NLA_PUT_U64(skb, L2TP_ATTR_RX_BYTES, session->stats.rx_bytes); | ||
600 | NLA_PUT_U64(skb, L2TP_ATTR_RX_SEQ_DISCARDS, session->stats.rx_seq_discards); | ||
601 | NLA_PUT_U64(skb, L2TP_ATTR_RX_OOS_PACKETS, session->stats.rx_oos_packets); | ||
602 | NLA_PUT_U64(skb, L2TP_ATTR_RX_ERRORS, session->stats.rx_errors); | ||
603 | nla_nest_end(skb, nest); | ||
604 | |||
605 | return genlmsg_end(skb, hdr); | ||
606 | |||
607 | nla_put_failure: | ||
608 | genlmsg_cancel(skb, hdr); | ||
609 | return -1; | ||
610 | } | ||
611 | |||
612 | static int l2tp_nl_cmd_session_get(struct sk_buff *skb, struct genl_info *info) | ||
613 | { | ||
614 | struct l2tp_session *session; | ||
615 | struct sk_buff *msg; | ||
616 | int ret; | ||
617 | |||
618 | session = l2tp_nl_session_find(info); | ||
619 | if (session == NULL) { | ||
620 | ret = -ENODEV; | ||
621 | goto out; | ||
622 | } | ||
623 | |||
624 | msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
625 | if (!msg) { | ||
626 | ret = -ENOMEM; | ||
627 | goto out; | ||
628 | } | ||
629 | |||
630 | ret = l2tp_nl_session_send(msg, info->snd_pid, info->snd_seq, | ||
631 | 0, session); | ||
632 | if (ret < 0) | ||
633 | goto err_out; | ||
634 | |||
635 | return genlmsg_unicast(genl_info_net(info), msg, info->snd_pid); | ||
636 | |||
637 | err_out: | ||
638 | nlmsg_free(msg); | ||
639 | |||
640 | out: | ||
641 | return ret; | ||
642 | } | ||
643 | |||
644 | static int l2tp_nl_cmd_session_dump(struct sk_buff *skb, struct netlink_callback *cb) | ||
645 | { | ||
646 | struct net *net = sock_net(skb->sk); | ||
647 | struct l2tp_session *session; | ||
648 | struct l2tp_tunnel *tunnel = NULL; | ||
649 | int ti = cb->args[0]; | ||
650 | int si = cb->args[1]; | ||
651 | |||
652 | for (;;) { | ||
653 | if (tunnel == NULL) { | ||
654 | tunnel = l2tp_tunnel_find_nth(net, ti); | ||
655 | if (tunnel == NULL) | ||
656 | goto out; | ||
657 | } | ||
658 | |||
659 | session = l2tp_session_find_nth(tunnel, si); | ||
660 | if (session == NULL) { | ||
661 | ti++; | ||
662 | tunnel = NULL; | ||
663 | si = 0; | ||
664 | continue; | ||
665 | } | ||
666 | |||
667 | if (l2tp_nl_session_send(skb, NETLINK_CB(cb->skb).pid, | ||
668 | cb->nlh->nlmsg_seq, NLM_F_MULTI, | ||
669 | session) <= 0) | ||
670 | break; | ||
671 | |||
672 | si++; | ||
673 | } | ||
674 | |||
675 | out: | ||
676 | cb->args[0] = ti; | ||
677 | cb->args[1] = si; | ||
678 | |||
679 | return skb->len; | ||
680 | } | ||
681 | |||
682 | static struct nla_policy l2tp_nl_policy[L2TP_ATTR_MAX + 1] = { | ||
683 | [L2TP_ATTR_NONE] = { .type = NLA_UNSPEC, }, | ||
684 | [L2TP_ATTR_PW_TYPE] = { .type = NLA_U16, }, | ||
685 | [L2TP_ATTR_ENCAP_TYPE] = { .type = NLA_U16, }, | ||
686 | [L2TP_ATTR_OFFSET] = { .type = NLA_U16, }, | ||
687 | [L2TP_ATTR_DATA_SEQ] = { .type = NLA_U8, }, | ||
688 | [L2TP_ATTR_L2SPEC_TYPE] = { .type = NLA_U8, }, | ||
689 | [L2TP_ATTR_L2SPEC_LEN] = { .type = NLA_U8, }, | ||
690 | [L2TP_ATTR_PROTO_VERSION] = { .type = NLA_U8, }, | ||
691 | [L2TP_ATTR_CONN_ID] = { .type = NLA_U32, }, | ||
692 | [L2TP_ATTR_PEER_CONN_ID] = { .type = NLA_U32, }, | ||
693 | [L2TP_ATTR_SESSION_ID] = { .type = NLA_U32, }, | ||
694 | [L2TP_ATTR_PEER_SESSION_ID] = { .type = NLA_U32, }, | ||
695 | [L2TP_ATTR_UDP_CSUM] = { .type = NLA_U8, }, | ||
696 | [L2TP_ATTR_VLAN_ID] = { .type = NLA_U16, }, | ||
697 | [L2TP_ATTR_DEBUG] = { .type = NLA_U32, }, | ||
698 | [L2TP_ATTR_RECV_SEQ] = { .type = NLA_U8, }, | ||
699 | [L2TP_ATTR_SEND_SEQ] = { .type = NLA_U8, }, | ||
700 | [L2TP_ATTR_LNS_MODE] = { .type = NLA_U8, }, | ||
701 | [L2TP_ATTR_USING_IPSEC] = { .type = NLA_U8, }, | ||
702 | [L2TP_ATTR_RECV_TIMEOUT] = { .type = NLA_MSECS, }, | ||
703 | [L2TP_ATTR_FD] = { .type = NLA_U32, }, | ||
704 | [L2TP_ATTR_IP_SADDR] = { .type = NLA_U32, }, | ||
705 | [L2TP_ATTR_IP_DADDR] = { .type = NLA_U32, }, | ||
706 | [L2TP_ATTR_UDP_SPORT] = { .type = NLA_U16, }, | ||
707 | [L2TP_ATTR_UDP_DPORT] = { .type = NLA_U16, }, | ||
708 | [L2TP_ATTR_MTU] = { .type = NLA_U16, }, | ||
709 | [L2TP_ATTR_MRU] = { .type = NLA_U16, }, | ||
710 | [L2TP_ATTR_STATS] = { .type = NLA_NESTED, }, | ||
711 | [L2TP_ATTR_IFNAME] = { | ||
712 | .type = NLA_NUL_STRING, | ||
713 | .len = IFNAMSIZ - 1, | ||
714 | }, | ||
715 | [L2TP_ATTR_COOKIE] = { | ||
716 | .type = NLA_BINARY, | ||
717 | .len = 8, | ||
718 | }, | ||
719 | [L2TP_ATTR_PEER_COOKIE] = { | ||
720 | .type = NLA_BINARY, | ||
721 | .len = 8, | ||
722 | }, | ||
723 | }; | ||
724 | |||
725 | static struct genl_ops l2tp_nl_ops[] = { | ||
726 | { | ||
727 | .cmd = L2TP_CMD_NOOP, | ||
728 | .doit = l2tp_nl_cmd_noop, | ||
729 | .policy = l2tp_nl_policy, | ||
730 | /* can be retrieved by unprivileged users */ | ||
731 | }, | ||
732 | { | ||
733 | .cmd = L2TP_CMD_TUNNEL_CREATE, | ||
734 | .doit = l2tp_nl_cmd_tunnel_create, | ||
735 | .policy = l2tp_nl_policy, | ||
736 | .flags = GENL_ADMIN_PERM, | ||
737 | }, | ||
738 | { | ||
739 | .cmd = L2TP_CMD_TUNNEL_DELETE, | ||
740 | .doit = l2tp_nl_cmd_tunnel_delete, | ||
741 | .policy = l2tp_nl_policy, | ||
742 | .flags = GENL_ADMIN_PERM, | ||
743 | }, | ||
744 | { | ||
745 | .cmd = L2TP_CMD_TUNNEL_MODIFY, | ||
746 | .doit = l2tp_nl_cmd_tunnel_modify, | ||
747 | .policy = l2tp_nl_policy, | ||
748 | .flags = GENL_ADMIN_PERM, | ||
749 | }, | ||
750 | { | ||
751 | .cmd = L2TP_CMD_TUNNEL_GET, | ||
752 | .doit = l2tp_nl_cmd_tunnel_get, | ||
753 | .dumpit = l2tp_nl_cmd_tunnel_dump, | ||
754 | .policy = l2tp_nl_policy, | ||
755 | .flags = GENL_ADMIN_PERM, | ||
756 | }, | ||
757 | { | ||
758 | .cmd = L2TP_CMD_SESSION_CREATE, | ||
759 | .doit = l2tp_nl_cmd_session_create, | ||
760 | .policy = l2tp_nl_policy, | ||
761 | .flags = GENL_ADMIN_PERM, | ||
762 | }, | ||
763 | { | ||
764 | .cmd = L2TP_CMD_SESSION_DELETE, | ||
765 | .doit = l2tp_nl_cmd_session_delete, | ||
766 | .policy = l2tp_nl_policy, | ||
767 | .flags = GENL_ADMIN_PERM, | ||
768 | }, | ||
769 | { | ||
770 | .cmd = L2TP_CMD_SESSION_MODIFY, | ||
771 | .doit = l2tp_nl_cmd_session_modify, | ||
772 | .policy = l2tp_nl_policy, | ||
773 | .flags = GENL_ADMIN_PERM, | ||
774 | }, | ||
775 | { | ||
776 | .cmd = L2TP_CMD_SESSION_GET, | ||
777 | .doit = l2tp_nl_cmd_session_get, | ||
778 | .dumpit = l2tp_nl_cmd_session_dump, | ||
779 | .policy = l2tp_nl_policy, | ||
780 | .flags = GENL_ADMIN_PERM, | ||
781 | }, | ||
782 | }; | ||
783 | |||
784 | int l2tp_nl_register_ops(enum l2tp_pwtype pw_type, const struct l2tp_nl_cmd_ops *ops) | ||
785 | { | ||
786 | int ret; | ||
787 | |||
788 | ret = -EINVAL; | ||
789 | if (pw_type >= __L2TP_PWTYPE_MAX) | ||
790 | goto err; | ||
791 | |||
792 | genl_lock(); | ||
793 | ret = -EBUSY; | ||
794 | if (l2tp_nl_cmd_ops[pw_type]) | ||
795 | goto out; | ||
796 | |||
797 | l2tp_nl_cmd_ops[pw_type] = ops; | ||
798 | |||
799 | out: | ||
800 | genl_unlock(); | ||
801 | err: | ||
802 | return 0; | ||
803 | } | ||
804 | EXPORT_SYMBOL_GPL(l2tp_nl_register_ops); | ||
805 | |||
806 | void l2tp_nl_unregister_ops(enum l2tp_pwtype pw_type) | ||
807 | { | ||
808 | if (pw_type < __L2TP_PWTYPE_MAX) { | ||
809 | genl_lock(); | ||
810 | l2tp_nl_cmd_ops[pw_type] = NULL; | ||
811 | genl_unlock(); | ||
812 | } | ||
813 | } | ||
814 | EXPORT_SYMBOL_GPL(l2tp_nl_unregister_ops); | ||
815 | |||
816 | static int l2tp_nl_init(void) | ||
817 | { | ||
818 | int err; | ||
819 | |||
820 | printk(KERN_INFO "L2TP netlink interface\n"); | ||
821 | err = genl_register_family_with_ops(&l2tp_nl_family, l2tp_nl_ops, | ||
822 | ARRAY_SIZE(l2tp_nl_ops)); | ||
823 | |||
824 | return err; | ||
825 | } | ||
826 | |||
827 | static void l2tp_nl_cleanup(void) | ||
828 | { | ||
829 | genl_unregister_family(&l2tp_nl_family); | ||
830 | } | ||
831 | |||
832 | module_init(l2tp_nl_init); | ||
833 | module_exit(l2tp_nl_cleanup); | ||
834 | |||
835 | MODULE_AUTHOR("James Chapman <jchapman@katalix.com>"); | ||
836 | MODULE_DESCRIPTION("L2TP netlink"); | ||
837 | MODULE_LICENSE("GPL"); | ||
838 | MODULE_VERSION("1.0"); | ||
839 | MODULE_ALIAS("net-pf-" __stringify(PF_NETLINK) "-proto-" \ | ||
840 | __stringify(NETLINK_GENERIC) "-type-" "l2tp"); | ||