Under Construction

C++: Vereinfachung der ifconf-Nutzung durch eine Klasse

Wird C++ als Programmiersprache verwendet, bietet es sich an die Nutzung der ifconf Struktur in eine kleine Klasse zu verpacken. Die Klasse könnte dann die etwas umständliche Iteration über eigene Iteratoren anbieten. Die beiden Aufrufe von ioctl(2) für die Abfrage der Größe und der eigentlichen Daten könnten in eine eigene Methode verpackt werden, welche dann auch die Allozierung des notwendigen Speichers übernehmen kann.

Wir stellen ein rudimentäres Beispiel einer solchen Klasse vor. Als Klassennamen haben wir IfConf verwendet. Die Klasse sollte die folgenden Methoden für die Iteration und die ioctl(2) Aufrufe haben:

class IfConf
{
public :

        IfConf();

        ~IfConf();

        iterator begin();

        iterator end();

        int ioctl( int sd );

private :

        ifconf conf;

        int ifreqBufferSize;
};

Die Struktur ifconf ist als Member Variable deklariert, man könnte auch überlegen diese als Elternklasse zu verwenden. In der Variablen ifreqBufferSize wird die Größe des aktuell allozierten Speichers für die ifreq Strukturen abgespeichert.

Wir starten mit dem Konstruktor IfConf(). Da ohne einen Aufruf von ioctl(2) mit SIOCGSIZIFCONF nicht bekannt ist, wieviel Speicher mindestens benötigt wird, starten wir erst einmal mit keiner Speicher Allozierung. Damit ergibt sich der folgende Konstruktor:

        IfConf::IfConf()
                : ifreqBufferSize(0)
        {
                conf.ifc_len = 0;
                conf.ifc_buf = nullptr;
        }

Die Methode ioctl() der Klasse soll die benötigte Puffer-Größe ermitteln, dann bei Bedarf entsprechend viel Speicher allozieren und dann letztlich die Netzwerk-Informationen abfragen:

        int IfConf::ioctl( int sd )
        {
                int minSize;
                // 1. Use ioctl with command SIOCGSIZIFCONF to determine needed buffer size
                if ( ::ioctl(sd,SIOCGSIZIFCONF,&minSize) == -1 )
                        return -1;

                // 2. if necessary reallocate memory
                if ( minSize > ifreqBufferSize )
                {
                        delete [] conf.ifc_buf;
                        // new can throw std::bad_alloc
                        conf.ifc_buf = new char[minSize];
                        ifreqBufferSize = minSize;
                }

                // 3. Use ioctl with command SIOCGIFCONF to get the network informations
                conf.ifc_len = ifreqBufferSize;
                return ::ioctl(sd,SIOCGIFCONF,&conf);
        }

Um sicherzustellen, dass der allozierte Speicher auch wieder frei gegeben wird, benötigt die Klasse auch einen Destruktor:

        IfConf::~IfConf()
        {
                delete [] conf.ifc_buf;
        }

Als Iterator implementieren wir eine minimale Iterator-Klasse um einen Zeiger auf eine ifreq Struktur:

Class IfConf
{

        class iterator
        {
        public :

                iterator( ifreq* ptr )
                        : req(ptr) {}

                ifreq& operator*() const
                        { return *req; }

                iterator& operator++()
                        { req = (ifreq*)((caddr_t)req+IFNAMSIZ+req->ifr_addr.sa_len); return *this; }

                friend bool operator!=( const iterator& iter1 , const iterator& iter2 )
                        { return iter1.req != iter2.req; }

        private :

                ifreq* req;
        };

};

Die Methode begin() der Klasse IfConf liefert dann einfach einen Iterator der auf den Beginn des Puffers zeigt:

        iterator begin()
                { return conf.ifc_req; }

Die Method end() liefert dann entsprechend einen Iterator der auf die Adresse hinter der letzten ifreq Struktur zeigt:

        iterator end()
                { return (ifreq*)(conf.ifc_buf+conf.ifc_len); }

Die Implementierung erlaubt dann z.B. die Verwendung einer Range-based for-Schleife:

for ( auto& req : conf )
        …

Damit lassen sich Konfigurations-Informationen aller Netzwerk-Interfaces sehr leicht und übersichtlich ermitteln:

IfConf conf;
int sd = socket(AF_INET,SOCK_DGRAM,0);
conf.ioctl(sd);
for ( auto& req : conf )
        …

Nachfolgend eine C++ Version des C-Programms get_ipconf.c aus dem vorigen Kapitel:

$ cat get_ifconf.cpp
/*
* 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 <cerrno>         // errno, perror()
#include <unistd.h>        // ioctl(), close()
#include <cstdio>         // fprintf(), printf()
#include <cstdlib>        // exit(), malloc()

class IfConf
{
public :

        class iterator
        {
        public :

                iterator( ifreq* ptr )
                        : req(ptr) {}

                ifreq& operator*() const
                        { return *req; }

                iterator& operator++()
                        { req = (ifreq*)((caddr_t)req+IFNAMSIZ+req->ifr_addr.sa_len); return *this; }

                friend bool operator!=( const iterator& iter1 , const iterator& iter2 )
                        { return iter1.req != iter2.req; }

        private :

                ifreq* req;
        };

        IfConf()
                : ifreqBufferSize(0)
        {
                conf.ifc_len = 0;
                conf.ifc_buf = nullptr;
        }

        ~IfConf()
                { delete [] conf.ifc_buf; }

        iterator begin()
                { return conf.ifc_req; }

        iterator end()
                { return (ifreq*)(conf.ifc_buf+conf.ifc_len); }

        int ioctl( int sd )
        {
                int minSize;
                // 1. Use ioctl with command SIOCGSIZIFCONF to determine needed buffer size
                if ( ::ioctl(sd,SIOCGSIZIFCONF,&minSize) == -1 )
                        return -1;

                // 2. if necessary reallocate memory
                if ( minSize > ifreqBufferSize )
                {
                        delete [] conf.ifc_buf;
                        // new can throw std::bad_alloc
                        conf.ifc_buf = new char[minSize];
                        ifreqBufferSize = minSize;
                }

                // 3. Use ioctl with command SIOCGIFCONF to get the network informations
                conf.ifc_len = ifreqBufferSize;
                return ::ioctl(sd,SIOCGIFCONF,&conf);
        }

private :

        ifconf conf;

        int ifreqBufferSize;
};

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 class IfConf.
        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 method ioctl() of newly defined class IfConf to get the data
        ret = conf.ioctl(sd);
        if ( ret == -1 )
        {
                perror("get_ifconf");
                exit(errno);
        }

        // 4. Print network informations
        for ( auto& req : conf )
        {
                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");
        }

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

        return 0;
}

$

Der Einfachheit halber wurde der komplette Code in nur einer Datei untergebracht. Es empfiehlt sich aber den Code für die Klasse IfConf in eigene Dateien zu separieren. Die printf(3) Aufrufe wurden nicht durch Verwendung von cout ersetzt. Und auch ansonsten bietet das Programm noch reichlich Möglichkeiten der Verbesserung. Es ging aber letztlich darum zu zeigen, wie die Benutzung von ioctl(2) in einzelnen Fällen durch die Einführung von Klassen wie IfConf deutlich vereinfacht werden kann.

Das Programm kann mit einem C++-Compiler übersetzt werden und liefert dann auch das gleiche Ergebnis wie die C-Version:

$ g++ -o get_ifconf get_ifconf.cpp
$ get_ifconf
en0: len: 128 family: 18 MAC: 32:7e:ba:40:74:5
en0: len: 16 family: 2 IP: 172.20.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
$