/* sml3_nw_if.c: Funktionen fuer NW-Schnittstellen */

/* Copyright 2012-2017 Kurt Nienhaus
 *
 * This file is part of libsammel3.
 * libsammel3 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * libsammel3 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with libsammel3.  If not, see <http://www.gnu.org/licenses/>.
 */

/* Get network interface configurations
 * inspired by: UNIX Network Programming Vol. 1, 2nd Ed.
 *              W. Richard Stevens
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include "config.h"
#include "sml3_fehler.h"
#include "sml3_util.h"
#include "sml3_nw_support.h"
#include "sml3_nw_if.h"
#ifdef SML3_HAVE_SYS_SOCKIO_H
# include <sys/sockio.h>
#endif
#ifdef SML3_HAVE_STRUCT_IFADDRS
# include <ifaddrs.h>
#endif
#ifdef SML3_HAVE_NET_IF_DL_H
# include <net/if_dl.h>
#endif

struct SML3_nw_if * SML3_nw_if_get(int, int);
void SML3_nw_if_free(struct SML3_nw_if *);

static struct SML3_nw_if * getaddr(int, int, int);
static void gethwaddr(struct SML3_nw_if *, int);
static int netmask2cidr(int, const char *);


/* SML3_nw_if_get [thread-sicher]:
 * Netzwerk-Schnittstellen erhalten
 * 1.Arg: family: 4 (=IPv4) oder 6 (=IPv6) oder 0 (=alles)
 * 2.Arg: 0 = ohne Aliase oder 1 = mit Aliasen
 * Rueckgabe: verkettete Liste mit Schnittstellen
 *            oder NULL:
 *             - SML3_fehlererrno() = 0: keine Schnittstellen
 *             - ansonsten: Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                 ENOMEM = Allokationsfehler
 *                 ENOTCONN = Socket nicht erhalten
 *                 ENXIO = Fehler Erhalt NW-Schnittstellen
 */
struct SML3_nw_if *
SML3_nw_if_get(int family, int doaliases)
{
  struct SML3_nw_if *ifihead;
#ifdef SML3_HAVE_NAMEINDEX
  struct if_nameindex *ifnidx;
#endif
  int sockfd;

  if (family != 4
#ifdef AF_INET6
    && family != 6
#endif
    && family != 0
  ) {
    SML3_fehlernew(EINVAL, "family invalid: %d\n", family);
    return NULL;
  }

  if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0
#ifdef AF_INET6
    && (sockfd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0
#endif
  ) {
    SML3_fehlernew(ENOTCONN, "socket: %s", SML3_strerror(errno));
    return NULL;
  }

  /* NW-Schnittstellen erhalten */
  ifihead = getaddr(family, doaliases, sockfd);
  if (ifihead == NULL && SML3_fehlererrno() != 0) { return NULL; }

  /* nicht konfigurierte NW-Schnittstellen erhalten */
#ifdef SML3_HAVE_NAMEINDEX
  ifnidx = if_nameindex();
  if (ifnidx != NULL) {
    struct SML3_nw_if *ifiptr, *ifihead2 ,*ifi2, **ifipnext2;
    int inr;

    ifihead2 = NULL;
    ifipnext2 = &ifihead2;
    for (inr = 0; (ifnidx[inr].if_index > 0 && ifnidx[inr].if_name != NULL); inr++) {
      for (ifiptr = ifihead; ifiptr != NULL; ifiptr = ifiptr->next) {
        if (ifiptr->index > 0 && ifnidx[inr].if_index == (unsigned int)ifiptr->index) { break; }
        if (strcmp(ifnidx[inr].if_name, ifiptr->name) == 0) { break; }
      }
      if (ifiptr == NULL) {  /* hinzufuegen */
        if ((ifi2 = calloc(1, sizeof(struct SML3_nw_if))) == NULL) {
          SML3_fehlernew(ENOMEM, "calloc: %s", SML3_strerror(errno));
          SML3_nw_if_free(ifihead);
          SML3_nw_if_free(ifihead2);
          if_freenameindex(ifnidx);
          return NULL;
        }
        *ifipnext2 = ifi2;
        ifipnext2 = &ifi2->next;
        snprintf(ifi2->name, sizeof(ifi2->name), "%s", ifnidx[inr].if_name);
        ifi2->index = (short)ifnidx[inr].if_index;
      }
    }

    if (ifihead2 != NULL) {
      for (ifipnext2 = &ifihead; *ifipnext2 != NULL; ifipnext2 = &(*ifipnext2)->next) {;}
      *ifipnext2 = ifihead2;
    }
    if_freenameindex(ifnidx);
  }
#endif

  /* Hardware-Adresse */
  gethwaddr(ifihead, sockfd);

  close(sockfd);

  return ifihead;
} /* Ende SML3_nw_if_get */


#ifdef SML3_HAVE_STRUCT_IFADDRS
static struct SML3_nw_if *
getaddr(int family, int doaliases, int sockfd)
{
  struct SML3_nw_if *ifihead ,*ifi, **ifipnext;
  struct sockaddr_in *sinptr;
  int faml, lastfaml, obalias;
  char lastname[sizeof(((struct SML3_nw_if *)0)->name)], *zptr;
  struct ifaddrs *ifas, *ifap;
  struct ifreq ifrb;
# ifdef AF_INET6
  struct sockaddr_in6 *sin6ptr;
# endif

  *lastname = '\0'; lastfaml = 0;
  ifihead = NULL;
  ifipnext = &ifihead;

  if (getifaddrs(&ifas) < 0) {
    SML3_fehlernew(ENXIO, "getifaddrs: %s", SML3_strerror(errno));
    return NULL;
  }

  for (ifap = ifas; ifap != NULL; ifap = ifap->ifa_next) {
    if (ifap->ifa_name == NULL) { continue; }

    if (ifap->ifa_addr != NULL) {
      if (ifap->ifa_addr->sa_family == AF_INET) {
        faml = 4;
# ifdef AF_INET6
      } else if (ifap->ifa_addr->sa_family == AF_INET6) {
        faml = 6;
# endif
      } else {
        continue;
      }
    } else {
      faml = 0;
    }
    if (family != 0 && faml != family) { continue; }

    /* ob Alias-Adresse */
    obalias = 0;
    if ((zptr = strchr(ifap->ifa_name, ':')) != NULL) { *zptr = '\0'; }  /* z.B. eth0:0 */
    if (strncmp(lastname, ifap->ifa_name, IFNAMSIZ) == 0 && lastfaml == faml) {
      if (!doaliases) { continue; }
      obalias = IFI_ALIAS;
    }
    memmove(lastname, ifap->ifa_name, IFNAMSIZ);
    lastfaml = faml;
    if (zptr != NULL) { *zptr = ':'; }

    /* Rueckgabe-struct anlegen */
    if ((ifi = calloc(1, sizeof(struct SML3_nw_if))) == NULL) {
      SML3_fehlernew(ENOMEM, "calloc: %s", SML3_strerror(errno));
      SML3_nw_if_free(ifihead);
      freeifaddrs(ifas);
      return NULL;
    }
    *ifipnext = ifi;
    ifipnext = &ifi->next;

    /* Rueckgabe-struct fuellen */

    ifi->family = faml;
    ifi->alias = obalias;
    snprintf(ifi->name, sizeof(ifi->name), "%s", ifap->ifa_name);

    /* Interface-Index */
    ifi->index = 0;
# ifdef SML3_HAVE_NAMEINDEX
    ifi->index = if_nametoindex(ifi->name);
# endif
# ifdef SIOCGIFINDEX
    if (ifi->index == 0) {
      memmove(ifrb.ifr_name, ifi->name, IFNAMSIZ);
      if (ioctl(sockfd, SIOCGIFINDEX, &ifrb) >= 0) {
#  ifdef SML3_HAVE_STRUCT_IFREQ_IFR_IFINDEX 
        ifi->index = ifrb.ifr_ifindex;
#  elif defined(SML3_HAVE_STRUCT_IFREQ_IFR_INDEX)
        ifi->index = ifrb.ifr_index;
#  endif
      }
    }
# endif

    /* IFF_* Konstanten aus <net/if.h> */
    ifi->flags = ifap->ifa_flags;

    /* Netzwerk-Adresse */
    if (ifi->family == 4) {
      sinptr = (struct sockaddr_in *)ifap->ifa_addr;
      if (sinptr != NULL) {
        SML3_inet_ntop(AF_INET, &sinptr->sin_addr, ifi->nwaddr, sizeof(ifi->nwaddr));
      }
# ifdef AF_INET6
    } else if (ifi->family == 6) {
      sin6ptr = (struct sockaddr_in6 *)ifap->ifa_addr;
      if (sin6ptr != NULL) {
        SML3_inet_ntop(AF_INET6, &sin6ptr->sin6_addr, ifi->nwaddr, sizeof(ifi->nwaddr));
      }
# endif
    }

    /* Broadcast-Adresse */
    if (ifi->flags & IFF_BROADCAST) {
      if (ifi->family == 4) {
        sinptr = (struct sockaddr_in *)ifap->ifa_broadaddr;
        if (sinptr != NULL) {
          SML3_inet_ntop(AF_INET, &sinptr->sin_addr, ifi->brdaddr, sizeof(ifi->brdaddr));
        }
      }
    }

    /* Ziel-Adresse */
    if (ifi->flags & IFF_POINTOPOINT) {
      if (ifi->family == 4) {
        sinptr = (struct sockaddr_in *)ifap->ifa_dstaddr;
        if (sinptr != NULL) {
          SML3_inet_ntop(AF_INET, &sinptr->sin_addr, ifi->dstaddr, sizeof(ifi->dstaddr));
        }
# ifdef AF_INET6
      } else if (ifi->family == 6) {
        sin6ptr = (struct sockaddr_in6 *)ifap->ifa_dstaddr;
        if (sin6ptr != NULL) {
          SML3_inet_ntop(AF_INET6, &sin6ptr->sin6_addr, ifi->dstaddr, sizeof(ifi->dstaddr));
        }
# endif
      }
    }

    /* Netzmaske und CIDR */
    if (ifi->family == 4) {
      sinptr = (struct sockaddr_in *)ifap->ifa_netmask;
      if (sinptr != NULL) {
        SML3_inet_ntop(AF_INET, &sinptr->sin_addr, ifi->netmask, sizeof(ifi->netmask));
        ifi->cidr = netmask2cidr(ifi->family, ifi->netmask);
      }
# ifdef AF_INET6
    } else if (ifi->family == 6) {
      sin6ptr = (struct sockaddr_in6 *)ifap->ifa_netmask;
      if (sin6ptr != NULL) {
        SML3_inet_ntop(AF_INET6, &sin6ptr->sin6_addr, ifi->netmask, sizeof(ifi->netmask));
        ifi->cidr = netmask2cidr(ifi->family, ifi->netmask);
      }
# endif
    }

    /* MTU */
# if defined(SIOCGIFMTU) && defined(SML3_HAVE_STRUCT_IFREQ_IFR_MTU)
    memmove(ifrb.ifr_name, ifi->name, IFNAMSIZ);
    if (ioctl(sockfd, SIOCGIFMTU, &ifrb) >= 0) {
      ifi->mtu = ifrb.ifr_mtu;
    }
# endif
  }
  freeifaddrs(ifas);

  if (ifihead == NULL) { SML3_fehlernew(0, NULL); }
  return ifihead;
} /* getaddr */

#else /* !SML3_HAVE_STRUCT_IFADDRS */

static struct SML3_nw_if *
getaddr(int family, int doaliases, int sockfd)
{
  struct SML3_nw_if *ifihead ,*ifi, **ifipnext;
  int faml, lastfaml, obalias, iclen, lastlen, erg;
  char lastname[sizeof(((struct SML3_nw_if *)0)->name)], *zptr;
  struct sockaddr_in *sinptr;
  char *icbuf = NULL, *icptr;
  struct ifconf ifc;
  struct ifreq *ifrp = NULL;

  if (family == 0) { family = 4; }
  if (family != 4) { SML3_fehlernew(0, NULL); return NULL; }  /* nur IPv4 */
  *lastname = '\0'; lastfaml = 0;
  ifihead = NULL;
  ifipnext = &ifihead;

  /* get buffer size */
  lastlen = 0;
  iclen = 100 * sizeof(struct ifreq);  /* initial buffer size guess */
  for (;;) {
    if ((icbuf = malloc(iclen)) == NULL) {
      SML3_fehlernew(ENOMEM, "malloc: %s", SML3_strerror(errno));
      SML3_nw_if_free(ifihead);
      return NULL;
    }
    ifc.ifc_len = iclen;
    ifc.ifc_buf = icbuf;
    erg = ioctl(sockfd, SIOCGIFCONF, &ifc);
    if (erg < 0) {
      if (errno != EINVAL || lastlen != 0) {
        SML3_fehlernew(ENXIO, "ioctl SIOCGIFCONF: %s", SML3_strerror(errno));
        free(icbuf); SML3_nw_if_free(ifihead);
        return NULL;
      }
    } else {
      if (ifc.ifc_len == lastlen) { break; }  /* success, len has not changed */
      lastlen = ifc.ifc_len;
    }
    iclen += 10 * sizeof(struct ifreq);  /* increment */
    free(icbuf);
  }

  for (icptr = icbuf; icptr < icbuf + ifc.ifc_len;) {  /* all found interfaces */
# ifdef SML3_HAVE_SOCKADDR_SA_LEN
    /* length of each (struct ifreq *)ptr is individual,
     * so copying to a allocated ifr will always force it aligned
     */
    iclen = IFNAMSIZ + ((struct ifreq *)icptr)->ifr_addr.sa_len;
    if (iclen < (int)sizeof(struct ifreq)) { iclen = sizeof(struct ifreq); }
    if ((ifrp = (ifrp == NULL ? malloc(iclen) : realloc(ifrp, iclen))) == NULL) {
      SML3_fehlernew(ENOMEM, "malloc/realloc: %s", SML3_strerror(errno));
      free(icbuf); SML3_nw_if_free(ifihead);
      return NULL;
    }
    memmove(ifrp, icptr, iclen);
    icptr += iclen;  /* for next one in buffer */
# else /* !SML3_HAVE_SOCKADDR_SA_LEN */
    ifrp = (struct ifreq *)icptr;
    switch(ifrp->ifr_addr.sa_family) {
#  ifdef AF_INET6
      case AF_INET6:
        iclen = (int)sizeof(struct sockaddr_in6);
        break;
#  endif
      case AF_INET:
      default:
        iclen = (int)sizeof(struct sockaddr);
        break;
    }
    if ((iclen += IFNAMSIZ)< (int)sizeof(struct ifreq)) { iclen = sizeof(struct ifreq); }
    icptr += iclen;  /* for next one in buffer */
# endif /* !SML3_HAVE_SOCKADDR_SA_LEN */

    if (ifrp->ifr_addr.sa_family == AF_INET) {
      faml = 4;
# ifdef AF_INET6
    } else if (ifrp->ifr_addr.sa_family == AF_INET6) {
      faml = 6;
# endif
    } else {
      continue;
    }
    if (family != 0 && faml != family) { continue; }

    /* ob Alias-Adresse */
    obalias = 0;
    if ((zptr = strchr(ifrp->ifr_name, ':')) != NULL) { *zptr='\0'; }  /* z.B. eth0:0 */
    if (strncmp(lastname, ifrp->ifr_name, IFNAMSIZ) == 0 && lastfaml == faml) {
      if (!doaliases) { continue; }
      obalias = IFI_ALIAS;
    }
    memmove(lastname, ifrp->ifr_name, IFNAMSIZ);
    lastfaml = faml;
    if (zptr != NULL) { *zptr = ':'; }

    /* Rueckgabe-struct anlegen */
    if ((ifi = calloc(1, sizeof(struct SML3_nw_if))) == NULL) {
      SML3_fehlernew(ENOMEM, "calloc: %s", SML3_strerror(errno));
      free(icbuf); SML3_nw_if_free(ifihead);
# ifdef SML3_HAVE_SOCKADDR_SA_LEN
      if (ifrp != NULL) { free(ifrp); }
# endif
      return NULL;
    }
    *ifipnext = ifi;
    ifipnext = &ifi->next;

    ifi->family = faml;
    ifi->alias = obalias;
    memmove(ifi->name, ifrp->ifr_name, IFNAMSIZ); ifi->name[IFNAMSIZ] = '\0';

    /* IFF_* Konstanten aus <net/if.h> */
    memmove(ifrp->ifr_name, ifi->name, IFNAMSIZ);
    if (ioctl(sockfd, SIOCGIFFLAGS, ifrp) == -1) {
      SML3_fehlernew(ENXIO, "ioctl SIOCGIFFLAGS: %s", SML3_strerror(errno));
      free(icbuf); SML3_nw_if_free(ifihead);
# ifdef SML3_HAVE_SOCKADDR_SA_LEN
      if (ifrp != NULL) { free(ifrp); }
# endif
      return NULL;
    }
    ifi->flags = ifrp->ifr_flags;

    /* MTU */
    ifi->mtu = 0;
# if defined(SIOCGIFMTU) && defined(SML3_HAVE_STRUCT_IFREQ_IFR_MTU)
    memmove(ifrp->ifr_name, ifi->name, IFNAMSIZ);
    if (ioctl(sockfd, SIOCGIFMTU, ifrp) >= 0) {
      ifi->mtu = ifrp->ifr_mtu;
    }
# endif

    /* Interface-Index */
    ifi->index = 0;
# ifdef SML3_HAVE_NAMEINDEX
    ifi->index = if_nametoindex(ifi->name);
# endif
# ifdef SIOCGIFINDEX
    if (ifi->index == 0) {
      memmove(ifrp->ifr_name, ifi->name, IFNAMSIZ);
      if (ioctl(sockfd, SIOCGIFINDEX, ifrp) >= 0) {
#  ifdef SML3_HAVE_STRUCT_IFREQ_IFR_IFINDEX 
        ifi->index = ifrp->ifr_ifindex;
#  elif defined(SML3_HAVE_STRUCT_IFREQ_IFR_INDEX)
        ifi->index = ifrp->ifr_index;
#  endif
      }
    }
# endif

    /* restliche Informationen je nach family setzen */
    switch(ifi->family) {

      case 4:

        /* Netzwerk-Adresse */
        memmove(ifrp->ifr_name, ifi->name, IFNAMSIZ);
        if (ioctl(sockfd, SIOCGIFADDR, ifrp) == -1) {
          SML3_fehlernew(ENXIO, "ioctl SIOCGIFADDR: %s",SML3_strerror(errno));
          free(icbuf); SML3_nw_if_free(ifihead);
# ifdef SML3_HAVE_SOCKADDR_SA_LEN
          if (ifrp != NULL) { free(ifrp); }
# endif
          return NULL;
        }
        sinptr = (struct sockaddr_in *)&ifrp->ifr_addr;
        if (sinptr != NULL) {
          SML3_inet_ntop(AF_INET, &sinptr->sin_addr, ifi->nwaddr, sizeof(ifi->nwaddr));
        }

# ifdef SIOCGIFBRDADDR
        /* Broadcast-Adresse */
        if (ifi->flags & IFF_BROADCAST) {
          memmove(ifrp->ifr_name, ifi->name, IFNAMSIZ);
          if (ioctl(sockfd, SIOCGIFBRDADDR, ifrp) == -1) {
            SML3_fehlernew(ENXIO, "ioctl SIOCGIFBRDADDR: %s",SML3_strerror(errno));
            free(icbuf); SML3_nw_if_free(ifihead);
# ifdef SML3_HAVE_SOCKADDR_SA_LEN
            if (ifrp != NULL) { free(ifrp); }
# endif
            return NULL;
          }
          sinptr = (struct sockaddr_in *)&ifrp->ifr_broadaddr;
          if (sinptr != NULL) {
            SML3_inet_ntop(AF_INET, &sinptr->sin_addr, ifi->brdaddr, sizeof(ifi->brdaddr));
          }
        }
# endif

# ifdef SIOCGIFDSTADDR
        /* Ziel-Adresse */
        if (ifi->flags & IFF_POINTOPOINT) {
          memmove(ifrp->ifr_name, ifi->name, IFNAMSIZ);
          if (ioctl(sockfd, SIOCGIFDSTADDR, ifrp) == -1) {
            SML3_fehlernew(ENXIO, "ioctl SIOCGIFDSTADDR: %s", SML3_strerror(errno));
            free(icbuf); SML3_nw_if_free(ifihead);
# ifdef SML3_HAVE_SOCKADDR_SA_LEN
            if (ifrp != NULL) { free(ifrp); }
# endif
            return NULL;
          }
          sinptr = (struct sockaddr_in *)&ifrp->ifr_dstaddr;
          if (sinptr != NULL) {
            SML3_inet_ntop(AF_INET, &sinptr->sin_addr, ifi->dstaddr, sizeof(ifi->dstaddr));
          }
        }
# endif

# ifdef SIOCGIFNETMASK
        /* Netzmaske */
        memmove(ifrp->ifr_name, ifi->name, IFNAMSIZ);
        if (ioctl(sockfd, SIOCGIFNETMASK, ifrp) >= 0) {
          sinptr = (struct sockaddr_in *)&ifrp->ifr_addr;
          if (sinptr != NULL) {
            SML3_inet_ntop(AF_INET, &sinptr->sin_addr, ifi->netmask, sizeof(ifi->netmask));
            ifi->cidr = netmask2cidr(4, ifi->netmask);
          }
        }
# endif
        break;

      case 6:
        break;
    }
  }

  if (icbuf != NULL) { free(icbuf); }
# ifdef SML3_HAVE_SOCKADDR_SA_LEN
  if (ifrp != NULL) { free(ifrp); }
# endif

  if (ifihead == NULL) { SML3_fehlernew(0, NULL); }
  return ifihead;
} /* getaddr */
#endif /* !SML3_HAVE_STRUCT_IFADDRS */


/* Hardware-Adresse */
static void
gethwaddr(struct SML3_nw_if *ifihead, int sockfd)
{
#if defined(SIOCGIFHWADDR) && (defined(SML3_HAVE_STRUCT_IFREQ_IFR_HWADDR) || defined(SML3_HAVE_STRUCT_IFREQ_IFR_ENADDR))
  struct ifreq ifrb;
  struct SML3_nw_if *ifi;
  int ipos, imax;

  if (ifihead == NULL) { return; }

  imax = 6;
  if (imax > SML3_NW_IF_HALEN) { imax = SML3_NW_IF_HALEN; }

  for (ifi = ifihead; ifi != NULL; ifi = ifi->next) {
    memmove(ifrb.ifr_name, ifi->name, IFNAMSIZ);
    if (ioctl(sockfd, SIOCGIFHWADDR, &ifrb) >= 0) {
      for (ipos = 0; ipos < imax; ipos++) {
# ifdef SML3_HAVE_STRUCT_IFREQ_IFR_HWADDR
        sprintf(ifi->hwaddr + (ipos * 3), "%.2x", (int)(unsigned char)ifrb.ifr_hwaddr.sa_data[ipos]); 
# endif
# ifdef SML3_HAVE_STRUCT_IFREQ_IFR_ENADDR
        sprintf(ifi->hwaddr + (ipos * 3), "%.2x", (int)(unsigned char)ifrb.ifr_enaddr[ipos]);
# endif
        ifi->hwaddr[(ipos * 3) + 2] = ':';
      }
      ifi->hwaddr[((imax - 1) * 3) + 2] = '\0';
    }
  }

#elif defined(SML3_HAVE_NET_IF_DL_H)
  char *icbuf = NULL, *icptr;
  struct ifconf ifc;
  struct ifreq *ifrp = NULL;
  int iclen, lastlen, erg;

  /* get buffer size */
  lastlen = 0;
  iclen = 100 * sizeof(struct ifreq);  /* initial buffer size guess */
  for (;;) {
    if ((icbuf = malloc(iclen)) == NULL) { return; }
    ifc.ifc_len = iclen;
    ifc.ifc_buf = icbuf;
    erg = ioctl(sockfd, SIOCGIFCONF, &ifc);
    if (erg < 0) {
      if (errno != EINVAL || lastlen != 0) { free(icbuf); return; }
    } else {
      if (ifc.ifc_len == lastlen) { break; }  /* success, len has not changed */
      lastlen = ifc.ifc_len;
    }
    iclen += 10 * sizeof(struct ifreq);  /* increment */
    free(icbuf);
  }

  for (icptr = icbuf; icptr < icbuf + ifc.ifc_len;) {  /* all found interfaces */
# ifdef SML3_HAVE_SOCKADDR_SA_LEN
    /* length of each (struct ifreq *)ptr is individual,
     * so copying to a allocated ifr will always force it aligned
     */
    iclen = IFNAMSIZ + ((struct ifreq *)icptr)->ifr_addr.sa_len;
    if (iclen < (int)sizeof(struct ifreq)) { iclen = sizeof(struct ifreq); }
    if ((ifrp = (ifrp == NULL ? malloc(iclen) : realloc(ifrp, iclen))) == NULL) { return; }
    memmove(ifrp, icptr, iclen);
    icptr += iclen;  /* for next one in buffer */
# else /* !SML3_HAVE_SOCKADDR_SA_LEN */
    ifrp = (struct ifreq *)icptr;
    switch(ifrp->ifr_addr.sa_family) {
#  ifdef AF_INET6
      case AF_INET6:
        iclen = (int)sizeof(struct sockaddr_in6);
        break;
#  endif
      case AF_INET:
      default:
        iclen = (int)sizeof(struct sockaddr);
        break;
    }
    if ((iclen += IFNAMSIZ)< (int)sizeof(struct ifreq)) { iclen = sizeof(struct ifreq); }
    icptr += iclen;  /* for next one in buffer */
# endif /* !SML3_HAVE_SOCKADDR_SA_LEN */

    /* assumes that AF_LINK precedes AF_INET or AF_INET6 */
    if (ifrp->ifr_addr.sa_family == AF_LINK) {
      struct sockaddr_dl *sdl = (struct sockaddr_dl *)&ifrp->ifr_addr;
      struct SML3_nw_if *ifi;
      for (ifi = ifihead; ifi != NULL; ifi = ifi->next) {
        if (ifi->index == 0) {
          if (strncmp(ifrp->ifr_name, ifi->name, IFNAMSIZ) == 0) {
            ifi->index = sdl->sdl_index;
          }
        }
        if (ifi->index == sdl->sdl_index) {
          int imax = sdl->sdl_alen;
          if (imax > SML3_NW_IF_HALEN) { imax = SML3_NW_IF_HALEN; }
          memmove(ifi->hwaddr, sdl->sdl_data + sdl->sdl_nlen, imax);
        }
      }
    }
  }
  if (icbuf != NULL) { free(icbuf); }
# ifdef SML3_HAVE_SOCKADDR_SA_LEN
  if (ifrp != NULL) { free(ifrp); }
# endif

#else
  ;
#endif
} /* Ende gethwaddr */


/* gibt CIDR aus Netzmaske zurueck */
static int
netmask2cidr(int family, const char *netmask)
{
  const char *p1, *p2;
  int nr, i1, i2, bm;

  bm = 0;
  if (family == 4) {
    p1 = netmask;
    for (i1 = 4; i1 > 0; i1--) {
      if ((p2 = strchr(p1, '.')) == NULL && i1 > 1) { return 0; }
      nr = atoi(p1);
      for (i2 = 7; i2 >= 0; i2--) {
        if ((nr & (1 << i2)) == 0) { return bm; }
        bm++;
      }
      if (p2 != NULL) { p1 = p2 + 1; }
    }
  } else if (family == 6) {
    p1 = netmask;
    for (i1 = 8; i1 > 0; i1--) {
      if ((p2 = strchr(p1, ':')) == NULL && i1 > 1) { return 0; }
      nr = (int)strtol(p1, NULL, 16);
      for (i2 = 15; i2 >= 0; i2--) {
        if ((nr & (1 << i2)) == 0) { return bm; }
        bm++;
      }
      if (p2 != NULL) { p1 = p2 + 1; }
    }
  }
  return bm;
} /* Ende netmask2cidr */


/* SML3_nw_if_free [thread-sicher]:
 * gibt 1.Arg frei
 * 1.Arg: mit SML3_nw_if_get() erhaltene Schnittstellenliste
 */
void
SML3_nw_if_free(struct SML3_nw_if *ifihead) {
  struct SML3_nw_if *ifi, *ifinext;

  for (ifi = ifihead; ifi != NULL; ifi = ifinext) {
    ifinext = ifi->next;
    free(ifi);
  }
} /* Ende SML3_nw_if_free */
