]>
Commit | Line | Data |
---|---|---|
38cd311a SH |
1 | /* |
2 | * ipl2tp.c "ip l2tp" | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License | |
6 | * as published by the Free Software Foundation; either version | |
7 | * 2 of the License, or (at your option) any later version. | |
8 | * | |
9 | * Original Author: James Chapman <jchapman@katalix.com> | |
10 | * | |
11 | */ | |
12 | ||
13 | #include <stdio.h> | |
14 | #include <stdlib.h> | |
15 | #include <string.h> | |
16 | #include <unistd.h> | |
17 | #include <errno.h> | |
18 | #include <sys/types.h> | |
19 | #include <sys/socket.h> | |
20 | #include <arpa/inet.h> | |
21 | #include <sys/ioctl.h> | |
22 | #include <linux/if.h> | |
23 | #include <linux/if_arp.h> | |
24 | #include <linux/ip.h> | |
25 | ||
38cd311a SH |
26 | #include <linux/genetlink.h> |
27 | #include <linux/l2tp.h> | |
4ef9ff2a | 28 | #include "libgenl.h" |
38cd311a SH |
29 | |
30 | #include "utils.h" | |
31 | #include "ip_common.h" | |
32 | ||
33 | enum { | |
34 | L2TP_ADD, | |
35 | L2TP_CHG, | |
36 | L2TP_DEL, | |
37 | L2TP_GET | |
38 | }; | |
39 | ||
40 | struct l2tp_parm { | |
41 | uint32_t tunnel_id; | |
42 | uint32_t peer_tunnel_id; | |
43 | uint32_t session_id; | |
44 | uint32_t peer_session_id; | |
38cd311a SH |
45 | enum l2tp_encap_type encap; |
46 | uint16_t local_udp_port; | |
47 | uint16_t peer_udp_port; | |
48 | int cookie_len; | |
49 | uint8_t cookie[8]; | |
50 | int peer_cookie_len; | |
51 | uint8_t peer_cookie[8]; | |
6618e334 CE |
52 | inet_prefix local_ip; |
53 | inet_prefix peer_ip; | |
38cd311a SH |
54 | |
55 | uint16_t pw_type; | |
31f63e7c AST |
56 | unsigned int udp6_csum_tx:1; |
57 | unsigned int udp6_csum_rx:1; | |
58 | unsigned int udp_csum:1; | |
59 | unsigned int recv_seq:1; | |
60 | unsigned int send_seq:1; | |
31f63e7c AST |
61 | unsigned int tunnel:1; |
62 | unsigned int session:1; | |
38cd311a SH |
63 | int reorder_timeout; |
64 | const char *ifname; | |
dd10baa5 JC |
65 | uint8_t l2spec_type; |
66 | uint8_t l2spec_len; | |
38cd311a SH |
67 | }; |
68 | ||
69 | struct l2tp_stats { | |
70 | uint64_t data_rx_packets; | |
71 | uint64_t data_rx_bytes; | |
72 | uint64_t data_rx_errors; | |
73 | uint64_t data_rx_oos_packets; | |
74 | uint64_t data_rx_oos_discards; | |
75 | uint64_t data_tx_packets; | |
76 | uint64_t data_tx_bytes; | |
77 | uint64_t data_tx_errors; | |
78 | }; | |
79 | ||
80 | struct l2tp_data { | |
81 | struct l2tp_parm config; | |
82 | struct l2tp_stats stats; | |
83 | }; | |
84 | ||
85 | /* netlink socket */ | |
86 | static struct rtnl_handle genl_rth; | |
87 | static int genl_family = -1; | |
88 | ||
89 | /***************************************************************************** | |
90 | * Netlink actions | |
91 | *****************************************************************************/ | |
92 | ||
93 | static int create_tunnel(struct l2tp_parm *p) | |
94 | { | |
6618e334 CE |
95 | uint32_t local_attr = L2TP_ATTR_IP_SADDR; |
96 | uint32_t peer_attr = L2TP_ATTR_IP_DADDR; | |
38cd311a | 97 | |
328d482c JA |
98 | GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION, |
99 | L2TP_CMD_TUNNEL_CREATE, NLM_F_REQUEST | NLM_F_ACK); | |
38cd311a SH |
100 | |
101 | addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id); | |
102 | addattr32(&req.n, 1024, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id); | |
103 | addattr8(&req.n, 1024, L2TP_ATTR_PROTO_VERSION, 3); | |
104 | addattr16(&req.n, 1024, L2TP_ATTR_ENCAP_TYPE, p->encap); | |
105 | ||
6618e334 CE |
106 | if (p->local_ip.family == AF_INET6) |
107 | local_attr = L2TP_ATTR_IP6_SADDR; | |
281db53f SH |
108 | addattr_l(&req.n, 1024, local_attr, &p->local_ip.data, |
109 | p->local_ip.bytelen); | |
6618e334 CE |
110 | |
111 | if (p->peer_ip.family == AF_INET6) | |
112 | peer_attr = L2TP_ATTR_IP6_DADDR; | |
281db53f SH |
113 | addattr_l(&req.n, 1024, peer_attr, &p->peer_ip.data, |
114 | p->peer_ip.bytelen); | |
6618e334 | 115 | |
38cd311a SH |
116 | if (p->encap == L2TP_ENCAPTYPE_UDP) { |
117 | addattr16(&req.n, 1024, L2TP_ATTR_UDP_SPORT, p->local_udp_port); | |
118 | addattr16(&req.n, 1024, L2TP_ATTR_UDP_DPORT, p->peer_udp_port); | |
9bf9d05b | 119 | if (p->udp_csum) |
c73fad78 | 120 | addattr8(&req.n, 1024, L2TP_ATTR_UDP_CSUM, 1); |
9bf9d05b SW |
121 | if (!p->udp6_csum_tx) |
122 | addattr(&req.n, 1024, L2TP_ATTR_UDP_ZERO_CSUM6_TX); | |
123 | if (!p->udp6_csum_rx) | |
124 | addattr(&req.n, 1024, L2TP_ATTR_UDP_ZERO_CSUM6_RX); | |
38cd311a SH |
125 | } |
126 | ||
86bf43c7 | 127 | if (rtnl_talk(&genl_rth, &req.n, NULL) < 0) |
38cd311a SH |
128 | return -2; |
129 | ||
130 | return 0; | |
131 | } | |
132 | ||
133 | static int delete_tunnel(struct l2tp_parm *p) | |
134 | { | |
328d482c JA |
135 | GENL_REQUEST(req, 128, genl_family, 0, L2TP_GENL_VERSION, |
136 | L2TP_CMD_TUNNEL_DELETE, NLM_F_REQUEST | NLM_F_ACK); | |
38cd311a SH |
137 | |
138 | addattr32(&req.n, 128, L2TP_ATTR_CONN_ID, p->tunnel_id); | |
139 | ||
86bf43c7 | 140 | if (rtnl_talk(&genl_rth, &req.n, NULL) < 0) |
38cd311a SH |
141 | return -2; |
142 | ||
143 | return 0; | |
144 | } | |
145 | ||
146 | static int create_session(struct l2tp_parm *p) | |
147 | { | |
328d482c JA |
148 | GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION, |
149 | L2TP_CMD_SESSION_CREATE, NLM_F_REQUEST | NLM_F_ACK); | |
38cd311a SH |
150 | |
151 | addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id); | |
152 | addattr32(&req.n, 1024, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id); | |
153 | addattr32(&req.n, 1024, L2TP_ATTR_SESSION_ID, p->session_id); | |
154 | addattr32(&req.n, 1024, L2TP_ATTR_PEER_SESSION_ID, p->peer_session_id); | |
155 | addattr16(&req.n, 1024, L2TP_ATTR_PW_TYPE, p->pw_type); | |
dd10baa5 JC |
156 | addattr8(&req.n, 1024, L2TP_ATTR_L2SPEC_TYPE, p->l2spec_type); |
157 | addattr8(&req.n, 1024, L2TP_ATTR_L2SPEC_LEN, p->l2spec_len); | |
38cd311a | 158 | |
281db53f SH |
159 | if (p->recv_seq) |
160 | addattr8(&req.n, 1024, L2TP_ATTR_RECV_SEQ, 1); | |
161 | if (p->send_seq) | |
162 | addattr8(&req.n, 1024, L2TP_ATTR_SEND_SEQ, 1); | |
281db53f SH |
163 | if (p->reorder_timeout) |
164 | addattr64(&req.n, 1024, L2TP_ATTR_RECV_TIMEOUT, | |
38cd311a | 165 | p->reorder_timeout); |
281db53f SH |
166 | if (p->cookie_len) |
167 | addattr_l(&req.n, 1024, L2TP_ATTR_COOKIE, | |
168 | p->cookie, p->cookie_len); | |
169 | if (p->peer_cookie_len) | |
170 | addattr_l(&req.n, 1024, L2TP_ATTR_PEER_COOKIE, | |
171 | p->peer_cookie, p->peer_cookie_len); | |
625df645 | 172 | if (p->ifname) |
38cd311a SH |
173 | addattrstrz(&req.n, 1024, L2TP_ATTR_IFNAME, p->ifname); |
174 | ||
86bf43c7 | 175 | if (rtnl_talk(&genl_rth, &req.n, NULL) < 0) |
38cd311a SH |
176 | return -2; |
177 | ||
178 | return 0; | |
179 | } | |
180 | ||
181 | static int delete_session(struct l2tp_parm *p) | |
182 | { | |
328d482c JA |
183 | GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION, |
184 | L2TP_CMD_SESSION_DELETE, NLM_F_REQUEST | NLM_F_ACK); | |
38cd311a SH |
185 | |
186 | addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id); | |
187 | addattr32(&req.n, 1024, L2TP_ATTR_SESSION_ID, p->session_id); | |
86bf43c7 | 188 | if (rtnl_talk(&genl_rth, &req.n, NULL) < 0) |
38cd311a SH |
189 | return -2; |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
98453b65 SH |
194 | static void print_cookie(const char *name, const char *fmt, |
195 | const uint8_t *cookie, int len) | |
38cd311a | 196 | { |
98453b65 SH |
197 | char abuf[32]; |
198 | size_t n; | |
199 | ||
200 | n = snprintf(abuf, sizeof(abuf), | |
201 | "%02x%02x%02x%02x", | |
202 | cookie[0], cookie[1], cookie[2], cookie[3]); | |
38cd311a | 203 | if (len == 8) |
98453b65 SH |
204 | snprintf(abuf + n, sizeof(abuf) - n, |
205 | "%02x%02x%02x%02x", | |
206 | cookie[4], cookie[5], | |
207 | cookie[6], cookie[7]); | |
208 | ||
209 | print_string(PRINT_ANY, name, fmt, abuf); | |
38cd311a SH |
210 | } |
211 | ||
212 | static void print_tunnel(const struct l2tp_data *data) | |
213 | { | |
214 | const struct l2tp_parm *p = &data->config; | |
6618e334 | 215 | char buf[INET6_ADDRSTRLEN]; |
38cd311a | 216 | |
98453b65 SH |
217 | open_json_object(NULL); |
218 | print_uint(PRINT_ANY, "tunnel_id", "Tunnel %u,", p->tunnel_id); | |
219 | print_string(PRINT_ANY, "encap", " encap %s", | |
220 | p->encap == L2TP_ENCAPTYPE_UDP ? "UDP" : | |
221 | p->encap == L2TP_ENCAPTYPE_IP ? "IP" : "??"); | |
b85076cd | 222 | print_nl(); |
98453b65 SH |
223 | |
224 | print_string(PRINT_ANY, "local", " From %s ", | |
225 | inet_ntop(p->local_ip.family, p->local_ip.data, | |
226 | buf, sizeof(buf))); | |
227 | print_string(PRINT_ANY, "peer", "to %s", | |
228 | inet_ntop(p->peer_ip.family, p->peer_ip.data, | |
229 | buf, sizeof(buf))); | |
b85076cd | 230 | print_nl(); |
98453b65 SH |
231 | |
232 | print_uint(PRINT_ANY, "peer_tunnel", " Peer tunnel %u", | |
233 | p->peer_tunnel_id); | |
b85076cd | 234 | print_nl(); |
38cd311a | 235 | |
f7982f5c | 236 | if (p->encap == L2TP_ENCAPTYPE_UDP) { |
98453b65 SH |
237 | print_string(PRINT_FP, NULL, |
238 | " UDP source / dest ports:", NULL); | |
239 | ||
240 | print_uint(PRINT_ANY, "local_port", " %hu", | |
241 | p->local_udp_port); | |
242 | print_uint(PRINT_ANY, "peer_port", "/%hu", | |
243 | p->peer_udp_port); | |
b85076cd | 244 | print_nl(); |
f7982f5c AST |
245 | |
246 | switch (p->local_ip.family) { | |
247 | case AF_INET: | |
98453b65 SH |
248 | print_bool(PRINT_JSON, "checksum", |
249 | NULL, p->udp_csum); | |
250 | print_string(PRINT_FP, NULL, | |
251 | " UDP checksum: %s\n", | |
252 | p->udp_csum ? "enabled" : "disabled"); | |
f7982f5c AST |
253 | break; |
254 | case AF_INET6: | |
98453b65 SH |
255 | if (is_json_context()) { |
256 | print_bool(PRINT_JSON, "checksum_tx", | |
257 | NULL, p->udp6_csum_tx); | |
258 | ||
259 | print_bool(PRINT_JSON, "checksum_rx", | |
260 | NULL, p->udp6_csum_tx); | |
261 | } else { | |
262 | printf(" UDP checksum: %s%s%s%s\n", | |
263 | p->udp6_csum_tx && p->udp6_csum_rx | |
264 | ? "enabled" : "", | |
265 | p->udp6_csum_tx && !p->udp6_csum_rx | |
266 | ? "tx" : "", | |
267 | !p->udp6_csum_tx && p->udp6_csum_rx | |
268 | ? "rx" : "", | |
269 | !p->udp6_csum_tx && !p->udp6_csum_rx | |
270 | ? "disabled" : ""); | |
271 | } | |
f7982f5c AST |
272 | break; |
273 | } | |
274 | } | |
98453b65 | 275 | close_json_object(); |
38cd311a SH |
276 | } |
277 | ||
278 | static void print_session(struct l2tp_data *data) | |
279 | { | |
280 | struct l2tp_parm *p = &data->config; | |
281 | ||
98453b65 | 282 | open_json_object(NULL); |
38cd311a | 283 | |
98453b65 SH |
284 | print_uint(PRINT_ANY, "session_id", "Session %u", p->session_id); |
285 | print_uint(PRINT_ANY, "tunnel_id", " in tunnel %u", p->tunnel_id); | |
b85076cd | 286 | print_nl(); |
98453b65 SH |
287 | |
288 | print_uint(PRINT_ANY, "peer_session_id", | |
289 | " Peer session %u,", p->peer_session_id); | |
290 | print_uint(PRINT_ANY, "peer_tunnel_id", | |
291 | " tunnel %u", p->peer_tunnel_id); | |
b85076cd | 292 | print_nl(); |
98453b65 SH |
293 | |
294 | if (p->ifname != NULL) { | |
295 | print_color_string(PRINT_ANY, COLOR_IFNAME, | |
296 | "interface", " interface name: %s" , p->ifname); | |
b85076cd | 297 | print_nl(); |
98453b65 SH |
298 | } |
299 | ||
458539ad GN |
300 | /* Show offsets only for plain console output (for legacy scripts) */ |
301 | print_uint(PRINT_FP, "offset", " offset %u,", 0); | |
302 | print_uint(PRINT_FP, "peer_offset", " peer offset %u\n", 0); | |
281db53f | 303 | |
38cd311a | 304 | if (p->cookie_len > 0) |
98453b65 SH |
305 | print_cookie("cookie", "cookie", |
306 | p->cookie, p->cookie_len); | |
38cd311a | 307 | if (p->peer_cookie_len > 0) |
98453b65 SH |
308 | print_cookie("peer_cookie", "peer cookie", |
309 | p->peer_cookie, p->peer_cookie_len); | |
38cd311a | 310 | |
3649d018 | 311 | if (p->reorder_timeout != 0) |
98453b65 SH |
312 | print_uint(PRINT_ANY, "reorder_timeout", |
313 | " reorder timeout: %u", p->reorder_timeout); | |
314 | ||
315 | ||
8a11421a | 316 | if (p->send_seq || p->recv_seq) { |
98453b65 SH |
317 | print_string(PRINT_FP, NULL, "%s sequence numbering:", _SL_); |
318 | ||
281db53f | 319 | if (p->send_seq) |
98453b65 | 320 | print_null(PRINT_ANY, "send_seq", " send", NULL); |
281db53f | 321 | if (p->recv_seq) |
98453b65 SH |
322 | print_null(PRINT_ANY, "recv_seq", " recv", NULL); |
323 | ||
8a11421a | 324 | } |
98453b65 SH |
325 | print_string(PRINT_FP, NULL, "\n", NULL); |
326 | close_json_object(); | |
38cd311a SH |
327 | } |
328 | ||
329 | static int get_response(struct nlmsghdr *n, void *arg) | |
330 | { | |
331 | struct genlmsghdr *ghdr; | |
332 | struct l2tp_data *data = arg; | |
333 | struct l2tp_parm *p = &data->config; | |
334 | struct rtattr *attrs[L2TP_ATTR_MAX + 1]; | |
62f9f94a | 335 | struct rtattr *nla_stats, *rta; |
38cd311a SH |
336 | int len; |
337 | ||
338 | /* Validate message and parse attributes */ | |
339 | if (n->nlmsg_type == NLMSG_ERROR) | |
340 | return -EBADMSG; | |
341 | ||
342 | ghdr = NLMSG_DATA(n); | |
343 | len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ghdr)); | |
344 | if (len < 0) | |
345 | return -1; | |
346 | ||
347 | parse_rtattr(attrs, L2TP_ATTR_MAX, (void *)ghdr + GENL_HDRLEN, len); | |
348 | ||
349 | if (attrs[L2TP_ATTR_PW_TYPE]) | |
350 | p->pw_type = rta_getattr_u16(attrs[L2TP_ATTR_PW_TYPE]); | |
351 | if (attrs[L2TP_ATTR_ENCAP_TYPE]) | |
352 | p->encap = rta_getattr_u16(attrs[L2TP_ATTR_ENCAP_TYPE]); | |
38cd311a SH |
353 | if (attrs[L2TP_ATTR_CONN_ID]) |
354 | p->tunnel_id = rta_getattr_u32(attrs[L2TP_ATTR_CONN_ID]); | |
355 | if (attrs[L2TP_ATTR_PEER_CONN_ID]) | |
356 | p->peer_tunnel_id = rta_getattr_u32(attrs[L2TP_ATTR_PEER_CONN_ID]); | |
357 | if (attrs[L2TP_ATTR_SESSION_ID]) | |
358 | p->session_id = rta_getattr_u32(attrs[L2TP_ATTR_SESSION_ID]); | |
359 | if (attrs[L2TP_ATTR_PEER_SESSION_ID]) | |
360 | p->peer_session_id = rta_getattr_u32(attrs[L2TP_ATTR_PEER_SESSION_ID]); | |
dd10baa5 JC |
361 | if (attrs[L2TP_ATTR_L2SPEC_TYPE]) |
362 | p->l2spec_type = rta_getattr_u8(attrs[L2TP_ATTR_L2SPEC_TYPE]); | |
363 | if (attrs[L2TP_ATTR_L2SPEC_LEN]) | |
364 | p->l2spec_len = rta_getattr_u8(attrs[L2TP_ATTR_L2SPEC_LEN]); | |
38cd311a | 365 | |
c73fad78 AST |
366 | if (attrs[L2TP_ATTR_UDP_CSUM]) |
367 | p->udp_csum = !!rta_getattr_u8(attrs[L2TP_ATTR_UDP_CSUM]); | |
368 | ||
35cc6ded AST |
369 | p->udp6_csum_tx = !attrs[L2TP_ATTR_UDP_ZERO_CSUM6_TX]; |
370 | p->udp6_csum_rx = !attrs[L2TP_ATTR_UDP_ZERO_CSUM6_RX]; | |
371 | ||
38cd311a SH |
372 | if (attrs[L2TP_ATTR_COOKIE]) |
373 | memcpy(p->cookie, RTA_DATA(attrs[L2TP_ATTR_COOKIE]), | |
374 | p->cookie_len = RTA_PAYLOAD(attrs[L2TP_ATTR_COOKIE])); | |
375 | ||
376 | if (attrs[L2TP_ATTR_PEER_COOKIE]) | |
377 | memcpy(p->peer_cookie, RTA_DATA(attrs[L2TP_ATTR_PEER_COOKIE]), | |
378 | p->peer_cookie_len = RTA_PAYLOAD(attrs[L2TP_ATTR_PEER_COOKIE])); | |
379 | ||
4d51b333 AST |
380 | if (attrs[L2TP_ATTR_RECV_SEQ]) |
381 | p->recv_seq = !!rta_getattr_u8(attrs[L2TP_ATTR_RECV_SEQ]); | |
382 | if (attrs[L2TP_ATTR_SEND_SEQ]) | |
383 | p->send_seq = !!rta_getattr_u8(attrs[L2TP_ATTR_SEND_SEQ]); | |
38cd311a SH |
384 | |
385 | if (attrs[L2TP_ATTR_RECV_TIMEOUT]) | |
386 | p->reorder_timeout = rta_getattr_u64(attrs[L2TP_ATTR_RECV_TIMEOUT]); | |
62f9f94a SP |
387 | |
388 | rta = attrs[L2TP_ATTR_IP_SADDR]; | |
389 | p->local_ip.family = AF_INET; | |
390 | if (!rta) { | |
391 | rta = attrs[L2TP_ATTR_IP6_SADDR]; | |
6618e334 | 392 | p->local_ip.family = AF_INET6; |
6618e334 | 393 | } |
62f9f94a SP |
394 | if (rta && get_addr_rta(&p->local_ip, rta, p->local_ip.family)) |
395 | return -1; | |
396 | ||
397 | rta = attrs[L2TP_ATTR_IP_DADDR]; | |
398 | p->peer_ip.family = AF_INET; | |
399 | if (!rta) { | |
400 | rta = attrs[L2TP_ATTR_IP6_DADDR]; | |
6618e334 | 401 | p->peer_ip.family = AF_INET6; |
6618e334 | 402 | } |
62f9f94a SP |
403 | if (rta && get_addr_rta(&p->peer_ip, rta, p->peer_ip.family)) |
404 | return -1; | |
405 | ||
38cd311a SH |
406 | if (attrs[L2TP_ATTR_UDP_SPORT]) |
407 | p->local_udp_port = rta_getattr_u16(attrs[L2TP_ATTR_UDP_SPORT]); | |
408 | if (attrs[L2TP_ATTR_UDP_DPORT]) | |
409 | p->peer_udp_port = rta_getattr_u16(attrs[L2TP_ATTR_UDP_DPORT]); | |
38cd311a SH |
410 | if (attrs[L2TP_ATTR_IFNAME]) |
411 | p->ifname = rta_getattr_str(attrs[L2TP_ATTR_IFNAME]); | |
412 | ||
413 | nla_stats = attrs[L2TP_ATTR_STATS]; | |
414 | if (nla_stats) { | |
415 | struct rtattr *tb[L2TP_ATTR_STATS_MAX + 1]; | |
416 | ||
417 | parse_rtattr_nested(tb, L2TP_ATTR_STATS_MAX, nla_stats); | |
418 | ||
419 | if (tb[L2TP_ATTR_TX_PACKETS]) | |
420 | data->stats.data_tx_packets = rta_getattr_u64(tb[L2TP_ATTR_TX_PACKETS]); | |
421 | if (tb[L2TP_ATTR_TX_BYTES]) | |
422 | data->stats.data_tx_bytes = rta_getattr_u64(tb[L2TP_ATTR_TX_BYTES]); | |
423 | if (tb[L2TP_ATTR_TX_ERRORS]) | |
424 | data->stats.data_tx_errors = rta_getattr_u64(tb[L2TP_ATTR_TX_ERRORS]); | |
425 | if (tb[L2TP_ATTR_RX_PACKETS]) | |
426 | data->stats.data_rx_packets = rta_getattr_u64(tb[L2TP_ATTR_RX_PACKETS]); | |
427 | if (tb[L2TP_ATTR_RX_BYTES]) | |
428 | data->stats.data_rx_bytes = rta_getattr_u64(tb[L2TP_ATTR_RX_BYTES]); | |
429 | if (tb[L2TP_ATTR_RX_ERRORS]) | |
430 | data->stats.data_rx_errors = rta_getattr_u64(tb[L2TP_ATTR_RX_ERRORS]); | |
431 | if (tb[L2TP_ATTR_RX_SEQ_DISCARDS]) | |
432 | data->stats.data_rx_oos_discards = rta_getattr_u64(tb[L2TP_ATTR_RX_SEQ_DISCARDS]); | |
433 | if (tb[L2TP_ATTR_RX_OOS_PACKETS]) | |
434 | data->stats.data_rx_oos_packets = rta_getattr_u64(tb[L2TP_ATTR_RX_OOS_PACKETS]); | |
435 | } | |
436 | ||
437 | return 0; | |
438 | } | |
439 | ||
281db53f SH |
440 | static int session_nlmsg(const struct sockaddr_nl *who, |
441 | struct nlmsghdr *n, void *arg) | |
38cd311a SH |
442 | { |
443 | int ret = get_response(n, arg); | |
444 | ||
445 | if (ret == 0) | |
446 | print_session(arg); | |
447 | ||
448 | return ret; | |
449 | } | |
450 | ||
451 | static int get_session(struct l2tp_data *p) | |
452 | { | |
328d482c JA |
453 | GENL_REQUEST(req, 128, genl_family, 0, L2TP_GENL_VERSION, |
454 | L2TP_CMD_SESSION_GET, | |
455 | NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST); | |
38cd311a | 456 | |
328d482c | 457 | req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq; |
38cd311a SH |
458 | |
459 | if (p->config.tunnel_id && p->config.session_id) { | |
460 | addattr32(&req.n, 128, L2TP_ATTR_CONN_ID, p->config.tunnel_id); | |
281db53f SH |
461 | addattr32(&req.n, 128, L2TP_ATTR_SESSION_ID, |
462 | p->config.session_id); | |
38cd311a SH |
463 | } |
464 | ||
465 | if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0) | |
466 | return -2; | |
467 | ||
98453b65 | 468 | new_json_obj(json); |
38cd311a SH |
469 | if (rtnl_dump_filter(&genl_rth, session_nlmsg, p) < 0) { |
470 | fprintf(stderr, "Dump terminated\n"); | |
471 | exit(1); | |
472 | } | |
98453b65 SH |
473 | delete_json_obj(); |
474 | fflush(stdout); | |
38cd311a SH |
475 | |
476 | return 0; | |
477 | } | |
478 | ||
281db53f SH |
479 | static int tunnel_nlmsg(const struct sockaddr_nl *who, |
480 | struct nlmsghdr *n, void *arg) | |
38cd311a SH |
481 | { |
482 | int ret = get_response(n, arg); | |
483 | ||
484 | if (ret == 0) | |
485 | print_tunnel(arg); | |
486 | ||
487 | return ret; | |
488 | } | |
489 | ||
490 | static int get_tunnel(struct l2tp_data *p) | |
491 | { | |
328d482c JA |
492 | GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION, |
493 | L2TP_CMD_TUNNEL_GET, | |
494 | NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST); | |
38cd311a | 495 | |
328d482c | 496 | req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq; |
38cd311a SH |
497 | |
498 | if (p->config.tunnel_id) | |
499 | addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->config.tunnel_id); | |
500 | ||
501 | if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0) | |
502 | return -2; | |
503 | ||
98453b65 | 504 | new_json_obj(json); |
38cd311a SH |
505 | if (rtnl_dump_filter(&genl_rth, tunnel_nlmsg, p) < 0) { |
506 | fprintf(stderr, "Dump terminated\n"); | |
507 | exit(1); | |
508 | } | |
98453b65 SH |
509 | delete_json_obj(); |
510 | fflush(stdout); | |
38cd311a SH |
511 | |
512 | return 0; | |
513 | } | |
514 | ||
515 | /***************************************************************************** | |
516 | * Command parser | |
517 | *****************************************************************************/ | |
518 | ||
38cd311a SH |
519 | static void usage(void) __attribute__((noreturn)); |
520 | ||
521 | static void usage(void) | |
522 | { | |
281db53f SH |
523 | fprintf(stderr, "Usage: ip l2tp add tunnel\n" |
524 | " remote ADDR local ADDR\n" | |
525 | " tunnel_id ID peer_tunnel_id ID\n" | |
526 | " [ encap { ip | udp } ]\n" | |
527 | " [ udp_sport PORT ] [ udp_dport PORT ]\n" | |
528 | " [ udp_csum { on | off } ]\n" | |
529 | " [ udp6_csum_tx { on | off } ]\n" | |
530 | " [ udp6_csum_rx { on | off } ]\n" | |
531 | "Usage: ip l2tp add session [ name NAME ]\n" | |
532 | " tunnel_id ID\n" | |
533 | " session_id ID peer_session_id ID\n" | |
534 | " [ cookie HEXSTR ] [ peer_cookie HEXSTR ]\n" | |
281db53f SH |
535 | " [ seq { none | send | recv | both } ]\n" |
536 | " [ l2spec_type L2SPEC ]\n" | |
537 | " ip l2tp del tunnel tunnel_id ID\n" | |
538 | " ip l2tp del session tunnel_id ID session_id ID\n" | |
539 | " ip l2tp show tunnel [ tunnel_id ID ]\n" | |
540 | " ip l2tp show session [ tunnel_id ID ] [ session_id ID ]\n" | |
541 | "\n" | |
542 | "Where: NAME := STRING\n" | |
543 | " ADDR := { IP_ADDRESS | any }\n" | |
544 | " PORT := { 0..65535 }\n" | |
545 | " ID := { 1..4294967295 }\n" | |
546 | " HEXSTR := { 8 or 16 hex digits (4 / 8 bytes) }\n" | |
547 | " L2SPEC := { none | default }\n"); | |
548 | ||
38cd311a SH |
549 | exit(-1); |
550 | } | |
551 | ||
552 | static int parse_args(int argc, char **argv, int cmd, struct l2tp_parm *p) | |
553 | { | |
554 | memset(p, 0, sizeof(*p)); | |
555 | ||
556 | if (argc == 0) | |
557 | usage(); | |
558 | ||
dd10baa5 JC |
559 | /* Defaults */ |
560 | p->l2spec_type = L2TP_L2SPECTYPE_DEFAULT; | |
561 | p->l2spec_len = 4; | |
9bf9d05b SW |
562 | p->udp6_csum_rx = 1; |
563 | p->udp6_csum_tx = 1; | |
dd10baa5 | 564 | |
38cd311a SH |
565 | while (argc > 0) { |
566 | if (strcmp(*argv, "encap") == 0) { | |
567 | NEXT_ARG(); | |
568 | if (strcmp(*argv, "ip") == 0) { | |
569 | p->encap = L2TP_ENCAPTYPE_IP; | |
570 | } else if (strcmp(*argv, "udp") == 0) { | |
571 | p->encap = L2TP_ENCAPTYPE_UDP; | |
572 | } else { | |
14645ec2 | 573 | fprintf(stderr, "Unknown tunnel encapsulation \"%s\"\n", *argv); |
38cd311a SH |
574 | exit(-1); |
575 | } | |
ae5555d3 JV |
576 | } else if (strcmp(*argv, "name") == 0) { |
577 | NEXT_ARG(); | |
625df645 PS |
578 | if (check_ifname(*argv)) |
579 | invarg("\"name\" not a valid ifname", *argv); | |
ae5555d3 | 580 | p->ifname = *argv; |
38cd311a SH |
581 | } else if (strcmp(*argv, "remote") == 0) { |
582 | NEXT_ARG(); | |
6618e334 CE |
583 | if (get_addr(&p->peer_ip, *argv, AF_UNSPEC)) |
584 | invarg("invalid remote address\n", *argv); | |
38cd311a SH |
585 | } else if (strcmp(*argv, "local") == 0) { |
586 | NEXT_ARG(); | |
6618e334 CE |
587 | if (get_addr(&p->local_ip, *argv, AF_UNSPEC)) |
588 | invarg("invalid local address\n", *argv); | |
38cd311a SH |
589 | } else if ((strcmp(*argv, "tunnel_id") == 0) || |
590 | (strcmp(*argv, "tid") == 0)) { | |
591 | __u32 uval; | |
56f5daac | 592 | |
38cd311a SH |
593 | NEXT_ARG(); |
594 | if (get_u32(&uval, *argv, 0)) | |
595 | invarg("invalid ID\n", *argv); | |
596 | p->tunnel_id = uval; | |
597 | } else if ((strcmp(*argv, "peer_tunnel_id") == 0) || | |
598 | (strcmp(*argv, "ptid") == 0)) { | |
599 | __u32 uval; | |
56f5daac | 600 | |
38cd311a SH |
601 | NEXT_ARG(); |
602 | if (get_u32(&uval, *argv, 0)) | |
603 | invarg("invalid ID\n", *argv); | |
604 | p->peer_tunnel_id = uval; | |
605 | } else if ((strcmp(*argv, "session_id") == 0) || | |
606 | (strcmp(*argv, "sid") == 0)) { | |
607 | __u32 uval; | |
56f5daac | 608 | |
38cd311a SH |
609 | NEXT_ARG(); |
610 | if (get_u32(&uval, *argv, 0)) | |
611 | invarg("invalid ID\n", *argv); | |
612 | p->session_id = uval; | |
613 | } else if ((strcmp(*argv, "peer_session_id") == 0) || | |
614 | (strcmp(*argv, "psid") == 0)) { | |
615 | __u32 uval; | |
56f5daac | 616 | |
38cd311a SH |
617 | NEXT_ARG(); |
618 | if (get_u32(&uval, *argv, 0)) | |
619 | invarg("invalid ID\n", *argv); | |
620 | p->peer_session_id = uval; | |
621 | } else if (strcmp(*argv, "udp_sport") == 0) { | |
622 | __u16 uval; | |
56f5daac | 623 | |
38cd311a SH |
624 | NEXT_ARG(); |
625 | if (get_u16(&uval, *argv, 0)) | |
626 | invarg("invalid port\n", *argv); | |
627 | p->local_udp_port = uval; | |
628 | } else if (strcmp(*argv, "udp_dport") == 0) { | |
629 | __u16 uval; | |
56f5daac | 630 | |
38cd311a SH |
631 | NEXT_ARG(); |
632 | if (get_u16(&uval, *argv, 0)) | |
633 | invarg("invalid port\n", *argv); | |
634 | p->peer_udp_port = uval; | |
9bf9d05b SW |
635 | } else if (strcmp(*argv, "udp_csum") == 0) { |
636 | NEXT_ARG(); | |
637 | if (strcmp(*argv, "on") == 0) | |
638 | p->udp_csum = 1; | |
639 | else if (strcmp(*argv, "off") == 0) | |
640 | p->udp_csum = 0; | |
641 | else | |
642 | invarg("invalid option for udp_csum\n", *argv); | |
643 | } else if (strcmp(*argv, "udp6_csum_rx") == 0) { | |
644 | NEXT_ARG(); | |
645 | if (strcmp(*argv, "on") == 0) | |
646 | p->udp6_csum_rx = 1; | |
647 | else if (strcmp(*argv, "off") == 0) | |
648 | p->udp6_csum_rx = 0; | |
649 | else | |
650 | invarg("invalid option for udp6_csum_rx\n" | |
651 | , *argv); | |
652 | } else if (strcmp(*argv, "udp6_csum_tx") == 0) { | |
653 | NEXT_ARG(); | |
654 | if (strcmp(*argv, "on") == 0) | |
655 | p->udp6_csum_tx = 1; | |
656 | else if (strcmp(*argv, "off") == 0) | |
657 | p->udp6_csum_tx = 0; | |
658 | else | |
659 | invarg("invalid option for udp6_csum_tx\n" | |
660 | , *argv); | |
38cd311a | 661 | } else if (strcmp(*argv, "offset") == 0) { |
2f75c5cf | 662 | fprintf(stderr, "Ignoring option \"offset\"\n"); |
38cd311a | 663 | NEXT_ARG(); |
38cd311a | 664 | } else if (strcmp(*argv, "peer_offset") == 0) { |
2f75c5cf | 665 | fprintf(stderr, "Ignoring option \"peer_offset\"\n"); |
38cd311a | 666 | NEXT_ARG(); |
38cd311a SH |
667 | } else if (strcmp(*argv, "cookie") == 0) { |
668 | int slen; | |
56f5daac | 669 | |
38cd311a SH |
670 | NEXT_ARG(); |
671 | slen = strlen(*argv); | |
672 | if ((slen != 8) && (slen != 16)) | |
673 | invarg("cookie must be either 8 or 16 hex digits\n", *argv); | |
674 | ||
675 | p->cookie_len = slen / 2; | |
676 | if (hex2mem(*argv, p->cookie, p->cookie_len) < 0) | |
677 | invarg("cookie must be a hex string\n", *argv); | |
678 | } else if (strcmp(*argv, "peer_cookie") == 0) { | |
679 | int slen; | |
56f5daac | 680 | |
38cd311a SH |
681 | NEXT_ARG(); |
682 | slen = strlen(*argv); | |
683 | if ((slen != 8) && (slen != 16)) | |
684 | invarg("cookie must be either 8 or 16 hex digits\n", *argv); | |
685 | ||
686 | p->peer_cookie_len = slen / 2; | |
687 | if (hex2mem(*argv, p->peer_cookie, p->peer_cookie_len) < 0) | |
688 | invarg("cookie must be a hex string\n", *argv); | |
dd10baa5 JC |
689 | } else if (strcmp(*argv, "l2spec_type") == 0) { |
690 | NEXT_ARG(); | |
691 | if (strcasecmp(*argv, "default") == 0) { | |
692 | p->l2spec_type = L2TP_L2SPECTYPE_DEFAULT; | |
693 | p->l2spec_len = 4; | |
694 | } else if (strcasecmp(*argv, "none") == 0) { | |
695 | p->l2spec_type = L2TP_L2SPECTYPE_NONE; | |
696 | p->l2spec_len = 0; | |
697 | } else { | |
281db53f SH |
698 | fprintf(stderr, |
699 | "Unknown layer2specific header type \"%s\"\n", | |
700 | *argv); | |
dd10baa5 JC |
701 | exit(-1); |
702 | } | |
8a11421a AST |
703 | } else if (strcmp(*argv, "seq") == 0) { |
704 | NEXT_ARG(); | |
705 | if (strcasecmp(*argv, "both") == 0) { | |
706 | p->recv_seq = 1; | |
707 | p->send_seq = 1; | |
708 | } else if (strcasecmp(*argv, "recv") == 0) { | |
709 | p->recv_seq = 1; | |
710 | } else if (strcasecmp(*argv, "send") == 0) { | |
711 | p->send_seq = 1; | |
712 | } else if (strcasecmp(*argv, "none") == 0) { | |
713 | p->recv_seq = 0; | |
714 | p->send_seq = 0; | |
715 | } else { | |
281db53f SH |
716 | fprintf(stderr, |
717 | "Unknown seq value \"%s\"\n", *argv); | |
8a11421a AST |
718 | exit(-1); |
719 | } | |
38cd311a SH |
720 | } else if (strcmp(*argv, "tunnel") == 0) { |
721 | p->tunnel = 1; | |
722 | } else if (strcmp(*argv, "session") == 0) { | |
723 | p->session = 1; | |
724 | } else if (matches(*argv, "help") == 0) { | |
725 | usage(); | |
726 | } else { | |
727 | fprintf(stderr, "Unknown command: %s\n", *argv); | |
728 | usage(); | |
729 | } | |
730 | ||
731 | argc--; argv++; | |
732 | } | |
733 | ||
734 | return 0; | |
735 | } | |
736 | ||
737 | ||
738 | static int do_add(int argc, char **argv) | |
739 | { | |
740 | struct l2tp_parm p; | |
741 | int ret = 0; | |
742 | ||
743 | if (parse_args(argc, argv, L2TP_ADD, &p) < 0) | |
744 | return -1; | |
745 | ||
746 | if (!p.tunnel && !p.session) | |
747 | missarg("tunnel or session"); | |
748 | ||
749 | if (p.tunnel_id == 0) | |
750 | missarg("tunnel_id"); | |
751 | ||
752 | /* session_id and peer_session_id must be provided for sessions */ | |
753 | if ((p.session) && (p.peer_session_id == 0)) | |
754 | missarg("peer_session_id"); | |
755 | if ((p.session) && (p.session_id == 0)) | |
756 | missarg("session_id"); | |
757 | ||
758 | /* peer_tunnel_id is needed for tunnels */ | |
759 | if ((p.tunnel) && (p.peer_tunnel_id == 0)) | |
760 | missarg("peer_tunnel_id"); | |
761 | ||
762 | if (p.tunnel) { | |
6618e334 | 763 | if (p.local_ip.family == AF_UNSPEC) |
38cd311a SH |
764 | missarg("local"); |
765 | ||
6618e334 | 766 | if (p.peer_ip.family == AF_UNSPEC) |
38cd311a SH |
767 | missarg("remote"); |
768 | ||
769 | if (p.encap == L2TP_ENCAPTYPE_UDP) { | |
770 | if (p.local_udp_port == 0) | |
771 | missarg("udp_sport"); | |
772 | if (p.peer_udp_port == 0) | |
773 | missarg("udp_dport"); | |
774 | } | |
775 | ||
776 | ret = create_tunnel(&p); | |
777 | } | |
778 | ||
779 | if (p.session) { | |
780 | /* Only ethernet pseudowires supported */ | |
781 | p.pw_type = L2TP_PWTYPE_ETH; | |
782 | ||
783 | ret = create_session(&p); | |
784 | } | |
785 | ||
786 | return ret; | |
787 | } | |
788 | ||
789 | static int do_del(int argc, char **argv) | |
790 | { | |
791 | struct l2tp_parm p; | |
792 | ||
793 | if (parse_args(argc, argv, L2TP_DEL, &p) < 0) | |
794 | return -1; | |
795 | ||
796 | if (!p.tunnel && !p.session) | |
797 | missarg("tunnel or session"); | |
798 | ||
799 | if ((p.tunnel) && (p.tunnel_id == 0)) | |
800 | missarg("tunnel_id"); | |
801 | if ((p.session) && (p.session_id == 0)) | |
802 | missarg("session_id"); | |
803 | ||
804 | if (p.session_id) | |
805 | return delete_session(&p); | |
806 | else | |
807 | return delete_tunnel(&p); | |
808 | ||
809 | return -1; | |
810 | } | |
811 | ||
812 | static int do_show(int argc, char **argv) | |
813 | { | |
814 | struct l2tp_data data; | |
815 | struct l2tp_parm *p = &data.config; | |
816 | ||
817 | if (parse_args(argc, argv, L2TP_GET, p) < 0) | |
818 | return -1; | |
819 | ||
820 | if (!p->tunnel && !p->session) | |
821 | missarg("tunnel or session"); | |
822 | ||
823 | if (p->session) | |
824 | get_session(&data); | |
825 | else | |
826 | get_tunnel(&data); | |
827 | ||
828 | return 0; | |
829 | } | |
830 | ||
38cd311a SH |
831 | int do_ipl2tp(int argc, char **argv) |
832 | { | |
e8977766 PS |
833 | if (argc < 1 || !matches(*argv, "help")) |
834 | usage(); | |
835 | ||
2b68cb77 SD |
836 | if (genl_init_handle(&genl_rth, L2TP_GENL_NAME, &genl_family)) |
837 | exit(1); | |
38cd311a | 838 | |
38cd311a SH |
839 | if (matches(*argv, "add") == 0) |
840 | return do_add(argc-1, argv+1); | |
6e30461e | 841 | if (matches(*argv, "delete") == 0) |
38cd311a SH |
842 | return do_del(argc-1, argv+1); |
843 | if (matches(*argv, "show") == 0 || | |
844 | matches(*argv, "lst") == 0 || | |
845 | matches(*argv, "list") == 0) | |
846 | return do_show(argc-1, argv+1); | |
38cd311a | 847 | |
281db53f SH |
848 | fprintf(stderr, |
849 | "Command \"%s\" is unknown, try \"ip l2tp help\".\n", *argv); | |
38cd311a SH |
850 | exit(-1); |
851 | } |