Fri, 11 May 2018 20:00:00 +0200
c, programmieren, software

C-Schnipsel: IP-Adressen einlesen und ausgeben

Wenn ich Netzwerkprogramme in C schreibe, kommt irgendwann der Wunsch auf, IP-Adressen auszugeben oder einzulesen. Sei es, dass ich einen Logeintrag mit einer Adresse schreiben will, oder eine Adresse aus einer Konfigurationsdatei einlesen.

Traditionell kamen dann oft inet_ntoa(), inet_aton() oder ähnliche Funktionen zum Einsatz. Insbesondere die beiden erwähnten Funktionen haben aber den Nachteil, dass sie sich nur für IPv4-Adressen eignen. Damit sind sie für neue Programme überholt und bei der Wartung vorhandener Programme ergibt sich vielleicht die Möglichkeit sie zu ersetzen.

Dafür bieten sich die beiden Funktionen inet_ntop() und inet_pton() an.

IP-Adressen in Text umwandeln

#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src,
                      char *dst, socklen_t size);

Die Funktion inet_ntop() wandelt eine IPv4- oder IPv6-Adresse aus einer Socketstruktur in die entsprechende Textdarstellung um.

Dazu übergebe ich die folgenden Parameter:

af - die Protokollfamilie: AF_INET für IPv4 oder AF_INET6 für IPv6,

src - die Socketstruktur mit der Adresse: einen Zeiger auf struct in_addr bei IPv4 oder einen Zeiger auf struct in6_addr bei IPv6,

dst - einen Puffer, der groß genug ist, die Textdarstellung aufzunehmen,

size - die Größe des Puffers (die Konstante INET6_ADDRSTRLEN ist groß genug für IPv4 und IPv6).

In Beej's Guide to Network Programming fand ich eine Hilfsfunktion, die mir einiges an Tipparbeit abnimmt, wenn ich Adressen aus einer struct sockaddr ausgeben will:

char *get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen) {
    switch(sa->sa_family) {
        case AF_INET:
             inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),
                       s, maxlen);
             break;

        case AF_INET6:
             inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr),
                       s, maxlen);
             break;

        default:
             strncpy(s, "Unknown AF", maxlen);
             return NULL;
    }
    return s;
}

Ich übergebe dieser Funktion einen Zeiger auf die struct sockaddr, außerdem einen Zeichenpuffer und dessen Länge. Zurück erhalte ich einen Zeiger auf eben diesen Puffer mit der Textdarstellung der Adresse oder NULL.

IP-Adressen von Text nach Binär umwandeln

#include <arpa/inet.h>

int inet_pton(int af, const char *src, void *dst);

Die Funktion inet_pton() wandelt die Textdarstellung einer IPv4- oder IPv6-Adresse in eine Socketstruktur, die ich in einem Programm verwenden kann, um.

Dazu übergebe ich die folgenden Parameter:

af - die Protokollfamilie: AF_INET für IPv4 oder AF_INET6 für IPv6,

src - die Textdarstellung, für IPv4 im Dotted-Decimal-Format ddd.ddd.ddd.ddd, für IPv6 in einer der gängigen Darstellungen,

dst - ein Zeiger auf struct in_addr bei AF_INET oder ein Zeiger auf struct in6_addr bei AF_INET6.

Die Funktion inet_pton() macht keine DNS-Anfrage, das heißt ich muss eine IP-Adresse im C-String übergeben. Will ich automatisch Hostadressen auflösen, kann ich auf getaddrinfo() zurückgreifen. Diese Funktion erläutere ich vielleicht in einem anderen Artikel.

Beispielprogramm

Das folgende Programm mag als Beispiel für die Verwendung von inet_pton() und inet_ntop() dienen. Es nimmt eine IPv6- oder IPv4-Adresse als Parameter in der Kommandozeile, wandelt diese in die entsprechende Binärstruktur und wieder zurück und gibt schließlich den konvertierten Text aus:

/*
 * inet_pton-inet_ntop.c
 * Copyright (C) 2018 Mathias Weidner <mathias@mamawe.net>
 *
 * Distributed under terms of the MIT license.
 *
 * see also: http://beej.us/guide/bgnet/
 *
 * This programs takes either an IPv6 or an IPv4 address on the command
 * line and prints. Default is IPv6, to switch to IPv4 use the command
 * line option -4.
 */

#include <arpa/inet.h>

#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void usage(const char * prog) {
    fprintf(stderr,"Usage: %s [-4] address\n", prog);
    exit(EXIT_FAILURE);
}

int main(int argc, char **argv) {
    int opt, result, flag = AF_INET6;
    struct sockaddr_storage addr = {};
    char string[INET6_ADDRSTRLEN] = "";

    while ((opt = getopt(argc, argv, "4")) != -1) {
        switch(opt) {
        case '4':
            flag = AF_INET;
            break;
        default:
            usage(argv[0]);
        }
    }
    if (optind >= argc) {
        usage(argv[0]);
    }
    if(0 >= (result = inet_pton(flag, argv[optind], &addr))) {
        if (0 == result) {
            fprintf(stderr,"Error: %s is not a valid address\n",
                    argv[optind]);
        }
        else {
            fprintf(stderr,"Error: unknown family: %d\n", flag);
        }
        exit(EXIT_FAILURE);
    }
    if (NULL == inet_ntop(flag, &addr, string, sizeof(string))) {
        perror("inet_ntop");
        exit(EXIT_FAILURE);
    }
    printf("%s\n", string);
    exit(EXIT_SUCCESS);
}

Ressourcen:

Ein guter Anlaufpunkt für Netzwerkprogrammierung ist das Buch von W. Richard Stevens: UNIX Network Programming, Volume 1, Networking APIs: Sockets and XTI.

Etwas neuer, dafür bei weitem nicht so umfangreich ist Beej's Guide to Network Programming, zu finden unter http://beej.us/guide/bgnet/. Eine deutsche Übersetzung gibt es unter der Adresse http://www.tobscore.com/socket-programmierung-in-c/.