Für selfHOST haben wir vor längerer Zeit Teile des Backends entwickelt, darunter den Export von Zonen aus der Datenbank ins DNS-System. Besondere Beachtung erhielten hierbei Umlautdomains: In der Datenbank werden diese „ganz normal“, also mit Umlauten, abgespeichert; beim Export in die DNS-Daten kommt das Perl-Modul IDNA::Punycode zum Einsatz.
Das Modul stellt eine Funktion „encode_punycode“ bereit, die den Punycode-String eines Strings mit UTF8-Werten zurückliefert. Davor musste dann noch der Präfix „xn--„. Eine entsprechende Zeile Programmcode sah nun so also etwa so aus:
$domain = 'xn--' . encode_punycode($domain);
(In Wirklichkeit ist es etwas aufwendiger, weil die einzelnen durch Punkt getrennten Teile eines vollständigen Hostnamens einzeln kodiert werden müssen, aber das sei hier mal dahingestellt.)
Nach Migration eines Teils der Software (die DynDNS-Updates wurden aus Performancegründen vom normalen Webserver separiert) auf einen separaten Server zeigten sich plötzlich merkwürdige Phänomene: Einzelne Domains waren plötzlich im DNS nicht mehr erreichbar, das Gros aber schon. Schnell kamen wir darauf, dass nur Umlautdomains betroffen waren – hier dann aber auch nicht alle. Wie konnte das sein? Die gesamte DynDNS-Update-Routine war ohne jede Änderung 1:1 auf das neue System kopiert worden …
Nach weiteren Analysen stellen wir festen: Wird die DNS-Zone durch den Webserver (altes System) neu geschrieben, zum Beispiel weil jemand DNS-Einträge webbasiert von Hand ändert, wird die Zone korrekt geschrieben. Wird die DNS-Zone durch den DynDNS-Server (neues System) neu geschrieben, tragen alle Umlautdomains einen doppelten Präfix. Von dieser Erkenntnis war es dann nur noch ein kleiner Schritt, um IDNA::Punycode als den Schuldigen auszumachen: Die Funktion setzt nun selbstständig den Präfix davor.
Der Autor bemerkt in der man page:
According to RFC 3490 the ACE prefix „xn--“ had been chosen as the standard. Thus, „xn--“ is also the default ACE prefix. For compatibility I’m leaving idn_prefix() in the module. Use „idn_prefix(undef)“ to get the old behaviour.
Nun, ist ja schön, dass er einem nun diese Arbeit abnimmt. Nur: Schon mal was von Abwärtskompatibilität gehört? Wenn ich eine Funktion gemäß der Dokumentation einsetze, erwarte ich, dass jene auch nach dem x-ten Update bei identischem Aufruf auch noch ein identisches Ergebnis liefert. Wie wär’s zum Beispiel hiermit gewesen:
- idn_prefix() wird als Funktion eingeführt und ermöglicht einem, mit einer einzigen Zeile Aufwand überall automatisch einen Präfix zu setzen. Benutzt man idn_prefix() nicht, verhält sich das Modul dem bisherigen Default entsprechend. Oder wie wär’s hiermit:
- encode_punycode() bleibt unangetastet, und es wird eine neue Funktion encode_punycode_with_prefix() eingeführt. Diese berücksichtigt die mittels idn_prefix() vorgenommene Einstellung, die dann meinetwegen auch standardmäßig auf ‚xn--‚ stehen kann.
So aber wurde die Abwärtskompatibilität (ohne Not!) gebrochen und zog auf diese Weise unnötigen Zeitaufwand für Support, Analyse und Fix nach sich. Schade!