]>
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> | |
28 | ||
29 | #include "utils.h" | |
30 | #include "ip_common.h" | |
31 | ||
32 | enum { | |
33 | L2TP_ADD, | |
34 | L2TP_CHG, | |
35 | L2TP_DEL, | |
36 | L2TP_GET | |
37 | }; | |
38 | ||
39 | struct l2tp_parm { | |
40 | uint32_t tunnel_id; | |
41 | uint32_t peer_tunnel_id; | |
42 | uint32_t session_id; | |
43 | uint32_t peer_session_id; | |
44 | uint32_t offset; | |
45 | uint32_t peer_offset; | |
46 | enum l2tp_encap_type encap; | |
47 | uint16_t local_udp_port; | |
48 | uint16_t peer_udp_port; | |
49 | int cookie_len; | |
50 | uint8_t cookie[8]; | |
51 | int peer_cookie_len; | |
52 | uint8_t peer_cookie[8]; | |
e2780880 SH |
53 | struct in_addr local_ip; |
54 | struct in_addr peer_ip; | |
38cd311a SH |
55 | |
56 | uint16_t pw_type; | |
57 | uint16_t mtu; | |
58 | int udp_csum:1; | |
59 | int recv_seq:1; | |
60 | int send_seq:1; | |
61 | int lns_mode:1; | |
62 | int data_seq:2; | |
63 | int tunnel:1; | |
64 | int session:1; | |
65 | int reorder_timeout; | |
66 | const char *ifname; | |
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 | { | |
95 | struct { | |
96 | struct nlmsghdr n; | |
97 | struct genlmsghdr g; | |
98 | char buf[1024]; | |
99 | } req; | |
100 | ||
101 | memset(&req, 0, sizeof(req)); | |
102 | req.n.nlmsg_type = genl_family; | |
103 | req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; | |
104 | req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); | |
105 | req.g.cmd = L2TP_CMD_TUNNEL_CREATE; | |
106 | req.g.version = L2TP_GENL_VERSION; | |
107 | ||
108 | addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id); | |
109 | addattr32(&req.n, 1024, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id); | |
110 | addattr8(&req.n, 1024, L2TP_ATTR_PROTO_VERSION, 3); | |
111 | addattr16(&req.n, 1024, L2TP_ATTR_ENCAP_TYPE, p->encap); | |
112 | ||
e2780880 SH |
113 | addattr32(&req.n, 1024, L2TP_ATTR_IP_SADDR, p->local_ip.s_addr); |
114 | addattr32(&req.n, 1024, L2TP_ATTR_IP_DADDR, p->peer_ip.s_addr); | |
38cd311a SH |
115 | if (p->encap == L2TP_ENCAPTYPE_UDP) { |
116 | addattr16(&req.n, 1024, L2TP_ATTR_UDP_SPORT, p->local_udp_port); | |
117 | addattr16(&req.n, 1024, L2TP_ATTR_UDP_DPORT, p->peer_udp_port); | |
118 | } | |
119 | ||
120 | if (rtnl_talk(&genl_rth, &req.n, 0, 0, NULL) < 0) | |
121 | return -2; | |
122 | ||
123 | return 0; | |
124 | } | |
125 | ||
126 | static int delete_tunnel(struct l2tp_parm *p) | |
127 | { | |
128 | struct { | |
129 | struct nlmsghdr n; | |
130 | struct genlmsghdr g; | |
131 | char buf[128]; | |
132 | } req; | |
133 | ||
134 | memset(&req, 0, sizeof(req)); | |
135 | req.n.nlmsg_type = genl_family; | |
136 | req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; | |
137 | req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); | |
138 | req.g.cmd = L2TP_CMD_TUNNEL_DELETE; | |
139 | req.g.version = L2TP_GENL_VERSION; | |
140 | ||
141 | addattr32(&req.n, 128, L2TP_ATTR_CONN_ID, p->tunnel_id); | |
142 | ||
143 | if (rtnl_talk(&genl_rth, &req.n, 0, 0, NULL) < 0) | |
144 | return -2; | |
145 | ||
146 | return 0; | |
147 | } | |
148 | ||
149 | static int create_session(struct l2tp_parm *p) | |
150 | { | |
151 | struct { | |
152 | struct nlmsghdr n; | |
153 | struct genlmsghdr g; | |
154 | char buf[1024]; | |
155 | } req; | |
156 | ||
157 | memset(&req, 0, sizeof(req)); | |
158 | req.n.nlmsg_type = genl_family; | |
159 | req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; | |
160 | req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); | |
161 | req.g.cmd = L2TP_CMD_SESSION_CREATE; | |
162 | req.g.version = L2TP_GENL_VERSION; | |
163 | ||
164 | addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id); | |
165 | addattr32(&req.n, 1024, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id); | |
166 | addattr32(&req.n, 1024, L2TP_ATTR_SESSION_ID, p->session_id); | |
167 | addattr32(&req.n, 1024, L2TP_ATTR_PEER_SESSION_ID, p->peer_session_id); | |
168 | addattr16(&req.n, 1024, L2TP_ATTR_PW_TYPE, p->pw_type); | |
169 | ||
170 | if (p->mtu) addattr16(&req.n, 1024, L2TP_ATTR_MTU, p->mtu); | |
171 | if (p->recv_seq) addattr(&req.n, 1024, L2TP_ATTR_RECV_SEQ); | |
172 | if (p->send_seq) addattr(&req.n, 1024, L2TP_ATTR_SEND_SEQ); | |
173 | if (p->lns_mode) addattr(&req.n, 1024, L2TP_ATTR_LNS_MODE); | |
174 | if (p->data_seq) addattr8(&req.n, 1024, L2TP_ATTR_DATA_SEQ, p->data_seq); | |
175 | if (p->reorder_timeout) addattr64(&req.n, 1024, L2TP_ATTR_RECV_TIMEOUT, | |
176 | p->reorder_timeout); | |
177 | if (p->offset) addattr16(&req.n, 1024, L2TP_ATTR_OFFSET, p->offset); | |
178 | if (p->cookie_len) addattr_l(&req.n, 1024, L2TP_ATTR_COOKIE, | |
179 | p->cookie, p->cookie_len); | |
180 | if (p->peer_cookie_len) addattr_l(&req.n, 1024, L2TP_ATTR_PEER_COOKIE, | |
181 | p->peer_cookie, p->peer_cookie_len); | |
182 | if (p->ifname && p->ifname[0]) | |
183 | addattrstrz(&req.n, 1024, L2TP_ATTR_IFNAME, p->ifname); | |
184 | ||
185 | if (rtnl_talk(&genl_rth, &req.n, 0, 0, NULL) < 0) | |
186 | return -2; | |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
191 | static int delete_session(struct l2tp_parm *p) | |
192 | { | |
193 | struct { | |
194 | struct nlmsghdr n; | |
195 | struct genlmsghdr g; | |
196 | char buf[128]; | |
197 | } req; | |
198 | ||
199 | memset(&req, 0, sizeof(req)); | |
200 | req.n.nlmsg_type = genl_family; | |
201 | req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; | |
202 | req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); | |
203 | req.g.cmd = L2TP_CMD_SESSION_DELETE; | |
204 | req.g.version = L2TP_GENL_VERSION; | |
205 | ||
206 | addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id); | |
207 | addattr32(&req.n, 1024, L2TP_ATTR_SESSION_ID, p->session_id); | |
208 | if (rtnl_talk(&genl_rth, &req.n, 0, 0, NULL) < 0) | |
209 | return -2; | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | static void print_cookie(char *name, const uint8_t *cookie, int len) | |
215 | { | |
216 | printf(" %s %02x%02x%02x%02x", name, | |
217 | cookie[0], cookie[1], | |
218 | cookie[2], cookie[3]); | |
219 | if (len == 8) | |
220 | printf("%02x%02x%02x%02x", | |
221 | cookie[4], cookie[5], | |
222 | cookie[6], cookie[7]); | |
223 | } | |
224 | ||
225 | static void print_tunnel(const struct l2tp_data *data) | |
226 | { | |
227 | const struct l2tp_parm *p = &data->config; | |
228 | ||
229 | printf("Tunnel %u, encap %s\n", | |
230 | p->tunnel_id, | |
231 | p->encap == L2TP_ENCAPTYPE_UDP ? "UDP" : | |
232 | p->encap == L2TP_ENCAPTYPE_IP ? "IP" : "??"); | |
e2780880 SH |
233 | printf(" From %s ", inet_ntoa(p->local_ip)); |
234 | printf("to %s\n", inet_ntoa(p->peer_ip)); | |
38cd311a SH |
235 | printf(" Peer tunnel %u\n", |
236 | p->peer_tunnel_id); | |
237 | ||
238 | if (p->encap == L2TP_ENCAPTYPE_UDP) | |
239 | printf(" UDP source / dest ports: %hu/%hu\n", | |
240 | p->local_udp_port, p->peer_udp_port); | |
241 | } | |
242 | ||
243 | static void print_session(struct l2tp_data *data) | |
244 | { | |
245 | struct l2tp_parm *p = &data->config; | |
246 | ||
247 | printf("Session %u in tunnel %u\n", | |
248 | p->session_id, p->tunnel_id); | |
249 | printf(" Peer session %u, tunnel %u\n", | |
250 | p->peer_session_id, p->peer_tunnel_id); | |
251 | ||
252 | if (p->ifname != NULL) { | |
253 | printf(" interface name: %s\n", p->ifname); | |
254 | } | |
255 | printf(" offset %u, peer offset %u\n", | |
256 | p->offset, p->peer_offset); | |
257 | if (p->cookie_len > 0) | |
258 | print_cookie("cookie", p->cookie, p->cookie_len); | |
259 | if (p->peer_cookie_len > 0) | |
260 | print_cookie("peer cookie", p->peer_cookie, p->peer_cookie_len); | |
261 | ||
262 | if (p->reorder_timeout != 0) { | |
263 | printf(" reorder timeout: %u\n", p->reorder_timeout); | |
264 | } | |
265 | } | |
266 | ||
267 | static int get_response(struct nlmsghdr *n, void *arg) | |
268 | { | |
269 | struct genlmsghdr *ghdr; | |
270 | struct l2tp_data *data = arg; | |
271 | struct l2tp_parm *p = &data->config; | |
272 | struct rtattr *attrs[L2TP_ATTR_MAX + 1]; | |
273 | struct rtattr *nla_stats; | |
274 | int len; | |
275 | ||
276 | /* Validate message and parse attributes */ | |
277 | if (n->nlmsg_type == NLMSG_ERROR) | |
278 | return -EBADMSG; | |
279 | ||
280 | ghdr = NLMSG_DATA(n); | |
281 | len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ghdr)); | |
282 | if (len < 0) | |
283 | return -1; | |
284 | ||
285 | parse_rtattr(attrs, L2TP_ATTR_MAX, (void *)ghdr + GENL_HDRLEN, len); | |
286 | ||
287 | if (attrs[L2TP_ATTR_PW_TYPE]) | |
288 | p->pw_type = rta_getattr_u16(attrs[L2TP_ATTR_PW_TYPE]); | |
289 | if (attrs[L2TP_ATTR_ENCAP_TYPE]) | |
290 | p->encap = rta_getattr_u16(attrs[L2TP_ATTR_ENCAP_TYPE]); | |
291 | if (attrs[L2TP_ATTR_OFFSET]) | |
292 | p->offset = rta_getattr_u16(attrs[L2TP_ATTR_OFFSET]); | |
293 | if (attrs[L2TP_ATTR_DATA_SEQ]) | |
294 | p->data_seq = rta_getattr_u16(attrs[L2TP_ATTR_DATA_SEQ]); | |
295 | if (attrs[L2TP_ATTR_CONN_ID]) | |
296 | p->tunnel_id = rta_getattr_u32(attrs[L2TP_ATTR_CONN_ID]); | |
297 | if (attrs[L2TP_ATTR_PEER_CONN_ID]) | |
298 | p->peer_tunnel_id = rta_getattr_u32(attrs[L2TP_ATTR_PEER_CONN_ID]); | |
299 | if (attrs[L2TP_ATTR_SESSION_ID]) | |
300 | p->session_id = rta_getattr_u32(attrs[L2TP_ATTR_SESSION_ID]); | |
301 | if (attrs[L2TP_ATTR_PEER_SESSION_ID]) | |
302 | p->peer_session_id = rta_getattr_u32(attrs[L2TP_ATTR_PEER_SESSION_ID]); | |
303 | ||
304 | p->udp_csum = !!attrs[L2TP_ATTR_UDP_CSUM]; | |
305 | if (attrs[L2TP_ATTR_COOKIE]) | |
306 | memcpy(p->cookie, RTA_DATA(attrs[L2TP_ATTR_COOKIE]), | |
307 | p->cookie_len = RTA_PAYLOAD(attrs[L2TP_ATTR_COOKIE])); | |
308 | ||
309 | if (attrs[L2TP_ATTR_PEER_COOKIE]) | |
310 | memcpy(p->peer_cookie, RTA_DATA(attrs[L2TP_ATTR_PEER_COOKIE]), | |
311 | p->peer_cookie_len = RTA_PAYLOAD(attrs[L2TP_ATTR_PEER_COOKIE])); | |
312 | ||
313 | p->recv_seq = !!attrs[L2TP_ATTR_RECV_SEQ]; | |
314 | p->send_seq = !!attrs[L2TP_ATTR_SEND_SEQ]; | |
315 | ||
316 | if (attrs[L2TP_ATTR_RECV_TIMEOUT]) | |
317 | p->reorder_timeout = rta_getattr_u64(attrs[L2TP_ATTR_RECV_TIMEOUT]); | |
e2780880 SH |
318 | if (attrs[L2TP_ATTR_IP_SADDR]) |
319 | p->local_ip.s_addr = rta_getattr_u32(attrs[L2TP_ATTR_IP_SADDR]); | |
320 | if (attrs[L2TP_ATTR_IP_DADDR]) | |
321 | p->peer_ip.s_addr = rta_getattr_u32(attrs[L2TP_ATTR_IP_DADDR]); | |
38cd311a SH |
322 | if (attrs[L2TP_ATTR_UDP_SPORT]) |
323 | p->local_udp_port = rta_getattr_u16(attrs[L2TP_ATTR_UDP_SPORT]); | |
324 | if (attrs[L2TP_ATTR_UDP_DPORT]) | |
325 | p->peer_udp_port = rta_getattr_u16(attrs[L2TP_ATTR_UDP_DPORT]); | |
326 | if (attrs[L2TP_ATTR_MTU]) | |
327 | p->mtu = rta_getattr_u16(attrs[L2TP_ATTR_MTU]); | |
328 | if (attrs[L2TP_ATTR_IFNAME]) | |
329 | p->ifname = rta_getattr_str(attrs[L2TP_ATTR_IFNAME]); | |
330 | ||
331 | nla_stats = attrs[L2TP_ATTR_STATS]; | |
332 | if (nla_stats) { | |
333 | struct rtattr *tb[L2TP_ATTR_STATS_MAX + 1]; | |
334 | ||
335 | parse_rtattr_nested(tb, L2TP_ATTR_STATS_MAX, nla_stats); | |
336 | ||
337 | if (tb[L2TP_ATTR_TX_PACKETS]) | |
338 | data->stats.data_tx_packets = rta_getattr_u64(tb[L2TP_ATTR_TX_PACKETS]); | |
339 | if (tb[L2TP_ATTR_TX_BYTES]) | |
340 | data->stats.data_tx_bytes = rta_getattr_u64(tb[L2TP_ATTR_TX_BYTES]); | |
341 | if (tb[L2TP_ATTR_TX_ERRORS]) | |
342 | data->stats.data_tx_errors = rta_getattr_u64(tb[L2TP_ATTR_TX_ERRORS]); | |
343 | if (tb[L2TP_ATTR_RX_PACKETS]) | |
344 | data->stats.data_rx_packets = rta_getattr_u64(tb[L2TP_ATTR_RX_PACKETS]); | |
345 | if (tb[L2TP_ATTR_RX_BYTES]) | |
346 | data->stats.data_rx_bytes = rta_getattr_u64(tb[L2TP_ATTR_RX_BYTES]); | |
347 | if (tb[L2TP_ATTR_RX_ERRORS]) | |
348 | data->stats.data_rx_errors = rta_getattr_u64(tb[L2TP_ATTR_RX_ERRORS]); | |
349 | if (tb[L2TP_ATTR_RX_SEQ_DISCARDS]) | |
350 | data->stats.data_rx_oos_discards = rta_getattr_u64(tb[L2TP_ATTR_RX_SEQ_DISCARDS]); | |
351 | if (tb[L2TP_ATTR_RX_OOS_PACKETS]) | |
352 | data->stats.data_rx_oos_packets = rta_getattr_u64(tb[L2TP_ATTR_RX_OOS_PACKETS]); | |
353 | } | |
354 | ||
355 | return 0; | |
356 | } | |
357 | ||
358 | static int session_nlmsg(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) | |
359 | { | |
360 | int ret = get_response(n, arg); | |
361 | ||
362 | if (ret == 0) | |
363 | print_session(arg); | |
364 | ||
365 | return ret; | |
366 | } | |
367 | ||
368 | static int get_session(struct l2tp_data *p) | |
369 | { | |
370 | struct { | |
371 | struct nlmsghdr n; | |
372 | struct genlmsghdr g; | |
373 | char buf[128]; | |
374 | } req; | |
375 | ||
376 | memset(&req, 0, sizeof(req)); | |
377 | req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); | |
378 | req.n.nlmsg_type = genl_family; | |
379 | req.n.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; | |
380 | req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq; | |
381 | ||
382 | req.g.cmd = L2TP_CMD_SESSION_GET; | |
383 | req.g.version = L2TP_GENL_VERSION; | |
384 | ||
385 | if (p->config.tunnel_id && p->config.session_id) { | |
386 | addattr32(&req.n, 128, L2TP_ATTR_CONN_ID, p->config.tunnel_id); | |
387 | addattr32(&req.n, 128, L2TP_ATTR_SESSION_ID, p->config.session_id); | |
388 | } | |
389 | ||
390 | if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0) | |
391 | return -2; | |
392 | ||
393 | if (rtnl_dump_filter(&genl_rth, session_nlmsg, p) < 0) { | |
394 | fprintf(stderr, "Dump terminated\n"); | |
395 | exit(1); | |
396 | } | |
397 | ||
398 | return 0; | |
399 | } | |
400 | ||
401 | static int tunnel_nlmsg(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) | |
402 | { | |
403 | int ret = get_response(n, arg); | |
404 | ||
405 | if (ret == 0) | |
406 | print_tunnel(arg); | |
407 | ||
408 | return ret; | |
409 | } | |
410 | ||
411 | static int get_tunnel(struct l2tp_data *p) | |
412 | { | |
413 | struct { | |
414 | struct nlmsghdr n; | |
415 | struct genlmsghdr g; | |
416 | char buf[1024]; | |
417 | } req; | |
418 | ||
419 | memset(&req, 0, sizeof(req)); | |
420 | req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); | |
421 | req.n.nlmsg_type = genl_family; | |
422 | req.n.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; | |
423 | req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq; | |
424 | ||
425 | req.g.cmd = L2TP_CMD_TUNNEL_GET; | |
426 | req.g.version = L2TP_GENL_VERSION; | |
427 | ||
428 | if (p->config.tunnel_id) | |
429 | addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->config.tunnel_id); | |
430 | ||
431 | if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0) | |
432 | return -2; | |
433 | ||
434 | if (rtnl_dump_filter(&genl_rth, tunnel_nlmsg, p) < 0) { | |
435 | fprintf(stderr, "Dump terminated\n"); | |
436 | exit(1); | |
437 | } | |
438 | ||
439 | return 0; | |
440 | } | |
441 | ||
442 | /***************************************************************************** | |
443 | * Command parser | |
444 | *****************************************************************************/ | |
445 | ||
446 | static int hex(char ch) | |
447 | { | |
448 | if ((ch >= 'a') && (ch <= 'f')) | |
449 | return ch - 'a' + 10; | |
450 | if ((ch >= '0') && (ch <= '9')) | |
451 | return ch - '0'; | |
452 | if ((ch >= 'A') && (ch <= 'F')) | |
453 | return ch - 'A' + 10; | |
454 | return -1; | |
455 | } | |
456 | ||
457 | static int hex2mem(const char *buf, uint8_t *mem, int count) | |
458 | { | |
459 | int i, j; | |
460 | int c; | |
461 | ||
462 | for (i = 0, j = 0; i < count; i++, j += 2) { | |
463 | c = hex(buf[j]); | |
464 | if (c < 0) | |
465 | goto err; | |
466 | ||
467 | mem[i] = c << 4; | |
468 | ||
469 | c = hex(buf[j + 1]); | |
470 | if (c < 0) | |
471 | goto err; | |
472 | ||
473 | mem[i] |= c; | |
474 | } | |
475 | ||
476 | return 0; | |
477 | ||
478 | err: | |
479 | return -1; | |
480 | } | |
481 | ||
482 | static void usage(void) __attribute__((noreturn)); | |
483 | ||
484 | static void usage(void) | |
485 | { | |
486 | fprintf(stderr, "Usage: ip l2tp add tunnel\n"); | |
487 | fprintf(stderr, " remote ADDR local ADDR\n"); | |
488 | fprintf(stderr, " tunnel_id ID peer_tunnel_id ID\n"); | |
489 | fprintf(stderr, " [ encap { ip | udp } ]\n"); | |
490 | fprintf(stderr, " [ udp_sport PORT ] [ udp_dport PORT ]\n"); | |
ae5555d3 | 491 | fprintf(stderr, "Usage: ip l2tp add session [ name NAME ]\n"); |
38cd311a SH |
492 | fprintf(stderr, " tunnel_id ID\n"); |
493 | fprintf(stderr, " session_id ID peer_session_id ID\n"); | |
494 | fprintf(stderr, " [ cookie HEXSTR ] [ peer_cookie HEXSTR ]\n"); | |
495 | fprintf(stderr, " [ offset OFFSET ] [ peer_offset OFFSET ]\n"); | |
496 | fprintf(stderr, " ip l2tp del tunnel tunnel_id ID\n"); | |
497 | fprintf(stderr, " ip l2tp del session tunnel_id ID session_id ID\n"); | |
498 | fprintf(stderr, " ip l2tp show tunnel [ tunnel_id ID ]\n"); | |
499 | fprintf(stderr, " ip l2tp show session [ tunnel_id ID ] [ session_id ID ]\n"); | |
500 | fprintf(stderr, "\n"); | |
501 | fprintf(stderr, "Where: NAME := STRING\n"); | |
502 | fprintf(stderr, " ADDR := { IP_ADDRESS | any }\n"); | |
503 | fprintf(stderr, " PORT := { 0..65535 }\n"); | |
504 | fprintf(stderr, " ID := { 1..4294967295 }\n"); | |
505 | fprintf(stderr, " HEXSTR := { 8 or 16 hex digits (4 / 8 bytes) }\n"); | |
506 | exit(-1); | |
507 | } | |
508 | ||
509 | static int parse_args(int argc, char **argv, int cmd, struct l2tp_parm *p) | |
510 | { | |
511 | memset(p, 0, sizeof(*p)); | |
512 | ||
513 | if (argc == 0) | |
514 | usage(); | |
515 | ||
516 | while (argc > 0) { | |
517 | if (strcmp(*argv, "encap") == 0) { | |
518 | NEXT_ARG(); | |
519 | if (strcmp(*argv, "ip") == 0) { | |
520 | p->encap = L2TP_ENCAPTYPE_IP; | |
521 | } else if (strcmp(*argv, "udp") == 0) { | |
522 | p->encap = L2TP_ENCAPTYPE_UDP; | |
523 | } else { | |
524 | fprintf(stderr, "Unknown tunnel encapsulation.\n"); | |
525 | exit(-1); | |
526 | } | |
ae5555d3 JV |
527 | } else if (strcmp(*argv, "name") == 0) { |
528 | NEXT_ARG(); | |
529 | p->ifname = *argv; | |
38cd311a SH |
530 | } else if (strcmp(*argv, "remote") == 0) { |
531 | NEXT_ARG(); | |
e2780880 | 532 | p->peer_ip.s_addr = get_addr32(*argv); |
38cd311a SH |
533 | } else if (strcmp(*argv, "local") == 0) { |
534 | NEXT_ARG(); | |
e2780880 | 535 | p->local_ip.s_addr = get_addr32(*argv); |
38cd311a SH |
536 | } else if ((strcmp(*argv, "tunnel_id") == 0) || |
537 | (strcmp(*argv, "tid") == 0)) { | |
538 | __u32 uval; | |
539 | NEXT_ARG(); | |
540 | if (get_u32(&uval, *argv, 0)) | |
541 | invarg("invalid ID\n", *argv); | |
542 | p->tunnel_id = uval; | |
543 | } else if ((strcmp(*argv, "peer_tunnel_id") == 0) || | |
544 | (strcmp(*argv, "ptid") == 0)) { | |
545 | __u32 uval; | |
546 | NEXT_ARG(); | |
547 | if (get_u32(&uval, *argv, 0)) | |
548 | invarg("invalid ID\n", *argv); | |
549 | p->peer_tunnel_id = uval; | |
550 | } else if ((strcmp(*argv, "session_id") == 0) || | |
551 | (strcmp(*argv, "sid") == 0)) { | |
552 | __u32 uval; | |
553 | NEXT_ARG(); | |
554 | if (get_u32(&uval, *argv, 0)) | |
555 | invarg("invalid ID\n", *argv); | |
556 | p->session_id = uval; | |
557 | } else if ((strcmp(*argv, "peer_session_id") == 0) || | |
558 | (strcmp(*argv, "psid") == 0)) { | |
559 | __u32 uval; | |
560 | NEXT_ARG(); | |
561 | if (get_u32(&uval, *argv, 0)) | |
562 | invarg("invalid ID\n", *argv); | |
563 | p->peer_session_id = uval; | |
564 | } else if (strcmp(*argv, "udp_sport") == 0) { | |
565 | __u16 uval; | |
566 | NEXT_ARG(); | |
567 | if (get_u16(&uval, *argv, 0)) | |
568 | invarg("invalid port\n", *argv); | |
569 | p->local_udp_port = uval; | |
570 | } else if (strcmp(*argv, "udp_dport") == 0) { | |
571 | __u16 uval; | |
572 | NEXT_ARG(); | |
573 | if (get_u16(&uval, *argv, 0)) | |
574 | invarg("invalid port\n", *argv); | |
575 | p->peer_udp_port = uval; | |
576 | } else if (strcmp(*argv, "offset") == 0) { | |
577 | __u8 uval; | |
578 | NEXT_ARG(); | |
579 | if (get_u8(&uval, *argv, 0)) | |
580 | invarg("invalid offset\n", *argv); | |
581 | p->offset = uval; | |
582 | } else if (strcmp(*argv, "peer_offset") == 0) { | |
583 | __u8 uval; | |
584 | NEXT_ARG(); | |
585 | if (get_u8(&uval, *argv, 0)) | |
586 | invarg("invalid offset\n", *argv); | |
587 | p->peer_offset = uval; | |
588 | } else if (strcmp(*argv, "cookie") == 0) { | |
589 | int slen; | |
590 | NEXT_ARG(); | |
591 | slen = strlen(*argv); | |
592 | if ((slen != 8) && (slen != 16)) | |
593 | invarg("cookie must be either 8 or 16 hex digits\n", *argv); | |
594 | ||
595 | p->cookie_len = slen / 2; | |
596 | if (hex2mem(*argv, p->cookie, p->cookie_len) < 0) | |
597 | invarg("cookie must be a hex string\n", *argv); | |
598 | } else if (strcmp(*argv, "peer_cookie") == 0) { | |
599 | int slen; | |
600 | NEXT_ARG(); | |
601 | slen = strlen(*argv); | |
602 | if ((slen != 8) && (slen != 16)) | |
603 | invarg("cookie must be either 8 or 16 hex digits\n", *argv); | |
604 | ||
605 | p->peer_cookie_len = slen / 2; | |
606 | if (hex2mem(*argv, p->peer_cookie, p->peer_cookie_len) < 0) | |
607 | invarg("cookie must be a hex string\n", *argv); | |
608 | } else if (strcmp(*argv, "tunnel") == 0) { | |
609 | p->tunnel = 1; | |
610 | } else if (strcmp(*argv, "session") == 0) { | |
611 | p->session = 1; | |
612 | } else if (matches(*argv, "help") == 0) { | |
613 | usage(); | |
614 | } else { | |
615 | fprintf(stderr, "Unknown command: %s\n", *argv); | |
616 | usage(); | |
617 | } | |
618 | ||
619 | argc--; argv++; | |
620 | } | |
621 | ||
622 | return 0; | |
623 | } | |
624 | ||
625 | ||
626 | static int do_add(int argc, char **argv) | |
627 | { | |
628 | struct l2tp_parm p; | |
629 | int ret = 0; | |
630 | ||
631 | if (parse_args(argc, argv, L2TP_ADD, &p) < 0) | |
632 | return -1; | |
633 | ||
634 | if (!p.tunnel && !p.session) | |
635 | missarg("tunnel or session"); | |
636 | ||
637 | if (p.tunnel_id == 0) | |
638 | missarg("tunnel_id"); | |
639 | ||
640 | /* session_id and peer_session_id must be provided for sessions */ | |
641 | if ((p.session) && (p.peer_session_id == 0)) | |
642 | missarg("peer_session_id"); | |
643 | if ((p.session) && (p.session_id == 0)) | |
644 | missarg("session_id"); | |
645 | ||
646 | /* peer_tunnel_id is needed for tunnels */ | |
647 | if ((p.tunnel) && (p.peer_tunnel_id == 0)) | |
648 | missarg("peer_tunnel_id"); | |
649 | ||
650 | if (p.tunnel) { | |
e2780880 | 651 | if (p.local_ip.s_addr == 0) |
38cd311a SH |
652 | missarg("local"); |
653 | ||
e2780880 | 654 | if (p.peer_ip.s_addr == 0) |
38cd311a SH |
655 | missarg("remote"); |
656 | ||
657 | if (p.encap == L2TP_ENCAPTYPE_UDP) { | |
658 | if (p.local_udp_port == 0) | |
659 | missarg("udp_sport"); | |
660 | if (p.peer_udp_port == 0) | |
661 | missarg("udp_dport"); | |
662 | } | |
663 | ||
664 | ret = create_tunnel(&p); | |
665 | } | |
666 | ||
667 | if (p.session) { | |
668 | /* Only ethernet pseudowires supported */ | |
669 | p.pw_type = L2TP_PWTYPE_ETH; | |
670 | ||
671 | ret = create_session(&p); | |
672 | } | |
673 | ||
674 | return ret; | |
675 | } | |
676 | ||
677 | static int do_del(int argc, char **argv) | |
678 | { | |
679 | struct l2tp_parm p; | |
680 | ||
681 | if (parse_args(argc, argv, L2TP_DEL, &p) < 0) | |
682 | return -1; | |
683 | ||
684 | if (!p.tunnel && !p.session) | |
685 | missarg("tunnel or session"); | |
686 | ||
687 | if ((p.tunnel) && (p.tunnel_id == 0)) | |
688 | missarg("tunnel_id"); | |
689 | if ((p.session) && (p.session_id == 0)) | |
690 | missarg("session_id"); | |
691 | ||
692 | if (p.session_id) | |
693 | return delete_session(&p); | |
694 | else | |
695 | return delete_tunnel(&p); | |
696 | ||
697 | return -1; | |
698 | } | |
699 | ||
700 | static int do_show(int argc, char **argv) | |
701 | { | |
702 | struct l2tp_data data; | |
703 | struct l2tp_parm *p = &data.config; | |
704 | ||
705 | if (parse_args(argc, argv, L2TP_GET, p) < 0) | |
706 | return -1; | |
707 | ||
708 | if (!p->tunnel && !p->session) | |
709 | missarg("tunnel or session"); | |
710 | ||
711 | if (p->session) | |
712 | get_session(&data); | |
713 | else | |
714 | get_tunnel(&data); | |
715 | ||
716 | return 0; | |
717 | } | |
718 | ||
719 | static int genl_parse_getfamily(struct nlmsghdr *nlh) | |
720 | { | |
721 | struct rtattr *tb[CTRL_ATTR_MAX + 1]; | |
722 | struct genlmsghdr *ghdr = NLMSG_DATA(nlh); | |
723 | int len = nlh->nlmsg_len; | |
724 | struct rtattr *attrs; | |
725 | ||
726 | if (nlh->nlmsg_type != GENL_ID_CTRL) { | |
727 | fprintf(stderr, "Not a controller message, nlmsg_len=%d " | |
728 | "nlmsg_type=0x%x\n", nlh->nlmsg_len, nlh->nlmsg_type); | |
729 | return -1; | |
730 | } | |
731 | ||
732 | if (ghdr->cmd != CTRL_CMD_NEWFAMILY) { | |
733 | fprintf(stderr, "Unknown controller command %d\n", ghdr->cmd); | |
734 | return -1; | |
735 | } | |
736 | ||
737 | len -= NLMSG_LENGTH(GENL_HDRLEN); | |
738 | ||
739 | if (len < 0) { | |
740 | fprintf(stderr, "wrong controller message len %d\n", len); | |
741 | return -1; | |
742 | } | |
743 | ||
744 | attrs = (struct rtattr *) ((char *) ghdr + GENL_HDRLEN); | |
745 | parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len); | |
746 | ||
747 | if (tb[CTRL_ATTR_FAMILY_ID] == NULL) { | |
748 | fprintf(stderr, "Missing family id TLV\n"); | |
749 | return -1; | |
750 | } | |
751 | ||
752 | return rta_getattr_u16(tb[CTRL_ATTR_FAMILY_ID]); | |
753 | } | |
754 | ||
755 | int genl_ctrl_resolve_family(const char *family) | |
756 | { | |
757 | struct { | |
758 | struct nlmsghdr n; | |
759 | struct genlmsghdr g; | |
760 | char buf[1024]; | |
761 | } req; | |
762 | ||
763 | memset(&req, 0, sizeof(req)); | |
764 | req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); | |
765 | req.n.nlmsg_flags = NLM_F_REQUEST; | |
766 | req.n.nlmsg_type = GENL_ID_CTRL; | |
767 | req.g.cmd = CTRL_CMD_GETFAMILY; | |
768 | ||
769 | addattr_l(&req.n, 1024, CTRL_ATTR_FAMILY_NAME, | |
770 | family, strlen(family) + 1); | |
771 | ||
772 | if (rtnl_talk(&genl_rth, &req.n, 0, 0, &req.n) < 0) { | |
773 | fprintf(stderr, "Error talking to the kernel\n"); | |
774 | return -2; | |
775 | } | |
776 | ||
777 | return genl_parse_getfamily(&req.n); | |
778 | } | |
779 | ||
780 | int do_ipl2tp(int argc, char **argv) | |
781 | { | |
782 | if (genl_family < 0) { | |
783 | if (rtnl_open_byproto(&genl_rth, 0, NETLINK_GENERIC) < 0) { | |
784 | fprintf(stderr, "Cannot open generic netlink socket\n"); | |
785 | exit(1); | |
786 | } | |
787 | ||
788 | genl_family = genl_ctrl_resolve_family(L2TP_GENL_NAME); | |
789 | if (genl_family < 0) | |
790 | exit(1); | |
791 | } | |
792 | ||
793 | if (argc < 1) | |
794 | usage(); | |
795 | ||
796 | if (matches(*argv, "add") == 0) | |
797 | return do_add(argc-1, argv+1); | |
798 | if (matches(*argv, "del") == 0) | |
799 | return do_del(argc-1, argv+1); | |
800 | if (matches(*argv, "show") == 0 || | |
801 | matches(*argv, "lst") == 0 || | |
802 | matches(*argv, "list") == 0) | |
803 | return do_show(argc-1, argv+1); | |
804 | if (matches(*argv, "help") == 0) | |
805 | usage(); | |
806 | ||
807 | fprintf(stderr, "Command \"%s\" is unknown, try \"ip l2tp help\".\n", *argv); | |
808 | exit(-1); | |
809 | } |