xref: /illumos-gate/usr/src/lib/libsocket/inet/getifaddrs.c (revision e3ae4b35c024af1196582063ecee3ab79367227d)
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 			if (dl_get_next(door_fd, linkid, DATALINK_CLASS_ALL,
436 			    DATALINK_ANY_MEDIATYPE, DLMGMT_ACTIVE,
437 			    &next_retval) != 0) {
438 				break;
439 			}
440 
441 			linkid = next_retval.lr_linkid;
442 			if (linkid == DATALINK_INVALID_LINKID)
443 				break;
444 
445 			/* get mac addr */
446 			iomp->dig_size = nmacaddr * sizeof (dld_macaddrinfo_t);
447 			iomp->dig_linkid = linkid;
448 
449 			if (ioctl(dld_fd, DLDIOC_MACADDRGET, iomp) < 0)
450 				continue;
451 
452 			dmip = (dld_macaddrinfo_t *)(iomp + 1);
453 
454 			/* get name */
455 			if (dl_get_name(door_fd, linkid, &name_retval) != 0)
456 				continue;
457 
458 			/* get MTU */
459 			dia.dia_linkid = linkid;
460 			if (ioctl(dld_fd, DLDIOC_ATTR, &dia) < 0)
461 				continue;
462 
463 			curr = calloc(1, sizeof (struct ifaddrs));
464 			if (curr == NULL)
465 				goto fail;
466 
467 			if (prev != NULL) {
468 				prev->ifa_next = curr;
469 			} else {
470 				/* First node in the linked list */
471 				*ifap = curr;
472 			}
473 			prev = curr;
474 
475 			if ((curr->ifa_name = strdup(name_retval.lr_link)) ==
476 			    NULL)
477 				goto fail;
478 
479 			curr->ifa_addr =
480 			    calloc(1, sizeof (struct sockaddr_storage));
481 			if (curr->ifa_addr == NULL)
482 				goto fail;
483 
484 			curr->ifa_data = calloc(1, sizeof (if_data_t));
485 			if (curr->ifa_data == NULL)
486 				goto fail;
487 
488 			curr->ifa_addr->sa_family = AF_LINK;
489 			ifa_addr = (struct sockaddr_dl *)curr->ifa_addr;
490 			ifa_data = curr->ifa_data;
491 
492 			(void) memcpy(ifa_addr->sdl_data, dmip->dmi_addr,
493 			    dmip->dmi_addrlen);
494 			ifa_addr->sdl_alen = dmip->dmi_addrlen;
495 
496 			ifa_data->ifi_mtu = dia.dia_max_sdu;
497 			ifa_data->ifi_type = dlpi_iftype(next_retval.lr_media);
498 
499 			/*
500 			 * get interface index
501 			 * This is only possible if the link has been plumbed.
502 			 */
503 			if (strlcpy(lifrl.lifr_name, name_retval.lr_link,
504 			    sizeof (lifrl.lifr_name)) >=
505 			    sizeof (lifrl.lifr_name))
506 				continue;
507 
508 			if (ioctl(sock4, SIOCGLIFINDEX, (caddr_t)&lifrl) >= 0) {
509 				ifa_addr->sdl_index = lifrl.lifr_index;
510 			} else if (ioctl(sock6, SIOCGLIFINDEX,
511 			    (caddr_t)&lifrl) >= 0) {
512 				/* retry for IPv6 */
513 				ifa_addr->sdl_index = lifrl.lifr_index;
514 			}
515 		}
516 	}
517 nolink:
518 	free(buf);
519 	free(iomp);
520 	(void) close(sock4);
521 	(void) close(sock6);
522 	if (door_fd >= 0)
523 		(void) close(door_fd);
524 	if (dld_fd >= 0)
525 		(void) close(dld_fd);
526 	return (0);
527 fail:
528 	err = errno;
529 	free(buf);
530 	buf = NULL;
531 	freeifaddrs(*ifap);
532 	*ifap = NULL;
533 	if (err == ENXIO)
534 		goto retry;
535 	free(iomp);
536 
537 	if (sock4 >= 0)
538 		(void) close(sock4);
539 	if (sock6 >= 0)
540 		(void) close(sock6);
541 	if (door_fd >= 0)
542 		(void) close(door_fd);
543 	if (dld_fd >= 0)
544 		(void) close(dld_fd);
545 	errno = err;
546 	return (-1);
547 }
548 
549 /*
550  * Do a SIOCGLIFCONF and store all the interfaces in `buf'.
551  */
552 int
553 getallifs(int s, sa_family_t af, struct lifreq **lifr, int *numifs,
554     int64_t lifc_flags)
555 {
556 	struct lifnum lifn;
557 	struct lifconf lifc;
558 	size_t bufsize;
559 	char *tmp;
560 	caddr_t *buf = (caddr_t *)lifr;
561 
562 	lifn.lifn_family = af;
563 	lifn.lifn_flags = lifc_flags;
564 
565 	*buf = NULL;
566 retry:
567 	if (ioctl(s, SIOCGLIFNUM, &lifn) < 0)
568 		goto fail;
569 
570 	/*
571 	 * When calculating the buffer size needed, add a small number
572 	 * of interfaces to those we counted.  We do this to capture
573 	 * the interface status of potential interfaces which may have
574 	 * been plumbed between the SIOCGLIFNUM and the SIOCGLIFCONF.
575 	 */
576 	bufsize = (lifn.lifn_count + 4) * sizeof (struct lifreq);
577 
578 	if ((tmp = realloc(*buf, bufsize)) == NULL)
579 		goto fail;
580 
581 	*buf = tmp;
582 	lifc.lifc_family = af;
583 	lifc.lifc_flags = lifc_flags;
584 	lifc.lifc_len = bufsize;
585 	lifc.lifc_buf = *buf;
586 	if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0)
587 		goto fail;
588 
589 	*numifs = lifc.lifc_len / sizeof (struct lifreq);
590 	if (*numifs >= (lifn.lifn_count + 4)) {
591 		/*
592 		 * If every entry was filled, there are probably
593 		 * more interfaces than (lifn.lifn_count + 4).
594 		 * Redo the ioctls SIOCGLIFNUM and SIOCGLIFCONF to
595 		 * get all the interfaces.
596 		 */
597 		goto retry;
598 	}
599 	return (0);
600 fail:
601 	free(*buf);
602 	*buf = NULL;
603 	return (-1);
604 }
605