root/usr/src/lib/libsocket/inet/interface_id.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) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <inet/common.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/sockio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>

#define IPIF_SEPARATOR_CHAR     ":"

/*
 * Given an interface name, this function retrives the associated
 * index value. Returns index value if successful, zero otherwise.
 * The length of the supplied interface name must be at most
 * IF_NAMESIZE-1 bytes
 */
uint32_t
if_nametoindex(const char *ifname)
{
        int             s;
        struct lifreq   lifr;
        int             save_err;
        size_t          size;


        /* Make sure the given name is not NULL */
        if ((ifname == NULL)||(*ifname == '\0')) {
                errno = ENXIO;
                return (0);
        }

        /*
         * Fill up the interface name in the ioctl
         * request message. Make sure that the length of
         * the given interface name <= (IF_NAMESIZE-1)
         */
        size = strlen(ifname);
        if (size > (IF_NAMESIZE - 1)) {
                errno = EINVAL;
                return (0);
        }

        strncpy(lifr.lifr_name, ifname, size +1);

        /* Check the v4 interfaces first */
        s = socket(AF_INET, SOCK_DGRAM, 0);
        if (s >= 0) {
                if (ioctl(s, SIOCGLIFINDEX, (caddr_t)&lifr) >= 0) {
                        (void) close(s);
                        return (lifr.lifr_index);
                }
                (void) close(s);
        }

        /* Check the v6 interface list */
        s = socket(AF_INET6, SOCK_DGRAM, 0);
        if (s < 0)
                return (0);

        if (ioctl(s, SIOCGLIFINDEX, (caddr_t)&lifr) < 0)
                lifr.lifr_index = 0;

        save_err = errno;
        (void) close(s);
        errno = save_err;
        return (lifr.lifr_index);
}

/*
 * Given an index, this function returns the associated interface
 * name in the supplied buffer ifname.
 * Returns physical interface name if successful, NULL otherwise.
 * The interface name returned will be at most IF_NAMESIZE-1 bytes.
 */
char *
if_indextoname(uint32_t ifindex, char *ifname)
{
        int             n;
        int             s;
        char            *buf;
        uint32_t        index;
        struct lifnum   lifn;
        struct lifconf  lifc;
        struct lifreq   *lifrp;
        int             numifs;
        size_t          bufsize;
        boolean_t       found;
        uint_t          flags;

        flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES | LIFC_UNDER_IPMP;

        /* A interface index of 0 is invalid */
        if (ifindex == 0) {
                errno = ENXIO;
                return (NULL);
        }

        s = socket(AF_INET6, SOCK_DGRAM, 0);
        if (s < 0) {
                s = socket(AF_INET, SOCK_DGRAM, 0);
                if (s < 0) {
                        return (NULL);
                }
        }

        /* Prepare to send a SIOCGLIFNUM request message */
        lifn.lifn_family = AF_UNSPEC;
        lifn.lifn_flags = flags;
        if (ioctl(s, SIOCGLIFNUM, (char *)&lifn) < 0) {
                int save_err = errno;
                (void) close(s);
                errno = save_err;
                return (NULL);
        }

        /*
         * NOTE: "+ 10" sleaze mitigates new IP interfaces showing up between
         * the SIOCGLIFNUM and the SIOCGLIFCONF.
         */
        numifs = lifn.lifn_count + 10;

        /*
         * Provide enough buffer to obtain the interface
         * list from the kernel as response to a SIOCGLIFCONF
         * request
         */

        bufsize = numifs * sizeof (struct lifreq);
        buf = malloc(bufsize);
        if (buf == NULL) {
                int save_err = errno;
                (void) close(s);
                errno = save_err;
                return (NULL);
        }
        lifc.lifc_family = AF_UNSPEC;
        lifc.lifc_flags = flags;
        lifc.lifc_len = bufsize;
        lifc.lifc_buf = buf;
        if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0) {
                int save_err = errno;
                (void) close(s);
                errno = save_err;
                free(buf);
                return (NULL);
        }

        lifrp = lifc.lifc_req;
        found = B_FALSE;
        for (n = lifc.lifc_len / sizeof (struct lifreq); n > 0; n--, lifrp++) {
                /*
                 * Obtain the index value of each interface, and
                 * match to see if the retrived index value matches
                 * the given one. If so we return the corresponding
                 * device name of that interface.
                 */
                size_t  size;

                index = if_nametoindex(lifrp->lifr_name);
                if (index == 0)
                        /* Oops the interface just disappeared */
                        continue;
                if (index == ifindex) {
                        size = strcspn(lifrp->lifr_name,
                            (char *)IPIF_SEPARATOR_CHAR);
                        lifrp->lifr_name[size] = '\0';
                        found = B_TRUE;
                        (void) strncpy(ifname, lifrp->lifr_name, size + 1);
                        break;
                }
        }
        (void) close(s);
        free(buf);
        if (!found) {
                errno = ENXIO;
                return (NULL);
        }
        return (ifname);
}

/*
 * This function returns all the interface names and indexes
 */
struct if_nameindex *
if_nameindex(void)
{
        int             n;
        int             s;
        boolean_t       found;
        char            *buf;
        struct lifnum   lifn;
        struct lifconf  lifc;
        struct lifreq   *lifrp;
        int             numifs;
        int             index;
        int             i;
        int             physinterf_num;
        size_t          bufsize;
        struct if_nameindex      *interface_list;
        struct if_nameindex      *interface_entry;

        s = socket(AF_INET6, SOCK_DGRAM, 0);
        if (s < 0) {
                s = socket(AF_INET, SOCK_DGRAM, 0);
                if (s < 0)
                        return (NULL);
        }

        lifn.lifn_family = AF_UNSPEC;
        lifn.lifn_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES;
        if (ioctl(s, SIOCGLIFNUM, (char *)&lifn) < 0)
                return (NULL);
        numifs = lifn.lifn_count;

        bufsize = numifs * sizeof (struct lifreq);
        buf = malloc(bufsize);
        if (buf == NULL) {
                int save_err = errno;
                (void) close(s);
                errno = save_err;
                return (NULL);
        }
        lifc.lifc_family = AF_UNSPEC;
        lifc.lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES;
        lifc.lifc_len = bufsize;
        lifc.lifc_buf = buf;
        if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0) {
                int save_err = errno;
                (void) close(s);
                errno = save_err;
                free(buf);
                return (NULL);
        }

        lifrp = lifc.lifc_req;
        (void) close(s);

        /* Allocate the array of if_nameindex structure */
        interface_list = malloc((numifs + 1) * sizeof (struct if_nameindex));
        if (!interface_list) {
                int save_err = errno;
                free(buf);
                errno = save_err;
                return (NULL);
        }
        /*
         * Make sure that terminator structure automatically
         * happens to be all zeroes.
         */
        bzero(interface_list, ((numifs + 1) * sizeof (struct if_nameindex)));
        interface_entry = interface_list;
        physinterf_num = 0;
        for (n = numifs; n > 0; n--, lifrp++) {
                size_t  size;

                size = strcspn(lifrp->lifr_name, (char *)IPIF_SEPARATOR_CHAR);
                found = B_FALSE;
                /*
                 * Search the current array to see if this interface
                 * already exists. Only compare the physical name.
                 */
                for (i = 0; i < physinterf_num; i++) {
                        if (strncmp(interface_entry[i].if_name,
                            lifrp->lifr_name, size) == 0) {
                                found = B_TRUE;
                                break;
                        }
                }

                /* New one. Allocate an array element and fill it */
                if (!found) {
                        /*
                         * Obtain the index value for the interface
                         */
                        interface_entry[physinterf_num].if_index =
                            if_nametoindex(lifrp->lifr_name);

                        if (interface_entry[physinterf_num].if_index == 0) {
                                /* The interface went away. Skip this entry. */
                                continue;
                        }

                        /*
                         * Truncate the name to ensure that it represents
                         * a physical interface.
                         */
                        lifrp->lifr_name[size] = '\0';
                        if ((interface_entry[physinterf_num].if_name =
                            strdup(lifrp->lifr_name)) == NULL) {
                                int save_err;

                                if_freenameindex(interface_list);
                                save_err = errno;
                                free(buf);
                                errno = save_err;
                                return (NULL);
                        }

                        physinterf_num++;
                }
        }

        /* Create the last one of the array */
        interface_entry[physinterf_num].if_name = NULL;
        interface_entry[physinterf_num].if_index = 0;

        /* Free up the excess array space */
        free(buf);
        interface_list = realloc(interface_list, ((physinterf_num + 1) *
            sizeof (struct if_nameindex)));

        return (interface_list);
}

/*
 * This function frees the the array that is created while
 * the if_nameindex function.
 */
void
if_freenameindex(struct if_nameindex *ptr)
{
        struct if_nameindex *p;

        if (ptr == NULL)
                return;

        /* First free the if_name member in each array element */
        for (p = ptr; p->if_name != NULL; p++)
                free(p->if_name);

        /* Now free up the array space */
        free(ptr);
}