Under Construction
C++: Vereinfachung der Benutzung von ifreqaddrs durch eine Klasse
Die Verwendung der Struktur ifreqaddrs im Zusammenhang mit ioctl(2) ist relativ komplex. Gründe hierfür sind die folgenden:
- Der benötigte Speicher hängt von der Anzahl der IP-Adressen ab und ist erst zur Laufzeit bekannt. Dies bedingt das Speicher dynamisch alloziert werden muss.
- Der Name des Netzwerk-Interfaces muss vor dem Aufruf in die Struktur kopiert werden (Feld: ifr_name).
- Das Feld naddrs wird sowohl für Input (Anzahl der IP-Adressen die in der Struktur maximal abgespeichert werden können), als auch als Output (Anzahl der IP-Adressen die tatsächlich zurückgeliefert wurden) verwendet.
- Der allozierte Speicher muss wieder frei gegeben werden um Memory Leaks zu vermeiden.
- Die Iteration über die zurückgegebenen IP-Adressen ist etwas sperrig.
Durch Einführung einer Wrapper-Klasse kann die Verwendung deutlich vereinfacht werden. Der Name des Netzwerk-Interfaces wird beim Konstruktor-Aufruf angegeben, dabei wird automatisch Speicher alloziert. Bei Bedarf soll die Wrapper-Klasse automatisch mehr Speicher allozieren. Über den Destruktor wird der Speicher dann wieder freigegeben.
Die Methode ioctl() der Klasse vergrößert automatisch den dynamisch allozierten Speicherbereich, falls nicht alle IP-Adressen in die aktuelle Struktur passen. Der ioctl() wird dann automatisch ein zweites Mal aufgerufen.
Um über die IP-Adressen zu iterieren, kann operator[] zusammen mit der Methode size() (liefert die aktuelle Anzahl von gespeicherten IP-Adressen in der Struktur) verwendet werden. Denkbar wäre auch die Implementierung von eigenen Iteratoren, worauf wir aber im Beispiel verzichtet haben.
Die Klasse haben wir IfReqAddrs benannt:
class IfReqAddrs
{
public :
int size() const;
IfReqAddrs( const std::string& name );
~IfReqAddrs();
::sockaddr_in* operator[]( int idx ) const;
int ioctl( int sd , int request );
private :
::ifreqaddrs* addrs;
int capacity;
};
In der Variablen addrs wird ein Zeiger auf eine ifreqaddrs Struktur mit variabler Größe gespeichert. Die Kapazität (Anzahl IPs) die aktuell maximal abgespeichert werden können, wird in capacity abgespeichert.
Die Allozierung von Speicher haben wir in eine eigene statische Methode ausgelagert:
static ::ifreqaddrs* IfReqAddrs::allocate( int num )
{
int size = sizeof(::ifreqaddrs)+(num-1)*sizeof(::sockaddr_in6);
:ifreqaddrs* tmp = (::ifreqaddrs*)new char[size];
tmp->ifaddrs = 0;
tmp->naddrs = 0; // no IP stored
return tmp;
}
Diese initialisiert die Werte von ifaddrs und naddrs auf 0 und gibt die Adresse des allozierten Speichers zurück.
Der Konstruktor alloziert eine Struktur für nur eine IP-Adresse (das absolute Minimum, welches aber für viele Situationen ausreichend sein dürfte) und trägt den Namen des Netzwerk-Interfaces in die Struktur ein:
IfReqAddrs::IfReqAddrs( const std::string& name )
: addrs(allocate(1)) , capacity(1)
{
strncpy(addrs->ifr_name,name.c_str(),IFNAMSIZ);
}
Der Destruktor gibt entsprechend den Speicher wieder frei:
IfReqAddrs::~IfReqAddrs()
{
delete [] addrs;
}
Die Methode ioctl() führt den System-Call ioctl(2) mit der aktuellen Kapazität durch. Falls dies nicht ausreichend sein sollte, wird mehr Speicher alloziert (ausgelagert in eine eigene Methode reserve()). Dann wird der ioctl(2) System-Call wiederholt.
Das C-Programm get_ip.c ist im folgenden nach C++ portiert worden, unter Verwendung der Klasse IfReqAddrs:
$ cat get_ips.cpp
/*
* Copyright (c) 2021 by PowerCampus 01 GmbH
*/
#include <sys/ioctl.h> // SIOCGIFADDRS
#include <sys/socket.h> // socket()
#include <netinet/in.h> // sockaddr, sockaddr_in, sockaddr_in6
#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 <cstring> // strncpy()
#include <string>
class IfReqAddrs
{
public :
// Return number of IPs stored.
int size() const
{ return addrs->naddrs; }
IfReqAddrs( const std::string& name )
: addrs(allocate(1)) , capacity(1)
{
strncpy(addrs->ifr_name,name.c_str(),IFNAMSIZ);
}
~IfReqAddrs()
{
delete [] addrs;
}
::sockaddr_in* operator[]( int idx ) const
{
return &(addrs->ifrasu[idx].addr_in);
}
void reserve( int newCapacity )
{
if ( newCapacity > capacity )
{
::ifreqaddrs* tmp = allocate(newCapacity);
strncpy(tmp->ifr_name,addrs->ifr_name,IFNAMSIZ);
::ifreqaddrs* old = addrs;
addrs = tmp;
capacity = newCapacity;
delete [] old;
}
}
int ioctl( int sd , int request )
{
addrs->naddrs = capacity;
int ret = ::ioctl(sd,request,addrs);
int total = addrs->ifaddrs;
if ( total > capacity )
{
reserve(total);
addrs->naddrs = capacity;
ret = ::ioctl(sd,request,addrs);
}
return ret;
}
private :
static ::ifreqaddrs* allocate( int num )
{
int size = sizeof(::ifreqaddrs)+(num-1)*sizeof(::sockaddr_in6);
::ifreqaddrs* tmp = (::ifreqaddrs*)new char[size];
tmp->ifaddrs = 0;
tmp->naddrs = 0; // no IP stored
return tmp;
}
::ifreqaddrs* addrs;
int capacity;
};
int main( int argc , char** argv )
{
int sd;
int ret;
// 1. Define variable of type IfReqAddrs.
IfReqAddrs req(argv[1]);
// 2. Create datagram socket for address family Inet.
sd = socket(AF_INET,SOCK_DGRAM,0);
if ( sd == -1 )
{
perror("get_ips");
exit(errno);
}
// 3. Use method ioctl() of newly defined class IfReqAddrs to get the data.
ret = req.ioctl(sd,SIOCGIFADDRS);
if ( ret == -1 )
{
perror("get_ips");
exit(errno);
}
// 4. Print all IP-Addresses.
for ( int i = 0 ; i < req.size() ; ++i )
{
printf("IP: %s\n",inet_ntoa(req[i]->sin_addr));
}
// 5. Close socket descriptor
close(sd);
return 0;
}
$
Der Einfachheit halber befindet sich der komplette Code in nur einer Datei get_ips.cpp. Für praktische Anwendungen sollte die Klasse IfReqAddrs in separate Dateien ausgelagert werden.
Die Übersetzung mit einem C++ Compiler gelingt problemlos:
$ g++ -o get_ips get_ips.cpp
$
Das Programm führt dann natürlich zur identischen Ausgabe wie die früher vorgestellte C-Version:
$ get_ips en1
IP: 199.15.16.17
IP: 199.15.16.18
IP: 199.15.16.19
$
Die Verwendung in der main()-Funktion ist deutlich einfacher als in der C-Version. Keine explizite Allozierung und De-Allozierung von Speicher. Die ioctl()-Methode wird nur einmal aufgerufen und behandelt transparent eine eventuell notwendige Speicher-Allozierung, sowie einen doppelten Aufruf des System-Calls ioctl(2).
Die Klasse IfReqAddrs lässt sich natürlich noch deutlich verbessern!