/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2017 RackTop Systems. * Copyright 2022 Sebastian Wiedenroth */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * directs folks towards our internal symbol, __getifaddrs. This * means we cannot name the original symbol 'getifaddrs' here or it will be * renamed. Instead, we use another redefine_extname to take care of this. Note, * the extern declaration is required as gcc and others will only apply this for * things they see an extern declaration for. */ #pragma redefine_extname getifaddrs_old getifaddrs extern int getifaddrs_old(struct ifaddrs **); /* * Create a linked list of `struct ifaddrs' structures, one for each * address that is UP. If successful, store the list in *ifap and * return 0. On errors, return -1 and set `errno'. * * The storage returned in *ifap is allocated dynamically and can * only be properly freed by passing it to `freeifaddrs'. */ int _getifaddrs(struct ifaddrs **ifap, boolean_t can_handle_links) { int err; char *cp; struct ifaddrs *curr; if (ifap == NULL) { errno = EINVAL; return (-1); } *ifap = NULL; if (can_handle_links) { err = getallifaddrs(AF_UNSPEC, ifap, LIFC_ENABLED); } else { err = getallifaddrs(AF_INET, ifap, LIFC_ENABLED); if (err != 0) return (err); /* Find end of the list to append to */ curr = *ifap; while (curr && curr->ifa_next) { curr = curr->ifa_next; } err = getallifaddrs(AF_INET6, curr ? &curr->ifa_next : ifap, LIFC_ENABLED); } if (err != 0) return (err); for (curr = *ifap; curr != NULL; curr = curr->ifa_next) { if ((cp = strchr(curr->ifa_name, ':')) != NULL) *cp = '\0'; } return (0); } /* * Legacy symbol * For a long time getifaddrs() only returned AF_INET and AF_INET6 entries. * Some consumers came to expect that no other address family may be returned. * To prevent existing binaries that can't handle AF_LINK entries from breaking * this symbol is kept around. Consumers that want the fixed behaviour need to * recompile and link to the fixed symbol. */ int getifaddrs_old(struct ifaddrs **ifap) { return (_getifaddrs(ifap, B_FALSE)); } /* * Current symbol * May return AF_INET, AF_INET6 and AF_LINK entries */ int __getifaddrs(struct ifaddrs **ifap) { return (_getifaddrs(ifap, B_TRUE)); } void freeifaddrs(struct ifaddrs *ifa) { struct ifaddrs *curr; while (ifa != NULL) { curr = ifa; ifa = ifa->ifa_next; free(curr->ifa_name); free(curr->ifa_addr); free(curr->ifa_netmask); free(curr->ifa_dstaddr); free(curr->ifa_data); free(curr); } } static uint_t dlpi_iftype(uint_t dlpitype) { switch (dlpitype) { case DL_ETHER: return (IFT_ETHER); case DL_ATM: return (IFT_ATM); case DL_CSMACD: return (IFT_ISO88023); case DL_TPB: return (IFT_ISO88024); case DL_TPR: return (IFT_ISO88025); case DL_FDDI: return (IFT_FDDI); case DL_IB: return (IFT_IB); case DL_OTHER: return (IFT_OTHER); } return (IFT_OTHER); } /* * Make a door call to dlmgmtd. * If successful the result is stored in rbuf and 0 returned. * On errors, return -1 and set `errno'. */ static int dl_door_call(int door_fd, void *arg, size_t asize, void *rbuf, size_t *rsizep) { int err; door_arg_t darg; darg.data_ptr = arg; darg.data_size = asize; darg.desc_ptr = NULL; darg.desc_num = 0; darg.rbuf = rbuf; darg.rsize = *rsizep; if (door_call(door_fd, &darg) == -1) { return (-1); } if (darg.rbuf != rbuf) { /* * The size of the input rbuf was not big enough so that * the door allocated the rbuf itself. In this case, return * the required size to the caller. */ err = errno; (void) munmap(darg.rbuf, darg.rsize); *rsizep = darg.rsize; errno = err; return (-1); } else if (darg.rsize != *rsizep) { return (-1); } return (0); } /* * Get the name from dlmgmtd by linkid. * If successful the result is stored in name_retval and 0 returned. * On errors, return -1 and set `errno'. */ static int dl_get_name(int door_fd, datalink_id_t linkid, dlmgmt_getname_retval_t *name_retval) { size_t name_sz = sizeof (*name_retval); dlmgmt_door_getname_t getname; bzero(&getname, sizeof (dlmgmt_door_getname_t)); getname.ld_cmd = DLMGMT_CMD_GETNAME; getname.ld_linkid = linkid; if (dl_door_call(door_fd, &getname, sizeof (getname), name_retval, &name_sz) < 0) { return (-1); } if (name_retval->lr_err != 0) { errno = name_retval->lr_err; return (-1); } return (0); } /* * Get the next link from dlmgmtd. * Start iterating by passing DATALINK_INVALID_LINKID as linkid. * The end is marked by next_retval.lr_linkid set to DATALINK_INVALID_LINKID. * If successful the result is stored in next_retval and 0 returned. * On errors, return -1 and set `errno'. */ static int dl_get_next(int door_fd, datalink_id_t linkid, datalink_class_t class, datalink_media_t dmedia, uint32_t flags, dlmgmt_getnext_retval_t *next_retval) { size_t next_sz = sizeof (*next_retval); dlmgmt_door_getnext_t getnext; bzero(&getnext, sizeof (dlmgmt_door_getnext_t)); getnext.ld_cmd = DLMGMT_CMD_GETNEXT; getnext.ld_class = class; getnext.ld_dmedia = dmedia; getnext.ld_flags = flags; getnext.ld_linkid = linkid; if (dl_door_call(door_fd, &getnext, sizeof (getnext), next_retval, &next_sz) < 0) { return (-1); } if (next_retval->lr_err != 0) { errno = next_retval->lr_err; return (-1); } return (0); } /* * Returns all addresses configured on the system. If flags contain * LIFC_ENABLED, only the addresses that are UP are returned. * Address list that is returned by this function must be freed * using freeifaddrs(). */ int getallifaddrs(sa_family_t af, struct ifaddrs **ifap, int64_t flags) { struct lifreq *buf = NULL; struct lifreq *lifrp; struct lifreq lifrl; int ret; int s, n, numifs; struct ifaddrs *curr, *prev; struct sockaddr_dl *ifa_addr = NULL; if_data_t *ifa_data = NULL; sa_family_t lifr_af; datalink_id_t linkid; dld_ioc_attr_t dia; dld_macaddrinfo_t *dmip; dld_ioc_macaddrget_t *iomp = NULL; dlmgmt_getnext_retval_t next_retval; dlmgmt_getname_retval_t name_retval; int bufsize; int nmacaddr = 1024; int sock4 = -1; int sock6 = -1; int door_fd = -1; int dld_fd = -1; int err; /* * Initialize ifap to NULL so we can safely call freeifaddrs * on it in case of error. */ if (ifap == NULL) return (EINVAL); *ifap = NULL; if ((sock4 = socket(AF_INET, SOCK_DGRAM, 0)) < 0 || (sock6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { goto fail; } bufsize = sizeof (dld_ioc_macaddrget_t) + nmacaddr * sizeof (dld_macaddrinfo_t); if ((iomp = malloc(bufsize)) == NULL) goto fail; retry: bzero(iomp, bufsize); /* Get all interfaces from SIOCGLIFCONF */ ret = getallifs(sock4, af, &buf, &numifs, (flags & ~LIFC_ENABLED)); if (ret != 0) goto fail; /* * Loop through the interfaces obtained from SIOCGLIFCOMF * and retrieve the addresses, netmask and flags. */ prev = NULL; lifrp = buf; for (n = 0; n < numifs; n++, lifrp++) { /* Prepare for the ioctl call */ (void) strncpy(lifrl.lifr_name, lifrp->lifr_name, sizeof (lifrl.lifr_name)); lifr_af = lifrp->lifr_addr.ss_family; if (af != AF_UNSPEC && lifr_af != af) continue; s = (lifr_af == AF_INET ? sock4 : sock6); if (ioctl(s, SIOCGLIFFLAGS, (caddr_t)&lifrl) < 0) goto fail; if ((flags & LIFC_ENABLED) && !(lifrl.lifr_flags & IFF_UP)) continue; /* * Allocate the current list node. Each node contains data * for one ifaddrs structure. */ curr = calloc(1, sizeof (struct ifaddrs)); if (curr == NULL) goto fail; if (prev != NULL) { prev->ifa_next = curr; } else { /* First node in the linked list */ *ifap = curr; } prev = curr; curr->ifa_flags = lifrl.lifr_flags; if ((curr->ifa_name = strdup(lifrp->lifr_name)) == NULL) goto fail; curr->ifa_addr = malloc(sizeof (struct sockaddr_storage)); if (curr->ifa_addr == NULL) goto fail; (void) memcpy(curr->ifa_addr, &lifrp->lifr_addr, sizeof (struct sockaddr_storage)); /* Get the netmask */ if (ioctl(s, SIOCGLIFNETMASK, (caddr_t)&lifrl) < 0) goto fail; curr->ifa_netmask = malloc(sizeof (struct sockaddr_storage)); if (curr->ifa_netmask == NULL) goto fail; (void) memcpy(curr->ifa_netmask, &lifrl.lifr_addr, sizeof (struct sockaddr_storage)); /* Get the destination for a pt-pt interface */ if (curr->ifa_flags & IFF_POINTOPOINT) { if (ioctl(s, SIOCGLIFDSTADDR, (caddr_t)&lifrl) < 0) goto fail; curr->ifa_dstaddr = malloc( sizeof (struct sockaddr_storage)); if (curr->ifa_dstaddr == NULL) goto fail; (void) memcpy(curr->ifa_dstaddr, &lifrl.lifr_addr, sizeof (struct sockaddr_storage)); } else if (curr->ifa_flags & IFF_BROADCAST) { if (ioctl(s, SIOCGLIFBRDADDR, (caddr_t)&lifrl) < 0) goto fail; curr->ifa_broadaddr = malloc( sizeof (struct sockaddr_storage)); if (curr->ifa_broadaddr == NULL) goto fail; (void) memcpy(curr->ifa_broadaddr, &lifrl.lifr_addr, sizeof (struct sockaddr_storage)); } } /* add AF_LINK entries */ if (af == AF_UNSPEC || af == AF_LINK) { /* * A datalink management door may not be available (for example * in a shared IP zone). Only enumerate AF_LINK entries if the * door exists. */ door_fd = open(DLMGMT_DOOR, O_RDONLY); if (door_fd < 0) { if (errno == ENOENT) goto nolink; goto fail; } if ((dld_fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0) goto fail; linkid = DATALINK_INVALID_LINKID; for (;;) { if (dl_get_next(door_fd, linkid, DATALINK_CLASS_ALL, DATALINK_ANY_MEDIATYPE, DLMGMT_ACTIVE, &next_retval) != 0) { break; } linkid = next_retval.lr_linkid; if (linkid == DATALINK_INVALID_LINKID) break; /* get mac addr */ iomp->dig_size = nmacaddr * sizeof (dld_macaddrinfo_t); iomp->dig_linkid = linkid; if (ioctl(dld_fd, DLDIOC_MACADDRGET, iomp) < 0) continue; dmip = (dld_macaddrinfo_t *)(iomp + 1); /* get name */ if (dl_get_name(door_fd, linkid, &name_retval) != 0) continue; /* get MTU */ dia.dia_linkid = linkid; if (ioctl(dld_fd, DLDIOC_ATTR, &dia) < 0) continue; curr = calloc(1, sizeof (struct ifaddrs)); if (curr == NULL) goto fail; if (prev != NULL) { prev->ifa_next = curr; } else { /* First node in the linked list */ *ifap = curr; } prev = curr; if ((curr->ifa_name = strdup(name_retval.lr_link)) == NULL) goto fail; curr->ifa_addr = calloc(1, sizeof (struct sockaddr_storage)); if (curr->ifa_addr == NULL) goto fail; curr->ifa_data = calloc(1, sizeof (if_data_t)); if (curr->ifa_data == NULL) goto fail; curr->ifa_addr->sa_family = AF_LINK; ifa_addr = (struct sockaddr_dl *)curr->ifa_addr; ifa_data = curr->ifa_data; (void) memcpy(ifa_addr->sdl_data, dmip->dmi_addr, dmip->dmi_addrlen); ifa_addr->sdl_alen = dmip->dmi_addrlen; ifa_data->ifi_mtu = dia.dia_max_sdu; ifa_data->ifi_type = dlpi_iftype(next_retval.lr_media); /* * get interface index * This is only possible if the link has been plumbed. */ if (strlcpy(lifrl.lifr_name, name_retval.lr_link, sizeof (lifrl.lifr_name)) >= sizeof (lifrl.lifr_name)) continue; if (ioctl(sock4, SIOCGLIFINDEX, (caddr_t)&lifrl) >= 0) { ifa_addr->sdl_index = lifrl.lifr_index; } else if (ioctl(sock6, SIOCGLIFINDEX, (caddr_t)&lifrl) >= 0) { /* retry for IPv6 */ ifa_addr->sdl_index = lifrl.lifr_index; } } } nolink: free(buf); free(iomp); (void) close(sock4); (void) close(sock6); if (door_fd >= 0) (void) close(door_fd); if (dld_fd >= 0) (void) close(dld_fd); return (0); fail: err = errno; free(buf); buf = NULL; freeifaddrs(*ifap); *ifap = NULL; if (err == ENXIO) goto retry; free(iomp); if (sock4 >= 0) (void) close(sock4); if (sock6 >= 0) (void) close(sock6); if (door_fd >= 0) (void) close(door_fd); if (dld_fd >= 0) (void) close(dld_fd); errno = err; return (-1); } /* * Do a SIOCGLIFCONF and store all the interfaces in `buf'. */ int getallifs(int s, sa_family_t af, struct lifreq **lifr, int *numifs, int64_t lifc_flags) { struct lifnum lifn; struct lifconf lifc; size_t bufsize; char *tmp; caddr_t *buf = (caddr_t *)lifr; lifn.lifn_family = af; lifn.lifn_flags = lifc_flags; *buf = NULL; retry: if (ioctl(s, SIOCGLIFNUM, &lifn) < 0) goto fail; /* * When calculating the buffer size needed, add a small number * of interfaces to those we counted. We do this to capture * the interface status of potential interfaces which may have * been plumbed between the SIOCGLIFNUM and the SIOCGLIFCONF. */ bufsize = (lifn.lifn_count + 4) * sizeof (struct lifreq); if ((tmp = realloc(*buf, bufsize)) == NULL) goto fail; *buf = tmp; lifc.lifc_family = af; lifc.lifc_flags = lifc_flags; lifc.lifc_len = bufsize; lifc.lifc_buf = *buf; if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0) goto fail; *numifs = lifc.lifc_len / sizeof (struct lifreq); if (*numifs >= (lifn.lifn_count + 4)) { /* * If every entry was filled, there are probably * more interfaces than (lifn.lifn_count + 4). * Redo the ioctls SIOCGLIFNUM and SIOCGLIFCONF to * get all the interfaces. */ goto retry; } return (0); fail: free(*buf); *buf = NULL; return (-1); }