Aus Linux-Magazin 08/2011

IPv6-tauglich programmieren in C/C++, Python und Perl

© buba mara, photocase.com

Programmiersprachen wie C, Python, Perl und das C++-Toolkit Qt können mit IPv6 umgehen. Das erfordert aber den Einsatz der richtigen Funktionen, Klassen und Methoden. Wie Entwickler sie einsetzen und so auch alte Anwendungen modernisieren, erläutert dieser Artikel.

Der Umstieg auf IPv6-Internet kann nur gelingen, wenn auch die Anwendungen mitmachen. Linux-Distributionen wie Open Suse haben ihre Pakete bereits Tests unterzogen. Dieser Beitrag erklärt, wie Entwickler ihre eigene Software sowohl für IPv4 als auch für IPv6 fit machen. Dabei geht es um die Sprachen C, Python, C++ mit dem Qt-Toolkit und – im Kasten “IPv6 in der Perl-Programmierung” – um Perl. Die meisten Beispielprogramme setzen keine native IPv6-Anbindung ans Internet voraus und lassen sich lokal auf aktuellen Linux-Distributionen ausprobieren.

Auch bei IPv6 ist es so, dass sich der Programmierer mit dem Systemaufruf »socket()« einen Endpunkt in Form eines File-Deskriptors erzeugt. Soll dieser IPv6 beherrschen, müssen die Domain »AF_INET6« und das Protokoll »PF_INET6« oder »AF_INET6« lauten. Als Typ bieten sich wie bei IPv4 weiterhin »SOCK_STREAM« für UDP und »SOCK_DRAM« für TCP an. Spannend wird es erst, wenn der Entwickler den Socket auch tatsächlich irgendwohin verbinden möchte. Für die Client-Seite gibt es dafür »connect()« , für die Server-Seite »bind()« . Beide erwarten die Struktur »const struct sockaddr*« , doch wo nimmt man diese her?

C – die Grundlage

RFC 3493 [1] beschreibt neben vielem anderem die Funktion »getaddrinfo()« , die eben diese »sockaddr« -Strukturen liefert. Sie löst die veraltete und nicht Multithreading-fähige (genauer: nicht reentrante) Funktion »gethostbyname()« ab (Abbildung 1). Generell dient »getaddrinfo()« dazu, einen möglichst sanften und problemlosen Übergang von IPv4 zu IPv6 zu ermöglichen. Sie übernimmt in einem Aufruf die Namensauflösung, das Parsen von IP-Adressen, die Erstellung der Adressenstrukturen und die Bereitstellung der Parameter für folgende Netzwerkaufrufe. Sie ist damit quasi das Schweizer Taschenmesser der IPv6-Programmierung.

Abbildung 1: Das Dokument RFC 3493 beschreibt das C-Interface für »getaddrinfo()«, das Implementierungen in vielen Sprachen zugrunde liegt.

Abbildung 1: Das Dokument RFC 3493 beschreibt das C-Interface für »getaddrinfo()«, das Implementierungen in vielen Sprachen zugrunde liegt.

Optionsreichtum

Listing 1 führt die Funktion mit ihren Parametern auf. »node« zeigt auf eine Zeichenkette, welche die Adresse genauer beschreibt. In der Regel ist dies etwa ein Hostname in der Form »www.linux-magazin.de« . Die üblichen Darstellungen von IPv4- oder IPv6-Adressen sind jedoch ebenso zulässig, etwa »127.0.0.1« oder »FF80::0201:02FF:FE03:0405%eth0« . Der Parameter »service« beschreibt die Portnummer. Erlaubt sind Servicebezeichner wie sie in »/etc/services« aufgelistet sind, zum Beispiel »http« oder »ssh« sowie auch die Portnummer in dezimaler Darstellung.

Listing 1

getaddrinfo()

01       int getaddrinfo(const char *node, const char *service,
02                       const struct addrinfo *hints,
03                       struct addrinfo **res);
04
05        void freeaddrinfo(struct addrinfo *res);

Der optionale Parameter »hints« kann auf eine »addrinfo« -Struktur zeigen, die wie in Listing 2 definiert ist. »res« verwendet diese Struktur ebenfalls, die auch mehrere Ergebnisse erlaubt, die über das »ai_next« -Feld verkettet sind. Zum Vermeiden von Speicherlöchern darf der Programmierer nicht vergessen »freeaddrinfo()« aufzurufen, wenn er die Adressen nicht mehr braucht.

Listing 2

struct addrinfo

01  struct addrinfo {
02                int              ai_flags;
03                int              ai_family;
04                int              ai_socktype;
05                int              ai_protocol;
06                size_t           ai_addrlen;
07                struct sockaddr *ai_addr;
08                char            *ai_canonname;
09                struct addrinfo *ai_next;
10            };

Als »ai_family« spezifiziert der Entwickler »AF_INET« , um IPv4-Adressen zu erhalten, und »AF_INET6« , um IPv6-Adressen zu bekommen. Wer beide Adressentypen handhaben will, sollte »AF_UNSPEC« angeben und erhält normalerweise zunächst die IPv6-Adresse(n) und danach die IPv4-Adresse(n). In der »res« -Struktur steht natürlich nie »AF_UNSPEC« , sondern die konkrete Adressenfamilie der zurückgegebenen Adresse.

Bei »ai_socktype« wird man in der Hints-Struktur »SOCK_STREAM« für UDP oder »SOCK_DGRAM« für TCP eintragen. Wer 0 einträgt, erhält dann bei Resultaten für jede Adresse mehrere Einträge mit den möglichen Typen. In der Hints-Struktur von »ai_protocol« darf sich der Programmierer ein Protokoll wünschen, also etwa »FP_INET« oder »FP_INET6« . Gewöhnlich reicht es aber, hier eine 0 anzugeben, um Ergebnisse zu bekommen, die zu den anderen Parametern passen.

Die drei Felder »ai_family« , »ai_socktype« und »ai_protocol« entsprechen praktischerweise genau den Parametern »domain« , »type« und »protocol« des Systemaufrufs »socket()« , weshalb der Entwickler diese einfach durchreichen kann.

Das Feld »ai_addrlen« speichert die Länge der zurückgegebenen »sockaddr_in« -Struktur. »ai_addr« enthält in der Struktur »res« einen Zeiger auf eine »sockaddr_in« -Struktur bei IPv4 oder »sockaddr_in6« bei IPv6. Dieser Zeiger lässt sich zusammen mit »ai_addrlen« als Parameter für die Systemcalls »connect()« , »bind()« , »sendto()« oder »recvfrom()« verwenden.

Das Feld »ai_canonname« enthält in der »res« -Struktur einen Zeiger auf den kanonischen Namen des Host, wenn dieser über die »ai_flags« angefragt wurde. Eine Übersicht über diese Flags gibt der Kasten “Details zu »ai_flags« “.

Details zu ai_flags

AI_ADDRCONFIG: Gibt der Programmierer dieses Flag an, erhält er nur Adressen, wenn irgendein Netzwerkinterface wenigstens eine Nicht-Loopback-Adresse des entsprechenden Typs konfiguriert hat. Das heißt, ein Gerät, das keine IPv6-Verbindung besitzt, bekommt nur IPv4-Adressen und andersherum.

Leider haben die Entwickler der aktuellen Glibc- [2] und Eglibc-Bibliotheken [3] die fragwürdige Entscheidung getroffen, IPv6-Link-Local-Adressen ebenfalls als konfigurierte Adressen gelten zu lassen. Das hat zur Folge, dass auch auf Rechnern ohne globale IPv6-Adresse »getaddrinfo()« zunächst unerreichbare IPv6-Adressen präsentiert.

Normalerweise schlägt in einer solchen Umgebung der Verbindungsaufbau recht schnell fehl, aber in manchen Fällen sind spürbare Timeouts abzuwarten, was diese Option eigentlich verhindern sollte. Abgesehen davon ist es natürlich empfehlenswert, diese Option auf der Client-Seite zu verwenden, um unnötige und fehleranfällige DNS-Abfragen und Verbindungsversuche zu vermeiden.

AI_PASSIVE: Liefert Serversockets, die sich für die Systemaufrufe »bind()« und »accept()« eignen.

AI_V4MAPPED: Liefert für jene »AF_INET6« -Sockets, für die keine IPv6-Adresse zu ermitteln ist, die gefundenen IPv4-Adressen als IPv4-gemappte IPv6-Adressen.

AI_ALL: Liefert immer alle IPv6- und alle IPv4-gemappten IPv6-Adressen (nur in Verbindung mit »AI_V4MAPPED« ).

AI_CANONNAME: Löst den kanonischen Hostnamen auf und liefert ihn im ersten Eintrag der »res« -Liste zurück.

AI_NUMERICHOST: Vermeidet DNS-Anfragen. Dafür muss »node« in einer numerischen Notation angegeben sein.

Gegenanzeigen

Eine Reihe von Funktionen sollte der Programmierer unbedingt vermeiden, wenn er IPv6-kompatible Software schreiben möchte. Das sind alle Funktionen, die eine »struct in_addr« als Parameter erwarten oder zurückliefern (siehe Listing 3). Für »inet_aton()« und »inet_ntoa()« , welche die numerische Ascii-Repräsentation und die »struct in_addr« ineinander umwandeln, gibt es die alternativen Funktionen »inet_pton()« und »inet_ntop()« , die einen zusätzlichen Parameter für die Adressenfamile erwarten und auch mit »in6_addr« -Strukturen umgehen können (Listing 4).

Listing 3

IPv6-inkompatible Funktionen

01     int inet_aton(const char *cp, struct in_addr *inp);
02     in_addr_t inet_addr(const char *cp);
03     in_addr_t inet_network(const char *cp);
04     char *inet_ntoa(struct in_addr in);
05     struct in_addr inet_makeaddr(int net, int host);
06     in_addr_t inet_lnaof(struct in_addr in);
07     in_addr_t inet_netof(struct in_addr in);

Listing 4

inet_pton und inet_ntop

01     int inet_pton(int af, const char *src, void *dst);
02     const char *inet_ntop(int af, const void *src,
03                           char *dst, socklen_t size);

Python

Python enthält seit Version 2.2 die Funktion »getaddrinfo()« im Standardmodul »socket« [4]. Die Umsetzung auf eine Skriptsprache ist hier gut gelungen, weil die hässliche Eigenschaft von C, unbenutzte Parameter in der »hints« – beziehungsweise »res« -Struktur zu haben, in Python entfällt. Die Funktion der Hints-Struktur aus dem C-Interface übernehmen in Python optionale Parameter:

socket.getaddrinfo(host, port,family=0,socktype=0, proto=0, flags=0)

Die notwendigen »AF_« – und »AI_« -Konstanten befinden sich ebenfalls im Socketmodul. Als Rückgabewert liefert »socket.getaddrinfo()« eine Liste von 5-Tupeln der folgenden Form zurück:

(family, socktype, proto, canonname, sockaddr)

Dabei sind die ersten drei Parameter jene, die der Programmierer an »socket.socket()« weitergeben kann, um ein Socketobjekt zu erstellen. Der »canonname« liefert den kanonischen Namen, falls über »flags« angefordert.

Der letzte Parameter »sockaddr« entspricht dem Feld »ai_addr« , Python liefert ihn als Tupel zurück, das je nach Adressenfamilie eine andere Länge besitzt. Der erste Eintrag dieser Tupel ist immer ein String, der aus der IP-Adresse in numerischer Notation besteht, der zweite Eintrag ist stets die Portnummer. Für IPv6 kommen noch die Einträge »flow info« und »scope id« hinzu. In beiden Fällen kann man diese Tupel als Parameter für die Socketmethoden »bind()« und »connect()« verwenden.

Ein kleiner Client

Das erste Beispielprogramm ist ein kleines Python-Skript (Listing 5), das über »getaddrinfo()« die vorhandenen Adressen für einen Host anzeigt und eine Verbindung zu diesen Adressen aufzubauen versucht. Damit lässt sich beispielsweise prüfen, ob ein bestimmter Webserver über IPv6 erreichbar ist. Der Code funktioniert sowohl mit Python 2.7 als auch mit 3.1.

Listing 5

getaddrinfo.py

01 #!/usr/bin/env python
02 import sys
03 from socket import *
04
05 host = sys.argv[1]
06 port = 80 if len(sys.argv)<3 else sys.argv[2]
07
08 for addrinfo in getaddrinfo(host, port,AF_UNSPEC, SOCK_STREAM):
09     family, socktype, proto, canonname, sockaddr = addrinfo
10     socketObject = socket(family, socktype, proto)
11     #socketObject = socket(*addrinfo[:3])
12     if socketObject is None: continue
13     haveConnection = False
14     try:
15         socketObject.connect(sockaddr)
16         socketObject.close()
17         haveConnection = True
18     except:
19         pass
20     familyString = "IPv6" if family==AF_INET6 else "IPv4"
21     args = familyString, sockaddr[0], haveConnection
22     print("{0} address {1}, connect = {2}".format(*args))

Als ersten Parameter erwartet das Skript einen Hostnamen, den es unverändert an »getaddrinfo()« weitergibt. Der zweite Parameter, falls vorhanden, kommt als Port-Argument für »getaddrinfo()« zur Verwendung (Zeile 8). Der Standardwert ist 80. Das Skript versucht nun zu jeder gefundenen Adresse eine Verbindung aufzubauen, indem es zunächst ein Socketobjekt mit den Family-, Socktype- und Proto-Parametern erzeugt, die zurückgeliefert wurden. Dann versucht es einen »connect()« auf »sockaddr« (Zeile 15). Wenn dies gelingt, wird es in »haveConnection« vermerkt.

Abbildung 2: Google ist unter IPv6 und IPv4 erreichbar, wie das Python-Beispielprogramm zeigt.

Abbildung 2: Google ist unter IPv6 und IPv4 erreichbar, wie das Python-Beispielprogramm zeigt.

Google liefert die IPv6-Adresse nur an ausgewählte Provider, die einen IPv6-Test bestanden haben. Man sieht an der Ausgabe, dass es zu einer Adressenfamilie durchaus mehrere Einträge geben kann (Abbildung 2). Über den Hostnamen [ipv6.google.com] erhält man jedoch stets eine IPv6-Adresse. Wie Facebook zeigt, kann man mit kreativer Rechtschreibung auch noch Schleichwerbung in einer IPv6-Adresse unterbringen:

$ python getaddrinfo.py www.facebook.com
IPv6 address 2620:0:1c08:4000:face:b00c:0:1,connect = True
IPv4 address 69.171.224.41,connect = True

Wie sieht das auf der Server-Seite aus? Für eine Ipv6-fähige Serverapplikation genügt es, als Host »None« , als Family »AF_UNSPEC« sowie bei den Flags »AI_PASSIVE« anzugeben, um Parameter und Adressen für Serversockets zu erhalten, die auf allen Netzwerkinterfaces auf ankommende Verbindungen lauschen. Normalerweise werden das zwei Sockets sein.

IPv4 und IPv6 in Konkurrenz

Das erste Problem besteht darin, ob sich ein IPv4- und ein IPv6-Socket zum selben Port gleichzeitig mit »bind« binden lassen. Die Antwort lautet: Es kommt darauf an – und zwar auf die Socketoption »IPV6_V6ONLY« , die ebenfalls in RFC 3493 beschrieben ist. Sie lässt sich in Python mit der Socketmethode »setsockopt()« setzen:

socket.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 1)

Leider ist es so, dass die Standardeinstellung dieser Option vom Betriebssystem und sogar von dessen Distribution abhängig ist. Auf einigen BSD-Systemen ist sie im Standardfall aktiv und auf Linux-Systemen kann der Administrator sie zur Laufzeit aktivieren:

# sysctl net.ipv6.bindv6only=1

Für den Programmierer empfiehlt es sich, diese Option immer explizit zu setzen, denn dann ist es in jedem Fall möglich, einen IPv6-Port zu binden, auch wenn der Port bereits über IPv4 gebunden ist.Ist »IPV6_V6ONLY« nicht aktiviert, werden auch ankommende IPv4-Verbindungen über eine V4-mapped-Adresse auf einen IPv6-Socket weitergeleitet.

So praktisch V4-mapped-Adressen sind, besitzen sie jedoch auch Nachteile: Zum einen haben sie eine andere textuelle Darstellung, was insbesondere in Log-Ausgaben zu veränderten Zeichenketten führt. Das kann Probleme in automatischen Analysewerkzeugen verursachen, die beispielsweise mit regulären Ausdrücken nach IPv4-Adressen suchen und die neuen Strings nicht erkennen. Zum anderen gibt es eine Reihe potenzieller Sicherheitsprobleme im Zusammenhang mit V4-mapped-Adressen [5], die daher rühren, dass die Applikation nicht unterscheiden kann, ob eine IPv4-Verbindung vorliegt oder eine IPv6-Verbindung mit manipulierter Adresse.

Blockiert

Das zweite Problem mit den beiden Serversockets liegt darin, dass der »accept()« -Aufruf im Normalfall blockiert, während er auf eine Verbindung wartet. Um damit umzugehen, könnte der Entwickler theoretisch zwei Serverprozesse starten oder mit mehreren Threads arbeiten.

Das ist aber gar nicht notwendig, denn das Problem lässt sich viel eleganter mit Select lösen. Damit kann das Programm auf mehrere Sockets gleichzeitig warten. Das mag ein wenig aufwändig erscheinen, doch in der Praxis kommen komplexere Anwendungen ohnehin nicht ohne »select« oder Alternativen wie »poll« oder »epoll« aus. In Python findet man »select()« im Standardmodul Select [6]:

readable, writable, special = select.select(rlist, wlist, xlist[, timeout])

Die Parameter »rlist« , »wlist« und »xlist« sind Listen von File- oder Socket-Objekten, die auf Lesbarkeit, Schreibbarkeit oder außergewöhnliche Ereignisse reagieren sollen. Die drei Rückgabewerte liefern Listen jener Objekte, bei denen tatsächlich der abgewartete Zustand eingetreten ist. Für ankommende Verbindungen ist dies »readable« .

In Python ist »accept()« als Methode des Socketobjekts abgebildet und liefert ein 2-Tupel zurück:

(conn, address) = socket.accept()

Dabei ist »conn« der neue Socket der aufgebauten Verbindung, »address« die Adresse der Gegenseite in Pythons Tupel-Notation.

Das nächste Codebeispiel (Listing 6) wendet die besprochenen Techniken praktisch an. Es lauscht auf einem Port, nimmt eingehende Verbindungen an und übermittelt die ankommende IP-Adresse. Abbildung 3 zeigt Telnet-Sitzungen mit dem Server per IPv6 und IPv4.

Listing 6

server.py

01 #!/usr/bin/env python
02 import sys
03 from socket import *
04 from select import select
05
06 host  = None
07 port  = sys.argv[1]
08 flags = AI_PASSIVE
09
10 serverSockets = []
11
12 for addrinfo in getaddrinfo(host, port, AF_UNSPEC, SOCK_STREAM, 0, flags):
13     family, socktype, proto, canonname, sockaddr = addrinfo
14     serverSocket = socket(family, socktype, proto)
15     if family==AF_INET6:
16         serverSocket.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 1)
17     serverSocket.bind(sockaddr)
18     serverSocket.listen(1)
19     serverSockets.append(serverSocket)
20
21
22 while True:
23     readable, writable, special = select( serverSockets, [], [])
24     for readSocket in readable:
25         connectionSocket, connectionAddress = readSocket.accept()
26         connectionSocket.send("Hello {0}\n".format(connectionAddress).encode("ascii"))
27         connectionSocket.close()
Abbildung 3: Der in Python umgesetzte Server antwortet auf per IPv6 und IPv4 eingehende Telnet-Verbindungen.

Abbildung 3: Der in Python umgesetzte Server antwortet auf per IPv6 und IPv4 eingehende Telnet-Verbindungen.

Das Qt-Toolkit

Das objektorientierte C++-Framework Qt bietet in der »QtNetwork« -Bibliothek [7] einige Abstraktionen zur Netzwerk-Programmierung an. Im Normalfall funktionieren Clientprogramme, welche die richtigen Methoden verwenden, automatisch mit IPv6. Zur Netzwerk-Programmierung stellt Qt die Klassen »QTcpSocket« und »QUdpSocket« bereit, die beide von der Basisklasse »QAbstractSocket« abgeleitet sind.

Um in einem Clientprogramm eine TCP-Verbindung herzustellen, erzeugt der Entwickler zunächst einen »QTcpSocket« und ruft die in der Basisklasse »QAbstractSocket« definierte Methode »connectToHost()« auf (Listing 7). Diese übernimmt die Namensauflösung, was im Normalfall unter der Decke mit »getaddrinfo()« geschieht.

Listing 7

connectToHost

01 void    connectToHost ( const QString & hostName, quint16 port, OpenMode openMode = ReadWrite )

Als kleines Beispiel dient ein minimales Qt-Programm (Listing 8), das eine Verbindung zu jenem Webserver herzustellen versucht, dessen Namen ihm der Anwender als ersten Kommandozeilen-Parameter übergibt. Als Antwort erhält der Benutzer die IP-Adresse (Abbildung 4).

Listing 8

simpleClient.cpp

01 #include <QtCore/QCoreApplication>
02 #include <QtCore/QStringList>
03 #include <QtNetwork/QHostAddress>
04 #include <QtNetwork/QTcpSocket>
05
06 #include <iostream>
07
08 int main(int argc, char *argv[])
09 {
10   QCoreApplication app(argc, argv);
11
12   QString host = app.arguments().at(1);
13   int port = 80;
14
15   QTcpSocket socket;
16   socket.connectToHost(host, port);
17
18   if (!socket.waitForConnected(1000)) {
19     std::cout << "Could not connect" << std::endl;
20     return 10;
21   }
22
23   QHostAddress peerAddress = socket.peerAddress();
24   QString address = peerAddress.toString();
25   std::cout << "Connected to "
26             << address.toAscii().constData() << std::endl;
27   socket.close();
28   return 0;
29 }
Abbildung 4: Ein minimales Qt-Programm für die Kommandozeile ermittelt IP-Adressen zu Hostnamen.

Abbildung 4: Ein minimales Qt-Programm für die Kommandozeile ermittelt IP-Adressen zu Hostnamen.

Für die Ausgabe kommt »QHostAddress« zum Einsatz [8]: Dies ist Qts Abstraktion von IPv4- und IPv6-Adressen, sie bietet unter anderem die Methode »QHostAddress::toString()« an.

Die »QCoreApplication« ermöglicht es, ein Qt-Programm ohne GUI zu erstellen (Listing 8, Zeile 10). Nach dem »connectToHost()« (Zeile 16), das sofort zurückkehrt, ruft der Code »waitForConnected()« auf, um auf die Verbindung zu warten (Zeile 18). Alternativ ist es möglich, das Signal »connected()« zu einem geeigneten Slot zu verbinden. Ist eine Verbindung erfolgt, gibt das Programm die »peerAddress« aus.

In einer Umgebung mit funktionierender IPv6-Unterstützung zeigt sich, dass das Programm tatsächlich ganz automatisch eine IPv6-Verbindung aufnimmt:

$ ./simpleClient www.google.com
Connected to 2A00:1450:4001:C01:0:0:0:68%0

Das ist praktisch, bietet aber beileibe nicht die Einflussmöglichkeiten, die »getaddrinfo()« dem C-Programmierer einräumt. Dem am nächsten kommt »QHostInfo« [9], das Methoden zur Namensauflösung bereitstellt. Leider sind über »QHostInfo« aber nicht alle Annehmlichkeiten von »getaddrinfo()« zugänglich: Es ist nicht möglich vorzugeben, ob man IPv4- oder IPv6-Adressen haben will, und der Entwickler kann keine Flags wie etwa »AI_ADDRCONFIG« angeben.

Die Anwendung ist einfach: Die statische Methode »QHostInfo::fromName(const QString& name)« liefert eine »QHostInfo« -Instanz zurück. Diese gibt über »QHostInfo::addresses()« eine »QList« von »QHostAddress-Instanzen« zurück. Es existiert auch eine nebenläufige Variante namens »lookupHost()« , welche die notwendigen Anfragen in separaten Threads erledigt und dann den angegebenen Slot des Objekts aufruft.

Ein Qt-Server

Serverapplikationen sind in Qt nicht von Haus aus IPv6-kompatibel: Die Klasse »QTcpServer« , die eine Abstraktion für den Serversocket darstellt, horcht immer nur auf einer Adresse, die ungeschickterweise in der Methode »listen()« mit der IPv4-Adresse »QHostAddress::Any« als Standardargument vorbelegt ist.

Als Lösung kann der Anwendungsentwickler zunächst beim »listen()« den IPv6-Parameter »QHostAddress::AnyIPv6« angeben. Das funktioniert auf allen Plattformen und Distributionen, auf denen die Socketoption »IPV6_V6ONLY« standardmäßig auf 0 steht. IPv4-Verbindungen erhält das resultierende Programm über V4-mapped-Adressen.

Leider gibt es keinen einfachen Weg, »QTcpServer« beizubringen, die Socketoption »IPV6_V6ONLY« zu verwenden. Eine Möglichkeit besteht darin, über »QTcpServer::setSocketDescriptor« dem Programm einen mit C-Mitteln erzeugten Socket-Deskriptor unterzuschieben.

Eine alternative Lösung findet der Programmierer, indem er sich etwas im Fundus der »QtNetwork« -Bibliothek umschaut. Dort findet sich die Klasse »QNetworkInterface« , die alle Netzwerkgeräte aufzählt. Zu jedem Gerät erzeugt sie eine Liste von »QNetworkAddressEntry« -Instanzen, von denen wiederum jede eine »QHostAddress« besitzt. Damit lassen sich alle IPv4- und IPv6-Adressen des Systems finden.

Der Entwickler erhält zusätzlich die Möglichkeit, den Serverport nur für bestimmte Netzwerkkarten zu öffnen. Der Code des Qt-Servers (Listings 9 und 10) iteriert über alle Netzwerkgeräte und -adressen (Zeile 29 in Listing 10). Hier ließe sich im Grunde auch die statische Methode »QNetworkInterface::allAddresses()« verwenden [10], doch leider liefert diese fehlerhafte Link-Local-Adressen.

Für jede Netzwerkadresse legt das Programm einen neuen »QTcpServer« an (Zeile 46, Listing 10) und verbindet ihn über einen »QSignalMapper« . Das ist notwendig, weil »QTcpServer« nur ein Void-Signal »newConnection()« sendet, was dem Empfänger nicht mitteilt, welcher von mehreren Sendern denn eine neue Verbindung bereithält. »QSignalMapper« wertet das Signal mit der Senderinformation auf, sodass das Programm dem Besucher mitteilen kann, von welcher IP-Adresse er kommt.

Listing 9

greeter.h

01 #ifndef GREETER_H
02 #define GREETER_H
03
04 #include <QtCore/QObject>
05 #include <QtNetwork/QTcpServer>
06 #include <QtNetwork/QTcpSocket>
07
08
09 class Greeter : public QObject
10 {
11   Q_OBJECT
12
13 public:
14   Greeter(QObject *parent) : QObject(parent) {}
15
16 public slots:
17   void newConnection(QObject* serverObject) {
18     QTcpServer* server = static_cast<QTcpServer*>(serverObject);
19
20     QTcpSocket* connection = server->nextPendingConnection();
21     connect(connection, SIGNAL(disconnected()),
22             connection, SLOT(deleteLater()));
23
24     QHostAddress peerAddress = connection->peerAddress();
25     QString address = peerAddress.toString();
26
27     connection->write("Hello ");
28     connection->write(address.toAscii());
29     connection->write("\n");
30     connection->disconnectFromHost();
31   }
32
33 };
34
35 #endif

Listing 10

server.cpp

01 #include <QtCore/QCoreApplication>
02 #include <QtCore/QSignalMapper>
03 #include <QtCore/QStringList>
04 #include <QtNetwork/QNetworkInterface>
05 #include <QtNetwork/QHostAddress>
06 #include <QtNetwork/QTcpServer>
07 #include <iostream>
08 #include "greeter.h"
09
10
11 int main(int argc, char *argv[])
12 {
13   QCoreApplication app(argc, argv);
14   int port = app.arguments().at(1).toInt();
15
16   Greeter* greeter = new Greeter(&app);
17
18   QSignalMapper* sigMap;
19   sigMap = new QSignalMapper(&app);
20   greeter->connect(sigMap,
21            SIGNAL(mapped(QObject *)),
22            SLOT(newConnection(QObject *)));
23
24   QList<QTcpServer> servers;
25
26   QList<QNetworkInterface> ifs;
27   ifs = QNetworkInterface::allInterfaces();
28
29   foreach(const QNetworkInterface& i, ifs) {
30     QList<QNetworkAddressEntry> entries;
31     entries = i.addressEntries();
32
33     foreach(const QNetworkAddressEntry& entry, entries) {
34       QHostAddress address = entry.ip();
35
36       // fix scope of link-local addresses
37       Q_IPV6ADDR addr6;// = address.toIPv6Address();
38       addr6 = address.toIPv6Address();
39       if (addr6[0] == 0xfe &&
40           addr6[1] == 0x80) {
41         QString name=i.humanReadableName();
42         address.setScopeId(name);
43       }
44
45       QTcpServer* server;
46       server = new QTcpServer(&app);
47       sigMap->setMapping(server, server);
48       sigMap->connect(server,
49                   SIGNAL(newConnection()),
50                   SLOT(map()));
51
52       server->listen(address, port);
53       if (!server->isListening()) {
54         std::cout << "Cannot listen on "
55                   << address.toString().toAscii().constData() << std::endl;
56       }
57     }
58   }
59
60   return app.exec();
61 }

IPv6 in der Perl-Programmierung

Wer in Perl IPv6-fähige Programme schreiben wollte, benötigte bisher das CPAN-Modul »Socket6« , das Funktionen wie »getaddrinfo()« bereitstellte. Seit Perl 5.14 (Mai 2011) steckt die nötige Funktionalität im Modul »Socket« [11] und damit im Perl-Core. Listing 11 zeigt seine Anwendung. Komfortabler gerät die Socketprogrammierung mit »IO::Socket« [12]. Das für IPv4 nötige »IO::Socket::INET« ist schon lange Teil des Perl-Core, für IPv6 existiert das ausgereifte Modul »IO::Socket::INET6« vom CPAN:

use IO::Socket::INET6;
my $sock6 = IO::Socket::INET6->new( '[::1]:12345' );
my $sock4 = IO::Socket::INET6->new( '127.0.0.1:12345' );

Das Modul ist abwärtskompatibel zu »IO::Socket: :INET« und kann auch IPv4-Verbindungen herstellen. Die Adressen akzeptiert es als Hostnamen und in IPv4- oder IPv6-Notation: Da »IO: :Socket::INET« eine einfachere Programmierung als Sockets im Libc-Stil bietet, nutzten es bisher viele Programme und Perl-Module, die damit nicht IPv6-tauglich sind. Darunter finden sich auch Core-Module wie »Net::SMTP« , »Net::FTP« und wichtige CPAN-Module wie »LWP« .

In vielen Fällen lassen sich diese Programme mit Hilfe von »Net::INET6Glue::INET_is_INET6« [13] IPv6-fähig machen, da dieses »IO::Socket::INET« mit »IO::Socket::INET6« ersetzt:

use Net::INET6Glue::INET_is_INET6;
use LWP::Simple;
print get('http://ipv6.google.com/');

Folgendes Kommando macht ein vorhandenes Programm IPv6-fähig:

$ perl -MNet::INET6Glue::INET_is_INET6 ipv4_programm.pl

»Net::INET6Glue::FTP« erweitert darüber hinaus auch »Net::FTP« um die für IPv6 erforderlichen Kommandos »EPRT« und »EPSV« . »IO::Socket::SSL« bietet eine einfache Nutzung von SSL und unterstützt automatisch IPv6, sofern »IO::Socket::INET6« installiert ist.

Darüber hinaus gibt es in Perl noch mehrere Bibliotheken für einen nicht blockierenden Umgang mit Sockets. Diese unterstützen vielfach auch IPv6, benutzen aber wie beispielsweise »AnyEvent« und POE für die Adressenauflösung nicht das blockierende »getaddrinfo()« , sondern besitzen ihren eigenen Resolver. Dieser liefert allerdings die Ergebnisse möglicherweise in einer anderen Reihenfolge, als es mit »getaddrinfo()« üblich ist.

Weitere Informationen zu IPv6 in der Perl-Programmierung finden sich unter anderem in Vorträgen auf den Deutschen Perl-Workshops 2009 [14] und 2010 [15]. (Steffen Ullrich)

Listing 11

Perl-Socket

01 #!/usr/bin/perl
02
03 use Socket6; # ab 5.14 reicht 'use Socket'
04 my @res = getaddrinfo('ipv6.google.com','http',AF_UNSPEC,SOCK_STREAM );
05 my $sock;
06 while (! $sock and @res) {
07     my ($fam,$type,$proto,$saddr,$cname) = splice(@res,0,5);
08     socket($sock,$fam,$type,$proto) or die $!;
09     connect($sock,$saddr) and last;
10     undef $sock;
11 }
12 $sock or die $!;

Fazit

Eigentlich ist es nicht schwer, ein Programm IPv6-kompatibel zu machen, wenn man weiß, worauf zu achten ist. Clientprogramme sind in der Regel unproblematisch, aber Programme mit Serverfunktionalität gewinnen an Komplexität, wenn der Entwickler alle Eventualitäten einkalkulieren möchte. Frameworks mit hohem Abstraktionsgrad können sich dabei als problematisch erweisen, wenn sie wichtige Funktionalität nicht in ihrer Abstraktionsschicht abbilden, wie etwa »IPV6_V6ONLY« im Qt-Toolkit. (mhu)

Der Autor

Peter Hrenka arbeitet seit 2002 als Software-Entwickler bei der Science + Computing AG in Tübingen. Dort entwickelt er zurzeit Software für verteilte Visualisierung.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben