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
spoof_get_lla(int s,const char * iface,struct lifreq * addrp,struct lifreq * llap)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
spoof_prepare_lla(struct nd_opt_lla * llap,struct lif_nd_req * nce,struct iovec * iov)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
spoof_prepare_pi(const char * prefix,int prefix_len,struct nd_opt_prefix_info * pip,struct iovec * iov)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
spoof_prepare_header(struct nd_router_advert * ichdrp,struct iovec * iov)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
spoof_set_max_hops(int s)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
spoof_bad_lla_optlen_test(int s,struct lif_nd_req * nce,sin6_t * multicast)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
spoof_bad_pi_optlen_test(int s,struct lif_nd_req * nce,sin6_t * multicast)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
spoof_bad_plen_test(int s,struct lif_nd_req * nce,sin6_t * multicast)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
spoof_link_local_test(int s,struct lif_nd_req * nce,sin6_t * multicast)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
spoof_good_test(int s,struct lif_nd_req * nce,sin6_t * multicast)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
spoof_dtrace_launch(void)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
spoof_dtrace_wait(pid_t dtrace,int * stat)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
spoof_run_proc(char * path,char * args[])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
spoof_network_teardown(char * testether,char * testvnic0,char * testvnic1)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
spoof_network_setup(char * testether,char * testvnic0,char * testvnic1)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
spoof_run_test(spoof_test_f * func,int s,struct lif_nd_req * nce,sin6_t * multicast)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
spoof_run_tests(int s,struct lif_nd_req * nce)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 %" _PRIdID ")",
582 dtrace);
583 }
584 (void) printf("Test of normal behaviour didn't succeed!\n");
585 return (EXIT_FAILURE);
586 }
587
588 return (0);
589 }
590
591 /*
592 * Make sure that we have all of the privileges we need to execute these tests,
593 * so that we can error out before we would fail.
594 */
595 void
spoof_check_privs(void)596 spoof_check_privs(void)
597 {
598 priv_set_t *privset = priv_allocset();
599
600 if (privset == NULL) {
601 err(EXIT_FAILURE, "Failed to allocate memory for "
602 "checking privileges");
603 }
604
605 if (getppriv(PRIV_EFFECTIVE, privset) != 0) {
606 err(EXIT_FAILURE, "Failed to get current privileges");
607 }
608
609 if (!priv_ismember(privset, PRIV_DTRACE_KERNEL)) {
610 errx(EXIT_FAILURE, "These tests need to be run as a user "
611 "capable of tracing the kernel.");
612 }
613
614 if (!priv_ismember(privset, PRIV_SYS_NET_CONFIG)) {
615 errx(EXIT_FAILURE, "These tests need to be run as a user "
616 "capable of creating and configuring network interfaces.");
617 }
618
619 if (!priv_ismember(privset, PRIV_NET_ICMPACCESS)) {
620 errx(EXIT_FAILURE, "These tests need to be run as a user "
621 "capable of sending ICMP packets.");
622 }
623
624 priv_freeset(privset);
625 }
626
627 int
main(void)628 main(void)
629 {
630 struct lifreq addr, llar;
631 int error, s;
632 char testether[LIFNAMSIZ];
633 char testvnic0[LIFNAMSIZ];
634 char testvnic1[LIFNAMSIZ];
635 pid_t curpid = getpid();
636
637 spoof_check_privs();
638
639 /*
640 * Set up the socket and test network for sending
641 */
642 s = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
643 if (s < 0) {
644 err(EXIT_FAILURE, "Failed to open ICMPv6 socket");
645 }
646
647 (void) snprintf(testether, sizeof (testether), "testether%d", curpid);
648 (void) snprintf(testvnic0, sizeof (testvnic0), "testvnic%d_0", curpid);
649 (void) snprintf(testvnic1, sizeof (testvnic1), "testvnic%d_1", curpid);
650
651 if (spoof_network_setup(testether, testvnic0, testvnic1) != 0) {
652 warnx("Failed to set up test network");
653 error = EXIT_FAILURE;
654 goto cleanup;
655 }
656
657 if (spoof_get_lla(s, testvnic0, &addr, &llar) != 0) {
658 warnx("Failed to get link-layer address");
659 error = EXIT_FAILURE;
660 goto cleanup;
661 }
662
663 if (setsockopt(s, IPPROTO_IPV6, IPV6_BOUND_IF,
664 (char *)&((sin6_t *)&addr.lifr_addr)->sin6_scope_id,
665 sizeof (int)) < 0) {
666 warn("Failed to set IPV6_UNICAST_HOPS socket option");
667 return (-1);
668 }
669
670 if (bind(s, (struct sockaddr *)&addr.lifr_addr, sizeof (sin6_t)) != 0) {
671 warnx("Failed to bind to link-local address");
672 error = EXIT_FAILURE;
673 goto cleanup;
674 }
675
676 error = spoof_run_tests(s, &llar.lifr_nd);
677
678 cleanup:
679 if (close(s) != 0) {
680 warnx("Failed to close ICMPv6 socket");
681 }
682 spoof_network_teardown(testether, testvnic0, testvnic1);
683 return (error);
684 }
685