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