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
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.
21 #include <sys/types.h>
22 #include <sys/socket.h>
23 #include <sys/sockio.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>
34 #include <arpa/inet.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
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.
65 spoof_get_lla(int s
, const char *iface
, struct lifreq
*addrp
,
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");
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");
95 spoof_prepare_lla(struct nd_opt_lla
*llap
, struct lif_nd_req
*nce
,
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
;
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
);
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
);
147 spoof_set_max_hops(int s
)
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");
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");
166 * Send bad option lengths in the Link-Layer Source Address option
169 spoof_bad_lla_optlen_test(int s
, struct lif_nd_req
*nce
, sin6_t
*multicast
)
172 struct iovec iovs
[3];
173 struct nd_router_advert ichdr
;
174 struct nd_opt_lla lla
;
175 struct nd_opt_prefix_info pi
;
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
);
189 old_lla_len
= lla
.nd_opt_lla_len
;
193 * Length is now smaller than the option is, so this should
196 lla
.nd_opt_lla_len
= 0;
197 if (sendmsg(s
, &msg6
, 0) < 0) {
198 warn("Failed to send ICMPv6 message");
203 * Length is bigger than the option, so the following prefix
206 lla
.nd_opt_lla_len
= 2;
207 if (sendmsg(s
, &msg6
, 0) < 0) {
208 warn("Failed to send ICMPv6 message");
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
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");
229 * Send bad option lengths in the Prefix Information option
232 spoof_bad_pi_optlen_test(int s
, struct lif_nd_req
*nce
, sin6_t
*multicast
)
235 struct iovec iovs
[3];
236 struct nd_router_advert ichdr
;
237 struct nd_opt_lla lla
;
238 struct nd_opt_prefix_info pi
;
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
);
252 old_pi_len
= pi
.nd_opt_pi_len
;
255 * Length is now smaller than the option is, so this should
258 pi
.nd_opt_pi_len
= 0;
259 if (sendmsg(s
, &msg6
, 0) < 0) {
260 warn("Failed to send ICMPv6 message");
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");
274 * Length is bigger than the option, so the following prefix
277 pi
.nd_opt_pi_len
= 5;
278 if (sendmsg(s
, &msg6
, 0) < 0) {
279 warn("Failed to send ICMPv6 message");
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
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");
300 * Advertise a prefix with a prefix length greater than 128.
303 spoof_bad_plen_test(int s
, struct lif_nd_req
*nce
, sin6_t
*multicast
)
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
);
322 if (sendmsg(s
, &msg6
, 0) < 0) {
323 warn("Failed to send ICMPv6 message");
331 * Advertise a link-local prefix, which should be disallowed and ignored.
334 spoof_link_local_test(int s
, struct lif_nd_req
*nce
, sin6_t
*multicast
)
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
);
353 if (sendmsg(s
, &msg6
, 0) < 0) {
354 warn("Failed to send ICMPv6 message");
362 spoof_good_test(int s
, struct lif_nd_req
*nce
, sin6_t
*multicast
)
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
);
381 if (sendmsg(s
, &msg6
, 0) < 0) {
382 warn("Failed to send ICMPv6 message");
389 static spoof_test_f
*test_cases
[] = {
390 spoof_bad_lla_optlen_test
,
391 spoof_bad_pi_optlen_test
,
393 spoof_link_local_test
396 static int test_cases_count
= sizeof (test_cases
) / sizeof (spoof_test_f
*);
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) }",
408 err(EXIT_FAILURE
, "Failed to execute dtrace");
415 spoof_dtrace_wait(pid_t dtrace
, int *stat
)
419 /* Give time for probe to fire before checking status */
422 while ((retpid
= waitpid(dtrace
, stat
, WNOHANG
)) == -1) {
426 err(EXIT_FAILURE
, "Failed to wait on child");
433 * Run a function that's going to exec in a child process, and don't return
437 spoof_run_proc(char *path
, char *args
[])
440 int childstat
= 0, status
= 0;
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) {
454 warn("Failed to wait on child");
458 status
= WEXITSTATUS(childstat
);
460 warnx("Child process %s exited with %d", path
, status
);
468 spoof_network_teardown(char *testether
, char *testvnic0
, char *testvnic1
)
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
);
479 (void) DLADM("delete-etherstub", testether
);
483 spoof_network_setup(char *testether
, char *testvnic0
, char *testvnic1
)
486 if (DLADM("create-etherstub", "-t", testether
) != 0) {
487 warnx("Failed to create etherstub for test network");
491 // Create source vnic
492 if (DLADM("create-vnic", "-t", "-l", testether
, testvnic0
) != 0) {
493 warnx("Failed to create source VNIC for test network");
497 if (IFCONFIG(testvnic0
, "inet6", "plumb", "up") != 0) {
498 warnx("Failed to plumb source VNIC for test network");
503 if (DLADM("create-vnic", "-t", "-l", testether
,
504 "-p", "protection=mac-nospoof,restricted,ip-nospoof,dhcp-nospoof",
506 warnx("Failed to create destination VNIC for test network");
510 if (IFCONFIG(testvnic1
, "inet6", "plumb", "up") != 0) {
511 warnx("Failed to plumb destination VNIC for test network");
519 spoof_run_test(spoof_test_f
*func
, int s
, struct lif_nd_req
*nce
,
523 (void) printf("Executing test case #%d...", cas
++);
524 if (func(s
, nce
, multicast
) == 0) {
525 (void) printf(" Done.\n");
527 (void) printf(" Error while running!\n");
532 spoof_run_tests(int s
, struct lif_nd_req
*nce
)
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 */
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");
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
);
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.
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
);
629 struct lifreq addr
, llar
;
631 char testether
[LIFNAMSIZ
];
632 char testvnic0
[LIFNAMSIZ
];
633 char testvnic1
[LIFNAMSIZ
];
634 pid_t curpid
= getpid();
639 * Set up the socket and test network for sending
641 s
= socket(PF_INET6
, SOCK_RAW
, IPPROTO_ICMPV6
);
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
;
656 if (spoof_get_lla(s
, testvnic0
, &addr
, &llar
) != 0) {
657 warnx("Failed to get link-layer address");
658 error
= EXIT_FAILURE
;
662 if (setsockopt(s
, IPPROTO_IPV6
, IPV6_BOUND_IF
,
663 (char *)&((sin6_t
*)&addr
.lifr_addr
)->sin6_scope_id
,
665 warn("Failed to set IPV6_UNICAST_HOPS socket option");
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
;
675 error
= spoof_run_tests(s
, &llar
.lifr_nd
);
679 warnx("Failed to close ICMPv6 socket");
681 spoof_network_teardown(testether
, testvnic0
, testvnic1
);