xref: /illumos-gate/usr/src/lib/libsocket/inet/getifaddrs.c (revision 2aa8db5932a99c01d32f2aea7dbbf15b4898169b)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2017 RackTop Systems.
25  * Copyright 2022 Sebastian Wiedenroth
26  * Copyright 2026 Oxide Computer Company
27  */
28 
29 #include <netdb.h>
30 #include <nss_dbdefs.h>
31 #include <netinet/in.h>
32 #include <sys/socket.h>
33 #include <string.h>
34 #include <strings.h>
35 #include <stdio.h>
36 #include <sys/sockio.h>
37 #include <sys/types.h>
38 #include <stdlib.h>
39 #include <net/if.h>
40 #include <door.h>
41 #include <fcntl.h>
42 #include <sys/mman.h>
43 #include <sys/dld_ioc.h>
44 #include <sys/dld.h>
45 #include <sys/dls_mgmt.h>
46 #include <sys/mac.h>
47 #include <sys/dlpi.h>
48 #include <net/if_types.h>
49 #include <ifaddrs.h>
50 #include <libsocket_priv.h>
51 
52 /*
53  * <ifaddrs.h> directs folks towards our internal symbol, __getifaddrs. This
54  * means we cannot name the original symbol 'getifaddrs' here or it will be
55  * renamed. Instead, we use another redefine_extname to take care of this. Note,
56  * the extern declaration is required as gcc and others will only apply this for
57  * things they see an extern declaration for.
58  */
59 #pragma redefine_extname getifaddrs_old getifaddrs
60 extern int getifaddrs_old(struct ifaddrs **);
61 
62 /*
63  * Create a linked list of `struct ifaddrs' structures, one for each
64  * address that is UP. If successful, store the list in *ifap and
65  * return 0.  On errors, return -1 and set `errno'.
66  *
67  * The storage returned in *ifap is allocated dynamically and can
68  * only be properly freed by passing it to `freeifaddrs'.
69  */
70 int
71 _getifaddrs(struct ifaddrs **ifap, boolean_t can_handle_links)
72 {
73 	int		err;
74 	char		*cp;
75 	struct ifaddrs	*curr;
76 
77 	if (ifap == NULL) {
78 		errno = EINVAL;
79 		return (-1);
80 	}
81 	*ifap = NULL;
82 
83 	if (can_handle_links) {
84 		err = getallifaddrs(AF_UNSPEC, ifap, LIFC_ENABLED);
85 	} else {
86 		err = getallifaddrs(AF_INET, ifap, LIFC_ENABLED);
87 		if (err != 0)
88 			return (err);
89 
90 		/* Find end of the list to append to */
91 		curr = *ifap;
92 		while (curr && curr->ifa_next) {
93 			curr = curr->ifa_next;
94 		}
95 
96 		err = getallifaddrs(AF_INET6, curr ? &curr->ifa_next : ifap,
97 		    LIFC_ENABLED);
98 	}
99 
100 	if (err != 0)
101 		return (err);
102 
103 	for (curr = *ifap; curr != NULL; curr = curr->ifa_next) {
104 		if ((cp = strchr(curr->ifa_name, ':')) != NULL)
105 			*cp = '\0';
106 	}
107 
108 	return (0);
109 }
110 
111 /*
112  * Legacy symbol
113  * For a long time getifaddrs() only returned AF_INET and AF_INET6 entries.
114  * Some consumers came to expect that no other address family may be returned.
115  * To prevent existing binaries that can't handle AF_LINK entries from breaking
116  * this symbol is kept around. Consumers that want the fixed behaviour need to
117  * recompile and link to the fixed symbol.
118  */
119 int
120 getifaddrs_old(struct ifaddrs **ifap)
121 {
122 	return (_getifaddrs(ifap, B_FALSE));
123 }
124 
125 /*
126  * Current symbol
127  * May return AF_INET, AF_INET6 and AF_LINK entries
128  */
129 int
130 __getifaddrs(struct ifaddrs **ifap)
131 {
132 	return (_getifaddrs(ifap, B_TRUE));
133 }
134 
135 void
136 freeifaddrs(struct ifaddrs *ifa)
137 {
138 	struct ifaddrs *curr;
139 
140 	while (ifa != NULL) {
141 		curr = ifa;
142 		ifa = ifa->ifa_next;
143 		free(curr->ifa_name);
144 		free(curr->ifa_addr);
145 		free(curr->ifa_netmask);
146 		free(curr->ifa_dstaddr);
147 		free(curr->ifa_data);
148 		free(curr);
149 	}
150 }
151 
152 static uint_t
153 dlpi_iftype(uint_t dlpitype)
154 {
155 	switch (dlpitype) {
156 	case DL_ETHER:
157 		return (IFT_ETHER);
158 
159 	case DL_ATM:
160 		return (IFT_ATM);
161 
162 	case DL_CSMACD:
163 		return (IFT_ISO88023);
164 
165 	case DL_TPB:
166 		return (IFT_ISO88024);
167 
168 	case DL_TPR:
169 		return (IFT_ISO88025);
170 
171 	case DL_FDDI:
172 		return (IFT_FDDI);
173 
174 	case DL_IB:
175 		return (IFT_IB);
176 
177 	case DL_OTHER:
178 		return (IFT_OTHER);
179 	}
180 
181 	return (IFT_OTHER);
182 }
183 
184 /*
185  * Make a door call to dlmgmtd.
186  * If successful the result is stored in rbuf and 0 returned.
187  * On errors, return -1 and set `errno'.
188  */
189 static int
190 dl_door_call(int door_fd, void *arg, size_t asize, void *rbuf, size_t *rsizep)
191 {
192 	int err;
193 	door_arg_t	darg;
194 	darg.data_ptr	= arg;
195 	darg.data_size	= asize;
196 	darg.desc_ptr	= NULL;
197 	darg.desc_num	= 0;
198 	darg.rbuf	= rbuf;
199 	darg.rsize	= *rsizep;
200 
201 	if (door_call(door_fd, &darg) == -1) {
202 		return (-1);
203 	}
204 
205 	if (darg.rbuf != rbuf) {
206 		/*
207 		 * The size of the input rbuf was not big enough so that
208 		 * the door allocated the rbuf itself. In this case, return
209 		 * the required size to the caller.
210 		 */
211 		err = errno;
212 		(void) munmap(darg.rbuf, darg.rsize);
213 		*rsizep = darg.rsize;
214 		errno = err;
215 		return (-1);
216 	} else if (darg.rsize != *rsizep) {
217 		return (-1);
218 	}
219 	return (0);
220 }
221 
222 
223 /*
224  * Get the name from dlmgmtd by linkid.
225  * If successful the result is stored in name_retval and 0 returned.
226  * On errors, return -1 and set `errno'.
227  */
228 static int
229 dl_get_name(int door_fd, datalink_id_t linkid,
230     dlmgmt_getname_retval_t *name_retval)
231 {
232 	size_t name_sz = sizeof (*name_retval);
233 	dlmgmt_door_getname_t getname;
234 	bzero(&getname, sizeof (dlmgmt_door_getname_t));
235 	getname.ld_cmd = DLMGMT_CMD_GETNAME;
236 	getname.ld_linkid = linkid;
237 
238 	if (dl_door_call(door_fd, &getname, sizeof (getname), name_retval,
239 	    &name_sz) < 0) {
240 		return (-1);
241 	}
242 	if (name_retval->lr_err != 0) {
243 		errno = name_retval->lr_err;
244 		return (-1);
245 	}
246 	return (0);
247 }
248 
249 /*
250  * Get the next link from dlmgmtd.
251  * Start iterating by passing DATALINK_INVALID_LINKID as linkid.
252  * The end is marked by next_retval.lr_linkid set to DATALINK_INVALID_LINKID.
253  * If successful the result is stored in next_retval and 0 returned.
254  * On errors, return -1 and set `errno'.
255  */
256 static int
257 dl_get_next(int door_fd, datalink_id_t linkid, datalink_class_t class,
258     datalink_media_t dmedia, uint32_t flags,
259     dlmgmt_getnext_retval_t *next_retval)
260 {
261 	size_t next_sz = sizeof (*next_retval);
262 	dlmgmt_door_getnext_t getnext;
263 	bzero(&getnext, sizeof (dlmgmt_door_getnext_t));
264 	getnext.ld_cmd = DLMGMT_CMD_GETNEXT;
265 	getnext.ld_class = class;
266 	getnext.ld_dmedia = dmedia;
267 	getnext.ld_flags = flags;
268 	getnext.ld_linkid = linkid;
269 
270 	if (dl_door_call(door_fd, &getnext, sizeof (getnext), next_retval,
271 	    &next_sz) < 0) {
272 		return (-1);
273 	}
274 	if (next_retval->lr_err != 0) {
275 		errno = next_retval->lr_err;
276 		return (-1);
277 	}
278 	return (0);
279 }
280 
281 /*
282  * Returns all addresses configured on the system. If flags contain
283  * LIFC_ENABLED, only the addresses that are UP are returned.
284  * Address list that is returned by this function must be freed
285  * using freeifaddrs().
286  */
287 int
288 getallifaddrs(sa_family_t af, struct ifaddrs **ifap, int64_t flags)
289 {
290 	struct lifreq *buf = NULL;
291 	struct lifreq *lifrp;
292 	struct lifreq lifrl;
293 	int ret;
294 	int s, n, numifs;
295 	struct ifaddrs *curr, *prev;
296 	struct sockaddr_dl *ifa_addr = NULL;
297 	if_data_t *ifa_data = NULL;
298 	sa_family_t lifr_af;
299 	datalink_id_t linkid;
300 	dld_ioc_attr_t dia;
301 	dld_macaddrinfo_t *dmip;
302 	dld_ioc_macaddrget_t *iomp = NULL;
303 	dlmgmt_getnext_retval_t next_retval;
304 	dlmgmt_getname_retval_t	name_retval;
305 	int bufsize;
306 	int nmacaddr = 1024;
307 	int sock4 = -1;
308 	int sock6 = -1;
309 	int door_fd = -1;
310 	int dld_fd = -1;
311 	int err;
312 
313 	/*
314 	 * Initialize ifap to NULL so we can safely call freeifaddrs
315 	 * on it in case of error.
316 	 */
317 	if (ifap == NULL)
318 		return (EINVAL);
319 	*ifap = NULL;
320 
321 	if ((sock4 = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ||
322 	    (sock6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
323 		goto fail;
324 	}
325 
326 	bufsize = sizeof (dld_ioc_macaddrget_t) + nmacaddr *
327 	    sizeof (dld_macaddrinfo_t);
328 	if ((iomp = malloc(bufsize)) == NULL)
329 		goto fail;
330 
331 retry:
332 	bzero(iomp, bufsize);
333 	/* Get all interfaces from SIOCGLIFCONF */
334 	ret = getallifs(sock4, af, &buf, &numifs, (flags & ~LIFC_ENABLED));
335 	if (ret != 0)
336 		goto fail;
337 
338 	/*
339 	 * Loop through the interfaces obtained from SIOCGLIFCOMF
340 	 * and retrieve the addresses, netmask and flags.
341 	 */
342 	prev = NULL;
343 	lifrp = buf;
344 	for (n = 0; n < numifs; n++, lifrp++) {
345 
346 		/* Prepare for the ioctl call */
347 		(void) strncpy(lifrl.lifr_name, lifrp->lifr_name,
348 		    sizeof (lifrl.lifr_name));
349 		lifr_af = lifrp->lifr_addr.ss_family;
350 		if (af != AF_UNSPEC && lifr_af != af)
351 			continue;
352 
353 		s = (lifr_af == AF_INET ? sock4 : sock6);
354 
355 		if (ioctl(s, SIOCGLIFFLAGS, (caddr_t)&lifrl) < 0)
356 			goto fail;
357 		if ((flags & LIFC_ENABLED) && !(lifrl.lifr_flags & IFF_UP))
358 			continue;
359 
360 		/*
361 		 * Allocate the current list node. Each node contains data
362 		 * for one ifaddrs structure.
363 		 */
364 		curr = calloc(1, sizeof (struct ifaddrs));
365 		if (curr == NULL)
366 			goto fail;
367 
368 		if (prev != NULL) {
369 			prev->ifa_next = curr;
370 		} else {
371 			/* First node in the linked list */
372 			*ifap = curr;
373 		}
374 		prev = curr;
375 
376 		curr->ifa_flags = lifrl.lifr_flags;
377 		if ((curr->ifa_name = strdup(lifrp->lifr_name)) == NULL)
378 			goto fail;
379 
380 		curr->ifa_addr = malloc(sizeof (struct sockaddr_storage));
381 		if (curr->ifa_addr == NULL)
382 			goto fail;
383 		(void) memcpy(curr->ifa_addr, &lifrp->lifr_addr,
384 		    sizeof (struct sockaddr_storage));
385 
386 		/* Get the netmask */
387 		if (ioctl(s, SIOCGLIFNETMASK, (caddr_t)&lifrl) < 0)
388 			goto fail;
389 		curr->ifa_netmask = malloc(sizeof (struct sockaddr_storage));
390 		if (curr->ifa_netmask == NULL)
391 			goto fail;
392 		(void) memcpy(curr->ifa_netmask, &lifrl.lifr_addr,
393 		    sizeof (struct sockaddr_storage));
394 
395 		/* Get the destination for a pt-pt interface */
396 		if (curr->ifa_flags & IFF_POINTOPOINT) {
397 			if (ioctl(s, SIOCGLIFDSTADDR, (caddr_t)&lifrl) < 0)
398 				goto fail;
399 			curr->ifa_dstaddr = malloc(
400 			    sizeof (struct sockaddr_storage));
401 			if (curr->ifa_dstaddr == NULL)
402 				goto fail;
403 			(void) memcpy(curr->ifa_dstaddr, &lifrl.lifr_addr,
404 			    sizeof (struct sockaddr_storage));
405 		} else if (curr->ifa_flags & IFF_BROADCAST) {
406 			if (ioctl(s, SIOCGLIFBRDADDR, (caddr_t)&lifrl) < 0)
407 				goto fail;
408 			curr->ifa_broadaddr = malloc(
409 			    sizeof (struct sockaddr_storage));
410 			if (curr->ifa_broadaddr == NULL)
411 				goto fail;
412 			(void) memcpy(curr->ifa_broadaddr, &lifrl.lifr_addr,
413 			    sizeof (struct sockaddr_storage));
414 		}
415 
416 	}
417 
418 	/* add AF_LINK entries */
419 	if (af == AF_UNSPEC || af == AF_LINK) {
420 		/*
421 		 * A datalink management door may not be available (for example
422 		 * in a shared IP zone). Only enumerate AF_LINK entries if the
423 		 * door exists.
424 		 */
425 		door_fd = open(DLMGMT_DOOR, O_RDONLY);
426 		if (door_fd < 0) {
427 			if (errno == ENOENT)
428 				goto nolink;
429 			goto fail;
430 		}
431 		if ((dld_fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
432 			goto fail;
433 
434 		linkid = DATALINK_INVALID_LINKID;
435 		for (;;) {
436 			struct sockaddr_storage *s;
437 			size_t len;
438 
439 			if (dl_get_next(door_fd, linkid, DATALINK_CLASS_ALL,
440 			    DATALINK_ANY_MEDIATYPE, DLMGMT_ACTIVE,
441 			    &next_retval) != 0) {
442 				break;
443 			}
444 
445 			linkid = next_retval.lr_linkid;
446 			if (linkid == DATALINK_INVALID_LINKID)
447 				break;
448 
449 			/* get mac addr */
450 			iomp->dig_size = nmacaddr * sizeof (dld_macaddrinfo_t);
451 			iomp->dig_linkid = linkid;
452 
453 			if (ioctl(dld_fd, DLDIOC_MACADDRGET, iomp) < 0)
454 				continue;
455 
456 			dmip = (dld_macaddrinfo_t *)(iomp + 1);
457 
458 			/* get name */
459 			if (dl_get_name(door_fd, linkid, &name_retval) != 0)
460 				continue;
461 
462 			/* get MTU */
463 			dia.dia_linkid = linkid;
464 			if (ioctl(dld_fd, DLDIOC_ATTR, &dia) < 0)
465 				continue;
466 
467 			curr = calloc(1, sizeof (struct ifaddrs));
468 			if (curr == NULL)
469 				goto fail;
470 
471 			if (prev != NULL) {
472 				prev->ifa_next = curr;
473 			} else {
474 				/* First node in the linked list */
475 				*ifap = curr;
476 			}
477 			prev = curr;
478 
479 			if ((curr->ifa_name = strdup(name_retval.lr_link)) ==
480 			    NULL)
481 				goto fail;
482 
483 			/*
484 			 * We split this allocation and assignment to satisfy
485 			 * smatch.
486 			 */
487 			s = calloc(1, sizeof (struct sockaddr_storage));
488 			if (s == NULL)
489 				goto fail;
490 			curr->ifa_addr = (struct sockaddr *)s;
491 
492 			curr->ifa_data = calloc(1, sizeof (if_data_t));
493 			if (curr->ifa_data == NULL)
494 				goto fail;
495 
496 			curr->ifa_addr->sa_family = AF_LINK;
497 			ifa_addr = (struct sockaddr_dl *)curr->ifa_addr;
498 			ifa_data = curr->ifa_data;
499 
500 			/*
501 			 * Place both the interface name and the address in
502 			 * sdl_data[] as long as both fit. The name will not
503 			 * include a NUL terminator in the data section. In
504 			 * general, both of these should fit due to the system
505 			 * constraints of a 31-character link name
506 			 * (MAXLINKNAMELEN).
507 			 *
508 			 * Verify both fit. If they don't, skip the interface
509 			 * name.
510 			 */
511 			len = strlen(curr->ifa_name);
512 			if (len + dmip->dmi_addrlen <=
513 			    sizeof (ifa_addr->sdl_data)) {
514 				ifa_addr->sdl_nlen = len;
515 				(void) memcpy(ifa_addr->sdl_data,
516 				    curr->ifa_name, len);
517 			}
518 			ifa_addr->sdl_alen = dmip->dmi_addrlen;
519 			(void) memcpy(LLADDR(ifa_addr), dmip->dmi_addr,
520 			    dmip->dmi_addrlen);
521 
522 			ifa_data->ifi_mtu = dia.dia_max_sdu;
523 			ifa_data->ifi_type = dlpi_iftype(next_retval.lr_media);
524 			ifa_data->ifi_addrlen = dmip->dmi_addrlen;
525 			ifa_addr->sdl_type = ifa_data->ifi_type;
526 
527 			/*
528 			 * get interface index
529 			 * This is only possible if the link has been plumbed.
530 			 */
531 			if (strlcpy(lifrl.lifr_name, name_retval.lr_link,
532 			    sizeof (lifrl.lifr_name)) >=
533 			    sizeof (lifrl.lifr_name))
534 				continue;
535 
536 			if (ioctl(sock4, SIOCGLIFINDEX, (caddr_t)&lifrl) >= 0) {
537 				ifa_addr->sdl_index = lifrl.lifr_index;
538 			} else if (ioctl(sock6, SIOCGLIFINDEX,
539 			    (caddr_t)&lifrl) >= 0) {
540 				/* retry for IPv6 */
541 				ifa_addr->sdl_index = lifrl.lifr_index;
542 			}
543 		}
544 	}
545 nolink:
546 	free(buf);
547 	free(iomp);
548 	(void) close(sock4);
549 	(void) close(sock6);
550 	if (door_fd >= 0)
551 		(void) close(door_fd);
552 	if (dld_fd >= 0)
553 		(void) close(dld_fd);
554 	return (0);
555 fail:
556 	err = errno;
557 	free(buf);
558 	buf = NULL;
559 	freeifaddrs(*ifap);
560 	*ifap = NULL;
561 	if (err == ENXIO)
562 		goto retry;
563 	free(iomp);
564 
565 	if (sock4 >= 0)
566 		(void) close(sock4);
567 	if (sock6 >= 0)
568 		(void) close(sock6);
569 	if (door_fd >= 0)
570 		(void) close(door_fd);
571 	if (dld_fd >= 0)
572 		(void) close(dld_fd);
573 	errno = err;
574 	return (-1);
575 }
576 
577 /*
578  * Do a SIOCGLIFCONF and store all the interfaces in `buf'.
579  */
580 int
581 getallifs(int s, sa_family_t af, struct lifreq **lifr, int *numifs,
582     int64_t lifc_flags)
583 {
584 	struct lifnum lifn;
585 	struct lifconf lifc;
586 	size_t bufsize;
587 	char *tmp;
588 	caddr_t *buf = (caddr_t *)lifr;
589 
590 	lifn.lifn_family = af;
591 	lifn.lifn_flags = lifc_flags;
592 
593 	*buf = NULL;
594 retry:
595 	if (ioctl(s, SIOCGLIFNUM, &lifn) < 0)
596 		goto fail;
597 
598 	/*
599 	 * When calculating the buffer size needed, add a small number
600 	 * of interfaces to those we counted.  We do this to capture
601 	 * the interface status of potential interfaces which may have
602 	 * been plumbed between the SIOCGLIFNUM and the SIOCGLIFCONF.
603 	 */
604 	bufsize = (lifn.lifn_count + 4) * sizeof (struct lifreq);
605 
606 	if ((tmp = realloc(*buf, bufsize)) == NULL)
607 		goto fail;
608 
609 	*buf = tmp;
610 	lifc.lifc_family = af;
611 	lifc.lifc_flags = lifc_flags;
612 	lifc.lifc_len = bufsize;
613 	lifc.lifc_buf = *buf;
614 	if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0)
615 		goto fail;
616 
617 	*numifs = lifc.lifc_len / sizeof (struct lifreq);
618 	if (*numifs >= (lifn.lifn_count + 4)) {
619 		/*
620 		 * If every entry was filled, there are probably
621 		 * more interfaces than (lifn.lifn_count + 4).
622 		 * Redo the ioctls SIOCGLIFNUM and SIOCGLIFCONF to
623 		 * get all the interfaces.
624 		 */
625 		goto retry;
626 	}
627 	return (0);
628 fail:
629 	free(*buf);
630 	*buf = NULL;
631 	return (-1);
632 }
633