root/usr/src/lib/libsocket/inet/getifaddrs.c
/*
 * 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 <netdb.h>
#include <nss_dbdefs.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <sys/sockio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <net/if.h>
#include <door.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/dld_ioc.h>
#include <sys/dld.h>
#include <sys/dls_mgmt.h>
#include <sys/mac.h>
#include <sys/dlpi.h>
#include <net/if_types.h>
#include <ifaddrs.h>
#include <libsocket_priv.h>

/*
 * <ifaddrs.h> 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);
}