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 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