Rechnen mit IPv4-Adressen in Programmiersprache c

Nicht unbedingt Perl, Syntax der Aufgabe entsprechend jedoch ähnlich.

Das Rechnen mit IP-Adressen ist kein Hexenwerk. Schritt für Schritt erklärt dieser Artikel, welche Berechnungen notwendig sind und wie sich daraus einzelne Funktionen ableiten lassen. Alle Funktionen und Codebeispiele sind in der Programmiersprache c dargestellt, damit kann eine komplette Library aufgebaut werden, siehe dazu auch Hinweise am Ende dieser Seite. Grundsätzlich geht es hier auch nur um das Internet Protokoll (IP) in der Version 4 (ipv4).

Rechnen mit großen Zahlen

Jede IP-Adresse, Netzmaske oder Broadcastadresse ist eine integer Zahl mit 32 Bit. Somit ist eine solche Adresse auf einer 32-Bit-Architektur gerade noch abbildbar. Ein:

printf("%u\n", 0xffffFFFF);

gibt diese größtmögliche Zahl aus: 4,294,967,295 (der Übersicht halber die Tausender mit Komma getrennt). Wichtig ist der Platzhalter %u in der printf-Anweisung, %u heißt unsigned integer und das bedeutet, dass sich die 32 Bit nur im positiven Bereich abspielen. Wird dieser Sachverhalt nicht beachtet, läge der Wertebereich bei plusminus 2,147,483,647 und es gäbe nur noch 31 Bit für den positiven Bereich. Merke: Alle Variablen für die numerischen Werte für IP-Adressen, Netzadressen und Broadcastadressen sind als

unsigned long ipaddr, netaddr, bcaddr;
oder
unsigned int ipaddr, netaddr, bcaddr;

zu deklarieren! Noch ein Wort zu 0xffffFFFF: Das ist ein 32-Bit-integer in hexadezimaler Schreibweise. Ein 'F' steht für vier Bit (Dezimalzahl 15), acht 'F' ergeben somit 32 Bit. Die Schreibweise ist case unsensitive, vier kleine und vier große 'F' sind also leicht überschaubar im Programmcode.

Die Oktetten innerhalb der 32 Bit

Bekanntlich schreibt niemand gerne große Zahlen, deswegen hat sich für die Darstellung von IP-Adressen die sogenannte Oktettenschreibweise durchgesetzt. Da sind 32 Bit einfach nur aufgeteilt in vier Oktetten mit jeweils acht Bit und mit einem Punkt getrennt aufgeschrieben:

Die IP-Adresse mit dem numerischen Wert 2 hoch 31 bitweise dargestellt:
10000000.00000000.00000000.00000000

Betrachten wir die erste Oktette allein für sich:
10000000

Die ergibt nach dezimal umgerechnet 2 hoch 7
128

Und so sieht die Dezimal- (Oktetten) Schreibweise aus:
128.0.0.0

Zwischen (2 hoch 31) und (2 hoch 7) liegen also 31 - 7 = 24 Bit. Gedanklich haben wir soeben die Zahl 128 um 24 Bit nach links geschoben.

Mit diesem Verständnis kommen wir kurz und knapp zu den Formeln der Umrechnung von Oktetten in die numerische Adresse und umgekehrt.

/*************************************************************************/
// rechnet ip (4 Oktetten einzeln übergeben) nach numerisch
// und gibt ipnum zurück
int ip_to_num(int a, int b, int c, int d){
    unsigned long ipnum; // IP numerisch <= 4294967295;
    ipnum = (a << 24) + (b << 16) + (c << 8) + d;
    return ipnum;
}
/*************************************************************************/

Die erste Oktette wird um 24 Bit (32 minus 8 Bit) nach links geschoben, die zweite Oktette um 16 Bit und die dritte Oktette um acht Bit. Die vierte Oktette wird einfach nur dazuaddiert. Daraus ergibt sich der numerische Wert eine IP-Adresse. Zum Prüfen: die Adresse 255.255.255.255 ergibt 2 hoch 32 (0xFFFFFFFF).

Zum Verschieben der Bits nach links oder nach rechts, gibt es in c den sogenannten Shift-Operator. Das sind die beiden Angel-Brackets (spitze Klammern), die entweder nach links (Left-Shift) oder nach rechts (Right-Shift) zeigen.

Bisher haben wir gesehen, wie mit Left-Shift die einzelnen Oktetten in eine 32-Bit-Zahl hineingeschoben werden um den numerischen Wert dieser Zahl zu ermitteln.

Andersherum, mit Right-Shift also, können die einzelnen Oktetten aus einer numerischen IP-Adresse auch wieder "herausgezogen" werden, dazu wird nach dem Shift-Operator eine 8-Bit-Maske über das Bitmuster gelegt:

/*************************************************************************/
// rechnet aus numerischer IP die 4 Oktetten zurück
// legt die 4 Oktetten auf eine Array, was per Pointer übergeben wurde
void num_to_okt(int * ar, unsigned long ipnum){
    ar[0] = (ipnum >> 24) & 0xff; // 11111111.0000... >> 00000000.00000000.00000000.11111111
    ar[1] = (ipnum >> 16) & 0xff; // 00000000.1100... >> 00000000.00000000.00000000.11000000
    ar[2] = (ipnum >> 8)  & 0xff; // usw.
    ar[3] =  ipnum        & 0xff;
}
/*************************************************************************/

Der Operator & vergleicht die ge-shiftete Oktette mit der Maske 0xff, eine Maske mit genau acht Bit. Infolge dieser logischen (bitweisen) UND-Verknüpfung wird die einzelne Oktette als Ganzzahl ermittelt.

Unterfunktionen mit Parametern und Rückgabewerten

Ein paar Worte seien noch gesagt zu diesem Thema. Anders als in Perl, kann eine Unterfunktion in c lediglich einen einzigen Wert und diesen auch nur nach einem speziellen Datentyp zurückgeben. Am einfachsten ist die Rückgabe eines Zahlenwertes vom Datentyp int oder float (Ganzzahl oder Fließkommazahl). Dementsprechend sind die Unterfunktionen deklariert wie int funktion... oder float funktionsname.... Eine Funktion, die nichts zurückgibt, ist mit dem Schlüsselwort void deklariert.

Die Rückgabe von mehr als einem Wert ist programmiertechnisch auch nicht unbedingt notwendig, für diese Fälle bietet c die Möglichkeit an, mit Pointern zu arbeiten, Pointer sind Zeiger auf Adressbereiche.

Im vorliegenden Artikel werden einige Funktionen, so wie obenstehend, beschrieben, die mit diesen Pointern arbeiten. Die Funktion num_to_okt beispielsweise bekommt einen Zeiger übergeben, der auf den Adressbereich eines Arrays mit integer-Werten zeigt. In der Funktion selbst wird dieses Array mit Werten (Oktetten) gefüllt. Der Zeiger auf das Array ar wird mit einen '*' gekennzeichnet.

Innerhalb der runden Klammern einer Funktions-Deklaration können weitere Parameter übergeben werden. Diese Parameter sind vom Datentyp her namentlich benannt und innerhalb der Sub-Funktion direkt ansprechbar wie z.B. die Variable ipnum obenstehend oder die einzelnen Oktetten a,b,c,d in der anderen Funktion.

Die übergabe eines Pointers auf ein Array aus der main() Funktion an eine Unterfunktion ist gar nicht mal so schwer. Je nach Datentyp wird das Array deklariert:

int zahlen[10];    // ein Array für 10 Zahlen vom Datentyp integer
char zeichen[20];  // ein Array für 20 beliebige Zeichen wie "a", "B" oder "." usw.

Gaanz wichtig ist die Initialisierung von Variablen in c, im Fall der Arrays passiert das in dem Moment, wo die Länge innnerhalb der Klammern [] vorgegeben wird, in diesem Moment wird im Hauptspeicher des Rechners der Speicherplatz mit der entsprechenden Größe reserviert.

Mit dem Aufruf einer Funktion like fx(zahlen) oder fy(zeichen) werden diesen Funktionen praktisch Zeiger auf Adressbereiche übergeben (Array zahlen oder Array zeichen). Die Funktionen selbst sind wie folgt deklariert:

void fx(int zahlen[]);
void fy(char zeichen[]);

oder in einer etwas anderen Schreibweise
void fx(int * zahlen);
void fy(char * zeichen);

Betrachte mit mir die Funktion:

/*************************************************************************/
// legt den IP-String auf das übergebene Array
void ipdots(char * ips, unsigned long ipnum){
    int okt[4];
    num_to_okt(okt, ipnum);
    sprintf(ips, "%d.%d.%d.%d", okt[0], okt[1], okt[2], okt[3]);
}
/*************************************************************************/

Aufgerufen wird diese Funktion wie folgt:

int main(){
    char ipstring[20];            // Naja, mit 20 Zeichen reichlich deklariert
    ipdots(ipstring, 0xffffFFFF); // erwarte auf ipstring die Zeichenkette 255.255.255.255
    printf("%s\n", ipstring);     // 255.255.255.255
    return 0;
}

Die Funktion ipdots() nimmt bekommt einen Zeiger auf die Adresse des Ergebnis-Arrays und eine Nummer übergeben. Der IP-String wird berechnet und auf das Ergebnis-Array gelegt, das geschieht mit der Funktion sprintf(). sprintf() formatiert die einzelnen Zahlen der Oktetten um als eine Zeichenkette.

Die Default-Class einer IP-Adresse

Die ersten vier Bits bestimmen die Default Class einer IP-Adresse. Es gilt für die ersten vier Bits:

0    class A  0-127
10   class B  128-191
110  class C  192-223
111  class D  224-239
1111 class E  ab 240

Zum Berechnen der Default-Class einer IP-Adresse wird also lediglich die erste Oktette benötigt und von dieser Oktette wiederum nur die ersten vier Bit. Um an diese vier Bit heranzukommen, wird der Shift-Operator angewandt. Obenstehende Bitmuster für die einzelnen Netzklassen A, B, C, D, E können in Zahlen ausgedrückt werden:

0    0
10   2
110  6
111  7
1111 0xF

Untenstehend die Funktion zur Berechnung der Default-Class.

/*************************************************************************/
// ermittelt die Default Class aus der ersten Oktette
// 0 class   A   0-127
// 10 class  B   128-191
// 110 class C   192-223
// 111 class D   224-239
// 1111 class E  ab 240
void default_class(char buffer[], int okt){
    if(okt >> 7 == 0)        { strcpy(buffer, "A");}
    else if( okt >> 4 == 0xF){ strcpy(buffer, "E (Reserviert)");}
    else if( okt >> 5 == 7)  { strcpy(buffer, "D (Multicast)");}
    else if( okt >> 5 == 6)  { strcpy(buffer, "C");}
    else if( okt >> 6 == 2)  { strcpy(buffer, "B");}
}
/*************************************************************************/

Bevor die Funktion strcpy() benutzt werden kann, wird mit der Prä-Prozessor-Anweisung:

#include <string.h>

die Library string.h geladen. strcpy() kopiert die sich ergebende Zeichenkette (z.B. "D (Multicast)") auf den buffer, welcher von der aufrufenden Funktion als Pointer übergeben wurde.

Maskenlänge und Netzmaske

Zur eindeutigen Bestimmung einer IP-Adresse ist neben dieser auch eine Netzmaske anzugeben. Gebräuchlich sind Angaben wie diese:

192.168.0.0 255.255.0.0

oder
192.168.0.0/16

Die Maskenlänge wird von links her gezählt, im Beispiel 255.255.0.0 haben wir 255 in der ersten Oktette, das sind acht Bit. Dazu kommen acht Bit aus der zweiten Oktette, womit sich eine Maskenlänge von 16 Bit ergibt, siehe Schreibweise weiter unten.

Für weitere Berechnungen zu einer bestimmten IP-Adresse muss die Maskenlänge (0..31) in einen numerischen Wert umgerechnet werden.

/*************************************************************************/
// maskenlänge zu einer uint 32 bit Zahl umrechnen
// masklen 0..32 !!!
int masklen_to_num(int masklen){
    if(masklen == 0) return 0;
    else return(0xffffFFFF <<(32 - masklen));
}
/*************************************************************************/

Ein paar Worte auch hierzu. Eine Maskenlänge von 0 ist klar, das sieht bitmäßig so aus:

00000000.00000000.00000000.00000000

Eine Maskenlänge von 16 sieht folgerichtig so aus (16 Bit v.l.n.r):

11111111.11111111.00000000.00000000

Obenstehende Funktion schiebt praktisch von rechts her eine bestimmte Anzahl Nullen (32 minus Maskenlänge) in die 32-Bit-Zahl 0xffffFFFF womit sich aus der Maskenlänge der Numerische Wert der Netzmaske ergibt.

Etwas trickreicher ist die Umrechnung einer 32-Bit-Zahl zur Maskenlänge. Hierbei ist nämlich genau zu prüfen, ob die Bits aufeinanderfolgend sind.

Zum Verständnis:

Die Netzmaske
255.255.0.0
ergibt lückenlos aneinandergereihte Bits
11111111.11111111.00000000.00000000
woraus sich eine gültige Maske ergibt.

Eine Netzmaske
255.0.128.0
hingegen, ergibt
11111111.00000000.10000000.00000000
ein Bitmuster mit NICHT aufeinanderfolgenden Bits,
somit ist diese Netzmaske nicht zulässig!!!

/*************************************************************************/
// numerische Maske (32 unsigned int) nach Maskenl. 0..32 umrechnen
// prueft die Gueltigkeit der sich ergebenden Maske
int num_to_masklen(unsigned long n){
    int bit;
    int masklen = 0;
    int merk;
    int changes = 0;
    // Beim Durchgehen durch die Bits darf es max nur einen Wechsel geben

    int i = 0;

    for(i = 0; i < 32; i++){
        bit = (n >> i) & 1;
        if(i == 0){ merk = bit; }

        if(bit != merk){
            // Wechsel
            changes++;
            merk = bit;
        }

        if(bit) masklen++;
    }

    if(changes <= 1){ return masklen ;}
    else{ return -1; } // Maske ist nicht gueltig
}
/*************************************************************************/

Die gezeigte Funktion schiebt per Shift jedes einzelne Bit an die Position 2 hoch 0 also nach ganz rechts. Hier kommen entweder nur Nullen oder Einsen an. Eine Netzmaske ist nur dann gültig, wenn es maximal einen Wechsel von 0 auf 1 gegeben hat. Sofern es keinen Wechsel gab, liegt eine Maskenlänge von 32 Bit vor.

Siehe auch die Tabelle weiter unten, sie zeigt den Zusammenhang zwischen gültigen Netzmasken in Oktettenschreibweise und Maskenlänge.

Netzadresse und Broadcastadresse

Zur Berechnung beider Adressen wird eine IP-Adresse und die Netz-Maske benötigt. Sofern beide Werte als integer-Zahlen vorliegen, ergeben sich Netz- sowie Broadcastadresse aufgrund einer einfachen binären Verknüpfung:

/*************************************************************************/
// gibt die Netzadresse numerisch zurück (ipnum, maskenum)
int get_netaddr(unsigned long ipnum, unsigned long masknum){
    return(ipnum & masknum);
}
/*************************************************************************/
// gibt die Broadcastadresse numerisch zurück (ipnum, maskenum)
int get_bcaddr(unsigned long ipnum, unsigned long masknum){
    return(ipnum |~ masknum);
}
/*************************************************************************/

Die Operatoren & und | sind bitweises AND und bitweises OR. Der Operator ~ dreht alle Bits von 0 auf 1 und umgekehrt (Komplement).

Der gesamte Bereich der IP-Adressen geht von 0.0.0.0 (Netzadresse) bis 255.255.255.255 (Broadcastadresse) was sich aus einer Masklenlänge von 0 ergibt. Das sind, wie bisher dargelegt wurde, 4,294,967,295 Hosts. Für Geräte (PC) stehen jedoch die Broadcastadresse und die Netzadresse nicht zur Verfügung, das bedeutet, dass bei jeder Netzberechnung zwei Adressen abgezogen werden müssen. Es ergibt sich folgende Aufstellung:

Netzmaske Maskenlaenge/Bit Anzahl moeglicher Hosts
255.255.255.255 32 1 (Single Host)
255.255.255.254 31 0 (nicht benutzbar)
255.255.255.252 30 2
255.255.255.248 29 6
255.255.255.240 28 14
255.255.255.224 27 30
255.255.255.192 26 62
255.255.255.128 25 126
255.255.255.0 24 254
255.255.254.0 23 510
255.255.252.0 22 1022
255.255.248.0 21 2046
255.255.240.0 20 4094
255.255.224.0 19 8190
255.255.192.0 18 16382
255.255.128.0 17 32766
255.255.0.0 16 65534
255.254.0.0 15 131070
255.252.0.0 14 262142
255.248.0.0 13 524286
255.240.0.0 12 1048574
255.224.0.0 11 2097150
255.192.0.0 10 4194302
255.128.0.0 9 8388606
255.0.0.0 8 16777214
254.0.0.0 7 33554430
252.0.0.0 6 67108862
248.0.0.0 5 134217726
240.0.0.0 4 268435454
224.0.0.0 3 536870910
192.0.0.0 2 1073741822
128.0.0.0 1 2147483646
0.0.0.0 0 4294967293

Die Tabelle von unten nach oben gelesen zeigt, dass jede Verlängerung der Maske um ein Bit das vorliegende Netz durch zwei teilt. Teilungen durch ungerade Zahlen sind nicht zulässig, ebenso Teilungen durch krumme Werte, die sich aus log2(teilungsfaktor) ergeben. Siehe dazu auch eine spezielle Funktion weiter unten.

Die erste Teilung des gesamten IP-Ranges ergibt:

Basis Netz mit Maskenlaenge 0 und 4294967293 Hosts

Netzadresse      Broadcastadresse
0.0.0.0          255.255.255.255

2 Subnetze mit Maskenlaenge 1 und 2147483646 Hosts

Netzadresse      Broadcastadresse
0.0.0.0          127.255.255.255
128.0.0.0        255.255.255.255

Die Netzadresse ist also die erste IP-Adresse in einem Netz und die Broadcastadresse ist die letzte IP-Adresse.

Abstraktion eines Netzes mit einer komplexen Datenstruktur

Bisher betrachteten wir die Eigenschaften eines IP-Netzes, wie Netzadresse, Broadcastadresse usw. Um dies zu berechnen, schrieben wir einige Funktionen, sogenannte Elementarfunktionen. Die Programmiersprache c erlaubt es, verschiedene Datentypen in einem sogenannten struct zusammenzufassen. Ein struct ist wie eine Karteikarte, ein Steckbrief sozusagen.

Ein Steckbrief für ein Netzwerk könnte beispeilsweise so aussehen:

/*************************************************************************/

   /* Struktur für ein Netz */

struct netx{
    unsigned long ipaddr;   // IP-Adresse numerisch
    int masklen;            // 0..32
    unsigned long netmask;  // Netzmaske numerisch
    unsigned long netaddr;  // Netzadresse numerisch
    unsigned long bcaddr;   // Broadcastadresse numerisch
    char netmasks[20];      // Netzmaske als String like 255.0.0.0
    char ipaddrs[20];       // IP-Adresse als String like 172.31.11.12
    char netaddrs[20];      // Netzadresse als String like 0.0.0.0
    char bcaddrs[20];       // Broadcastadresse als String like 255.255.255.255
    char defaultclass[20];  // Default Class A, B, C, D, E
};

/*************************************************************************/

In einem solchen struct sind praktisch mehrere Datentypen miteinander vereint. Zur Verwendung des structs wird dieses noch vor der main()-Funktion deklariert. Dazu kommt eine Funktion, welche das struct mit Daten füllt, diese Funktion bekommt einen Pointer auf das struct übergeben, die Oktetten und die Maskenlänge:


/*************************************************************************/
// füllt das struct netx mit Werten
// benutzt dazu die weiter oben beschriebenen Elementarfunktionen
void fillnet(struct netx *p, int o1, int o2, int o3, int o4, int mlen){
    char b[20]; // buffer

    p->ipaddr = ip_to_num(o1,o2,o3,o4);
    p->masklen = mlen;
    p->netmask = masklen_to_num(mlen);
    p->netaddr = get_netaddr(p->ipaddr, p->netmask);
    p->bcaddr = get_bcaddr(p->ipaddr, p->netmask);

    ipdots(b, p->netmask);
    strcpy(p->netmasks, b);

    ipdots(b, p->ipaddr);
    strcpy(p->ipaddrs, b);

    ipdots(b, p->netaddr);
    strcpy(p->netaddrs, b);

    ipdots(b, p->bcaddr);
    strcpy(p->bcaddrs, b);

    default_class(b, o1);
    strcpy(p->defaultclass, b);
}
/*************************************************************************/
int main(){
    struct netx *p;   // Ein Pointer auf das struct;

    // Speicher anfordern für den pointer (Initialisierung)
    p = (struct netx *)malloc(sizeof(struct netx));


    // Nun kann eine Funktion mit dem Pointer im Argument aufgerufen werden
    fillnet(p, 192, 168, 0, 0, 24); // Pointer, oktette1..oktette4, maskenlänge

    // danach kann mit net.* auf jeden Wert des structs zugegriffen werden, Beispiele:
    printf("IP: %s\n", p->ipaddrs);
    printf("Netzadresse: %s\n", p->netaddrs);
    printf("Broadcastadresse: %s\n", p->bcaddrs);


    return 0;
}

Weitere nützliche Funktionen auf Bitebene

Sofern Netze in Subnetze geteilt werden sollen, ist zu beachten, dass der Teiler eine ganzzahlige Potenz von 2 ist. In der c-Library math.h gibt es zwar die Funktion log2(), diese liefert jedoch eine Zahl vom Datentyp float, was weitere Prüfungen erfordert. Untenstehende Funktion tut unabhängig von der math.h und ermittelt den Logarithmus gleich als einen Integer. Im Fehlfall gibt die Funktion -1 zurück. Beachte: Der Logarithmus 1 zur Basis 2 ist 0.

/*************************************************************************/
// Funktion prüft, ob die Zahl eine ganzzahlige Potenz von 2 ist
// Im Fehlfall wird -1 zurückgegeben
int logint2(unsigned long n){

    int i = 0;
    int bit = 0;
    int einser = 0;
    int pos = 0;
    for(i = 0; i < 32; i++){
        bit = (n >> i) & 1;
        if(bit){
            pos = i;
            einser++;
        }
    }

    if(einser > 1){ return -1;}
    else{ return pos; }
}

/*************************************************************************/

int main(){
    printf("%d\n", logint2(16));    // 4
    return 0;
}
/*************************************************************************/

Die nächste hier vorgestellte Funktion legt das Bitmuster einer 32-Bit-Zahl auf ein Array mit integer-Werten. Diese Funktion leistet gute Dienste zur Ermittelung der Common-Bits mehrerer IP-Adressen, beispielsweise zur Berechnen einer Route-Summary:

Gegeben sind die Adressen
192.168.0.0
192.168.128.0

Gesucht ist die Anzahl der führenden Bits, die beide Adressen gmeinsam haben.

Das Beispiel ist noch überschaubar, nur die ersten beiden Oktetten sind gleich, das ergibt 16 Bit. Somit ergibt sich die Route-Summary wie folgt:

192.168.0.0/16
/*************************************************************************/
// bitmuster einer Zahl auf ein array legen
void bitmuster(int * ar, unsigned long n){
    int i = 0;
    for(i = 0; i < 32; i++){
        ar[i] = (n >> i) & 1;
    }
}
/*************************************************************************/
int main(){
    int i;
    int bits[33];         // hier werden die Bits abgelegt
    bitmuster(bits, 255); // nur ein Beispiel

    // umgekehrt ausgeben
    for(i = 31; i > -1; i--){
        printf("%d", bits[i]);
    }
    printf("\n");
    // 00000000000000000000000011111111

    return 0;
}
/*************************************************************************/

Parsen eines IP-Strings und Prüfung von Benutzereingaben

Um an die einzelnen Oktetten und die Maskenlänge einer IP-Adresse like 172.31.11.12/24 zu kommen, wird dieser String gesplittet. Bewährt hat sich dazu diese Funktion:

/*************************************************************************/
// splittet nach "." und "/"
// nice to use for 172.31.32.0/24
void split(int * ar, char buffer[]){
    char * pch;
    int i = 0;
    pch = strtok(buffer, "./");
    while (pch != NULL){
        // printf("%s\n", pch);
        ar[i] = atoi(pch);
        pch = strtok (NULL, "./");
        i++;
    }
}
// Achtung strok() zerlegt den buffer im Speicherbereich!!!
// Nach Verlassen dieser Funktion ist buffer[] nicht mehr im Original!!!
/*************************************************************************/
// Die Verwendung obenstehender Funktion split()
int main(){
    int okt[10]; // Array mit Integer-Werten für die Oktetten und Maskenl.
    char buffer[20]; // hier liegt die IP-Adresse als Zeichenkette

    strcpy(buffer, "172.31.32.0/24"); // IP auf den buffer kopieren
    split(okt, buffer);

    // Die Oktetten liegen nun auf okt[0] bis okt[3]
    // Die Maskenlänge liegt auf okt[4]

    return 0;
}
/*************************************************************************/

Wichtig: Liegt der IP-String als eine Benutzereingabe vor, sind bereits vor der Anwendung von split() Prüfungen notwendig:

/*************************************************************************/
// ein IP-String wie 255.255.255.255/30 darf nicht länger als 18 sein
// muss genau 3 dots und einen slash enthalten
// darf ausser dots und slashes nur Ziffern enthalten
int valips(char buffer[]){
    if(strlen(buffer) > 18) return 0;

    int dots = 0;  // dots
    int slhs = 0;  // slashes
    int muell = 0; // nicht dot, nicht slash, nicht ziffer
    int i;
    for(i = 0; i < strlen(buffer); i++){
        if( (int)buffer[i] == 46) dots++;
        if( (int)buffer[i] == 47) slhs++;
        if( (int)buffer[i] < 46 || (int)buffer[i] > 57 ) muell++;
    }
    if( dots != 3 ) return 0;
    else if( slhs != 1 ) return 0;
    else if( muell > 0 ) return 0;
    else return 1;
}
/*************************************************************************/

Nur wenn die Prüfung der Zeichenkette keinen Fehler ergibt, kann diese an split() übergeben werden. Die Sinnfälligkeit der einzelnen Oktetten und die Sinnfälligkeit der Maskenlänge ist später gesondert zu prüfen.

Sortieren von IP-Adressen

Die IP-Adressen müssen numerisch in einem Array vorliegen. Untenstehende Funktion sortiert aufsteigend nach einem eigenen, von mir am 22.4.2008 entwickelten Algorithmus. Dabei wird ein Zahlen-Array mit bspw. 10 Zahlen lediglich nur fünfmal durchlaufen, danach ist es sortiert. Ein Array mit 11 Zahlen wird sechsmal durchlaufen. Das macht diese Sortierfunktion sehr performant.

Funktionsweise: Bei jedem Durchlauf wird das Minimum UND das Maximum ermittelt, sowie die Positionen dieser Werte im Array. Das Minimum tauscht dann seinen Platz mit der Startposition und dem dortigen Wert. Das Maximum tauscht den Platz mit der Stopposition. Das ist der Gewinn dieses Algorithmus gegenüber einem Selection Sort, bei dem lediglich das Minimum verschoben wird.

// keine Rekursion, Funktion muss iterativ aufgerufen werden!
void xSort(int * p, int start, int stop){
    unsigned long min = 0xffffFFFF; // GAZ, Größte Anzunehmende Zahl unsigned int
    unsigned long max = 0;
    int posmin;
    int posmax;
    int i;
    for(i = start; i < stop; i++){
        if( p[i] < min ) {
            min = p[i];
            posmin = i;
        }
        if( p[i] > max ) {
            max = p[i];
            posmax = i;
        }
    }

    // nun die Zahlen auf den richtigen Positionen ablegen
    // merke wert auf p[start]
    unsigned long wsta = p[start];
    p[start] = min;
    p[posmin] = wsta;

    // posmax ist posmin, wenn max auf start stand
    if(posmax == start){posmax = posmin;}

    // merke wert auf p[stop-1]
    unsigned long wsto = p[stop-1];

    p[stop-1] = max;
    p[posmax] = wsto;
}
/*************************************************************************/
// Beispiel Funktionsaufruf
int main(){
    int i;
    int zn[] = {1,2,3,4,5};

    int size = sizeof(zn)/sizeof(int);
    printf("zn hat %d Zahlen\n", size);

    // iteration über xSort
    for(i = 0; i < size - i; i++) xSort(zn, i, size - i);

    // Ausgabe
    for(i = 0; i < size; i++) printf ("%d\n", zn[i]);

    return 0;
}

Die Verwendung der hier vorgestellten Funktionen

Zweckmäßig werden alle Funktionen in einer Library, z.B. ip-lib.c zusammengefasst, hierin wird auch das struct deklariert. Diese Library kann als ASCII-Datei mit einer Präprozessor-Anweisung eingebunden werden:

#include "ip-lib.c"

Darüber hinaus ist es möglich, die Library zu kompilieren und später beim Übersetzen des Programmcodes zu linken:

Die Library vorkompilieren:
gcc -c -Wall ip-lib.c

Das erzeugt die Datei ip-lib.o

Als vorkompilierte Object-Datei wie folgt linken
gcc a.c -o a.exe ip-lib.o

Mit dem Vorkompilieren werden auch mögliche Fehler im Code frühzeitig erkannt.

Portieren der Funktionen nach Perl

Alle hier gezeigten Formeln zur Berechnung von IP-Adressen sind, unter Beachtung der Syntax auch in Perl funktional. Perl kennt Bit-Operatoren genauso wie Referenzen auf Arrays. Da Perl keine Datentypen kennt, ergeben sich sogar einige Vereinfachungen, aber Vorsicht: Pragma strict und -w sind ein absolutes Muss! Der Vollständigkeit halber ein Beispiel in Perl:

#!/usr/bin/perl -w
use strict;

my @z = (); # Array fuer die Oktetten
# Uebergebe Array-Referenz und eine grosse Zahl
num_to_okt(\@z, 0xFFFFFFFF);

print join(".", @z), "\n"; # 255.255.255.255

sub num_to_okt{
    my $ar = shift;    # Die Referenz auf das Array mit den Oktetten
    my $ipnum = shift; # Die IP-Adresse numerisch
    $$ar[0] = ($ipnum >> 24) & 0xff;
    $$ar[1] = ($ipnum >> 16) & 0xff;
    $$ar[2] = ($ipnum >> 8)  & 0xff;
    $$ar[3] =  $ipnum        & 0xff;
}

Anbieter: nmq​rstx-18­@yahoo.de, die Seite verwendet funktionsbedingt einen Session-Cookie und ist Bestandteil meines nach modernen Aspekten in Perl entwickelten Frameworks.