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