/* References:
 * https://lists.samba.org/archive/samba-technical/2009-February/063079.html
 * http://stackoverflow.com/questions/4139405/#4139811
 * https://github.com/steve-o/openpgm/blob/master/openpgm/pgm/getifaddrs.c
 */

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sockio.h>

#include "ifaddrs.h"

#define MAX(x,y) ((x)>(y)?(x):(y))
#define SIZE(p) MAX((p).ss_len,sizeof(p))


static struct sockaddr *
sa_dup (struct sockaddr_storage *sa1)
{
    struct sockaddr *sa2;
    size_t sz = sizeof(struct sockaddr_storage);
    sa2 = (struct sockaddr *) calloc(1,sz);
    memcpy(sa2,sa1,sz);
    return(sa2);
}


void freeifaddrs (struct ifaddrs *ifp)
{
    if (NULL == ifp) return;
    free(ifp->ifa_name);
    free(ifp->ifa_addr);
    free(ifp->ifa_netmask);
    free(ifp->ifa_dstaddr);
    freeifaddrs(ifp->ifa_next);
    free(ifp);
}


int getifaddrs (struct ifaddrs **ifap)
{
    int sd = -1;
    char *ccp, *ecp;
    struct lifconf ifc;
    struct lifreq *ifr;
    struct lifnum lifn;
    struct ifaddrs *cifa = NULL; /* current */
    struct ifaddrs *pifa = NULL; /* previous */
    const size_t IFREQSZ = sizeof(struct lifreq);

    sd = socket(AF_INET, SOCK_STREAM, 0);
    if (sd < 0)
        goto error;

    ifc.lifc_buf = NULL;
    *ifap = NULL;
    /* find how much memory to allocate for the SIOCGLIFCONF call */
    lifn.lifn_family = AF_UNSPEC;
    lifn.lifn_flags = 0;
    if (ioctl(sd, SIOCGLIFNUM, &lifn) < 0)
        goto error;

    /* Sun and Apple code likes to pad the interface count here in case interfaces
     * are coming up between calls */
    lifn.lifn_count += 4;

    ifc.lifc_family = AF_UNSPEC;
    ifc.lifc_len = lifn.lifn_count * sizeof(struct lifreq);
    ifc.lifc_buf = calloc(1, ifc.lifc_len);
    if (ioctl(sd, SIOCGLIFCONF, &ifc) < 0)
        goto error;

    ccp = (char *)ifc.lifc_req;
    ecp = ccp + ifc.lifc_len;

    while (ccp < ecp) {

        ifr = (struct lifreq *) ccp;
        cifa = (struct ifaddrs *) calloc(1, sizeof(struct ifaddrs));
        cifa->ifa_next = NULL;
        cifa->ifa_name = strdup(ifr->lifr_name);

        if (pifa == NULL) *ifap = cifa; /* first one */
        else pifa->ifa_next = cifa;

        if (ioctl(sd, SIOCGLIFADDR, ifr, IFREQSZ) < 0)
            goto error;
        cifa->ifa_addr = sa_dup(&ifr->lifr_addr);

        if (ioctl(sd, SIOCGLIFNETMASK, ifr, IFREQSZ) < 0)
            goto error;
        cifa->ifa_netmask = sa_dup(&ifr->lifr_addr);

        cifa->ifa_flags = 0;
        cifa->ifa_dstaddr = NULL;

        if (0 == ioctl(sd, SIOCGLIFFLAGS, ifr)) /* optional */
            cifa->ifa_flags = ifr->lifr_flags;

        if (ioctl(sd, SIOCGLIFDSTADDR, ifr, IFREQSZ) < 0) {
            if (0 == ioctl(sd, SIOCGLIFBRDADDR, ifr, IFREQSZ))
                cifa->ifa_dstaddr = sa_dup(&ifr->lifr_addr);
        }
        else cifa->ifa_dstaddr = sa_dup(&ifr->lifr_addr);

        pifa = cifa;
        ccp += IFREQSZ;
    }
    free(ifc.lifc_buf);
    close(sd);
    return 0;
error:
    if (ifc.lifc_buf != NULL)
        free(ifc.lifc_buf);
    if (sd != -1)
        close(sd);
    freeifaddrs(*ifap);
    return (-1);
}
