Under Construction

Konfigurations-Informationen für alle Netzwerk-Interfaces (SIOCGSIZIFCONF, SIOCGIFCONF)

In vielen Fällen ist eine schnelle Übersicht aller verfügbaren Netzwerk-Interfaces gewünscht. Hierfür gibt es den ioctl(2) SIOCGIFCONF, welcher über die Struktur ifconf Informationen für alle verfügbaren Netzwerk-Interfaces zurück gibt. Die Struktur ifconf ist in der Header-Datei /usr/include/net/if.h definiert:

/*
* Structure used in SIOCGIFCONF request.
* Used to retrieve interface configuration
* for machine (useful for programs which
* must know all networks accessible).
*/
struct  ifconf {
        int     ifc_len;                /* size of associated buffer */
        union {
                caddr_t ifcu_buf;
                struct  ifreq *ifcu_req;
        } ifc_ifcu;
#define ifc_buf ifc_ifcu.ifcu_buf       /* buffer address */
#define ifc_req ifc_ifcu.ifcu_req       /* array of structures returned */
};

Die Verwendung der Struktur ist nicht trivial. Das liegt im Wesentlichen daran das zur Compile-Zeit nicht bekannt ist, wieviele Netzwerk-Interfaces es gibt und damit auch nicht wieviele Bytes für das Speichern der Informationen für alle Netzwerk-Interfaces benötigt werden. Für die Benutzung der Struktur in einem ioctl(2) muss die Adresse eines Puffers in ifc_buf übergeben werden und die Größe des angegebenen Puffers (Anzahl Bytes) muss in ifc_len angegeben werden:

struct ifconf conf;
ifconf.ifc_buf = (caddr_t)malloc(512);
conf.ifc_len = 512;
ioctl(sd,SIOCGIFCONF,&conf);

Die minimal benötigte Größe für den Puffer kann über einen Aufruf von ioctl(2) mit dem Kommando SIOCGSIZIFCONF bestimmt werden:

struct ifconf conf;
int minSize;
ioctl(sd,SIOCGSIZIFCONF,&minSize);
ifconf.ifc_buf = (caddr_t)malloc(minSize);
conf.ifc_len = minSize;
ioctl(sd,SIOCGIFCONF,&conf);

Damit kann vorab immer die benötigte Puffer-Größe ermittelt werden. Nach Aufruf des ioctl(2) mit Kommando SIOCGIFCONF wird der Wert in ifc_len überschrieben und gibt an, wieviele Bytes in den Puffer (ifcu_buf) kopiert wurden. Wurde beim Aufruf des ioctl(2) die korrekte Größe des angegebenen Puffers in ifc_len eingetragen, dann ist ein Schreiben über das Ende des Puffers hinweg durch den ioctl(2) nicht möglich. Wenn der angegebene Puffer zu klein ist, werden einfach nicht alle Informationen zurückgeliefert.

Hinweis: Es werden aber immer vollständige ifreq Strukturen zurückgegeben, es wird also nicht inmitten einer solchen Struktur abgeschnitten!

Eine weitere Komplikation ergibt sich bei der Auswertung der zurückgelieferten Daten. Für jedes Netzwerk-Interface wird mindestens eine ifreq Struktur für den Link-Layer (Ethernet), sowie für jede konfigurierte IP-Adresse eine ifreq Struktur für Inet (IPv4) und, falls konfiguriert, für jede IPv6-Adresse eine ifreq Struktur für Inet6 (IPv6) zurückgegeben. Die Größen der Strukturen sind unterschiedlich! Im angegebenen Puffer ist damit kein Array von ifreq Strukturen gleicher Größe abgelegt, ein Array-Zugriff (ifc_req[idx]) oder ein Iterieren ist daher auf einfache Weise nicht möglich.

Schauen wir uns den relevanten Teil der Struktur ifreq aus /usr/include/net/if.h noch einmal an:

struct  ifreq {
#ifndef IFNAMSIZ        /* Also in net_if.h */
#define IFNAMSIZ        16
#endif
        char    ifr_name[IFNAMSIZ];             /* if name, e.g. "en0" */
        union {
                struct  sockaddr ifru_addr;

        } ifr_ifru;
};

Die Größe ergibt sich aus den 16 Bytes für den Interface-Namen (ifr_name, IFNAMSIZ) und der Größe der Struktur sockaddr. Allerdings steht in der Definition die generische Socket-Adreß Struktur sockaddr mit einer Größe von 16 Bytes. Verwendet werden aber dann die folgenden spezifischen Strukturen:

    • AF_INET  struct sockaddr_in (Größe 16 Bytes)   /usr/include/netinet/in.h
    • AF_INET6 struct sockaddr_in6 (Größe 28 Bytes)  /usr/include/netinet/in.h
    • AF_LINK  struct sockaddr_dl (Größe 128 Bytes)  /usr/include/net/if_dl.h

Unabhängig von der tatsächlichen spezifischen Socket-Adreß Struktur, besitzen alle ein 1 Byte Längenfeld (ifr_addr.sa_len) am Anfang der Struktur, welches die tatsächliche Länge der Struktur angibt. Ist req ein Zeiger auf eine ifreq Struktur im Puffer, dann kommt man durch Addition von IFNAMSIZE + ifr_addr.sa_len zum Beginn der nächsten ifreq Struktur:

struct ifreq* req = conf.ifc_req;
req += (struct ifreq*)((caddr_t)req + IFNAMSIZ + req->ifr_addr.sa_len);

Dies lässt sich in eine kleine for-Schleife verpacken, mit der man dann über alle ifreq Strukturen im Puffer unabhängig von der Größe iterieren kann:

for ( struct ifreq* req = conf.ifc_req ; \
          (caddr_t)req < conf.ifc_buf + conf.ifc_len ; \
          req = (struct ifreq*)((caddr_t)req+IFNAMSIZ+req->ifr_addr.sa_len) )
{

}

Nachfolgend sind alle genannten Punkte in einem kleinen Programm zusammengefasst, welches für jede gefundene ifreq Struktur den Netzwerk-Interface Namen (ifr_name), die Länge der ifreq (bzw. sockaddr) Struktur, die Adreß-Familie und die jeweilige Adresse (MAC, IP, IPv6) ausgibt:

$ cat get_ifconf.c
/*
* Copyright (c) 2021 by PowerCampus 01 GmbH
*/
#include <sys/ioctl.h>     // SIOCGSIZIFCONF, SIOCGIFCONF
#include <sys/socket.h>    // socket()
#include <netinet/in.h>    // sockaddr, sockaddr_in, sockaddr_in6
#include <net/if.h>        // ifconf
#include <net/if_dl.h>     // sockaddr_dl
#include <arpa/inet.h>     // inet_ntoa(), inet_ntop(), ether_ntoa()
#include <errno.h>         // errno, perror()
#include <unistd.h>        // ioctl(), close()
#include <stdio.h>         // fprintf(), printf()
#include <stdlib.h>        // exit(), malloc()

int main()
{
        int sd;
        int minSize;
        int ret;
        char ipv6[INET6_ADDRSTRLEN];
        struct sockaddr_in* addr;
        struct sockaddr_in6* addr6;
        struct sockaddr_dl* addrEth;

        // 1. Define variable of type struct ifconf.
        struct ifconf conf;

        // 2. Create datagram socket for address family Inet.
        sd = socket(AF_INET,SOCK_DGRAM,0);
        if ( sd == -1 )
        {
                perror("get_ifconf");
                exit(errno);
        }

        // 3. Use ioctl with command SIOCGSIZIFCONF to determine needed buffer size
        ret = ioctl(sd,SIOCGSIZIFCONF,&minSize);
        if ( ret == -1 )
        {
                perror("get_ifconf");
                exit(errno);
        }

        // 4. Allocate enough memory for the ioctl request.
        conf.ifc_buf = (caddr_t)malloc(minSize);
        if ( conf.ifc_buf == NULL )
        {
                fprintf(stderr,"get_ifconf: insufficient memory\n");
                exit(ENOMEM);
        }
        conf.ifc_len = minSize;

        // 5. Use ioctl with command SIOCGIFCONF to get the network informations
        ret = ioctl(sd,SIOCGIFCONF,&conf);
        if ( ret == -1 )
        {
                perror("get_ifconf");
                exit(errno);
        }

        // 6. Print network informations
        for ( struct ifreq* req = conf.ifc_req ; \
                (caddr_t)req < conf.ifc_buf + conf.ifc_len ; \
                req = (struct ifreq*)((caddr_t)req+IFNAMSIZ+req->ifr_addr.sa_len) )
        {
                printf("%s: len: %d family: %d", \
                        req->ifr_name, \
                        (unsigned int)req->ifr_addr.sa_len, \
                        (unsigned int)req->ifr_addr.sa_family);
                if ( req->ifr_addr.sa_family == AF_INET )
                {
                        addr = (struct sockaddr_in*)&(req->ifr_addr);
                        printf(" IP: %s",inet_ntoa(addr->sin_addr));
                }
                if ( req->ifr_addr.sa_family == AF_INET6 )
                {
                        addr6 = (struct sockaddr_in6*)&(req->ifr_addr);
                        printf(" IPv6: %s",inet_ntop(AF_INET6,&(addr6->sin6_addr),ipv6,INET6_ADDRSTRLEN));
                }
                if ( req->ifr_addr.sa_family == AF_LINK )
                {
                        addrEth = (struct sockaddr_dl*)&(req->ifr_addr);
                        printf(" MAC: %s",ether_ntoa((struct ether_addr*)(&(addrEth->sdl_data[addrEth->sdl_nlen]))));
                }
                printf("\n");
        }

        // 7. Free memory
        free(conf.ifc_buf);

        // 8. Close socket descriptor
        close(sd);

        return 0;
}

-bash-5.1$

Das Programm kann mit einem C-Compiler übersetzt und anschließend ohne Argumente gestartet werden:

$ gcc -o get_ifconf get_ifconf.c
$ get_ifconf
en0: len: 128 family: 18 MAC: 32:7e:ba:40:74:5
en0: len: 16 family: 2 IP: 192.168.170.250
en1: len: 128 family: 18 MAC: 32:7e:ba:40:74:6
en1: len: 16 family: 2 IP: 199.15.16.17
en1: len: 16 family: 2 IP: 199.15.16.18
en1: len: 16 family: 2 IP: 199.15.16.19
lo0: len: 128 family: 18 MAC: 0:0:0:0:0:0
lo0: len: 16 family: 2 IP: 127.0.0.1
lo0: len: 28 family: 24 IPv6: ::1
$

In der Beispiel-Ausgabe kann man ganz gut die unterschiedlichen Typen (family AF_INET=2AF_LINK=18 und AF_INET6=24) und Längen der Socket-Adressen sehen. Auf dem Netzwerk-Interface en1 sind 3 IP-Adressen konfiguriert. Das Loopback-Interface lo0 besitzt neben einer IP-Adresse auch eine IPv6 Adresse.