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 * This is a basic set of sanity checks for getifaddrs(3SOCKET) and its behavior
18 * with AF_LINK addresses. This is generally designed around being a regression
19 * test for #16383 and #16384 which were missing data entries in both the
20 * sockaddr_dl and the struct if_data. Rather than change the system and see
21 * what's there, we instead walk the links in the system assuming there is
22 * usually at least one interface present.
23 */
24
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <ifaddrs.h>
28 #include <net/if.h>
29 #include <stdlib.h>
30 #include <err.h>
31 #include <libdladm.h>
32 #include <libdllink.h>
33 #include <string.h>
34 #include <stdbool.h>
35 #include <libdlpi.h>
36
37 typedef struct {
38 const struct ifaddrs *ic_ifa;
39 int ic_ret;
40 uint32_t ic_nmatch;
41 } ifaddr_cb_t;
42
43 typedef struct {
44 bool mc_found;
45 bool *mc_pass;
46 const char *mc_name;
47 const struct ifaddrs *mc_ifa;
48 } mac_cb_t;
49
50 static boolean_t
dladm_walk_mac_cb(void * arg,dladm_macaddr_attr_t * attr)51 dladm_walk_mac_cb(void *arg, dladm_macaddr_attr_t *attr)
52 {
53 mac_cb_t *cb = arg;
54 const struct ifaddrs *ifa = cb->mc_ifa;
55 const struct sockaddr_dl *dl = (struct sockaddr_dl *)ifa->ifa_addr;
56 const struct if_data *if_data = ifa->ifa_data;
57
58 if (attr->ma_addrlen != dl->sdl_alen)
59 return (B_TRUE);
60
61 if (memcmp(LLADDR(dl), attr->ma_addr, attr->ma_addrlen) != 0) {
62 return (B_TRUE);
63 }
64
65 cb->mc_found = true;
66 if (if_data != NULL && if_data->ifi_addrlen != attr->ma_addrlen) {
67 warnx("TEST FAILED: link %s: found if_data address length "
68 "0x%x, but expected 0x%x", cb->mc_name,
69 if_data->ifi_addrlen, attr->ma_addrlen);
70 *cb->mc_pass = false;
71 }
72
73 return (B_TRUE);
74 }
75
76 static int
dladm_walk_cb(dladm_handle_t hdl,datalink_id_t id,void * arg)77 dladm_walk_cb(dladm_handle_t hdl, datalink_id_t id, void *arg)
78 {
79 dladm_status_t dlret;
80 ifaddr_cb_t *cb = arg;
81 char name[MAXLINKNAMELEN];
82 char dlerr[DLADM_STRSIZE];
83 uint32_t media;
84 char buf[DLADM_PROP_VAL_MAX];
85 char *valptr[1];
86 uint_t valcnt;
87
88 dlret = dladm_datalink_id2info(hdl, id, NULL, NULL, &media, name,
89 sizeof (name));
90 if (dlret != DLADM_STATUS_OK) {
91 cb->ic_ret = EXIT_FAILURE;
92 warnx("INTERNAL TEST FAILURE: failed to get datalink "
93 "information for link 0x%x: %s", id, dladm_status2str(dlret,
94 dlerr));
95
96 return (DLADM_WALK_CONTINUE);
97 }
98
99 /*
100 * Before we scan the list looking for this, see if the link is up. In
101 * particular, dladm will see all datalinks; however, if it isn't really
102 * in use by some client, then it's likely that it's not actually in our
103 * getifaddrs() list. If filtering to links that are not in an unknown
104 * state gives us false positives then we can revisit this.
105 */
106 valptr[0] = buf;
107 valcnt = 1;
108 if (dladm_get_linkprop(hdl, id, DLADM_PROP_VAL_CURRENT, "state", valptr,
109 &valcnt) != DLADM_STATUS_OK) {
110 warnx("skipping datalink %s as we could not get \"state\" link "
111 "link property", name);
112 return (DLADM_WALK_CONTINUE);
113 }
114
115 if (strcmp(buf, "up") != 0 && strcmp(buf, "down") != 0) {
116 return (DLADM_WALK_CONTINUE);
117 }
118
119 for (const struct ifaddrs *i = cb->ic_ifa; i != NULL; i = i->ifa_next) {
120 bool pass = true;
121
122 if (strcmp(name, i->ifa_name) != 0)
123 continue;
124 if (i->ifa_addr->sa_family != AF_LINK)
125 continue;
126
127 struct sockaddr_dl *dl = (struct sockaddr_dl *)i->ifa_addr;
128 uint_t ift_type = dlpi_iftype(media);
129
130 if (ift_type != dl->sdl_type) {
131 pass = false;
132 warnx("TEST FAILED: link %s: found type 0x%x, but "
133 "expected type 0x%x (dlpi 0x%x)", name,
134 dl->sdl_type, ift_type, media);
135 }
136
137 size_t nlen = strlen(name);
138 if (nlen != dl->sdl_nlen) {
139 pass = false;
140 warnx("TEST FAILED: link %s: name length mismatch: "
141 "found %u, expected %zu", name, dl->sdl_nlen, nlen);
142 }
143
144 if (dl->sdl_nlen > 0) {
145 if (strncmp(name, &dl->sdl_data[0], dl->sdl_nlen) !=
146 0) {
147 pass = false;
148 warnx("TEST FAILED: link %s: sockaddr_dl does "
149 "not match name", name);
150 }
151 }
152
153 /*
154 * Walk device MAC addresses to ensure that this looks right.
155 */
156 mac_cb_t mcb = { false, &pass, name, i };
157 (void) dladm_walk_macaddr(hdl, id, &mcb, dladm_walk_mac_cb);
158 if (!mcb.mc_found) {
159 pass = false;
160 warnx("TEST FAILED: link %s: failed to find matching "
161 "mac address", name);
162 }
163
164 /*
165 * Check a few last aspects of the ifi_data. The ifi_addrlen has
166 * already been filled in. We need to verify the MTU and type.
167 */
168 if (i->ifa_data != NULL) {
169 struct if_data *data = i->ifa_data;
170
171 if (data->ifi_type != ift_type) {
172 pass = false;
173 warnx("TEST FAILED: link %s: found if_data "
174 "ifi_type 0x%x, but expected type 0x%x "
175 "(dlpi 0x%x)", name, data->ifi_type,
176 ift_type, media);
177 }
178
179 valptr[0] = buf;
180 valcnt = 1;
181 if (dladm_get_linkprop(hdl, id, DLADM_PROP_VAL_CURRENT,
182 "mtu", valptr, &valcnt) == DLADM_STATUS_OK) {
183 /*
184 * Assume libdladm gives us something
185 * reasonable.
186 */
187 ulong_t val = strtoul(buf, NULL, 10);
188 if (val != data->ifi_mtu) {
189 pass = false;
190 warnx("TEST FAILED: link %s: found "
191 "MTU %u, expected %lu", name,
192 data->ifi_mtu, val);
193 }
194 }
195 } else {
196 pass = false;
197 warnx("TEST FAILED: link %s: found NULL ifa_data "
198 "pointer", name);
199 }
200
201 if (pass) {
202 (void) printf("TEST PASSED: %s AF_LINK entry looks "
203 "right\n", name);
204 } else {
205 cb->ic_ret = EXIT_FAILURE;
206 }
207
208 cb->ic_nmatch++;
209 return (DLADM_WALK_CONTINUE);
210 }
211
212 warnx("TEST FAILED: failed to find matching ifaddrs entry for datalink "
213 "%s (0x%x)", name, id);
214 cb->ic_ret = EXIT_FAILURE;
215 return (DLADM_WALK_CONTINUE);
216 }
217
218 int
main(void)219 main(void)
220 {
221 struct ifaddrs *ifa;
222 dladm_status_t dlret;
223 dladm_handle_t dladm;
224 char dlerr[DLADM_STRSIZE];
225 ifaddr_cb_t cb;
226
227 if (getifaddrs(&ifa) != 0) {
228 err(EXIT_FAILURE, "INTERNAL TEST FAILURE: getifaddrs() failed: "
229 "test cannot proceed");
230 }
231
232 dlret = dladm_open(&dladm);
233 if (dlret != DLADM_STATUS_OK) {
234 errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
235 "initialize libdladm handle: %s", dladm_status2str(dlret,
236 dlerr));
237 }
238
239 cb.ic_ifa = ifa;
240 cb.ic_ret = EXIT_SUCCESS;
241 cb.ic_nmatch = 0;
242 (void) dladm_walk_datalink_id(dladm_walk_cb, dladm, &cb,
243 DATALINK_CLASS_PHYS | DATALINK_CLASS_VNIC, DATALINK_ANY_MEDIATYPE,
244 DLADM_OPT_ACTIVE);
245 dladm_close(dladm);
246 freeifaddrs(ifa);
247
248 if (cb.ic_nmatch == 0) {
249 cb.ic_ret = EXIT_FAILURE;
250 warnx("no AF_LINK entries found");
251 }
252
253 if (cb.ic_ret == EXIT_SUCCESS) {
254 (void) printf("All tests passed successfully\n");
255 }
256 return (cb.ic_ret);
257 }
258