Mon, 07 Sep 2020 08:00:00 +0200
c, programmieren, software

C-Schnipsel: Netzwerkschnittstellen identifizieren

Schreibe ich ein Programm mit Stream-Sockets, muss ich mich im Allgemeinen nicht um die Netzwerkschnittstellen des Rechners kümmern, weil der TCP-Stack dafür Sorge trägt, das ausgehende Daten an der richtigen Schnittstelle gesendet werden. Bei einfachen Anwendungen mit Datagramm-Sockets sieht das ähnlich aus. In den meisten Fällen muss ich mich auch hier nicht darum kümmern.

Es gibt aber ein paar Spezialfälle, bei denen ich mir klar werden muss, an welchen Schnittstellen ich Datagramme empfangen habe und an welchen ich sende.

Jeder Rechner mit einem Netzwerkadapter, hat mindestens zwei Netzwerkschnittstellen. Nämlich die für den Adapter und die für das Loopback-Device lo. Weitere Schnittstellen kommen hinzu, wenn mehrere Netzwerkadapter existieren, wenn virtuelle Adapter für VMs angelegt werden oder wenn Tunnelschnittstellen durch entsprechende Protokolle wie GRE oder IPIP bereitgestellt werden.

Bei Adressen mit link-lokalem Gültigkeitsbereich oder bei Multicast-Anwendungen benötige ich die richtige Schnittstelle um das Datagramm zum Empfänger zu senden.

Adressen mit link-lokalem Gültigkeitsbereich sind mit SLAAC gebildete Adressen bei IPv6, beziehungsweise APIPA- oder Zeroconf-Adressen bei IPv4. Obwohl die Adressen eindeutig innerhalb ihres Netzsegments sind, ist es möglich, dass die gleiche Adresse in zwei verschiedenen Netzsegmenten verwendet wird. Ist mein Rechner an beiden Segmenten angeschlossen, kann ich die beiden Peers mit derselben Adresse nur anhand der Schnittstelle unterscheiden.

Will ich bei IPv6 Multicast-Gruppen beitreten oder diese verlassen, muss ich ebenfalls das Interface angeben.

RFC3493 beschreibt in Sektion 4 die Datenstrukturen und Funktionen, mit denen ich Informationen über die vorhandenen Schnittstellen eines Rechners bekommen kann.

Das sind im wesentlichen die folgenden Funktionen:

if_indextoname(), die den vom Socket-Interface verwendeten Interface-Index in einen lesbaren Namen für Ausgaben zum Benutzer umwandelt,

if_nametoindex(), die den Namen, zum Beispiel aus einer Konfigurationsdatei, in den zugehörigen Index umwandelt,

if_nameindex(), die einen Zeiger auf ein Array von struct if_nameindex liefert, welches die Indizes und Namen aller Schnittstellen enthält,

if_freenameindex(), die die von if_nameindex() allokierten Ressourcen wieder freigibt.

Die Funktionen sind in der Datei "net/if.h" definiert, ebenso wie die struct if_nameindex und die Konstante IF_NAMESIZE, mit der ich ein char-Array definieren kann, das groß genug ist, um den längsten Schnittstellen-Namen und das abschließende 0-Byte zu halten.

Das folgende Beispielprogramm zeigt die Verwendung der Funktionen und Datenstrukturen aus "net/if.h":

/* interfaces.c - identify interfaces for network sockets */

#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    struct if_nameindex *ifs = if_nameindex();
    if (NULL == ifs) {
        perror("if_nameindex");
        abort();
    }

    char if_name[IF_NAMESIZE];

    for (int i = 0; 0 < ifs[i].if_index; i++) {

        if_indextoname(ifs[i].if_index, if_name);
        unsigned int if_index = if_nametoindex(if_name);

        printf("interface[%d]: %s\n",
               if_index,
               if_name);
    }

    if_freenameindex(ifs);
} // main()

Das Programm kann mit folgendem Befehl übersetzt werden:

cc -o interfaces interfaces.c

Auf meinem Rechner liefert es folgende Ausgabe:

interface[1]: lo
interface[2]: enp0s25
interface[3]: wlp3s0
interface[4]: wwp0s29u1u4i6
interface[5]: virbr0
interface[6]: virbr0-nic
interface[7]: vboxnet0
interface[8]: vboxnet1

Man sieht deutlich, dass die Interface-Indizes, entgegen der Konvention bei Arrays in der Programmiersprache C, mit 1 beginnen. Dies ist dem Umstand geschuldet, dass der Interface-Index 0 bei der Funktion if_nametoindex() einen ungültigen Index anzeigt. Bei dem von if_nameindex() gelieferten Array signalisiert der Interface-Index das Ende des Arrays.

Damit endet dieser Exkurs in die Welt der Netzwerkschnittstellen und deren Namen. Socketfunktionen nutzen vorzugsweise den Interface-Index, bei Konfigurationsdateien sowie bei der Ausgabe für den Benutzer bin ich aber eher am Namen interessiert.