7.6.2013 14:00 - 14:30 Uhr
Bernd Eckenfels: Java-Anwendungen für IPv6 fit machen
Dass die Java-Plattform IPv6 unterstützt, ist allgemein bekannt – oder es wird vermutet. Aber sind dazu Änderungen an Anwendungen notwendig, die bisher nur für IPv4 getestet wurden? Welche Funktionen der IPv6-Protokollfamilie werden unterstützt? Welche Besonderheiten sind zu beachten, um Dual-Stack-fähige Anwendungen in Java zu erstellen?
Der Vortrag betrachtet die Java-API für Netzwerkkommunikation und untersucht diese auf Relevanz für IPv6. Ein besonderes Augenmerk wird auf die Umsetzung von Dual-Stack-fähigen Geschäftsanwendungen (TCP- und TLS-Protokoll) gelegt. Implementierungsdetails von Java SE 7 (OpenJDK, Oracle und IBM) sind Teil des Vortrags.
Ich werde nicht auf die mobilen Java Profile oder Android eingehen. Und über Java 8 gibt es in dem Bereich keine Neuerungen zu vermelden.
Aber schaut man sich den Java Installer genauer an, so ist es nicht das einzige Problem. Das Auto-Update lässt sich immer recht viel Zeit. Es hält "Recover" Kopien des kompletten Installers vor, es hat keine vernünftige Windows 7 Unterstützung (UAC Anforderung im Control Panel für Systemweite Einstellungen zum Auto Update funktionieren nicht). Außerdem ist eine Java Installation schnell mal kaputt, und der Installer bekommt es nicht immer hin die Registry aufzuräumen.
Alles in allem ist man als Verwender von Applets oder WebStart (und JavaFX) sehr stark mit Support beschäftigt, weil sich immer mal wieder inkompatible Änderungen einschleichen. Ganz aktuell quäle ich mich z.B. mit Problemen beim Aufruf der JavaScript bridge auf Web Seiten herum. Aber auch die Dokumentation zum Deploy Toolkit, die Detection im Browser, ja selbst die Startup Zeiten sind immer noch (trotz neuem Plug-In und Java Kernel) verbesserungsfähig.
Wenigstens gegen die Toolbar läuft jetzt eine change.org Petition:
https://www.change.org/petitions/oracle-corporation-stop-bundling-ask-toolbar-with-the-java-installer
Übrigens, kleiner Tipp. Wenn man die JRE (oder das JDK) nicht von java.com installiert, sondern den (Offline) Installer von den Oracle Seiten nimmt, so bekommt man die Frage nach der Ask-Toolbar nicht. Dürfte alle Firmen-Admins ohne Softwareverteilung freuen.
Diese Form des SSL DoS lässt sich aber zum Glück einfach erkennen und dank Rate Limits in Firewalls oder SSL Accelerators auch einfach abwehren. Etwas problematischer ist es da schon, dass auch in einer bestehenden TLS Verbindung die Gegenstelle eine neue Aushandlung der Schlüsseldaten anfordern kann. Dazu sind keine von der Firewall bemerkbaren neuen Verbindungen notwendig, und auch die Handshake Records die bei SSL ausgetauscht werden sind als solche nur mit anfälliger Heuristik zu erkennen.
Wenn man das nun mit der Eigenheit von RSA kombiniert, dass das entschlüsseln auf der Serverseite mehr CPU Zeit beansprucht als die Verschlüsselunsoperation, so erhält man eine neue Klasse von Angriffen auf die die
Jedenfalls ist es für einen öffentlich angebotenen SSL Server wichtig die Renegotiation Anfragen der Clients zu erkennen und zu limitieren. Neben den häufig verwendeten nativen Libraries wie puressl oder openssl gibt es auch noch weitere Implementierungen, z.B. den SSLServerSocket bei Java (im Falle der JVM von Oracle mit der ehemals von Sun stammenden JSSE Implementierung). Für diese bleiben aktuell zwei Strategien:
a) HandshakeListener auf jedem SSLServerSocket registrieren: dieser wird bei jedem erfolgreichen Handshake aufgerufen, er kann also mitzählen und ggf. Gegenmaßnahmen einleiten. Das hat aber einen großen Nachteil: der Listener selbst wird Asynchron in einem neuen Thread aufgerufen. Und dies auch dann wenn die Funktion im Listener nicht mehr macht als einen Zähler hochzählen. Das starten des Threads ist im Vergleich dazu viel belastender für den Server. Es sollte also vermieden werden überhaupt einen Listener zu registrieren.
b) Nach dem initialen Handshake die Liste der erlaubten Ciphers auf eine leere Liste setzen. Das führt dazu, dass alle weiteren Handshake Anfragen mit einem SSL Alert abgebrochen werden. Leider gilt das allerdings auch für Handshakes die vom Server angefordert werden (z.B. wenn Client Zertifikate angefordert werden sollen) oder auch bei sehr seltenem Schlüsselaustausch (bei lange bestehenden SSL Verbindungen sinnvoll).
Ich würde mir deswegen wünschen Oracle bessert hier an zwei Stellen nach: zum einen sollte man bei SSLServersockets direkt die Renegotiation die von Clients angefordert wird mittels eines Flags ignorieren/ablehnen können. Zudem macht es Sinn wenn der SSLServerSocket diese automatisch in der Anzahl begrenzen. Zum anderen sollte es möglich sein auf die Erzeugung eines Threads pro Handshake Event zu verzichten, z.b. indem der Anwender einen eigenen Executor mitgeben kann (der einen Pool hat oder aber bei sehr leichtgewichtigen Listener einfach synchron ausführt).
Übrigens bietet die JSSE2 Implementierung von IBM eine Systemproperty (com.ibm.jsse2.renegotiate=NONE) an, mit der man die Renegotiation ausschalten kann. Im Falle von Oracle kann nur die unsichere Renegotiation (ohne RFC5746) verboten werden. Das hilft aber nicht gegen den DOS Angriff.
Zum Weiterlesen
- SSL/TLS and Computational DoS (Eric Rescorla)
- SSL computational DoS mitigation (Vincent Bernant)
- Forward Secrecy for Google HTTPS (Adam Langley)
- JSSESocketServer.java testcode (GitHub Bernd Eckenfels)
C:> java -XX:+PrintFlagsFinal -version | find "GCLog"
uintx GCLogFileSize = 0 {product}
uintx NumberOfGCLogFiles = 0 {product}
bool UseGCLogFileRotation = false {product}
java version "1.7.0_07"
Java(TM) SE Runtime Environment (build 1.7.0_07-b10)
Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode)Ich würde das ganze immer zusammen mit der Details und Datestamp Option verwenden:java -Xloggc:log/app.vgc
-XX:GCLogFileSize=10M -XX:NumberOfGCLogFiles=10 -XX:+UseGCLogFileRotation
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:-PrintGCTimeStampsBei IBM kann man das übrigens dank kompaktem Syntax und Variablenexpansion deutlich besser machen:$ java -Xverbosegclog:app.%Y%m%d.%H%M%S.%pid.vgc,10,10000Und dazu liefert IBM sogar noch ein Handbuch...
Man sollte diese Feature (nicht nur aus Kompatibilitätsgründen) nicht ungeprüft übernehmen, zum Beispiel hat JBoss AS einen OracleValidConnectionChecker, der mittels Relfection die Oracle Spezialfunktion
pingDatabase() verwendet. Alternativ gibt es eine generische Implementierung, die einfach ein Statement absetzt.Die Prüfung ob eine DB connection noch funktioniert (entweder regelmässig im Hintergrund oder bei jedem ausleihen aus dem Pool) ist recht wichtig für einen stabilen Systembetrieb - aber auch sehr Performancerelevant. Mit pingDatabase würde man so vorgehen:
Wenn man aber die Implementierung analysiert, so stellt man schnell fest, dass die aktuelle Version des Treibers hier nicht sonderlich effizient implementiert ist. Es wird das SQL Statement
"SELECT 'X' FROM DUAL" abgesetzt. (Immerhin wird dazu vorher der Rückgabetyp deklariert: ((OracleStatement)stmt).defineColumnType (1, Types.CHAR, 1);)Es wird kein prepared statement eingesetzt, dass heisst der Datenbank Server muss bei jedem Aufruf das Statement mühsam parsen. Wenn ein Timeout angegeben wurde, so startet der JDBC Treiber bei jedem Ping einen neuen Thread. Auf die timeout Funktion sollte also auf jeden Fall verzichtet werden (Oracle hat diese Variante auch deprecated).
Letzteres wird dazu verwendet virtuelle Hosts auf einem SSL Port zu unterstützen: Bisher konnte ein SSL Server nämlich nicht wissen an welchen (der potentiell vielen) virtuellen Dienste hinter einer IP Adresse sich der SSL Client wenden will. Besonders ärgerlich ist dies im Fall von HTTP/s, dort ist es die Regel dass Hoster sehr viele Kunden-Domains hinter ein und der selben IP-Adresse betreiben. In HTTP/1.1 wird der gewünschte Servername in der Anfrage mitgegeben (Host: Header). So kann der HTTP Server entscheiden welche Webseiten er ausliefern soll.
Im Falle des SSL Server Zertifikats (welches im SSL Handshake schon vor der HTTP Anfrage ausgetauscht wird), kann dies der Webserver aber nicht. Er muss raten welches Zertifikat er dem Client präsentieren soll, und das schlägt natürlich in der Regel fehl. Mit der Extension wird der Servername auch im Handshake mitgeschickt, und der Server kann sein Zertifikat passend auswählen.
Problem bei der Geschichte ist: der Server darf auf eine solche Namensanfrage mit einem SSL Alert (Warning) reagieren. In dieser sagt er, dass er sich für den angefragten Host nicht zuständig fühlt. Das kommt bei aktuellen Webserver Installationen häufig vor, weil diese einfach nicht korrekt eingerichtet sind (und die modernen Browser die SNI unterstützen diese Warnung auch einfach ignorieren). Da das zurückgelieferte Default Zertifikat oftmals den richtigen Hostnamen (in einer der Attribute) enthält, klappt der gesicherte Handschlag im Alltag dennoch. Nicht jedoch mit Java 7 SSL Clients, JSSE macht daraus eine fatale Exception:
javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
at sun.security.ssl.ClientHandshaker.handshakeAlert
at sun.security.ssl.SSLSocketImpl.recvAlert
at sun.security.ssl.SSLSocketImpl.readRecord
at sun.security.ssl.SSLSocketImpl.performInitialHandshake
at sun.security.ssl.SSLSocketImpl.startHandshake
at sun.security.ssl.SSLSocketImpl.startHandshake
at sun.net.www.protocol.https.HttpsClient.afterConnect
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStreamIch habe deswegen einen Bugreport aufgemacht, jedoch wurde dieser wieder kommentarlos geschlossen. Falls Sie nun trotz Oracle's widerstreben die Notwendigkeit haben mit einem Web Server zu kommunizieren der SNI nicht richtig eingerichtet hat, so bleiben nur 2 Möglichkeiten über:
a) TLS aus der Liste der unterstützten Protokolle entfernen - mit einem SSLv3 Handshake wird kein Extension Record übertragen, und entsprechend klappt auch der Handshake (solange der Server SNI nicht benötigt).
b) den SSL Socket so initialisieren, dass die SSLEngine den Host nicht kennt. Dieser sogenannte Host hint wird für mehrere Dinge verwendet, kann aber auch weggelassen werden. Erreichen kann man dies, indem man den Socket statt mit
s=factory.createSocket(name, port); mit "s=factory.createSocket(); s.connect(name,port);" erzeugt.Übrigens ist dies ein ziemlich unerwartetes Verhalten: SSL mit Kerberos Authentifizierung würde nur auf die erste Art und Weise funktionieren, da hierfür die Identität des Servers bekannt sein muss.
Der Punkt a) ist ein schneller Fix, kommt aber in der Praxis eigentlich nicht in Frage, da man mit Java 7 ja eher daran Interesse hat TLSv1.1 oder TLSv1.2 zu verwenden um Lücken wie z.B. den BEAST-Angriff auszuschließen. Daher bleibt es nur übrig entweder den Anwendungscode zu ändern (oder wenn man diesen nicht selbst geschrieben hat, wie im Falle einer häufig verwendeten URLConnection oder beim Apache HTTPClient) oder aber mindestens eine eigene SSLSocketFactory zu implementieren, die auf die 2-stufige Erzeugung des SSLSockets aufsetzt.
Update: Ich habe in den Sourcen grade eine System Property gefunden, mit der man abschalten kann, dass der ClientHandshaker die SNI Extention sendet. Dies lässt sich als Work around gut verwenden: System.setProperty("jsse.enableSNIExtension", "false"); (muss vor der Verwendung von SSL Klassen im Programm, oder auf der Command Line gesetzt werden).
Beim Aufbau der Verbindung (TCP) direkt mit dem jTDS Treiber kommt es zu folgendem Fehler:
java.sql.SQLException: I/O Error: Software caused connection abort: recv failed State : 08S01 Error : 0
Und die folgende Exception wirft der Microsoft JDBC Driver for SQL Server:
com.microsoft.sqlserver.jdbc.SQLServerException: Connection reset State : 08S01 Error : 0
Wenn die Treiber durch einen Connection Pool benutzt werden, oder innerhalb einer Datasource, so kann es sogar zum Hängen (wegen Endlosschleife) kommen.
Eine Möglichkeit ist es, beim jTDS Treiber anzugeben, dass man kein SSL machen möchte (sollte aber eigentlich auch der default sein, laut jTDS FAQ). Dies kann man mit dem JDBC URL Property "
;ssl=no" erreichen. Wenn der Server allerdings auf "Force Encryption" konfiguriert ist, so wird er dann die Logins ablehnen. Beim Microsoft Treiber würde das property "encrypt=false" lauten, dies half aber in meinen Versuchen (mit MS SQL Server 2008 R2 Express) nicht.Eine weitere Möglichkeit ist es den SSL/TLS CBC-Fix per Java System Property abzuschalten:
-Djsse.enableCBCProtection=falseDies wirkt sich aber auf alle anderen SSL Verbindungen innerhalb dieser VM ebenfalls aus. Es gibt Berichte, dass dieses Problem mit JavaSE 6.0 Update 30 behoben sei, das kann ich aber weder nachvollziehen, noch lassen die ReleaseNotes darauf schließen. Ich habe mal einen Fehlerbericht bei jTDS dazu geöffnet.
String readToString1(InputStream in) throws IOException
{
byte[]buf = new byte[256];
StringBuilder sb = new StringBuilder();
int n;
do {
n = in.read(buf, 0, 256);
if (n > 0) {
String s = new String(buf, 0, n, "UTF-8");
sb.append(s);
}
} while(n > 0);
return sb.toString();
}Diese Funktion soll einen InputStream dessen Zeichen UTF-8 codiert sind in einen String lesen. Problem (mal abgesehen von der unnötigen Verwendung des if und den temporär angelegten String Objekten) bei dieser Funktion ist allerdings, dass der 256-byte Puffer in einen String umgewandelt wird, denn dabei werden ein oder mehrere Bytes durch den Zeichenkonverter gelesen. Falls der Zeichenkonverter dabei am ende des Puffers anlangt, so ist das Zeichen unvollständig. Das führt dann dazu dass ein Ersetzungszeichen (das Fragezeichen) am Ende des String steht. Man sollte solche starren bytepuffer also vermeiden, wenn man diese in Zeichen umwandeln will.Besser ist folgendes Vorgehen:
String readToString2(InputStream in) throws IOException
{
char[] buf = new char[128];
StringBuilder sb = new StringBuilder();
Reader r = new InputStreamReader(in, "UTF-8");
int n;
while((n = r.read(buf, 0, 128)) != -1)
{
sb.append(buf, 0, n);
}
return sb.toString();
}Um das unterschiedliche Verhalten zu testen, erzeuge ich einen InputStream der an ungeraden Byte Positionen Umlaute hat (also zuerst ein 1 byte Zeichen und dann lauter 2 byte Umlaute) und übergebe beiden Testmethoden diese Streams. Zusätzlich benutze ich noch Mockito, um zu ermitteln welche Methoden wie aufgerufen wurden:import static org.mockito.Mockito.*;
public static void main(String[] args) throws IOException
{
byte[] umlaut = "ö".getBytes("UTF-8");
byte[] inbuf = new byte[513];
inbuf[0] = 'X'; for(int i = 0;i<256;i++)
{inbuf[i*2+1] = umlaut[0]; inbuf[i*2+2] = umlaut[1]; }
InputStream in1 = spy(new ByteArrayInputStream(inbuf));
System.out.println(" readToString1()=" + readToString1(in1));
// make sure the inputstream was used with efficient block reads
verify(in1, times(4)).read(any(byte[].class), eq(0), eq(256));
verifyNoMoreInteractions(in1);
InputStream in2 = spy(new ByteArrayInputStream(inbuf));
System.out.println(" readToString2()=" + readToString2(in2));
// make sure the inputstream was used with efficient block reads
verify(in2, atMost(2)).read(any(byte[].class), eq(0), eq(8192));
verify(in2, atMost(1)).available();
verifyNoMoreInteractions(in2);
}Und hier das Ergebnis (gekürtzt): readToString1()=Xööö...ööö??ööö...ööö??
readToString2()=Xööö...öööööööö...ööööö
Am Ende jeden Puffers zerschneidet die erste Methode die 2 Bytes eines Umlauts, und deswegen erscheinen an diesen Stellen das Füllzeichen des Character Konverters. Bei UTF-8 streams ist es unwahrscheinlich dass ein multi-byte Zeichen ausgerechnet genau auf eine Blockgrenze fällt - umso unwahrscheinlicher ist es, dass ein Problem damit beim Testen auffällt.Übrigens ist es nicht notwendig hier einen BufferedInputStream oder BufferedReader zu verwenden. Der Reader wird ja bereits mit einem char array buffer (und nicht einzelnen Zeichen) gelesen. Zudem liest der InputSreamReader() aus dem darunterliegenden InputStream mit einem StreamDecoder der einen eigenen Lesepuffer (bei den Sun Klassen ist das ein 8kb Puffer) hat.
Im September 2011 haben die Sicherheitsforscher Duong und Rizzo nachgewiesen, dass eine - seit 2002 bekannte und in TLS 1.1 behobene - Schwachstelle in den SSL 3.0 und TLS 1.0 Verschlüsselungsprotokollen nicht nur theoretisch ist, sonder wirklich ausgenutzt werden können. Unter bestimmten Bedingungen erlaubt diese Schwachstelle einem Angreifer Teile aus einer SSL/TLS geschützten Übertragung zu ermitteln.
Gezeigt wurde dies am Beispiel eines abgefangenen paypal http Session Cookies, was erlaubte eine Browser Sitzung zu übernehmen. Das Problem ist unter dem Namen „BEAST“ (Browser Exploit Against SSL/TLS) bekannt, und wird unter der Common Vulnerability ID CVE-2011-3389 geführt. Bei der Demonstration wurde ein Java Applet verwendet um die notwendigen Daten einzuschleusen, nach Duong/Rizzo es soll aber auch mit WebSockets oder JavaScript XHR Aufrufen möglich sein.
Dies zugrundeliegende kryptografische Schwäche ist ein generelles Problem vom SSL 3.0/TLS 1.0. Es wurde aber als 2002 nur als theoretischer Angriff gesehen, jetzt sind Angriffe mit Hilfe der erweiterten Funktionen des WebBrowsers bekannt geworden. Da es für einen erfolgreichen Angriff nicht nur notwendig ist, dass die verschlüsselte Verbindung abgehört werden kann, sondern auch, dass der Angreifer in den Klartext eigene Stück einfügen kann (Chosen Plaintext). Dies ist durch die Verwendung von JavaScript auf Webseiten relativ einfach möglich.
Wird bei SSL/TLS eine Blockchiffre im CBC (Cipher Block Chaining) Modus verwendet, so benutzt SSL 3.0 und TLS 1.0 einen vom Vorgängerblock abgeleiteten Initialisierungsvektor. Da sich dieser ermitteln lässt ist hier ein Problem gegeben, das durch geschicktes einfügen von Füllzeichen in den Klartext erlaubt Inhalte zeichenweise an den Blockgrenzen zu ermitteln.
Dies zu behaben bedarf es einer neuen Protokollversion: TLS 1.1. Aber in TLS 1.0 und SSL 3.0 kann es nicht so einfach verhindert werden. Somit hilft mittelfristig nur ein Update auf diese „neuen“ Protokolle, die zwar schon Jahrelang verfügbar sind, sich aber in der Praxis in den meisten WebServern und WebBrowsern noch nicht durchgesetzt haben (vor allem nicht per default aktiviert sind).
Mögliche Gegenmaßnahmen zu BEAST (und deren Probleme) sind:
- SSL/TLS Ciphers nicht im CBC Modus verwenden. Diese können in den gängigen Browsern und Servern abgeschalten werden. Die Gefahr dass dann Gegenstellen keine gemeinsame Verschlüsselung aushandeln können ist allerdings groß. Sollte also nur bei einer kleinen Anzahl von bekannten Kommunikationsgegenstellen benutzt werden.
- Statt die CBC Chiffren abzuschalten kann auf jedenfall die Stromchiffre RC4 (TLS_RSA_WITH_RC4_128_SHA) bevorzugt werden. Diese verwendet kein CBC und ist damit nicht betroffen. Dies macht Google z.B. schon seit einiger Zeit. RC4 ist nicht unumstritten, es gibt Angriffe gegen das Verfahren, die aber in SSL nicht auftreten.
- Oracle Java (JSSE) ab Version 1.6.0_29 und 7.0.0_1 implementiert einen CBC Schutz (der mit dem System Property jsse.enableCBCProtection aus kompatibilitätsgründen wieder abgeschalten werden kann) bei dem der erste Block in zwei mit der Länge 1 und (n-1) gesplittet wird. Erst mit Java 7 wird TLS 1.1 und 1.2 unterstützt.
- Der gleiche Fix wird gerade in Chrome Beta 15 getestet, es gab schon Kompatiblitätsprobleme.
- Für Opera 10.51 war der Fix geplant, ist aktuell noch nicht vorhanden (Opera benutzt keine WebSockets).
- Dieser Schutz wird auch in Mozilla Firefox (via NSS library) eingebaut, wartet dort aber noch auf eine Lösung der Kompatibilitätsprobleme und ist somit nicht Bestandteil von Firefox 7 (Mozilla sagt die WebSockets sind nicht verwundbar).
- Es ist zu erwarten dass Microsoft für den Internet Explorer nachzieht, bisher empfehlen sie nur die RC4 Chiffre zu bevorzugen (Windows XP scheint dies zu tun), Vorsicht bei dem Umgang mit http Seiten walten zu lassen und ggf. TLS 1.1 auf Client und Server zu aktivieren (immerhin unterstützen Microsoft Produkte dies schon, wird nur aus Kompatibilitätsgründen nicht aktiviert).
- Die Option TLS 1.1 (oder neuer) zu verwenden wird leider noch einige Zeit auf sich warten lassen. Besonders da SSL 3.0 und TLS 1.0 abgeschalten werden müssten, um zu verhindern das Angreifer diese erzwingen. Leider hängt openssl der TLS 1.1 Entwicklung nach, so dass auch Apache hier nur mit der RC4 Cipher gerettet werden kann (alternativ kann man mod_gnutls oder mod_nss verwenden, die sind aber beide weniger stark in der Nutzung).
Bei IPv6 kann jedes Netzwerkinterface mehrere IPv6 Adressen haben, die dann entweder in unterschiedlichen Bereichen (Scopes) genutzt werden können (localhost, link, site, global) oder die unterschiedliche Bevorzugt (valid, prefered) sind. Durch die Unterstützung von Renumbering (stateless autoconfiguration) haben Adressen unterschiedliche Lebenszeiten. Zudem gibt es Adressen die über eine Migrations-/Tunnel Technologie wie Toredo, ISATAP oder 6to4 bereitgestellt werden, und nicht immer benutzt werden sollen.
Idealerweise würde eine Anwendung oder das Betriebssystem alle möglichen Quell/Ziel-Adresskombinationen ermitteln, und alle (aussichtsreichsten zuerst) durchprobieren. RFC 3484 beschreibt ein Verfahren für die Default Address Selection für IPv6. Der von Microsoft Research verfasste Entwurf gibt Regeln vor wie die Auswahl von Ziel- und Quelladressen zu geschehen hat, und definiert auch eine Möglichkeit dass der Administrator eines Systems eigene Gewichtungen definieren kann.
Ideal wäre eine Laufzeit Funktion, der man einen Hostnamen übergibt, und die dann die Verbindung zur Gegenstelle herstellt und dabei alle Regeln des RFC 3484 (und dringend notwendiger zukünftiger Verbesserungen) beachtet. Durch die Trennung zwischen Kernel und Usermode, und aus Gründen der Kompatibilität mit existierendem Netzwerkcode verwenden die meisten* Systeme allerdings ein anderes Verfahren. Bestehende Funktionen wie z.B.
getaddrinfo(3) wurden erweitert: die Auflösung von Hostnamen in Adressen liefert jetzt eine nach Präferenzen sortierte Liste der Zieladressen zurück. Dabei greift die Bibliotheksfunktion auf Adress- und Routinginformationen des Kernels zurück. Denn es müssen für jede zurückgelieferte Zieladresse auch die potentiellen Quelladressen bestimmt und bewertet werden.Unter Windows kann die Sortierung mit der prefixpolicy (
netsh.exe interface ipv6 show prefixpolicies) angezeigt werden. Linux Systeme speichern die Konfiguration in /etc/gai.conf, aktuelle Einstellungen können mit dem iproute2 Paket angesehen werden (ip addrlabel). Das ganze ist im Kernel und der glibc implementiert.Auch bei Java wurde kein komplett neues Verfahren für den Verbindungsaufbau für IPv6 definiert**. Die Anwendung selbst ist dafür zuständig alle möglichen Zieladressen der Reihe nach durchzuprobieren. Wenn die Anwendung keine Quelladresse angibt (was sie vermeiden sollte) so wird dann der Kernel für jeden der Zieladressen eine Quelladresse auswählen. Wenn eine Adresse nicht erreichbar ist, so muss die nächste Adresse verwendet werden. Wenn alle Adressen nicht erreichbar sind, so sollte eine Fehlermeldung zurückgegeben werden die alle probierten Zieladressen benennt und den ersten (oder alle) Fehlermeldungen benennt.
Beispielhaft kann dies so aussehen:
Socket connectToHost(String host, int port)
throws UnknownHostException, SocketException
{
IOException firstException = null;
InetAddress[] addressArray = InetAddress.getAllByName(host);
for(InetAddress addr : addressArray)
{
try {
return new Socket(addr, port);
} catch (IOException ex) {
if (firstException == null)
firstException = ex;
}
}
// build informative error message
StringBuilder msg = new StringBuilder("Unable to connect to host=");
msg.append(host); msg.append(" port=");
msg.append(String.valueOf(port)); msg.append(" [");
for(int i=0;i < addressArray.length;i++)
{
if (i != 0)
msg.append(',');
msg.append(addressArray[i]);
}
msg.append("]: "); msg.append(firstException.getMessage());
SocketException se = new SocketException(msg.toString());
se.initCause(firstException);
throw se;
}Dieser Code überlässt die Auswahl einer Quelle dem Kernel (es werden also nicht alle möglichen Kombinationen durchprobiert). Ebenso ist kein Handling für Timeouts enthalten, und ein Cache der Verbindungszustände erinnert oder gar ein paralleler Aufbau zu mehreren Zielen ist noch nicht enthalten. Trotzdem ist der Code schon recht komplex, sollte also nicht mehrfach implementiert werden müssen.
Von
InetAddress.getByName(String host) würde ich auf jeden Fall Abstand nehmen. Diese Methode gibt nur die bevorzugte Addresse zurück, und führt bei DualStack Anwendungen dazu, dass nicht IPv6 und IPv4 Adressen durchprobiert werden.* Microsoft ist typischerweise Entwicklerfreundlicher und muss weniger Rücksicht nehmen auf etablierte APIs, deswegen gibt es die Funktion WSAConnectByName() die alle Addressen selbst durchprobiert.
** Java kennt
Socket(String name, int port), dieser Konstruktor verwendet aber keine Schleife um alle möglichen Adressen zu kontaktieren.
Layout Bernd Eckenfels based on by Ricky Wilson | Serendipity Template by Carl Galloway | Login
Impressum
Bernd Eckenfels
Mörscher Str. 8
76185 Karlsruhe
bernd-2013@eckenfels.net
Read More

Kommentare