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:
- Zuerst wird der ioctl(2) mit der Standard-Größe der Struktur gestartet (1 IP-Adresse).
- Der erste Aufruf liefert über ifaddrs zurück wieviele IP-Adressen das Netzwerk-Interface besitzt.
- 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
$