Skip to content

IPv6 default address selection in Java

Da bei IPv6 Nodes (also Rechner und andere Teilnehmer an einem Netzwerk) schon konzeptionell mehrere Adressen haben ist die Auswahl welche Quell- und Zieladresse verwendet werden soll eine wichtige Funktion. Insbesondere dann wenn ein Rechner im Dual-Stack Betrieb sowohl IPv4 als auch IPv6 Adressen erreichen kann. 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.

jarsigner mit Tokens

jarsigner mit ASEKey Token Dialog unter Windows 7Um Java Applets und JAR Files zu signieren nutzt man den jarsigner, der normalerweise ein Java-Keystore (.jks) file nutzt. Es ist allerdings ungünstig Codesigning Zertifikate einfach so in Files herumliegen zu haben (auch wenn man diese gesondert sichert). Es ist anzuraten ein Zertifikat besser in einer SmartCard oder einem Token zu speichern. Der Oracle jarsigner.exe verwendet die Java Security Provider der JCE API. Um damit ein Hardware Token anzusprechen gibt es im wesentlichen 2 Methoden. Entweder den PKCS#11 Provider (benötigt eine PKCS#11 library (.dll oder .so) des Hardwareherstellers) oder unter Windows den Microsoft CryptoAPI (MSCAPI) treiber. Letzteres hat den Vorteil dass die meisten Smartcards unter Windows 7 oder 2008 bereits Treiber beim einstecken installieren. Diese Minitreiber sind nur begrenzt nutzbar, aber für das signieren reicht es aus. Bei meinen Versuchen habe ich aber einige Einschränkungen gefunden:
  • Es wird Java 5,6 oder 7(>b144) benötigt.
  • Ich habe nur die Java Distribution von Sun/Oracle getestet
  • In der 64bit Variante wird der Provider aktuell nur von Java 7 ausgeliefert
  • Über MSCAPI wird immer in getrenntem Dialog nach PIN gefragt
Anbei Beispielbefehle zum auslesen des Keystores (in dem auch das Codesigning Zertifikat das auf dem USB Token gespeichert ist sichtbar ist) sowie dem Signaturvorgang:
c:\Program Files (x86)\Java\jdk1.6.0_24\bin>keytool -list -storetype Windows-MY

Keystore-Typ: Windows-MY
Keystore-Provider: SunMSCAPI

Ihr Keystore enthält 4 Einträge.
...
FIRMA AG, PrivateKeyEntry,
Zertifikatsfingerabdruck (MD5): 57:EF:97:04:EA:91:EE:FF:CF:BF:7F:75:AE:E1:A2:7D
...
c:\Program Files (x86)\Java\jdk1.6.0_24\bin>jarsigner.exe -storetype Windows-MY
%TEMP%\test.jar "FIRMA AG"
Als Speicher für die Zertifikate verwende ich ein Athena ASEKey Crypto Token in das ich mit den Athena IDProtect Tools auf einem anderen Computer das Codesigning Zertifikat von Verisign (Promo:WINQUAL99) importiert habe. Das Zertifikat ist übrigens ein Authenticode Class 3 Zertifikat das es aktuell für Teilnehmer am Microsoft WinQual Programm für ein Jahr günstig ($99) gibt. Entgegen den Angaben von Verisign lässt sich dieses auch problemlos für Java Signig verwenden. Einziges Problem bei der Sache, mit -storepass oder -keypass lässt sich die PIN für die SmartCard nicht angeben, diese fragt immer mit einem Popup nach entsprechender Freigabe. Ich habe in den Athena Tools keine Möglichkeit gefunden diese abzuschalten (allerdings geht es mit der PKCS#11 API, wie ich im nächsten Beitrag berichten werde).
c:\Program Files (x86)\Java\jdk1.6.0_24\bin>jarsigner -verify -certs -verbose %T
EMP%\test.jar

         134 Thu Jun 02 23:27:48 CEST 2011 META-INF/MANIFEST.MF
         258 Fri Jun 03 00:04:02 CEST 2011 META-INF/FIRMA_AG.SF
        4764 Fri Jun 03 00:04:02 CEST 2011 META-INF/FIRMA_AG.RSA
           0 Thu Jun 02 23:23:38 CEST 2011 META-INF/
sm     15104 Sun Mar 06 04:56:06 CET 2011 jarsigner.exe

      X.509, CN=FIRMA AG, OU=Software Development, 
                OU=Digital ID Class 3 - Microsoft Software Validation v2,
                  O=FIRMA AG, ST=Baden-Wuerttemberg, C=DE
      [certificate is valid from 21.12.10 01:00 to 22.12.11 00:59]
      X.509, CN=VeriSign Class 3 Code Signing 2010 CA
      [certificate is valid from 08.02.10 01:00 to 08.02.20 00:59]
      [KeyUsage extension does not support code signing]
      X.509, CN=VeriSign Class 3 Public Primary Certification Authority - G5
      [certificate is valid from 08.11.06 01:00 to 17.07.36 01:59]
      [KeyUsage extension does not support code signing]


  s = signature was verified
  m = entry is listed in manifest
  k = at least one certificate was found in keystore
  i = at least one certificate was found in identity scope

jar verified.
Update: Ich habe das ganze gerade mit der b144 von Java 7 getestet, und damit lassen sich sowohl mit 32bit als auch mit der 64bit Version der MSCAPI Provider für die Signatur von JAR Files via Smart Token nutzen.

Amazon Web Services als PaaS Anbieter

Mit dem neuen (Beta) Dienst Beanstalk steigt Amazon in das Platform-as-a-Service Geschäft ein. Vergleichbar mit dem Google AppEngine for Java Dienst können Java Entwickler einfach Web Anwendungen in WAR Files verpacken und in die Cloud hochladen. Amazon kümmert sich um die Skalierung aller Komponenten. Man könnte fast annehmen ursprünglich war das WAR Deployment Modell genau so gedacht. Amazon rechnet die unterliegenden Infrastrukturdienste ab. Das ergibt sich aus den Freiheitsgraden die man hat, macht die Rechnung aber komplex - und vor allem für Low-Traffic Anwendungen eventuell nicht gerade preiswert. Die Amazon Beispielrechnung kommt z.B. auf $30/Month. Immerhin können die sehr günstigen EC2 Micro Instanzen ($0.02/h) genutzt werden.

Lambdas in Java

Gerade bastle ich an einem Wrapper für DataSources der im Umgang mit Datenbanken helfen soll (zusätzliche Statistiken, blocking bei DB Fehlern und pausieren von DB Anfragen). Dabei wrappe ich zwei Methoden getConnection() und getConnection(String, String). Nur der Aufruf der realen Methode unterscheidet sich, der restliche Code ist in beiden Methoden gleich:

CODE:
public Connection getConnection(String user, String pass) ... {     verifyState();       while(true) {         try {             Connection c = ds.getConnection(user, pass);             verifyConnection(c); // throws SQLEx             return c;         } catch (SQLException e) {             handleException(e); // throws SQLEx         }     } } public Connection getConnection() ... {     verifyState();       while(true) {         try {             Connection c = ds.getConnection();             verifyConnection(c); // throws SQLEx             return c;         } catch (SQLException e) {             handleException(e); // throws SQLEx         }     } }

Im Sinne von DRY (don't repeat yourself) ist dies aber unschön, weil der (in Realität noch komplexere) Retry code doppelt vorkommt, und ich immer beide Methoden anpassen muss. Dieses Problem kommt oft bei Frameworks vor, und nennt sich "the whole in the middle" Muster. Eine Lösung wäre:

CODE:
public Connection getConnection(String user, String pass) {     return smartConnect(true, user, pass); } public Connection getConnection() {     return smartConnect(false, null, null); } Connection smartConnect(boolean hasArgs, String user, String pass) {     verifyState();       while(true) {         try {             Connection c;             // --- the whole in the middle             if (hasArgs)                 c = ds.getConnection(user, pass);             else                 c = ds.getConnection();             // ---             verifyConnection(c); // throws SQLEx             return c;         } catch (SQLException e) {             handleException(e);         }     } }

Das funktioniert aber nur im einfachsten Fall und es verkompliziert leider den Framework code, was auch wieder der Verständlichkeit schadet.

In C# 3.0 kann man das (wie ich grade gesehen habe) mit einer Lambda Action lösen (man kann also im Prinzip anonyme Funktionsblöcke übergeben). In Java müßte man dazu ein Objekt übergeben:

CODE:
public Connection getConnection(String user, String pass) {     return smartConnect(         new ConnectionProvider() {              Connection provide() { return ds.getConnection(user, pass); }}); } public Connection getConnection() {     return smartConnect(         new ConnectionProvider() {             Connection provide() { return ds.getConnection(); }}); } Connection smartConnect(ConectionProvider cp) {     verifyState();       while(true) {         try {             Connection c = cp.provide();             verifyConnection(c); // throws SQLEx             return c;         } catch (SQLException e) {             handleException(e); // throws SQLEx         }     } } abstract class ConnectionProvider {   Connection provide(); }

Der smartConnect() code wird damit lesbarer, und man kann auch komplexere Aktivitäten injizieren (eventuell mit Argumenten zur provide() methode), aber man muss jetzt noch eine extra Klasse definieren und alles in allem wird es auch mehr Code. Bei jedem Aufruf wird ein zusätzliches Objekt erzeugt. Ich denke ich werde also eher bei dem "if" Ansatz bleiben.

Ich vermute mal der Syntaktische Zucker "Lambda Expression" erzeugt bei C# auch ein extra Call Objekt, aber es fällt wesentlich weniger zusätzlichen Code beim Aufruf an.

Eclipse MAT und IBM Heapdumps (Cont.)

In meinem letzten Artikel zum IBM Plugin für den Eclipse Memory (Dump) Analyser (MAT) hatte ich noch beschrieben, dass der Import nur mit IBM Systemdumps zurechtkommt. IBM hat jetzt nachgelegt und kann nun auch das Portable Heap Dump (PHD) Format verarbeiten. Dabei gibt es aber Einschränkungen zu beachten. Dieses Format enthält zum Beispiel keine detaillierten Feld Inhalte und ohne Javacore Files fehlen Classloader Details. ibm.com/developerworks/java/jdk/tools/mat.html Das System Format sollte man bevorzugen, die Out of Memory Dumps werden aber normalerweise im PHD Format abgelegt, und diese können jetzt auch gelesen werden.

Java Concurrency Termine

Gleich zwei Termine zum Thema Java Concurrency stehen in der Region an: Java User Group Karlsruhe - "The Secrets of Concurrency" Mittwoch, 2009-02-11 19:15 Uhr Dr Heinz Kabutz (PhD CompSci) Author des The Java(tm) Specialists' Newsletter Ort: Uni Karlsruhe, ATIS, Am Fasanengarten 5, Geb. 50.34, 76131 Karlsruhe, Raum UG102 und ObjektForum Stuttgart - Herausforderung Multikern-Systeme Montag, 2009-02-16, 18.30 Uhr Prof. Dr. Walter F. Tichy Universität Karlsruhe / FZI Ort: Alte Scheuer, Degerloch

JBoss Hilfe gesucht

Ich bin auf der Suche nach (bezahlten) Experten im Bereich JbossMQ. Wir haben uns natürlich zuerst an RedHat gewannt mit unserer Anfrage. Die Annahme war, wir könnten uns eine Fragestunde mit einem der Entwickler kaufen (für eine spezifische 4.2 JBoss Version). Leider bietet RedHat diese Option nicht an. Deswegen haben wir uns sogar überlegt eine entsprechende Partnerschaft mit Support Vertrag abzuschliessen. Leider würde uns auch die nicht ermöglichen unsere bisherige Release Stratagie weiterzufahren, mal ganz abgesehen davon dass wir erheblichen Zusatzaufwand (Arbeit, Geld) für die Kundenlizensierung aufbringen müßten. Effektiv wäre dies ein embedded kommerzielles Produkt, was wir uns damit einhandeln würden. Diese unflexiblen Haltung ist eine Enttäuschung für uns. Es sieht danach aus, dass RH sich für dieses Vorgehen entschieden hat aus der Angst mit bezahltem Support für Jboss.org sich das Geschäftsmodell für JBoss.com kaputt zu machen. Diese Strenge Trennung zwischen den Produkten und die Distanzierung von der Community war mir so bis dahin noch nicht bewusst geworden. Aus dem Grund suche ich jetzt Entwickler (idealerweise Commiter) die sich mit JBossMQ auskennen (konkret geht es um das bekannte Problem dass MDBs sich aufhängen). Wir bezahlen nach Aufwand, bitte direkt Kontakt mit mir aufnehmen.

Pointer: Bad Java PRNG (Dinge die man beachten sollte)

Es ist immer wieder interessant die Analysen von Dan Dyer zu lesen. Er ist der Kopf hinter Java library uncommons-math, die insbesondere brauchbare Alternativen für Zufallszahlengeneratoren liefert.
int[] vals = new int[8];
for (int i = 0; i < 1500; i++)
    vals[new Random(i).nextInt(8)]++;
System.out.println(Arrays.toString(vals));
Dass dieser Code "falsch" ist weil er jeweils einen neuen PRNG erzeugt ist offensichtlich, warum es aber keinerlei vernünftig verteilte Zufallszahlen liefert (trotz den unterschiedlichen Seeds) ist eine ganz andere Sache, die er in seinem Artikel gut beschreibt.

Java Heapdumps und IBM

Die IBM JVM unterscheidet sich in einigen Aspekten deutlich von der Sun JVM. Im Bereich Betrieb gibt es eine ganze Reihe von Features die im IBM Java Diagnostics Guide (5.0, 6.0) zusammengefasst sind. Einen Bereich - den Java Heapdump - möchte ich hier mal näher beleuchten: Die IBM JVM kennt den klassischen Java Heap Dump, also einen Abzug der Java Objekte im Speicher. Diese Heap Dumps gibt es in verschiedenen Formaten. IBM unterstützt eine Textvariante und das sogenannten Portable Heap Dump Format, eingeschaltet mit -Xdump:heap. Problem ist, dass es nur von IBM Analysetools ausgewertet werden kann. Einer der besten freien Heap Dump Betrachtern ist das von der SAP gestiftete Projekt Eclipse MAT. Im Sun Umfeld sind diese Heap Dumps im HPROF Binary Format. Das Memory Analyser Tool kann deswegen Dumps von Sun, SAP und HP VM lesen. Gerade hat aber IBM ein Eclipse Pluging bereitgestellt, den IBM DTFJ Adapter. Dieses Plugin der den MAT um Import Möglichkeiten erweitert kann aber das PHD Format nicht verarbeiten. Die Java spezifischen Heap Dumps haben ein Vorbild: die Speicherabzüge (Core Dumps) der Betriebsysteme. Core Files oder Dr.Watson Logfiles werden angelegt wenn eine Anwendung eine kritische Ausnahme verursacht (z.B. Zugriffsschutzfehler). Im Falle der IBM VM kann man solche Core Dumps aber ebenfalls für die Analyse von Java Heaps verwenden. Die von IBM genannten System Dumps werden mit der -Xdump:system option bei der VM eingeschaltet. Wenn ein Core Dump stattfindet (entweder bei OutOfMemeoryErrors, bei Signalen, einem echten JVM Crash oder bei frei definierbaren Events) so wird dieser im System spezifischen Format in ein File geschrieben. IBM liefert ein Tool mit das sich jextract nennt (und im JRE Unterverzeichnis des SDK zu finden ist). Man muss dabei die jextract Version direkt aus dem verwendeten JRE nehmem (insbesondere auch auf dem Rechner auf dem der Coredump erzeugt wurde). Dies packt den aufbereiteten Core Dump zusammen mit systemspezifischen Details (z.B. Kopien der Symbole in den Libraries) in ein ZIP File. Dieses bearbeitete ZIP File kann direkt in den MAT (mit IBM Update) importiert werden. Meiner Erfahrung nach klappt dies nur, wenn jextract keine Warnung oder Fehler gemeldet hat. Der Ansatz auf dem Produktivsystem den Dump vorzuverarbeiten finde ich ganz geschickt, denn nicht immer hat der Entwickler Zugriff auf ein System mit vergleichbarer Kapazität. Übrigens kann dieses ZIP auch von dem IBM Diagnose Tool Framework for Java verarbeitet werden, so kann man Analyseprogramme die den Heap durchsuchen auch direkt in Java schreiben und auf dem Produktivserver ausführen.