1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Inject packets with all sorts of encapsulation into the kernel. |
4 | * |
5 | * IPv4/IPv6 outer layer 3 |
6 | * GRE/GUE/BARE outer layer 4, where bare is IPIP/SIT/IPv4-in-IPv6/.. |
7 | * IPv4/IPv6 inner layer 3 |
8 | */ |
9 | |
10 | #define _GNU_SOURCE |
11 | |
12 | #include <stddef.h> |
13 | #include <arpa/inet.h> |
14 | #include <asm/byteorder.h> |
15 | #include <error.h> |
16 | #include <errno.h> |
17 | #include <linux/if_packet.h> |
18 | #include <linux/if_ether.h> |
19 | #include <linux/ipv6.h> |
20 | #include <netinet/ip.h> |
21 | #include <netinet/in.h> |
22 | #include <netinet/udp.h> |
23 | #include <poll.h> |
24 | #include <stdbool.h> |
25 | #include <stdlib.h> |
26 | #include <stdio.h> |
27 | #include <string.h> |
28 | #include <sys/ioctl.h> |
29 | #include <sys/socket.h> |
30 | #include <sys/stat.h> |
31 | #include <sys/time.h> |
32 | #include <sys/types.h> |
33 | #include <unistd.h> |
34 | |
35 | #define CFG_PORT_INNER 8000 |
36 | |
37 | /* Add some protocol definitions that do not exist in userspace */ |
38 | |
39 | struct grehdr { |
40 | uint16_t unused; |
41 | uint16_t protocol; |
42 | } __attribute__((packed)); |
43 | |
44 | struct guehdr { |
45 | union { |
46 | struct { |
47 | #if defined(__LITTLE_ENDIAN_BITFIELD) |
48 | __u8 hlen:5, |
49 | control:1, |
50 | version:2; |
51 | #elif defined (__BIG_ENDIAN_BITFIELD) |
52 | __u8 version:2, |
53 | control:1, |
54 | hlen:5; |
55 | #else |
56 | #error "Please fix <asm/byteorder.h>" |
57 | #endif |
58 | __u8 proto_ctype; |
59 | __be16 flags; |
60 | }; |
61 | __be32 word; |
62 | }; |
63 | }; |
64 | |
65 | static uint8_t cfg_dsfield_inner; |
66 | static uint8_t cfg_dsfield_outer; |
67 | static uint8_t cfg_encap_proto; |
68 | static bool cfg_expect_failure = false; |
69 | static int = AF_UNSPEC; /* optional SIT prefix */ |
70 | static int cfg_l3_inner = AF_UNSPEC; |
71 | static int cfg_l3_outer = AF_UNSPEC; |
72 | static int cfg_num_pkt = 10; |
73 | static int cfg_num_secs = 0; |
74 | static char cfg_payload_char = 'a'; |
75 | static int cfg_payload_len = 100; |
76 | static int cfg_port_gue = 6080; |
77 | static bool cfg_only_rx; |
78 | static bool cfg_only_tx; |
79 | static int cfg_src_port = 9; |
80 | |
81 | static char buf[ETH_DATA_LEN]; |
82 | |
83 | #define INIT_ADDR4(name, addr4, port) \ |
84 | static struct sockaddr_in name = { \ |
85 | .sin_family = AF_INET, \ |
86 | .sin_port = __constant_htons(port), \ |
87 | .sin_addr.s_addr = __constant_htonl(addr4), \ |
88 | }; |
89 | |
90 | #define INIT_ADDR6(name, addr6, port) \ |
91 | static struct sockaddr_in6 name = { \ |
92 | .sin6_family = AF_INET6, \ |
93 | .sin6_port = __constant_htons(port), \ |
94 | .sin6_addr = addr6, \ |
95 | }; |
96 | |
97 | INIT_ADDR4(in_daddr4, INADDR_LOOPBACK, CFG_PORT_INNER) |
98 | INIT_ADDR4(in_saddr4, INADDR_LOOPBACK + 2, 0) |
99 | INIT_ADDR4(out_daddr4, INADDR_LOOPBACK, 0) |
100 | INIT_ADDR4(out_saddr4, INADDR_LOOPBACK + 1, 0) |
101 | INIT_ADDR4(, INADDR_LOOPBACK, 0) |
102 | INIT_ADDR4(, INADDR_LOOPBACK + 1, 0) |
103 | |
104 | INIT_ADDR6(in_daddr6, IN6ADDR_LOOPBACK_INIT, CFG_PORT_INNER) |
105 | INIT_ADDR6(in_saddr6, IN6ADDR_LOOPBACK_INIT, 0) |
106 | INIT_ADDR6(out_daddr6, IN6ADDR_LOOPBACK_INIT, 0) |
107 | INIT_ADDR6(out_saddr6, IN6ADDR_LOOPBACK_INIT, 0) |
108 | INIT_ADDR6(, IN6ADDR_LOOPBACK_INIT, 0) |
109 | INIT_ADDR6(, IN6ADDR_LOOPBACK_INIT, 0) |
110 | |
111 | static unsigned long util_gettime(void) |
112 | { |
113 | struct timeval tv; |
114 | |
115 | gettimeofday(&tv, NULL); |
116 | return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); |
117 | } |
118 | |
119 | static void util_printaddr(const char *msg, struct sockaddr *addr) |
120 | { |
121 | unsigned long off = 0; |
122 | char nbuf[INET6_ADDRSTRLEN]; |
123 | |
124 | switch (addr->sa_family) { |
125 | case PF_INET: |
126 | off = __builtin_offsetof(struct sockaddr_in, sin_addr); |
127 | break; |
128 | case PF_INET6: |
129 | off = __builtin_offsetof(struct sockaddr_in6, sin6_addr); |
130 | break; |
131 | default: |
132 | error(1, 0, "printaddr: unsupported family %u\n" , |
133 | addr->sa_family); |
134 | } |
135 | |
136 | if (!inet_ntop(addr->sa_family, ((void *) addr) + off, nbuf, |
137 | sizeof(nbuf))) |
138 | error(1, errno, "inet_ntop" ); |
139 | |
140 | fprintf(stderr, "%s: %s\n" , msg, nbuf); |
141 | } |
142 | |
143 | static unsigned long add_csum_hword(const uint16_t *start, int num_u16) |
144 | { |
145 | unsigned long sum = 0; |
146 | int i; |
147 | |
148 | for (i = 0; i < num_u16; i++) |
149 | sum += start[i]; |
150 | |
151 | return sum; |
152 | } |
153 | |
154 | static uint16_t build_ip_csum(const uint16_t *start, int num_u16, |
155 | unsigned long sum) |
156 | { |
157 | sum += add_csum_hword(start, num_u16); |
158 | |
159 | while (sum >> 16) |
160 | sum = (sum & 0xffff) + (sum >> 16); |
161 | |
162 | return ~sum; |
163 | } |
164 | |
165 | static void (void *, uint8_t proto, |
166 | uint32_t src, uint32_t dst, |
167 | int payload_len, uint8_t tos) |
168 | { |
169 | struct iphdr *iph = header; |
170 | |
171 | iph->ihl = 5; |
172 | iph->version = 4; |
173 | iph->tos = tos; |
174 | iph->ttl = 8; |
175 | iph->tot_len = htons(sizeof(*iph) + payload_len); |
176 | iph->id = htons(1337); |
177 | iph->protocol = proto; |
178 | iph->saddr = src; |
179 | iph->daddr = dst; |
180 | iph->check = build_ip_csum(start: (void *) iph, num_u16: iph->ihl << 1, sum: 0); |
181 | } |
182 | |
183 | static void ipv6_set_dsfield(struct ipv6hdr *ip6h, uint8_t dsfield) |
184 | { |
185 | uint16_t val, *ptr = (uint16_t *)ip6h; |
186 | |
187 | val = ntohs(*ptr); |
188 | val &= 0xF00F; |
189 | val |= ((uint16_t) dsfield) << 4; |
190 | *ptr = htons(val); |
191 | } |
192 | |
193 | static void (void *, uint8_t proto, |
194 | struct sockaddr_in6 *src, |
195 | struct sockaddr_in6 *dst, |
196 | int payload_len, uint8_t dsfield) |
197 | { |
198 | struct ipv6hdr *ip6h = header; |
199 | |
200 | ip6h->version = 6; |
201 | ip6h->payload_len = htons(payload_len); |
202 | ip6h->nexthdr = proto; |
203 | ip6h->hop_limit = 8; |
204 | ipv6_set_dsfield(ip6h, dsfield); |
205 | |
206 | memcpy(&ip6h->saddr, &src->sin6_addr, sizeof(ip6h->saddr)); |
207 | memcpy(&ip6h->daddr, &dst->sin6_addr, sizeof(ip6h->daddr)); |
208 | } |
209 | |
210 | static uint16_t build_udp_v4_csum(const struct iphdr *iph, |
211 | const struct udphdr *udph, |
212 | int num_words) |
213 | { |
214 | unsigned long pseudo_sum; |
215 | int num_u16 = sizeof(iph->saddr); /* halfwords: twice byte len */ |
216 | |
217 | pseudo_sum = add_csum_hword(start: (void *) &iph->saddr, num_u16); |
218 | pseudo_sum += htons(IPPROTO_UDP); |
219 | pseudo_sum += udph->len; |
220 | return build_ip_csum(start: (void *) udph, num_u16: num_words, sum: pseudo_sum); |
221 | } |
222 | |
223 | static uint16_t build_udp_v6_csum(const struct ipv6hdr *ip6h, |
224 | const struct udphdr *udph, |
225 | int num_words) |
226 | { |
227 | unsigned long pseudo_sum; |
228 | int num_u16 = sizeof(ip6h->saddr); /* halfwords: twice byte len */ |
229 | |
230 | pseudo_sum = add_csum_hword(start: (void *) &ip6h->saddr, num_u16); |
231 | pseudo_sum += htons(ip6h->nexthdr); |
232 | pseudo_sum += ip6h->payload_len; |
233 | return build_ip_csum(start: (void *) udph, num_u16: num_words, sum: pseudo_sum); |
234 | } |
235 | |
236 | static void (void *, int payload_len, |
237 | uint16_t dport, int family) |
238 | { |
239 | struct udphdr *udph = header; |
240 | int len = sizeof(*udph) + payload_len; |
241 | |
242 | udph->source = htons(cfg_src_port); |
243 | udph->dest = htons(dport); |
244 | udph->len = htons(len); |
245 | udph->check = 0; |
246 | if (family == AF_INET) |
247 | udph->check = build_udp_v4_csum(header - sizeof(struct iphdr), |
248 | udph, len >> 1); |
249 | else |
250 | udph->check = build_udp_v6_csum(ip6h: header - sizeof(struct ipv6hdr), |
251 | udph, num_words: len >> 1); |
252 | } |
253 | |
254 | static void (void *, uint8_t proto) |
255 | { |
256 | struct guehdr *gueh = header; |
257 | |
258 | gueh->proto_ctype = proto; |
259 | } |
260 | |
261 | static void (void *, uint16_t proto) |
262 | { |
263 | struct grehdr *greh = header; |
264 | |
265 | greh->protocol = htons(proto); |
266 | } |
267 | |
268 | static int l3_length(int family) |
269 | { |
270 | if (family == AF_INET) |
271 | return sizeof(struct iphdr); |
272 | else |
273 | return sizeof(struct ipv6hdr); |
274 | } |
275 | |
276 | static int build_packet(void) |
277 | { |
278 | int ol3_len = 0, ol4_len = 0, il3_len = 0, il4_len = 0; |
279 | int el3_len = 0; |
280 | |
281 | if (cfg_l3_extra) |
282 | el3_len = l3_length(family: cfg_l3_extra); |
283 | |
284 | /* calculate header offsets */ |
285 | if (cfg_encap_proto) { |
286 | ol3_len = l3_length(family: cfg_l3_outer); |
287 | |
288 | if (cfg_encap_proto == IPPROTO_GRE) |
289 | ol4_len = sizeof(struct grehdr); |
290 | else if (cfg_encap_proto == IPPROTO_UDP) |
291 | ol4_len = sizeof(struct udphdr) + sizeof(struct guehdr); |
292 | } |
293 | |
294 | il3_len = l3_length(family: cfg_l3_inner); |
295 | il4_len = sizeof(struct udphdr); |
296 | |
297 | if (el3_len + ol3_len + ol4_len + il3_len + il4_len + cfg_payload_len >= |
298 | sizeof(buf)) |
299 | error(1, 0, "packet too large\n" ); |
300 | |
301 | /* |
302 | * Fill packet from inside out, to calculate correct checksums. |
303 | * But create ip before udp headers, as udp uses ip for pseudo-sum. |
304 | */ |
305 | memset(buf + el3_len + ol3_len + ol4_len + il3_len + il4_len, |
306 | cfg_payload_char, cfg_payload_len); |
307 | |
308 | /* add zero byte for udp csum padding */ |
309 | buf[el3_len + ol3_len + ol4_len + il3_len + il4_len + cfg_payload_len] = 0; |
310 | |
311 | switch (cfg_l3_inner) { |
312 | case PF_INET: |
313 | build_ipv4_header(header: buf + el3_len + ol3_len + ol4_len, |
314 | IPPROTO_UDP, |
315 | src: in_saddr4.sin_addr.s_addr, |
316 | dst: in_daddr4.sin_addr.s_addr, |
317 | payload_len: il4_len + cfg_payload_len, |
318 | tos: cfg_dsfield_inner); |
319 | break; |
320 | case PF_INET6: |
321 | build_ipv6_header(header: buf + el3_len + ol3_len + ol4_len, |
322 | IPPROTO_UDP, |
323 | src: &in_saddr6, dst: &in_daddr6, |
324 | payload_len: il4_len + cfg_payload_len, |
325 | dsfield: cfg_dsfield_inner); |
326 | break; |
327 | } |
328 | |
329 | build_udp_header(header: buf + el3_len + ol3_len + ol4_len + il3_len, |
330 | payload_len: cfg_payload_len, CFG_PORT_INNER, family: cfg_l3_inner); |
331 | |
332 | if (!cfg_encap_proto) |
333 | return il3_len + il4_len + cfg_payload_len; |
334 | |
335 | switch (cfg_l3_outer) { |
336 | case PF_INET: |
337 | build_ipv4_header(header: buf + el3_len, proto: cfg_encap_proto, |
338 | src: out_saddr4.sin_addr.s_addr, |
339 | dst: out_daddr4.sin_addr.s_addr, |
340 | payload_len: ol4_len + il3_len + il4_len + cfg_payload_len, |
341 | tos: cfg_dsfield_outer); |
342 | break; |
343 | case PF_INET6: |
344 | build_ipv6_header(header: buf + el3_len, proto: cfg_encap_proto, |
345 | src: &out_saddr6, dst: &out_daddr6, |
346 | payload_len: ol4_len + il3_len + il4_len + cfg_payload_len, |
347 | dsfield: cfg_dsfield_outer); |
348 | break; |
349 | } |
350 | |
351 | switch (cfg_encap_proto) { |
352 | case IPPROTO_UDP: |
353 | build_gue_header(header: buf + el3_len + ol3_len + ol4_len - |
354 | sizeof(struct guehdr), |
355 | proto: cfg_l3_inner == PF_INET ? IPPROTO_IPIP |
356 | : IPPROTO_IPV6); |
357 | build_udp_header(header: buf + el3_len + ol3_len, |
358 | payload_len: sizeof(struct guehdr) + il3_len + il4_len + |
359 | cfg_payload_len, |
360 | dport: cfg_port_gue, family: cfg_l3_outer); |
361 | break; |
362 | case IPPROTO_GRE: |
363 | build_gre_header(header: buf + el3_len + ol3_len, |
364 | proto: cfg_l3_inner == PF_INET ? ETH_P_IP |
365 | : ETH_P_IPV6); |
366 | break; |
367 | } |
368 | |
369 | switch (cfg_l3_extra) { |
370 | case PF_INET: |
371 | build_ipv4_header(header: buf, |
372 | proto: cfg_l3_outer == PF_INET ? IPPROTO_IPIP |
373 | : IPPROTO_IPV6, |
374 | src: extra_saddr4.sin_addr.s_addr, |
375 | dst: extra_daddr4.sin_addr.s_addr, |
376 | payload_len: ol3_len + ol4_len + il3_len + il4_len + |
377 | cfg_payload_len, tos: 0); |
378 | break; |
379 | case PF_INET6: |
380 | build_ipv6_header(header: buf, |
381 | proto: cfg_l3_outer == PF_INET ? IPPROTO_IPIP |
382 | : IPPROTO_IPV6, |
383 | src: &extra_saddr6, dst: &extra_daddr6, |
384 | payload_len: ol3_len + ol4_len + il3_len + il4_len + |
385 | cfg_payload_len, dsfield: 0); |
386 | break; |
387 | } |
388 | |
389 | return el3_len + ol3_len + ol4_len + il3_len + il4_len + |
390 | cfg_payload_len; |
391 | } |
392 | |
393 | /* sender transmits encapsulated over RAW or unencap'd over UDP */ |
394 | static int setup_tx(void) |
395 | { |
396 | int family, fd, ret; |
397 | |
398 | if (cfg_l3_extra) |
399 | family = cfg_l3_extra; |
400 | else if (cfg_l3_outer) |
401 | family = cfg_l3_outer; |
402 | else |
403 | family = cfg_l3_inner; |
404 | |
405 | fd = socket(family, SOCK_RAW, IPPROTO_RAW); |
406 | if (fd == -1) |
407 | error(1, errno, "socket tx" ); |
408 | |
409 | if (cfg_l3_extra) { |
410 | if (cfg_l3_extra == PF_INET) |
411 | ret = connect(fd, (void *) &extra_daddr4, |
412 | sizeof(extra_daddr4)); |
413 | else |
414 | ret = connect(fd, (void *) &extra_daddr6, |
415 | sizeof(extra_daddr6)); |
416 | if (ret) |
417 | error(1, errno, "connect tx" ); |
418 | } else if (cfg_l3_outer) { |
419 | /* connect to destination if not encapsulated */ |
420 | if (cfg_l3_outer == PF_INET) |
421 | ret = connect(fd, (void *) &out_daddr4, |
422 | sizeof(out_daddr4)); |
423 | else |
424 | ret = connect(fd, (void *) &out_daddr6, |
425 | sizeof(out_daddr6)); |
426 | if (ret) |
427 | error(1, errno, "connect tx" ); |
428 | } else { |
429 | /* otherwise using loopback */ |
430 | if (cfg_l3_inner == PF_INET) |
431 | ret = connect(fd, (void *) &in_daddr4, |
432 | sizeof(in_daddr4)); |
433 | else |
434 | ret = connect(fd, (void *) &in_daddr6, |
435 | sizeof(in_daddr6)); |
436 | if (ret) |
437 | error(1, errno, "connect tx" ); |
438 | } |
439 | |
440 | return fd; |
441 | } |
442 | |
443 | /* receiver reads unencapsulated UDP */ |
444 | static int setup_rx(void) |
445 | { |
446 | int fd, ret; |
447 | |
448 | fd = socket(cfg_l3_inner, SOCK_DGRAM, 0); |
449 | if (fd == -1) |
450 | error(1, errno, "socket rx" ); |
451 | |
452 | if (cfg_l3_inner == PF_INET) |
453 | ret = bind(fd, (void *) &in_daddr4, sizeof(in_daddr4)); |
454 | else |
455 | ret = bind(fd, (void *) &in_daddr6, sizeof(in_daddr6)); |
456 | if (ret) |
457 | error(1, errno, "bind rx" ); |
458 | |
459 | return fd; |
460 | } |
461 | |
462 | static int do_tx(int fd, const char *pkt, int len) |
463 | { |
464 | int ret; |
465 | |
466 | ret = write(fd, pkt, len); |
467 | if (ret == -1) |
468 | error(1, errno, "send" ); |
469 | if (ret != len) |
470 | error(1, errno, "send: len (%d < %d)\n" , ret, len); |
471 | |
472 | return 1; |
473 | } |
474 | |
475 | static int do_poll(int fd, short events, int timeout) |
476 | { |
477 | struct pollfd pfd; |
478 | int ret; |
479 | |
480 | pfd.fd = fd; |
481 | pfd.events = events; |
482 | |
483 | ret = poll(&pfd, 1, timeout); |
484 | if (ret == -1) |
485 | error(1, errno, "poll" ); |
486 | if (ret && !(pfd.revents & POLLIN)) |
487 | error(1, errno, "poll: unexpected event 0x%x\n" , pfd.revents); |
488 | |
489 | return ret; |
490 | } |
491 | |
492 | static int do_rx(int fd) |
493 | { |
494 | char rbuf; |
495 | int ret, num = 0; |
496 | |
497 | while (1) { |
498 | ret = recv(fd, &rbuf, 1, MSG_DONTWAIT); |
499 | if (ret == -1 && errno == EAGAIN) |
500 | break; |
501 | if (ret == -1) |
502 | error(1, errno, "recv" ); |
503 | if (rbuf != cfg_payload_char) |
504 | error(1, 0, "recv: payload mismatch" ); |
505 | num++; |
506 | } |
507 | |
508 | return num; |
509 | } |
510 | |
511 | static int do_main(void) |
512 | { |
513 | unsigned long tstop, treport, tcur; |
514 | int fdt = -1, fdr = -1, len, tx = 0, rx = 0; |
515 | |
516 | if (!cfg_only_tx) |
517 | fdr = setup_rx(); |
518 | if (!cfg_only_rx) |
519 | fdt = setup_tx(); |
520 | |
521 | len = build_packet(); |
522 | |
523 | tcur = util_gettime(); |
524 | treport = tcur + 1000; |
525 | tstop = tcur + (cfg_num_secs * 1000); |
526 | |
527 | while (1) { |
528 | if (!cfg_only_rx) |
529 | tx += do_tx(fd: fdt, pkt: buf, len); |
530 | |
531 | if (!cfg_only_tx) |
532 | rx += do_rx(fd: fdr); |
533 | |
534 | if (cfg_num_secs) { |
535 | tcur = util_gettime(); |
536 | if (tcur >= tstop) |
537 | break; |
538 | if (tcur >= treport) { |
539 | fprintf(stderr, "pkts: tx=%u rx=%u\n" , tx, rx); |
540 | tx = 0; |
541 | rx = 0; |
542 | treport = tcur + 1000; |
543 | } |
544 | } else { |
545 | if (tx == cfg_num_pkt) |
546 | break; |
547 | } |
548 | } |
549 | |
550 | /* read straggler packets, if any */ |
551 | if (rx < tx) { |
552 | tstop = util_gettime() + 100; |
553 | while (rx < tx) { |
554 | tcur = util_gettime(); |
555 | if (tcur >= tstop) |
556 | break; |
557 | |
558 | do_poll(fd: fdr, POLLIN, timeout: tstop - tcur); |
559 | rx += do_rx(fd: fdr); |
560 | } |
561 | } |
562 | |
563 | fprintf(stderr, "pkts: tx=%u rx=%u\n" , tx, rx); |
564 | |
565 | if (fdr != -1 && close(fdr)) |
566 | error(1, errno, "close rx" ); |
567 | if (fdt != -1 && close(fdt)) |
568 | error(1, errno, "close tx" ); |
569 | |
570 | /* |
571 | * success (== 0) only if received all packets |
572 | * unless failure is expected, in which case none must arrive. |
573 | */ |
574 | if (cfg_expect_failure) |
575 | return rx != 0; |
576 | else |
577 | return rx != tx; |
578 | } |
579 | |
580 | |
581 | static void __attribute__((noreturn)) usage(const char *filepath) |
582 | { |
583 | fprintf(stderr, "Usage: %s [-e gre|gue|bare|none] [-i 4|6] [-l len] " |
584 | "[-O 4|6] [-o 4|6] [-n num] [-t secs] [-R] [-T] " |
585 | "[-s <osrc> [-d <odst>] [-S <isrc>] [-D <idst>] " |
586 | "[-x <otos>] [-X <itos>] [-f <isport>] [-F]\n" , |
587 | filepath); |
588 | exit(1); |
589 | } |
590 | |
591 | static void parse_addr(int family, void *addr, const char *optarg) |
592 | { |
593 | int ret; |
594 | |
595 | ret = inet_pton(family, optarg, addr); |
596 | if (ret == -1) |
597 | error(1, errno, "inet_pton" ); |
598 | if (ret == 0) |
599 | error(1, 0, "inet_pton: bad string" ); |
600 | } |
601 | |
602 | static void parse_addr4(struct sockaddr_in *addr, const char *optarg) |
603 | { |
604 | parse_addr(AF_INET, addr: &addr->sin_addr, optarg); |
605 | } |
606 | |
607 | static void parse_addr6(struct sockaddr_in6 *addr, const char *optarg) |
608 | { |
609 | parse_addr(AF_INET6, addr: &addr->sin6_addr, optarg); |
610 | } |
611 | |
612 | static int parse_protocol_family(const char *filepath, const char *optarg) |
613 | { |
614 | if (!strcmp(optarg, "4" )) |
615 | return PF_INET; |
616 | if (!strcmp(optarg, "6" )) |
617 | return PF_INET6; |
618 | |
619 | usage(filepath); |
620 | } |
621 | |
622 | static void parse_opts(int argc, char **argv) |
623 | { |
624 | int c; |
625 | |
626 | while ((c = getopt(argc, argv, "d:D:e:f:Fhi:l:n:o:O:Rs:S:t:Tx:X:" )) != -1) { |
627 | switch (c) { |
628 | case 'd': |
629 | if (cfg_l3_outer == AF_UNSPEC) |
630 | error(1, 0, "-d must be preceded by -o" ); |
631 | if (cfg_l3_outer == AF_INET) |
632 | parse_addr4(addr: &out_daddr4, optarg: optarg); |
633 | else |
634 | parse_addr6(addr: &out_daddr6, optarg: optarg); |
635 | break; |
636 | case 'D': |
637 | if (cfg_l3_inner == AF_UNSPEC) |
638 | error(1, 0, "-D must be preceded by -i" ); |
639 | if (cfg_l3_inner == AF_INET) |
640 | parse_addr4(addr: &in_daddr4, optarg: optarg); |
641 | else |
642 | parse_addr6(addr: &in_daddr6, optarg: optarg); |
643 | break; |
644 | case 'e': |
645 | if (!strcmp(optarg, "gre" )) |
646 | cfg_encap_proto = IPPROTO_GRE; |
647 | else if (!strcmp(optarg, "gue" )) |
648 | cfg_encap_proto = IPPROTO_UDP; |
649 | else if (!strcmp(optarg, "bare" )) |
650 | cfg_encap_proto = IPPROTO_IPIP; |
651 | else if (!strcmp(optarg, "none" )) |
652 | cfg_encap_proto = IPPROTO_IP; /* == 0 */ |
653 | else |
654 | usage(filepath: argv[0]); |
655 | break; |
656 | case 'f': |
657 | cfg_src_port = strtol(optarg, NULL, 0); |
658 | break; |
659 | case 'F': |
660 | cfg_expect_failure = true; |
661 | break; |
662 | case 'h': |
663 | usage(filepath: argv[0]); |
664 | break; |
665 | case 'i': |
666 | if (!strcmp(optarg, "4" )) |
667 | cfg_l3_inner = PF_INET; |
668 | else if (!strcmp(optarg, "6" )) |
669 | cfg_l3_inner = PF_INET6; |
670 | else |
671 | usage(filepath: argv[0]); |
672 | break; |
673 | case 'l': |
674 | cfg_payload_len = strtol(optarg, NULL, 0); |
675 | break; |
676 | case 'n': |
677 | cfg_num_pkt = strtol(optarg, NULL, 0); |
678 | break; |
679 | case 'o': |
680 | cfg_l3_outer = parse_protocol_family(argv[0], optarg); |
681 | break; |
682 | case 'O': |
683 | cfg_l3_extra = parse_protocol_family(argv[0], optarg); |
684 | break; |
685 | case 'R': |
686 | cfg_only_rx = true; |
687 | break; |
688 | case 's': |
689 | if (cfg_l3_outer == AF_INET) |
690 | parse_addr4(&out_saddr4, optarg); |
691 | else |
692 | parse_addr6(&out_saddr6, optarg); |
693 | break; |
694 | case 'S': |
695 | if (cfg_l3_inner == AF_INET) |
696 | parse_addr4(&in_saddr4, optarg); |
697 | else |
698 | parse_addr6(&in_saddr6, optarg); |
699 | break; |
700 | case 't': |
701 | cfg_num_secs = strtol(optarg, NULL, 0); |
702 | break; |
703 | case 'T': |
704 | cfg_only_tx = true; |
705 | break; |
706 | case 'x': |
707 | cfg_dsfield_outer = strtol(optarg, NULL, 0); |
708 | break; |
709 | case 'X': |
710 | cfg_dsfield_inner = strtol(optarg, NULL, 0); |
711 | break; |
712 | } |
713 | } |
714 | |
715 | if (cfg_only_rx && cfg_only_tx) |
716 | error(1, 0, "options: cannot combine rx-only and tx-only" ); |
717 | |
718 | if (cfg_encap_proto && cfg_l3_outer == AF_UNSPEC) |
719 | error(1, 0, "options: must specify outer with encap" ); |
720 | else if ((!cfg_encap_proto) && cfg_l3_outer != AF_UNSPEC) |
721 | error(1, 0, "options: cannot combine no-encap and outer" ); |
722 | else if ((!cfg_encap_proto) && cfg_l3_extra != AF_UNSPEC) |
723 | error(1, 0, "options: cannot combine no-encap and extra" ); |
724 | |
725 | if (cfg_l3_inner == AF_UNSPEC) |
726 | cfg_l3_inner = AF_INET6; |
727 | if (cfg_l3_inner == AF_INET6 && cfg_encap_proto == IPPROTO_IPIP) |
728 | cfg_encap_proto = IPPROTO_IPV6; |
729 | |
730 | /* RFC 6040 4.2: |
731 | * on decap, if outer encountered congestion (CE == 0x3), |
732 | * but inner cannot encode ECN (NoECT == 0x0), then drop packet. |
733 | */ |
734 | if (((cfg_dsfield_outer & 0x3) == 0x3) && |
735 | ((cfg_dsfield_inner & 0x3) == 0x0)) |
736 | cfg_expect_failure = true; |
737 | } |
738 | |
739 | static void print_opts(void) |
740 | { |
741 | if (cfg_l3_inner == PF_INET6) { |
742 | util_printaddr(msg: "inner.dest6" , addr: (void *) &in_daddr6); |
743 | util_printaddr(msg: "inner.source6" , addr: (void *) &in_saddr6); |
744 | } else { |
745 | util_printaddr(msg: "inner.dest4" , addr: (void *) &in_daddr4); |
746 | util_printaddr(msg: "inner.source4" , addr: (void *) &in_saddr4); |
747 | } |
748 | |
749 | if (!cfg_l3_outer) |
750 | return; |
751 | |
752 | fprintf(stderr, "encap proto: %u\n" , cfg_encap_proto); |
753 | |
754 | if (cfg_l3_outer == PF_INET6) { |
755 | util_printaddr(msg: "outer.dest6" , addr: (void *) &out_daddr6); |
756 | util_printaddr(msg: "outer.source6" , addr: (void *) &out_saddr6); |
757 | } else { |
758 | util_printaddr(msg: "outer.dest4" , addr: (void *) &out_daddr4); |
759 | util_printaddr(msg: "outer.source4" , addr: (void *) &out_saddr4); |
760 | } |
761 | |
762 | if (!cfg_l3_extra) |
763 | return; |
764 | |
765 | if (cfg_l3_outer == PF_INET6) { |
766 | util_printaddr(msg: "extra.dest6" , addr: (void *) &extra_daddr6); |
767 | util_printaddr(msg: "extra.source6" , addr: (void *) &extra_saddr6); |
768 | } else { |
769 | util_printaddr(msg: "extra.dest4" , addr: (void *) &extra_daddr4); |
770 | util_printaddr(msg: "extra.source4" , addr: (void *) &extra_saddr4); |
771 | } |
772 | |
773 | } |
774 | |
775 | int main(int argc, char **argv) |
776 | { |
777 | parse_opts(argc, argv); |
778 | print_opts(); |
779 | return do_main(); |
780 | } |
781 | |