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 2026 Oxide Computer Company
14 */
15
16 /*
17 * Verify that a few sources all agree on socket index information:
18 * getifaddrs(3SOCKET), if_nametoindex(3SOCKET), and manually walking
19 * interfaces. if_nametoindex() doesn't always handle the fact that we can have
20 * two interfaces (v4 and v6) with the same name. if_nametoindex() prefers v4
21 * addresses over v6. So when both are up, we'll need to be careful.
22 *
23 * This test assumes the set of interfaces isn't changing durings its lifetime.
24 */
25
26 #include <stdlib.h>
27 #include <err.h>
28 #include <sys/types.h>
29 #include <sys/socket.h>
30 #include <ifaddrs.h>
31 #include <net/if.h>
32 #include <sys/sockio.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <sys/sysmacros.h>
37
38 int
main(void)39 main(void)
40 {
41 int ret = EXIT_SUCCESS;
42 int s4, s6;
43 struct ifaddrs *ifa;
44
45 if ((s4 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
46 err(EXIT_FAILURE, "failed to get IPv4 socket");
47 }
48
49 if ((s6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
50 err(EXIT_FAILURE, "failed to get IPv6 socket");
51 }
52
53 if (getifaddrs(&ifa) != 0) {
54 err(EXIT_FAILURE, "failed to get address information");
55 }
56
57 for (struct ifaddrs *i = ifa; i != NULL; i = i->ifa_next) {
58 struct lifreq lif;
59 char name[IF_NAMESIZE];
60 sa_family_t fam = i->ifa_addr->sa_family;
61
62 if (fam != AF_INET && fam != AF_INET6) {
63 continue;
64 }
65
66 /*
67 * First ask the kernel our index.
68 */
69 (void) memset(&lif, 0, sizeof (lif));
70 if (strlcpy(lif.lifr_name, i->ifa_name,
71 sizeof (lif.lifr_name)) >= sizeof (lif.lifr_name)) {
72 errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: encountered "
73 "interface name %s that would overflow struct "
74 "lifreq length!", i->ifa_name);
75 }
76
77 if (ioctl(fam == AF_INET ? s4 : s6, SIOCGLIFINDEX, &lif) != 0) {
78 warn("TEST FAILED: failed to ask kernel for index of "
79 "interface %s (family 0x%x", i->ifa_name, fam);
80 ret = EXIT_FAILURE;
81 continue;
82 }
83
84 if (if_indextoname(lif.lifr_index, name) == NULL) {
85 warn("TEST FAILED: if_indextoname() failed to convert "
86 "index 0x%x back to %s", lif.lifr_index,
87 i->ifa_name);
88 ret = EXIT_FAILURE;
89 continue;
90 }
91
92 if (strcmp(name, i->ifa_name) != 0) {
93 warn("TEST FAILED: if_indextoname() returned name %s "
94 "for index 0x%x, but expected %s", name,
95 lif.lifr_index, i->ifa_name);
96 ret = EXIT_FAILURE;
97 } else {
98 (void) printf("TEST PASSED: Mapped %s (fam 0x%x) index "
99 "back to expected name\n", i->ifa_name, fam);
100 }
101
102 /*
103 * Now go from the name to the index. In general we expect the
104 * IPv4 and IPv6 interfaces with the same name to share the same
105 * index.
106 */
107 uint_t idx = if_nametoindex(i->ifa_name);
108 if (idx == 0) {
109 warn("TEST FAILED: if_nametoindex() unexpected failed "
110 "on %s (fam 0x%x)", i->ifa_name, fam);
111 ret = EXIT_FAILURE;
112 } else if (idx != lif.lifr_index) {
113 warnx("TEST FAILED: if_nametoindex() on %s (fam 0x%x) "
114 "returned index 0x%x, but expected 0x%x from "
115 "SIOCGLIFINDEX\n", i->ifa_name, fam, idx,
116 lif.lifr_index);
117 ret = EXIT_FAILURE;
118 } else {
119 (void) printf("TEST PASSED: %s (fam 0x%x) name to "
120 "index round tripped successfully\n", i->ifa_name,
121 fam);
122 }
123 }
124
125 const char *bad_names[] = { "", "nonumber", "thisiswaytoolongforanif0",
126 "bad/char23", "if1234567890123456" };
127 for (size_t i = 0; i < ARRAY_SIZE(bad_names); i++) {
128 uint_t idx = if_nametoindex(bad_names[i]);
129 if (idx != 0) {
130 warnx("TEST FAILED: if_nametoindex() returned idx 0x%x "
131 "on invalid name '%s': expected failure", idx,
132 bad_names[i]);
133 ret = EXIT_FAILURE;
134 } else if (errno != ENXIO) {
135 warnx("TEST FAILED: if_nametoindex() returned %s on "
136 "invalid name '%s': expected ENXIO",
137 strerrorname_np(errno), bad_names[i]);
138 ret = EXIT_FAILURE;
139 } else {
140 (void) printf("TEST PASSED: if_nametoindex() failed "
141 "bad name '%s' with ENXIO\n", bad_names[i]);
142 }
143 }
144
145 freeifaddrs(ifa);
146 (void) close(s6);
147 (void) close(s4);
148 if (ret == EXIT_SUCCESS) {
149 (void) printf("All tests passed successfully\n");
150 }
151 return (ret);
152 }
153