Merge remote-tracking branch 'origin/master'
[unleashed/lotheac.git] / usr / src / test / os-tests / tests / spoof-ras / spoof-ras.c
blobe36f084f641da1b961bf6f2ceffdb0e238f8437a
1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
13 * Copyright 2015 Joyent, Inc. All rights reserved.
16 #include <strings.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <errno.h>
20 #include <err.h>
21 #include <sys/types.h>
22 #include <sys/socket.h>
23 #include <sys/sockio.h>
24 #include <sys/wait.h>
25 #include <unistd.h>
26 #include <signal.h>
27 #include <netinet/in_systm.h> /* legacy network types needed by ip_icmp.h */
28 #include <netinet/in.h>
29 #include <netinet/ip.h>
30 #include <netinet/ip6.h>
31 #include <netinet/ip_icmp.h>
32 #include <netinet/icmp6.h>
33 #include <net/if.h>
34 #include <arpa/inet.h>
35 #include <priv.h>
38 * This program is meant to test the behaviour of processing incoming Router
39 * Advertisements when IP spoofing protection (ip-nospoof) is enabled. When
40 * run, it creates an etherstub on which it places two VNICs: a source VNIC,
41 * and a destination VNIC with protection enabled. It then sends out spoofed
42 * Router Advertisements with varying incorrect values.
44 * IMPORTANT: These tests expect that there is no other IPv6 traffic on the
45 * machine that would be delivered to a VNIC with spoofing protection enabled,
46 * since this would trip the DTrace probes installed by this suite of tests.
47 * Care should therefore be taken to not run it as a part of any series of
48 * tests which may be executed in such an environment, as it could lead to
49 * spurious failures.
52 #define DLADM(args...) spoof_run_proc("/usr/sbin/dladm", \
53 (char *[]) { "dladm", args, NULL })
54 #define IFCONFIG(args...) spoof_run_proc("/usr/sbin/ifconfig", \
55 (char *[]) { "ifconfig", args, NULL })
57 typedef struct sockaddr_in6 sin6_t;
58 typedef int (spoof_test_f)(int, struct lif_nd_req *, sin6_t *);
61 * Get the link-layer address of the given interface by querying
62 * the neighbour cache.
64 static int
65 spoof_get_lla(int s, const char *iface, struct lifreq *addrp,
66 struct lifreq *llap)
68 if (strstr(iface, ":")) {
69 warnx("Specified interface should be the zeroth "
70 "logical interface on the physical device.");
73 bzero(addrp, sizeof (*addrp));
74 bzero(llap, sizeof (*llap));
76 (void) strlcpy(addrp->lifr_name, iface, LIFNAMSIZ);
77 if (ioctl(s, SIOCGLIFADDR, addrp) < 0) {
78 warn("Unable to get link-local address");
79 return (-1);
82 (void) strlcpy(llap->lifr_name, iface, LIFNAMSIZ);
83 bcopy(&addrp->lifr_addr, &llap->lifr_nd.lnr_addr,
84 sizeof (struct sockaddr_storage));
86 if (ioctl(s, SIOCLIFGETND, llap) < 0) {
87 warn("Failed to get link-layer address");
88 return (-1);
91 return (0);
94 static void
95 spoof_prepare_lla(struct nd_opt_lla *llap, struct lif_nd_req *nce,
96 struct iovec *iov)
98 uint_t optlen;
100 bzero(llap, sizeof (*llap));
101 llap->nd_opt_lla_type = ND_OPT_SOURCE_LINKADDR;
102 optlen = ((sizeof (struct nd_opt_hdr) +
103 nce->lnr_hdw_len + 7) / 8) * 8;
104 llap->nd_opt_lla_len = optlen / 8;
105 bcopy(&nce->lnr_hdw_addr,
106 &llap->nd_opt_lla_hdw_addr, nce->lnr_hdw_len);
108 iov->iov_base = (caddr_t)llap;
109 iov->iov_len = optlen;
112 static void
113 spoof_prepare_pi(const char *prefix, int prefix_len,
114 struct nd_opt_prefix_info *pip, struct iovec *iov)
116 bzero(pip, sizeof (*pip));
118 pip->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
119 pip->nd_opt_pi_len = 4;
120 pip->nd_opt_pi_prefix_len = prefix_len;
121 pip->nd_opt_pi_flags_reserved =
122 ND_OPT_PI_FLAG_AUTO | ND_OPT_PI_FLAG_ONLINK;
123 pip->nd_opt_pi_valid_time = 86400;
124 pip->nd_opt_pi_preferred_time = 86400;
125 if (inet_pton(AF_INET6, prefix, &pip->nd_opt_pi_prefix) == 0) {
126 errx(EXIT_FAILURE, "The prefix \"%s\" is "
127 "not a valid input prefix", prefix);
130 iov->iov_base = (caddr_t)pip;
131 iov->iov_len = sizeof (*pip);
134 static void
135 spoof_prepare_header(struct nd_router_advert *ichdrp, struct iovec *iov)
137 bzero(ichdrp, sizeof (*ichdrp));
139 ichdrp->nd_ra_type = ND_ROUTER_ADVERT;
140 ichdrp->nd_ra_curhoplimit = 0;
142 iov->iov_base = (caddr_t)ichdrp;
143 iov->iov_len = sizeof (*ichdrp);
146 static int
147 spoof_set_max_hops(int s)
149 int ttl = 255;
151 if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
152 (char *)&ttl, sizeof (ttl)) < 0) {
153 warn("Failed to set IPV6_UNICAST_HOPS socket option");
154 return (-1);
156 if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
157 (char *)&ttl, sizeof (ttl)) < 0) {
158 warn("Failed to set IPV6_UNICAST_HOPS socket option");
159 return (-1);
162 return (0);
166 * Send bad option lengths in the Link-Layer Source Address option
168 static int
169 spoof_bad_lla_optlen_test(int s, struct lif_nd_req *nce, sin6_t *multicast)
171 struct msghdr msg6;
172 struct iovec iovs[3];
173 struct nd_router_advert ichdr;
174 struct nd_opt_lla lla;
175 struct nd_opt_prefix_info pi;
176 uint8_t old_lla_len;
178 spoof_prepare_header(&ichdr, &iovs[0]);
179 spoof_prepare_lla(&lla, nce, &iovs[1]);
180 spoof_prepare_pi("fd00::", 64, &pi, &iovs[2]);
182 /* Prepare message */
183 bzero(&msg6, sizeof (struct msghdr));
184 msg6.msg_name = multicast;
185 msg6.msg_namelen = sizeof (sin6_t);
186 msg6.msg_iov = iovs;
187 msg6.msg_iovlen = 3;
189 old_lla_len = lla.nd_opt_lla_len;
193 * Length is now smaller than the option is, so this should
194 * be rejected.
196 lla.nd_opt_lla_len = 0;
197 if (sendmsg(s, &msg6, 0) < 0) {
198 warn("Failed to send ICMPv6 message");
199 return (-1);
203 * Length is bigger than the option, so the following prefix
204 * will be offset.
206 lla.nd_opt_lla_len = 2;
207 if (sendmsg(s, &msg6, 0) < 0) {
208 warn("Failed to send ICMPv6 message");
209 return (-1);
213 * Restore the length, but shorten the amount of data to send, so we're
214 * sending truncated packets. (Stop before 0, so that we still send part
215 * of the option.)
217 lla.nd_opt_lla_len = old_lla_len;
218 for (iovs[1].iov_len--; iovs[1].iov_len > 0; iovs[1].iov_len--) {
219 if (sendmsg(s, &msg6, 0) < 0) {
220 warn("Failed to send ICMPv6 message");
221 return (-1);
225 return (0);
229 * Send bad option lengths in the Prefix Information option
231 static int
232 spoof_bad_pi_optlen_test(int s, struct lif_nd_req *nce, sin6_t *multicast)
234 struct msghdr msg6;
235 struct iovec iovs[3];
236 struct nd_router_advert ichdr;
237 struct nd_opt_lla lla;
238 struct nd_opt_prefix_info pi;
239 uint8_t old_pi_len;
241 spoof_prepare_header(&ichdr, &iovs[0]);
242 spoof_prepare_lla(&lla, nce, &iovs[1]);
243 spoof_prepare_pi("fd00::", 64, &pi, &iovs[2]);
245 /* Prepare message */
246 bzero(&msg6, sizeof (struct msghdr));
247 msg6.msg_name = multicast;
248 msg6.msg_namelen = sizeof (sin6_t);
249 msg6.msg_iov = iovs;
250 msg6.msg_iovlen = 3;
252 old_pi_len = pi.nd_opt_pi_len;
255 * Length is now smaller than the option is, so this should
256 * be rejected.
258 pi.nd_opt_pi_len = 0;
259 if (sendmsg(s, &msg6, 0) < 0) {
260 warn("Failed to send ICMPv6 message");
261 return (-1);
265 * Length is smaller than a PI option should be.
267 pi.nd_opt_pi_len = 3;
268 if (sendmsg(s, &msg6, 0) < 0) {
269 warn("Failed to send ICMPv6 message");
270 return (-1);
274 * Length is bigger than the option, so the following prefix
275 * will be offset.
277 pi.nd_opt_pi_len = 5;
278 if (sendmsg(s, &msg6, 0) < 0) {
279 warn("Failed to send ICMPv6 message");
280 return (-1);
284 * Restore the length, but shorten the amount of data to send, so we're
285 * sending truncated packets. (Stop before 0, so that we still send part
286 * of the option.)
288 pi.nd_opt_pi_len = old_pi_len;
289 for (iovs[2].iov_len--; iovs[2].iov_len > 0; iovs[2].iov_len--) {
290 if (sendmsg(s, &msg6, 0) < 0) {
291 warn("Failed to send ICMPv6 message");
292 return (-1);
296 return (0);
300 * Advertise a prefix with a prefix length greater than 128.
302 static int
303 spoof_bad_plen_test(int s, struct lif_nd_req *nce, sin6_t *multicast)
305 struct msghdr msg6;
306 struct iovec iovs[3];
307 struct nd_router_advert ichdr;
308 struct nd_opt_lla lla;
309 struct nd_opt_prefix_info pi;
311 spoof_prepare_header(&ichdr, &iovs[0]);
312 spoof_prepare_lla(&lla, nce, &iovs[1]);
313 spoof_prepare_pi("fd00::", 130, &pi, &iovs[2]);
315 /* Prepare message */
316 bzero(&msg6, sizeof (struct msghdr));
317 msg6.msg_name = multicast;
318 msg6.msg_namelen = sizeof (sin6_t);
319 msg6.msg_iov = iovs;
320 msg6.msg_iovlen = 3;
322 if (sendmsg(s, &msg6, 0) < 0) {
323 warn("Failed to send ICMPv6 message");
324 return (-1);
327 return (0);
331 * Advertise a link-local prefix, which should be disallowed and ignored.
333 static int
334 spoof_link_local_test(int s, struct lif_nd_req *nce, sin6_t *multicast)
336 struct msghdr msg6;
337 struct iovec iovs[3];
338 struct nd_router_advert ichdr;
339 struct nd_opt_lla lla;
340 struct nd_opt_prefix_info pi;
342 spoof_prepare_header(&ichdr, &iovs[0]);
343 spoof_prepare_lla(&lla, nce, &iovs[1]);
344 spoof_prepare_pi("fe80::", 64, &pi, &iovs[2]);
346 /* Prepare message */
347 bzero(&msg6, sizeof (struct msghdr));
348 msg6.msg_name = multicast;
349 msg6.msg_namelen = sizeof (sin6_t);
350 msg6.msg_iov = iovs;
351 msg6.msg_iovlen = 3;
353 if (sendmsg(s, &msg6, 0) < 0) {
354 warn("Failed to send ICMPv6 message");
355 return (-1);
358 return (0);
361 static int
362 spoof_good_test(int s, struct lif_nd_req *nce, sin6_t *multicast)
364 struct msghdr msg6;
365 struct iovec iovs[3];
366 struct nd_router_advert ichdr;
367 struct nd_opt_lla lla;
368 struct nd_opt_prefix_info pi;
370 spoof_prepare_header(&ichdr, &iovs[0]);
371 spoof_prepare_lla(&lla, nce, &iovs[1]);
372 spoof_prepare_pi("fd00::", 64, &pi, &iovs[2]);
374 /* Prepare message */
375 bzero(&msg6, sizeof (struct msghdr));
376 msg6.msg_name = multicast;
377 msg6.msg_namelen = sizeof (sin6_t);
378 msg6.msg_iov = iovs;
379 msg6.msg_iovlen = 3;
381 if (sendmsg(s, &msg6, 0) < 0) {
382 warn("Failed to send ICMPv6 message");
383 return (-1);
386 return (0);
389 static spoof_test_f *test_cases[] = {
390 spoof_bad_lla_optlen_test,
391 spoof_bad_pi_optlen_test,
392 spoof_bad_plen_test,
393 spoof_link_local_test
396 static int test_cases_count = sizeof (test_cases) / sizeof (spoof_test_f *);
398 static pid_t
399 spoof_dtrace_launch(void)
401 pid_t child_pid = fork();
402 if (child_pid == (pid_t)-1) {
403 err(EXIT_FAILURE, "Failed to fork to execute dtrace");
404 } else if (child_pid == (pid_t)0) {
405 (void) execl("/usr/sbin/dtrace", "dtrace", "-q",
406 "-n", "sdt:mac:insert_slaac_ip:generated-addr { exit(10) }",
407 NULL);
408 err(EXIT_FAILURE, "Failed to execute dtrace");
411 return (child_pid);
414 static pid_t
415 spoof_dtrace_wait(pid_t dtrace, int *stat)
417 int retpid;
419 /* Give time for probe to fire before checking status */
420 (void) sleep(5);
422 while ((retpid = waitpid(dtrace, stat, WNOHANG)) == -1) {
423 if (errno == EINTR)
424 continue;
426 err(EXIT_FAILURE, "Failed to wait on child");
429 return (retpid);
433 * Run a function that's going to exec in a child process, and don't return
434 * until it exits.
436 static int
437 spoof_run_proc(char *path, char *args[])
439 pid_t child_pid;
440 int childstat = 0, status = 0;
442 child_pid = fork();
443 if (child_pid == (pid_t)-1) {
444 err(EXIT_FAILURE, "Unable to fork to execute %s", path);
445 } else if (child_pid == (pid_t)0) {
446 (void) execv(path, args);
447 err(EXIT_FAILURE, "Failed to execute %s", path);
450 while (waitpid(child_pid, &childstat, 0) == -1) {
451 if (errno == EINTR)
452 continue;
454 warn("Failed to wait on child");
455 return (-1);
458 status = WEXITSTATUS(childstat);
459 if (status != 0) {
460 warnx("Child process %s exited with %d", path, status);
461 return (-1);
464 return (0);
467 static void
468 spoof_network_teardown(char *testether, char *testvnic0, char *testvnic1)
470 // Delete dest vnic
471 (void) IFCONFIG(testvnic1, "inet6", "unplumb");
472 (void) DLADM("delete-vnic", testvnic1);
474 // Delete source vnic
475 (void) IFCONFIG(testvnic0, "inet6", "unplumb");
476 (void) DLADM("delete-vnic", testvnic0);
478 // Delete etherstub
479 (void) DLADM("delete-etherstub", testether);
482 static int
483 spoof_network_setup(char *testether, char *testvnic0, char *testvnic1)
485 // Create etherstub
486 if (DLADM("create-etherstub", "-t", testether) != 0) {
487 warnx("Failed to create etherstub for test network");
488 return (-1);
491 // Create source vnic
492 if (DLADM("create-vnic", "-t", "-l", testether, testvnic0) != 0) {
493 warnx("Failed to create source VNIC for test network");
494 return (-1);
497 if (IFCONFIG(testvnic0, "inet6", "plumb", "up") != 0) {
498 warnx("Failed to plumb source VNIC for test network");
499 return (-1);
502 // Create dest vnic
503 if (DLADM("create-vnic", "-t", "-l", testether,
504 "-p", "protection=mac-nospoof,restricted,ip-nospoof,dhcp-nospoof",
505 testvnic1) != 0) {
506 warnx("Failed to create destination VNIC for test network");
507 return (-1);
510 if (IFCONFIG(testvnic1, "inet6", "plumb", "up") != 0) {
511 warnx("Failed to plumb destination VNIC for test network");
512 return (-1);
515 return (0);
518 static void
519 spoof_run_test(spoof_test_f *func, int s, struct lif_nd_req *nce,
520 sin6_t *multicast)
522 static int cas = 1;
523 (void) printf("Executing test case #%d...", cas++);
524 if (func(s, nce, multicast) == 0) {
525 (void) printf(" Done.\n");
526 } else {
527 (void) printf(" Error while running!\n");
531 static int
532 spoof_run_tests(int s, struct lif_nd_req *nce)
534 int cas, stat;
535 pid_t dtrace;
536 sin6_t multicast;
538 /* Prepare all-nodes multicast address */
539 bzero(&multicast, sizeof (multicast));
540 multicast.sin6_family = AF_INET6;
541 (void) inet_pton(AF_INET6, "ff02::1", &multicast.sin6_addr);
543 dtrace = spoof_dtrace_launch();
545 /* Wait an adequate amount of time for the probes to be installed */
546 (void) sleep(5);
549 * We send a packet where everything is good, except for the hop limit.
550 * This packet should be rejected.
552 spoof_run_test(spoof_good_test, s, nce, &multicast);
554 if (spoof_set_max_hops(s) != 0) {
555 warnx("Failed to set hop limit on socket");
556 return (EXIT_FAILURE);
559 for (cas = 0; cas < test_cases_count; cas++) {
560 spoof_run_test(test_cases[cas], s, nce, &multicast);
564 if (spoof_dtrace_wait(dtrace, &stat) != 0) {
565 (void) printf("One or more tests of bad behaviour failed!\n");
566 return (EXIT_FAILURE);
570 * Now that we've executed all of the test cases that should fail, we
571 * can execute the test that should succeed, to make sure the normal
572 * case works properly. This should trip the dtrace probe.
574 spoof_run_test(spoof_good_test, s, nce, &multicast);
576 if (spoof_dtrace_wait(dtrace, &stat) != 0 && WIFEXITED(stat) &&
577 WEXITSTATUS(stat) == 10) {
578 (void) printf("Tests completed successfully!\n");
579 } else {
580 if (kill(dtrace, SIGKILL) != 0) {
581 warn("Failed to kill dtrace child (pid %d)", dtrace);
583 (void) printf("Test of normal behaviour didn't succeed!\n");
584 return (EXIT_FAILURE);
587 return (0);
591 * Make sure that we have all of the privileges we need to execute these tests,
592 * so that we can error out before we would fail.
594 void
595 spoof_check_privs(void)
597 priv_set_t *privset = priv_allocset();
599 if (privset == NULL) {
600 err(EXIT_FAILURE, "Failed to allocate memory for "
601 "checking privileges");
604 if (getppriv(PRIV_EFFECTIVE, privset) != 0) {
605 err(EXIT_FAILURE, "Failed to get current privileges");
608 if (!priv_ismember(privset, PRIV_DTRACE_KERNEL)) {
609 errx(EXIT_FAILURE, "These tests need to be run as a user "
610 "capable of tracing the kernel.");
613 if (!priv_ismember(privset, PRIV_SYS_NET_CONFIG)) {
614 errx(EXIT_FAILURE, "These tests need to be run as a user "
615 "capable of creating and configuring network interfaces.");
618 if (!priv_ismember(privset, PRIV_NET_ICMPACCESS)) {
619 errx(EXIT_FAILURE, "These tests need to be run as a user "
620 "capable of sending ICMP packets.");
623 priv_freeset(privset);
627 main(void)
629 struct lifreq addr, llar;
630 int error, s;
631 char testether[LIFNAMSIZ];
632 char testvnic0[LIFNAMSIZ];
633 char testvnic1[LIFNAMSIZ];
634 pid_t curpid = getpid();
636 spoof_check_privs();
639 * Set up the socket and test network for sending
641 s = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
642 if (s < 0) {
643 err(EXIT_FAILURE, "Failed to open ICMPv6 socket");
646 (void) snprintf(testether, sizeof (testether), "testether%d", curpid);
647 (void) snprintf(testvnic0, sizeof (testvnic0), "testvnic%d_0", curpid);
648 (void) snprintf(testvnic1, sizeof (testvnic1), "testvnic%d_1", curpid);
650 if (spoof_network_setup(testether, testvnic0, testvnic1) != 0) {
651 warnx("Failed to set up test network");
652 error = EXIT_FAILURE;
653 goto cleanup;
656 if (spoof_get_lla(s, testvnic0, &addr, &llar) != 0) {
657 warnx("Failed to get link-layer address");
658 error = EXIT_FAILURE;
659 goto cleanup;
662 if (setsockopt(s, IPPROTO_IPV6, IPV6_BOUND_IF,
663 (char *)&((sin6_t *)&addr.lifr_addr)->sin6_scope_id,
664 sizeof (int)) < 0) {
665 warn("Failed to set IPV6_UNICAST_HOPS socket option");
666 return (-1);
669 if (bind(s, (struct sockaddr *)&addr.lifr_addr, sizeof (sin6_t)) != 0) {
670 warnx("Failed to bind to link-local address");
671 error = EXIT_FAILURE;
672 goto cleanup;
675 error = spoof_run_tests(s, &llar.lifr_nd);
677 cleanup:
678 if (close(s) != 0) {
679 warnx("Failed to close ICMPv6 socket");
681 spoof_network_teardown(testether, testvnic0, testvnic1);
682 return (error);