xref: /illumos-gate/usr/src/lib/libsocket/inet/getifaddrs.c (revision dd72704bd9e794056c558153663c739e2012d721)
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 = calloc(1, bufsize)) == NULL)
328 		goto fail;
329 
330 retry:
331 	/* Get all interfaces from SIOCGLIFCONF */
332 	ret = getallifs(sock4, af, &buf, &numifs, (flags & ~LIFC_ENABLED));
333 	if (ret != 0)
334 		goto fail;
335 
336 	/*
337 	 * Loop through the interfaces obtained from SIOCGLIFCOMF
338 	 * and retrieve the addresses, netmask and flags.
339 	 */
340 	prev = NULL;
341 	lifrp = buf;
342 	for (n = 0; n < numifs; n++, lifrp++) {
343 
344 		/* Prepare for the ioctl call */
345 		(void) strncpy(lifrl.lifr_name, lifrp->lifr_name,
346 		    sizeof (lifrl.lifr_name));
347 		lifr_af = lifrp->lifr_addr.ss_family;
348 		if (af != AF_UNSPEC && lifr_af != af)
349 			continue;
350 
351 		s = (lifr_af == AF_INET ? sock4 : sock6);
352 
353 		if (ioctl(s, SIOCGLIFFLAGS, (caddr_t)&lifrl) < 0)
354 			goto fail;
355 		if ((flags & LIFC_ENABLED) && !(lifrl.lifr_flags & IFF_UP))
356 			continue;
357 
358 		/*
359 		 * Allocate the current list node. Each node contains data
360 		 * for one ifaddrs structure.
361 		 */
362 		curr = calloc(1, sizeof (struct ifaddrs));
363 		if (curr == NULL)
364 			goto fail;
365 
366 		if (prev != NULL) {
367 			prev->ifa_next = curr;
368 		} else {
369 			/* First node in the linked list */
370 			*ifap = curr;
371 		}
372 		prev = curr;
373 
374 		curr->ifa_flags = lifrl.lifr_flags;
375 		if ((curr->ifa_name = strdup(lifrp->lifr_name)) == NULL)
376 			goto fail;
377 
378 		curr->ifa_addr = malloc(sizeof (struct sockaddr_storage));
379 		if (curr->ifa_addr == NULL)
380 			goto fail;
381 		(void) memcpy(curr->ifa_addr, &lifrp->lifr_addr,
382 		    sizeof (struct sockaddr_storage));
383 
384 		/* Get the netmask */
385 		if (ioctl(s, SIOCGLIFNETMASK, (caddr_t)&lifrl) < 0)
386 			goto fail;
387 		curr->ifa_netmask = malloc(sizeof (struct sockaddr_storage));
388 		if (curr->ifa_netmask == NULL)
389 			goto fail;
390 		(void) memcpy(curr->ifa_netmask, &lifrl.lifr_addr,
391 		    sizeof (struct sockaddr_storage));
392 
393 		/* Get the destination for a pt-pt interface */
394 		if (curr->ifa_flags & IFF_POINTOPOINT) {
395 			if (ioctl(s, SIOCGLIFDSTADDR, (caddr_t)&lifrl) < 0)
396 				goto fail;
397 			curr->ifa_dstaddr = malloc(
398 			    sizeof (struct sockaddr_storage));
399 			if (curr->ifa_dstaddr == NULL)
400 				goto fail;
401 			(void) memcpy(curr->ifa_dstaddr, &lifrl.lifr_addr,
402 			    sizeof (struct sockaddr_storage));
403 		} else if (curr->ifa_flags & IFF_BROADCAST) {
404 			if (ioctl(s, SIOCGLIFBRDADDR, (caddr_t)&lifrl) < 0)
405 				goto fail;
406 			curr->ifa_broadaddr = malloc(
407 			    sizeof (struct sockaddr_storage));
408 			if (curr->ifa_broadaddr == NULL)
409 				goto fail;
410 			(void) memcpy(curr->ifa_broadaddr, &lifrl.lifr_addr,
411 			    sizeof (struct sockaddr_storage));
412 		}
413 
414 	}
415 
416 	/* add AF_LINK entries */
417 	if (af == AF_UNSPEC || af == AF_LINK) {
418 		/*
419 		 * A datalink management door may not be available (for example
420 		 * in a shared IP zone). Only enumerate AF_LINK entries if the
421 		 * door exists.
422 		 */
423 		door_fd = open(DLMGMT_DOOR, O_RDONLY);
424 		if (door_fd < 0) {
425 			if (errno == ENOENT)
426 				goto nolink;
427 			goto fail;
428 		}
429 		if ((dld_fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
430 			goto fail;
431 
432 		linkid = DATALINK_INVALID_LINKID;
433 		for (;;) {
434 			if (dl_get_next(door_fd, linkid, DATALINK_CLASS_ALL,
435 			    DATALINK_ANY_MEDIATYPE, DLMGMT_ACTIVE,
436 			    &next_retval) != 0) {
437 				break;
438 			}
439 
440 			linkid = next_retval.lr_linkid;
441 			if (linkid == DATALINK_INVALID_LINKID)
442 				break;
443 
444 			/* get mac addr */
445 			iomp->dig_size = nmacaddr * sizeof (dld_macaddrinfo_t);
446 			iomp->dig_linkid = linkid;
447 
448 			if (ioctl(dld_fd, DLDIOC_MACADDRGET, iomp) < 0)
449 				continue;
450 
451 			dmip = (dld_macaddrinfo_t *)(iomp + 1);
452 
453 			/* get name */
454 			if (dl_get_name(door_fd, linkid, &name_retval) != 0)
455 				continue;
456 
457 			/* get MTU */
458 			dia.dia_linkid = linkid;
459 			if (ioctl(dld_fd, DLDIOC_ATTR, &dia) < 0)
460 				continue;
461 
462 			curr = calloc(1, sizeof (struct ifaddrs));
463 			if (curr == NULL)
464 				goto fail;
465 
466 			if (prev != NULL) {
467 				prev->ifa_next = curr;
468 			} else {
469 				/* First node in the linked list */
470 				*ifap = curr;
471 			}
472 			prev = curr;
473 
474 			if ((curr->ifa_name = strdup(name_retval.lr_link)) ==
475 			    NULL)
476 				goto fail;
477 
478 			curr->ifa_addr =
479 			    calloc(1, sizeof (struct sockaddr_storage));
480 			if (curr->ifa_addr == NULL)
481 				goto fail;
482 
483 			curr->ifa_data = calloc(1, sizeof (if_data_t));
484 			if (curr->ifa_data == NULL)
485 				goto fail;
486 
487 			curr->ifa_addr->sa_family = AF_LINK;
488 			ifa_addr = (struct sockaddr_dl *)curr->ifa_addr;
489 			ifa_data = curr->ifa_data;
490 
491 			(void) memcpy(ifa_addr->sdl_data, dmip->dmi_addr,
492 			    dmip->dmi_addrlen);
493 			ifa_addr->sdl_alen = dmip->dmi_addrlen;
494 
495 			ifa_data->ifi_mtu = dia.dia_max_sdu;
496 			ifa_data->ifi_type = dlpi_iftype(next_retval.lr_media);
497 
498 			/*
499 			 * get interface index
500 			 * This is only possible if the link has been plumbed.
501 			 */
502 			if (strlcpy(lifrl.lifr_name, name_retval.lr_link,
503 			    sizeof (lifrl.lifr_name)) >=
504 			    sizeof (lifrl.lifr_name))
505 				continue;
506 
507 			if (ioctl(sock4, SIOCGLIFINDEX, (caddr_t)&lifrl) >= 0) {
508 				ifa_addr->sdl_index = lifrl.lifr_index;
509 			} else if (ioctl(sock6, SIOCGLIFINDEX,
510 			    (caddr_t)&lifrl) >= 0) {
511 				/* retry for IPv6 */
512 				ifa_addr->sdl_index = lifrl.lifr_index;
513 			}
514 		}
515 	}
516 nolink:
517 	free(buf);
518 	free(iomp);
519 	(void) close(sock4);
520 	(void) close(sock6);
521 	if (door_fd >= 0)
522 		(void) close(door_fd);
523 	if (dld_fd >= 0)
524 		(void) close(dld_fd);
525 	return (0);
526 fail:
527 	err = errno;
528 	free(buf);
529 	free(iomp);
530 	freeifaddrs(*ifap);
531 	*ifap = NULL;
532 	if (err == ENXIO)
533 		goto retry;
534 
535 	if (sock4 >= 0)
536 		(void) close(sock4);
537 	if (sock6 >= 0)
538 		(void) close(sock6);
539 	if (door_fd >= 0)
540 		(void) close(door_fd);
541 	if (dld_fd >= 0)
542 		(void) close(dld_fd);
543 	errno = err;
544 	return (-1);
545 }
546 
547 /*
548  * Do a SIOCGLIFCONF and store all the interfaces in `buf'.
549  */
550 int
551 getallifs(int s, sa_family_t af, struct lifreq **lifr, int *numifs,
552     int64_t lifc_flags)
553 {
554 	struct lifnum lifn;
555 	struct lifconf lifc;
556 	size_t bufsize;
557 	char *tmp;
558 	caddr_t *buf = (caddr_t *)lifr;
559 
560 	lifn.lifn_family = af;
561 	lifn.lifn_flags = lifc_flags;
562 
563 	*buf = NULL;
564 retry:
565 	if (ioctl(s, SIOCGLIFNUM, &lifn) < 0)
566 		goto fail;
567 
568 	/*
569 	 * When calculating the buffer size needed, add a small number
570 	 * of interfaces to those we counted.  We do this to capture
571 	 * the interface status of potential interfaces which may have
572 	 * been plumbed between the SIOCGLIFNUM and the SIOCGLIFCONF.
573 	 */
574 	bufsize = (lifn.lifn_count + 4) * sizeof (struct lifreq);
575 
576 	if ((tmp = realloc(*buf, bufsize)) == NULL)
577 		goto fail;
578 
579 	*buf = tmp;
580 	lifc.lifc_family = af;
581 	lifc.lifc_flags = lifc_flags;
582 	lifc.lifc_len = bufsize;
583 	lifc.lifc_buf = *buf;
584 	if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0)
585 		goto fail;
586 
587 	*numifs = lifc.lifc_len / sizeof (struct lifreq);
588 	if (*numifs >= (lifn.lifn_count + 4)) {
589 		/*
590 		 * If every entry was filled, there are probably
591 		 * more interfaces than (lifn.lifn_count + 4).
592 		 * Redo the ioctls SIOCGLIFNUM and SIOCGLIFCONF to
593 		 * get all the interfaces.
594 		 */
595 		goto retry;
596 	}
597 	return (0);
598 fail:
599 	free(*buf);
600 	*buf = NULL;
601 	return (-1);
602 }
603