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