xref: /illumos-gate/usr/src/test/os-tests/tests/spoof-ras/spoof-ras.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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.
6  *
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.
10  */
11 
12 /*
13  * Copyright 2015 Joyent, Inc. All rights reserved.
14  */
15 
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>
36 
37 /*
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.
43  *
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.
50  */
51 
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 })
56 
57 typedef	struct	sockaddr_in6	sin6_t;
58 typedef	int	(spoof_test_f)(int, struct lif_nd_req *, sin6_t *);
59 
60 /*
61  * Get the link-layer address of the given interface by querying
62  * the neighbour cache.
63  */
64 static int
65 spoof_get_lla(int s, const char *iface, struct lifreq *addrp,
66     struct lifreq *llap)
67 {
68 	if (strstr(iface, ":")) {
69 		warnx("Specified interface should be the zeroth "
70 		    "logical interface on the physical device.");
71 	}
72 
73 	bzero(addrp, sizeof (*addrp));
74 	bzero(llap, sizeof (*llap));
75 
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);
80 	}
81 
82 	(void) strlcpy(llap->lifr_name, iface, LIFNAMSIZ);
83 	bcopy(&addrp->lifr_addr, &llap->lifr_nd.lnr_addr,
84 	    sizeof (struct sockaddr_storage));
85 
86 	if (ioctl(s, SIOCLIFGETND, llap) < 0) {
87 		warn("Failed to get link-layer address");
88 		return (-1);
89 	}
90 
91 	return (0);
92 }
93 
94 static void
95 spoof_prepare_lla(struct nd_opt_lla *llap, struct lif_nd_req *nce,
96     struct iovec *iov)
97 {
98 	uint_t optlen;
99 
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);
107 
108 	iov->iov_base = (caddr_t)llap;
109 	iov->iov_len = optlen;
110 }
111 
112 static void
113 spoof_prepare_pi(const char *prefix, int prefix_len,
114     struct nd_opt_prefix_info *pip, struct iovec *iov)
115 {
116 	bzero(pip, sizeof (*pip));
117 
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);
128 	}
129 
130 	iov->iov_base = (caddr_t)pip;
131 	iov->iov_len = sizeof (*pip);
132 }
133 
134 static void
135 spoof_prepare_header(struct nd_router_advert *ichdrp, struct iovec *iov)
136 {
137 	bzero(ichdrp, sizeof (*ichdrp));
138 
139 	ichdrp->nd_ra_type = ND_ROUTER_ADVERT;
140 	ichdrp->nd_ra_curhoplimit = 0;
141 
142 	iov->iov_base = (caddr_t)ichdrp;
143 	iov->iov_len = sizeof (*ichdrp);
144 }
145 
146 static int
147 spoof_set_max_hops(int s)
148 {
149 	int ttl = 255;
150 
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);
155 	}
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);
160 	}
161 
162 	return (0);
163 }
164 
165 /*
166  * Send bad option lengths in the Link-Layer Source Address option
167  */
168 static int
169 spoof_bad_lla_optlen_test(int s, struct lif_nd_req *nce, sin6_t *multicast)
170 {
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;
177 
178 	spoof_prepare_header(&ichdr, &iovs[0]);
179 	spoof_prepare_lla(&lla, nce, &iovs[1]);
180 	spoof_prepare_pi("fd00::", 64, &pi, &iovs[2]);
181 
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;
188 
189 	old_lla_len = lla.nd_opt_lla_len;
190 
191 
192 	/*
193 	 * Length is now smaller than the option is, so this should
194 	 * be rejected.
195 	 */
196 	lla.nd_opt_lla_len = 0;
197 	if (sendmsg(s, &msg6, 0) < 0) {
198 		warn("Failed to send ICMPv6 message");
199 		return (-1);
200 	}
201 
202 	/*
203 	 * Length is bigger than the option, so the following prefix
204 	 * will be offset.
205 	 */
206 	lla.nd_opt_lla_len = 2;
207 	if (sendmsg(s, &msg6, 0) < 0) {
208 		warn("Failed to send ICMPv6 message");
209 		return (-1);
210 	}
211 
212 	/*
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.)
216 	 */
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);
222 		}
223 	}
224 
225 	return (0);
226 }
227 
228 /*
229  * Send bad option lengths in the Prefix Information option
230  */
231 static int
232 spoof_bad_pi_optlen_test(int s, struct lif_nd_req *nce, sin6_t *multicast)
233 {
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;
240 
241 	spoof_prepare_header(&ichdr, &iovs[0]);
242 	spoof_prepare_lla(&lla, nce, &iovs[1]);
243 	spoof_prepare_pi("fd00::", 64, &pi, &iovs[2]);
244 
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;
251 
252 	old_pi_len = pi.nd_opt_pi_len;
253 
254 	/*
255 	 * Length is now smaller than the option is, so this should
256 	 * be rejected.
257 	 */
258 	pi.nd_opt_pi_len = 0;
259 	if (sendmsg(s, &msg6, 0) < 0) {
260 		warn("Failed to send ICMPv6 message");
261 		return (-1);
262 	}
263 
264 	/*
265 	 * Length is smaller than a PI option should be.
266 	 */
267 	pi.nd_opt_pi_len = 3;
268 	if (sendmsg(s, &msg6, 0) < 0) {
269 		warn("Failed to send ICMPv6 message");
270 		return (-1);
271 	}
272 
273 	/*
274 	 * Length is bigger than the option, so the following prefix
275 	 * will be offset.
276 	 */
277 	pi.nd_opt_pi_len = 5;
278 	if (sendmsg(s, &msg6, 0) < 0) {
279 		warn("Failed to send ICMPv6 message");
280 		return (-1);
281 	}
282 
283 	/*
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.)
287 	 */
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);
293 		}
294 	}
295 
296 	return (0);
297 }
298 
299 /*
300  * Advertise a prefix with a prefix length greater than 128.
301  */
302 static int
303 spoof_bad_plen_test(int s, struct lif_nd_req *nce, sin6_t *multicast)
304 {
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;
310 
311 	spoof_prepare_header(&ichdr, &iovs[0]);
312 	spoof_prepare_lla(&lla, nce, &iovs[1]);
313 	spoof_prepare_pi("fd00::", 130, &pi, &iovs[2]);
314 
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;
321 
322 	if (sendmsg(s, &msg6, 0) < 0) {
323 		warn("Failed to send ICMPv6 message");
324 		return (-1);
325 	}
326 
327 	return (0);
328 }
329 
330 /*
331  * Advertise a link-local prefix, which should be disallowed and ignored.
332  */
333 static int
334 spoof_link_local_test(int s, struct lif_nd_req *nce, sin6_t *multicast)
335 {
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;
341 
342 	spoof_prepare_header(&ichdr, &iovs[0]);
343 	spoof_prepare_lla(&lla, nce, &iovs[1]);
344 	spoof_prepare_pi("fe80::", 64, &pi, &iovs[2]);
345 
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;
352 
353 	if (sendmsg(s, &msg6, 0) < 0) {
354 		warn("Failed to send ICMPv6 message");
355 		return (-1);
356 	}
357 
358 	return (0);
359 }
360 
361 static int
362 spoof_good_test(int s, struct lif_nd_req *nce, sin6_t *multicast)
363 {
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;
369 
370 	spoof_prepare_header(&ichdr, &iovs[0]);
371 	spoof_prepare_lla(&lla, nce, &iovs[1]);
372 	spoof_prepare_pi("fd00::", 64, &pi, &iovs[2]);
373 
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;
380 
381 	if (sendmsg(s, &msg6, 0) < 0) {
382 		warn("Failed to send ICMPv6 message");
383 		return (-1);
384 	}
385 
386 	return (0);
387 }
388 
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
394 };
395 
396 static int test_cases_count = sizeof (test_cases) / sizeof (spoof_test_f *);
397 
398 static pid_t
399 spoof_dtrace_launch(void)
400 {
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");
409 	}
410 
411 	return (child_pid);
412 }
413 
414 static pid_t
415 spoof_dtrace_wait(pid_t dtrace, int *stat)
416 {
417 	int retpid;
418 
419 	/* Give time for probe to fire before checking status */
420 	(void) sleep(5);
421 
422 	while ((retpid = waitpid(dtrace, stat, WNOHANG)) == -1) {
423 		if (errno == EINTR)
424 			continue;
425 
426 		err(EXIT_FAILURE, "Failed to wait on child");
427 	}
428 
429 	return (retpid);
430 }
431 
432 /*
433  * Run a function that's going to exec in a child process, and don't return
434  * until it exits.
435  */
436 static int
437 spoof_run_proc(char *path, char *args[])
438 {
439 	pid_t child_pid;
440 	int childstat = 0, status = 0;
441 
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);
448 	}
449 
450 	while (waitpid(child_pid, &childstat, 0) == -1) {
451 		if (errno == EINTR)
452 			continue;
453 
454 		warn("Failed to wait on child");
455 		return (-1);
456 	}
457 
458 	status = WEXITSTATUS(childstat);
459 	if (status != 0) {
460 		warnx("Child process %s exited with %d", path, status);
461 		return (-1);
462 	}
463 
464 	return (0);
465 }
466 
467 static void
468 spoof_network_teardown(char *testether, char *testvnic0, char *testvnic1)
469 {
470 	// Delete dest vnic
471 	(void) IFCONFIG(testvnic1, "inet6", "unplumb");
472 	(void) DLADM("delete-vnic", testvnic1);
473 
474 	// Delete source vnic
475 	(void) IFCONFIG(testvnic0, "inet6", "unplumb");
476 	(void) DLADM("delete-vnic", testvnic0);
477 
478 	// Delete etherstub
479 	(void) DLADM("delete-etherstub", testether);
480 }
481 
482 static int
483 spoof_network_setup(char *testether, char *testvnic0, char *testvnic1)
484 {
485 	// Create etherstub
486 	if (DLADM("create-etherstub", "-t", testether) != 0) {
487 		warnx("Failed to create etherstub for test network");
488 		return (-1);
489 	}
490 
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);
495 	}
496 
497 	if (IFCONFIG(testvnic0, "inet6", "plumb", "up") != 0) {
498 		warnx("Failed to plumb source VNIC for test network");
499 		return (-1);
500 	}
501 
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);
508 	}
509 
510 	if (IFCONFIG(testvnic1, "inet6", "plumb", "up") != 0) {
511 		warnx("Failed to plumb destination VNIC for test network");
512 		return (-1);
513 	}
514 
515 	return (0);
516 }
517 
518 static void
519 spoof_run_test(spoof_test_f *func, int s, struct lif_nd_req *nce,
520     sin6_t *multicast)
521 {
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");
528 	}
529 }
530 
531 static int
532 spoof_run_tests(int s, struct lif_nd_req *nce)
533 {
534 	int cas, stat;
535 	pid_t dtrace;
536 	sin6_t multicast;
537 
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);
542 
543 	dtrace = spoof_dtrace_launch();
544 
545 	/* Wait an adequate amount of time for the probes to be installed */
546 	(void) sleep(5);
547 
548 	/*
549 	 * We send a packet where everything is good, except for the hop limit.
550 	 * This packet should be rejected.
551 	 */
552 	spoof_run_test(spoof_good_test, s, nce, &multicast);
553 
554 	if (spoof_set_max_hops(s) != 0) {
555 		warnx("Failed to set hop limit on socket");
556 		return (EXIT_FAILURE);
557 	}
558 
559 	for (cas = 0; cas < test_cases_count; cas++) {
560 		spoof_run_test(test_cases[cas], s, nce, &multicast);
561 	}
562 
563 
564 	if (spoof_dtrace_wait(dtrace, &stat) != 0) {
565 		(void) printf("One or more tests of bad behaviour failed!\n");
566 		return (EXIT_FAILURE);
567 	}
568 
569 	/*
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.
573 	 */
574 	spoof_run_test(spoof_good_test, s, nce, &multicast);
575 
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);
582 		}
583 		(void) printf("Test of normal behaviour didn't succeed!\n");
584 		return (EXIT_FAILURE);
585 	}
586 
587 	return (0);
588 }
589 
590 /*
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.
593  */
594 void
595 spoof_check_privs(void)
596 {
597 	priv_set_t *privset = priv_allocset();
598 
599 	if (privset == NULL) {
600 		err(EXIT_FAILURE, "Failed to allocate memory for "
601 		    "checking privileges");
602 	}
603 
604 	if (getppriv(PRIV_EFFECTIVE, privset) != 0) {
605 		err(EXIT_FAILURE, "Failed to get current privileges");
606 	}
607 
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.");
611 	}
612 
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.");
616 	}
617 
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.");
621 	}
622 
623 	priv_freeset(privset);
624 }
625 
626 int
627 main(void)
628 {
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();
635 
636 	spoof_check_privs();
637 
638 	/*
639 	 * Set up the socket and test network for sending
640 	 */
641 	s = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
642 	if (s < 0) {
643 		err(EXIT_FAILURE, "Failed to open ICMPv6 socket");
644 	}
645 
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);
649 
650 	if (spoof_network_setup(testether, testvnic0, testvnic1) != 0) {
651 		warnx("Failed to set up test network");
652 		error = EXIT_FAILURE;
653 		goto cleanup;
654 	}
655 
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;
660 	}
661 
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);
667 	}
668 
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;
673 	}
674 
675 	error = spoof_run_tests(s, &llar.lifr_nd);
676 
677 cleanup:
678 	if (close(s) != 0) {
679 		warnx("Failed to close ICMPv6 socket");
680 	}
681 	spoof_network_teardown(testether, testvnic0, testvnic1);
682 	return (error);
683 }
684