Under Construction

Abfragen aller IP-Adressen eines Netzwerk-Interfaces (SIOCGIFADDRS)

Mit dem ioctl(2) SIOCGIFADDR kann die primäre IP-Adresse eines Netzwerk-Interfaces abgefragt werden. Benötigt man alle konfigurierten IP-Adressen eines Netzwerk-Interfaces kann dazu der ioctl(2) SIOCGIFADDRS verwendet werden. Die IP-Adressen werden in der Struktur ifreqaddrs aus /usr/include/net/if.h abgelegt:

struct  ifreqaddrs {
        char    ifr_name[IFNAMSIZ];     /* if name, e.g. "en0" */
        int     ifaddrs;                /* returned number of address on if */
        int     naddrs;                 /* number of addresses returned in req*/
        union {
                struct sockaddr_in      addr_in;
                struct sockaddr_in6     addr_in6;
        } ifrasu[1];
};

Wie bei den meisten anderen Request-Strukturen muss der Name des Netzwerk-Interfaces vor Aufruf des ioctl(2) in ifr_name abgelegt werden. Die IP-Adressen werden im Array ifrasu zurückgegeben. Allerdings sieht die Struktur nur ein Array Element vor. Damit alle IP-Adressen zurückgegeben werden können, muss der Aufrufer des ioctl(2) sicher stellen, dass genügend Platz zur Verfügung steht, pro Adresse wird die Größe einer sockaddr_in6 Struktur (diese ist mit 28 Bytes Größer als die sockaddr_in Struktur mit 16 Bytes) benötigt. Der Speicher kann dann dynamisch alloziert werden, z.B. für 5 IP-Adressen:

struct ifreqaddrs* req = (struct ifreqaddrs*)malloc( sizeof(struct ifreqaddrs) + 4*sizeof(struct sockaddr_in6) );

Hinweis: Da in ifreqaddrs standardmäßig Platz für 1 IP-Adresse ist, wird nur Speicher für 4 weitere IP-Adressen benötigt.

Damit der System-Call ioctl(2) weiß für wieviele IP-Adressen Platz in der Struktur vorhanden ist, muss die Anzahl der IP-Adressen für die Speicher reserviert wurde, angegeben werden. Dies geschieht über Setzen des Feldes naddrs:

req->naddrs = 5;

Es werden dann nicht mehr als 5 IP-Adressen zurückgegeben. Wieviele IP-Adressen das Netzwerk-Interface insgesamt besitzt, wird über das Feld ifaddrs zurückgegeben. Der System-Call ioctl(2) überschreibt außerdem das Feld naddrs und liefert zurück wieviele IP-Adressen in der Struktur zurückgeliefert wurden. Das können in diesem Fall dann höchstens 5 IP-Adressen sein.

Erwartet man das ein Netzwerk-Interface mehrere IP-Adressen besitzt, könnte man natürlich mit einer entsprechend großen Struktur für z.B. 20 IP-Adressen starten. In den meisten Fällen wird dies mehr als ausreichend sein. Da es aber in einzelnen Fällen auch einmal deutlich mehr IP-Adressen sein können, empfiehlt sich die folgende Strategie:

    1. Zuerst wird der ioctl(2) mit der Standard-Größe der Struktur gestartet (1 IP-Adresse).
    2. Der erste Aufruf liefert über ifaddrs zurück wieviele IP-Adressen das Netzwerk-Interface besitzt.
    3. Besitzt das Interface mehr als eine IP-Adresse, wird dynamisch entsprechend viel Speicher für alle IP-Adressen des Interfaces alloziert und der ioctl(2) einfach noch einmal wiederholt.

Nachfolgend ist dies in einem kleinen C-Programm gezeigt, welches alle IP-Adressen eines angegebenen Netzwerk-Interfaces ausgibt:

$ cat get_ips.c
/*
* Copyright (c) 2021 by PowerCampus 01 GmbH
*/
#include <sys/ioctl.h>     // SIOCGIFADDRS
#include <sys/socket.h>    // socket()
#include <netinet/in.h>    // sockaddr, sockaddr_in
#include <net/if.h>        // ifreqaddrs
#include <arpa/inet.h>     // inet_ntoa()
#include <errno.h>         // errno, perror()
#include <unistd.h>        // ioctl(), close()
#include <stdio.h>         // fprintf()
#include <stdlib.h>        // exit(), malloc()
#include <string.h>        // strncpy()

int main( int argc , char** argv )
{
        int sd;
        int ret;
        struct sockaddr_in* addr;
        struct ifreqaddrs* req;

        // 1. Provide buffer for the request with space for a single IP.
        struct ifreqaddrs req1;
        req1.naddrs = 1; // we have space for one IP address
        req = &req1;

        // 2. Store interface name in ifr_name.
        strncpy(req->ifr_name,argv[1],IFNAMSIZ);

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

        // 4. Call ioctl with command SIOCGIFADDRS.
        ret = ioctl(sd,SIOCGIFADDRS,req);
        if ( ret == -1 )
        {
                perror("get_ips");
                exit(errno);
        }

        // 5. Check whether there is more than one IP address.
        if ( req1.ifaddrs > 1 )
        {

                // 5.1. dynamically allocate space for up to ifaddrs IPs.

                req = (struct ifreqaddrs*)malloc(sizeof(struct ifreqaddrs)+(req->ifaddrs-1)*sizeof(struct sockaddr_in6));
                if ( req == NULL )
                {
errno = ENOMEM;
                        perror("get_ips");
                        exit(errno);
                }
                req->naddrs = req1.ifaddrs;

                // 5.2. Store interface name in ifr_name.
                strncpy(req->ifr_name,argv[1],IFNAMSIZ);

                // 5.3. Call ioctl again with command SIOCGIFADDRS.
                ret = ioctl(sd,SIOCGIFADDRS,req);
                if ( ret == -1 )
                {
                        perror("get_ips");
                        exit(errno);
                }
        }

        // 6. Print all IP-Addresses.
        for ( int i = 0 ; i < req->naddrs ; ++i )
        {
                addr = (struct sockaddr_in*)&(req->ifrasu[i]);
                printf("IP: %s\n",inet_ntoa(addr->sin_addr));
        }

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

        // 8. If we have dynamically allocated memory, free the memory.
        if ( req->ifaddrs > 1 )
                free(req);

        return 0;
}

$

Das Programm lässt sich ohne Probleme kompilieren:

$ gcc -o get_ips get_ips.c
$

Am Beispiel des Netzwerk-Interfaces en1 sieht man das tatsächlich alle IP-Adressen ausgegeben werden:

$ get_ips en1
IP: 199.15.16.17
IP: 199.15.16.18
IP: 199.15.16.19
$