Kategorie: Development

Domain-Robot meets DNS

Die http.net arbeitet ja ständig an der Verbesserung ihrer Schnittstellen. Und immer, wenn das Wetter uns nicht an den Strand lockt (oder die Getränke Supportanfragen ausgegangen sind), dann sitzen wir natürlich im Büro und überlegen, wie wir unseren Partnern ihre Arbeit noch weiter erleichtern können. Ja, so sind wir ;-)

Heute haben wir uns mal den Robot vorgenommen und haben gedacht: wie wär’s mit einem kleinen Hack!

Warum sollte man nicht einfach bei einem Domain-Template auch ein DNS-Template mit senden können? Eine bessere Verknüpfung der Komponenten war schließlich auch in den Kommentaren zur Umfrage gefordert worden. Was das Partnerweb und andere Schnittstellen betrifft, wird das ein wenig aufwändiger werden, aber beim SMTP-Robot ist die Lösung eigentlich ganz einfach. Schreiben wir doch mal auf, was wir haben wollen:


From: support@example.com
To: domreg@routing.net
Subject: REG: name.de

#internal: 999999/******

domain: name.de
owner-c: TEST1-HTTP
admin-c: TEST2-HTTP
tech-c: TEST3-HTTP
zone-c: TEST4-HTTP
nserver: ns.routing.net
nserver: ns8.routing.net

dnszone: name.de
primary: ns.routing.net
mailbox: dnsmaster@example.com
namesrv: ns.routing.net 213.160.64.64
namesrv: ns8.routing.net 213.160.65.64
arecord: @ 213.160.69.3
crecord: www @

So, jetzt muss man dem Robot nur noch sagen, dass er zuerst den unteren Teil an seinen Kollegen, den DNS-Robot, weitergeben und dann den ersten Teil selbst verarbeiten soll. Fertig :-)

Also, Spaß beiseite. Folgendes gilt ab sofort:

An das Ende eines Domreg-Templates kann man ein DNS-Template hängen, das mit der Zeile


dnszone: <domain>

(anstelle der domain-Zeile) beginnt. Daraus werden zwei separate Aufträge erzeugt, nämlich ein DNS-Template, das sofort an den den DNS-Robot übergeben wird, und ein Domain-Template, das etwa 2 bis 3 Minuten später an den Domreg-Robot gesendet wird, um sicherzustellen, dass die Zone zuerst eingetragen wird, falls dies für den Domain-Auftrag erforderlich ist.

Bei REG- und KK-Aufträgen an den Domreg-Robot wird ein REG-Auftrag für den DNS-Robot erzeugt, CLOSE- und TRANSIT-Aufträge kann man mit einem DNS-CLOSE verbinden und einem Domain-UPDATE kann man ein DNS-UPDATE anhängen. For Robot-Templates ohne dnszone-Schlüssel ändert sich natürlich nichts.

Einfach mal ausprobieren!

etwas backorder background

Backordern ist eine sportliche Angelegenheit. Es läuft ungefähr so:

Jede Nacht holen wir uns von der Registry die Liste mit den Domains ab, die 5 Tage später freigegeben werden. Das sind jeden Tag so an die 80.000 Namen, von denen 99.99% vollkommen uninteressant sind (adultentertainermedicals.com, …). Ein paar wenige schöne Namen sind aber oft dabei, so etwa verteilt wie die vierblättrigen Kleeblätter auf einer Kuhwiese.

Unser neuer Suchagent (powered by: EM) bietet die Möglichkeit, auf einen bestimmten Namen zu warten oder sich über freiwerdende Namen, die einem gegebenen Muster entsprechen, täglich informieren zu lassen. Jedenfalls, sobald die Domains in unserer Datenbank stehen, können sie bestellt werden.

Am Stichtag um 2 Uhr PM EST fängt die Registry an, die Domains freizugeben. Das zieht sich so 2 Stunden hin und es kommt darauf an, derjenige zu sein, der in genau der Millisekunde einen Auftrag für die Domain sendet, in der sie frei wird. In diesem weltweiten Bonbonregen treten jeden Tag Hunderte von Registraren an, Grabber und Snapper, die Giganten der Branche und neuerdings auch ein ambitionierter Berliner Provider.

Also, hier sind schon Ellenbogen gefragt. Unser Client läuft momentan in 4 Instanzen auf 2 Servern mit jeweils 5 bis 10 Threads, je nach Auftragsmenge. Jeder Thread öffnet in dem relevanten Zeitfenster eine persistente TCP-Session und sendet für die bestellten Domains 3 bis 4 REG-Aufträge pro Sekunde ab, bis entweder die Domain registriert oder die Zeit um ist. In der Praxis ist damit insgesamt ein durchschnittliches Intervall von ca. 20-30 Aufträgen pro Sekunde zu erreichen. Die Aufgabe ist nicht völlig trivial, da die Registry mehrere Pools unterschiedlicher Priorität mit jeweils einer begrenzten Anzahl paralleler Connections zur Verfügung stellt, von denen ein Teil für den Normalbetrieb garantiert sein muss.

Meistens klappt’s auch, aber leider nicht immer. Testläufe haben etwa eine Erfolgsquote von gefühlt 80% bei einigermaßen interessanten Namen ergeben. Statistische Aussagen dieser Art sind jedoch so schwierig wie die Frage nach dem Wert eines Domainnamens. Das ist also ein schönes Hobby, und den Programmierern kann endlich nicht mehr nachgesagt werden, zu wenig Sport zu treiben… ;)

Wir kennen das sicherlich alle:

Es gibt da eine SOAP-Schnittstelle, mit der man unbedingt etwas klären muss.
Menschen haben es da etwas schwerer als Rechner.

Also dann, man nehme sich kurzerhand einen solchen (Computer) und entscheide, welche Sprache man heute sprechen möchte. Heute, was bei mir eigentlich gestern war, fiel die Wahl auf Python.

Zum Schreiben eines SOAP-Clients bietet sich nach kurzer Befragung des Internet der Inhaber eines äußerst themengerechten, netten Logos an: SUDS

Alles ist etwas simpler, als es am Anfang scheint, nicht umsonst ist der erste Satz auf der offiziellen Seite dieses Frameworks, ich zitiere:

„Suds is a lightweight SOAP python client for consuming Web Services.“

Nicht wesentlich unterhalb dieses Satzes gelangt man auf eine nette “Getting Started”-Doku, die eigentlich alles enthält, was man sich für solch einen Einstieg erwünscht.
Erst einmal importiert man das Framework, oder, in diesem Fall, noch etwas weniger.

from suds.client import Client

Anschließend bastelt man sich einen solchen Client und gibt ihm auch gleich die Adresse des Servers.

client = Client('https://test/foo.bar/service.asmx?WSDL')

Solch ein Server hat sehr genaue Vorstellungen von Gesprächen, daher wird er uns genau sagen, was er in welcher Form gesagt bekommen möchte. Möchte man sich das anschauen, bietet es sich an mit

print client

einen Blick auf diese Anforderungen zu werfen.

Gehen wir nun davon aus, dass wir eine Methode benutzen wollen, die neben einem ganz normalen String auch noch einen komplexen Typ verlangt, eine Authentifizierung. Diese validiert die Berechtigung des Fragestellers mittels zweier Strings, dem Benutzernamen und dem Passwort. Da unser Client den Typ schon kennt, suchen auch wir uns dessen vordefinierten Namen. (Wichtig: Es geht explizit um die Anforderung des Servers, nicht um Typen die Teil von SUDS oder Python wären!)

Nehmen wir an, sein Name ist „AuthData“ und er beinhaltet die beiden Strings „username“ und „password“

auth = client.factory.create('AuthData')

bewirkt, dass eine Variable des Types AuthData nach den Spezifikationen des Servers erstellt wird.

Mit auth.username = 'admin' und auth.password = 'geheim'
(Beispiel für unsichere Zugangsdaten)

werden die Werte gefüllt.

Die Antwort des Servers wird erst zwischengespeichert

result = client.service.HalloServer(auth, 'Hallo')

und dann zum Beispiel mit einem

print result

ausgegeben. Fertig liest sich das dann:

from suds.client import Client
client = Client('https://test/foo.bar/service.asmx?WSDL')
auth = client.factory.create('AuthData')
auth.username = 'admin'
auth.password = 'geheim'
result = client.service.HalloServer(auth, 'Guten Morgen')
print result

Wer wissen möchte, wie so etwas realistisch in Bezug auf die http.net API aussehen würde, kann das Beispiel demnächst auch als Python-Sampleclient auf der API Dokuseite wiederfinden.

Klassen mal anders

Hier wieder eine kleine Aufgabe für unsere Azubis.


function A(p) {
   this.f = function(n) {
     return this[n] || (this[n] = p(this.f(n-1),this.f(n-2)))
   }
}

function B(p) {
   A.call(this,p);
   for (var i = 0; (this[i] = B.arguments[++i]); ); }

B.prototype = new A;

function f(n) {
   return (new B(function(x,y){return(x+y)},1,1)).f(n);
}

  1. Was ist f(17)?
  2. Welche Programmiersprache ist das?

Gnu Libidn EsZett Hotfix

Dies ist kein Update der Libidn auf IDNA2008. Ziel ist es, mit einfachen Mitteln das IDNA2003-Mapping von Codepoints der Kategorie PVALID (RFC 5892), insbesondere also des "ß" (U+00DF; LATIN SMALL LETTER SHARP S), bei Bedarf unterdrücken zu können. Ausgangspunkt ist die Erfordernis, kurzfristig Domainnamen mit "ß" innerhalb der DE-Zone verabeiten zu können.

Die Änderungen werden hier nur für die C#-Version in libidn-1.9 erwähnt und sind ggf. auf andere Sprachen oder Versionen zu übertragen.

Ziel ist es, die Funktionen ToAscii und ToUnicode um einen Parameter bool useIDNA2008 zu erweitern, der die Wirkung hat, dass PVALID Codepoints vom Mapping ausgenommen werden.

(1) Hinzufügen einer Klasse IDNA2008, die alle PVALID Codepoints enthält:


  // IDNA2008.cs
  namespace Gnu.Inet.Encoding {
    class IDNA2008
    {
      // rfc5892 PVALID codepoints
      public static char[] PVALID = new char[] {
        '\u00DF', // LATIN SMALL LETTER SHARP S
        '\u03C2', // GREEK SMALL LETTER FINAL SIGMA
        '\u06FD', // ARABIC SIGN SINDHI AMPERSAND
        '\u06FE', // ARABIC SIGN SINDHI POSTPOSITION ME
        '\u0F0B', // TIBETAN MARK INTERSYLLABIC TSHEG
        '\u3007'  // IDEOGRAPHIC NUMBER ZERO
      };
    }
  }

(2) Überladen der Funktion Map (Stringprep.cs) mit einem dritten Parameter, der zu ignorierende Codepoints bezeichnet:


  internal static void Map(StringBuilder s, char[] search, string[] replace, char[] ignore)
  {
    for (int i = 0; i < search.Length; i++)
    {
      char c = search[i];

      // check if c should be ignored
      bool ign = false;
      for (int t = 0; t < ignore.Length; t++)
      {
        if (ignore[t] == c)
        {
          ign = true;
          break;
        }
      }
      if (ign)
        continue;

      int j = 0;
      while (j < s.Length)
      {
        if (c == s[j])
        {
          //s.deleteCharAt(j);
          s.Remove(j, 1);
          if (null != replace[i])
          {
            s.Insert(j, replace[i]);
            j += replace[i].Length - 1;
          }
        }
        else
        {
          j++;
        }
      }
    }
  }

(3) Überladen der Funktion Nameprep (Strinprep.cs) mit einem dritten Parameter bool useIDNA2008:


  public static string NamePrep(string input, bool allowUnassigned, bool useIDNA2008)
  {
    if (input == null)
    {
      throw new System.NullReferenceException();
    }

    StringBuilder s = new StringBuilder(input);

    if (!allowUnassigned && Contains(s, RFC3454.A1))
    {
      throw new StringprepException(StringprepException.CONTAINS_UNASSIGNED);
    }

    Filter(s, RFC3454.B1);

    // EsZett Hotfix
    if (useIDNA2008)
      Map(s, RFC3454.B2search, RFC3454.B2replace, IDNA2008.PVALID);
    else
      Map(s, RFC3454.B2search, RFC3454.B2replace);

    s = new StringBuilder(NFKC.NormalizeNFKC(s.ToString()));
    // B.3 is only needed if NFKC is not used, right?
    // map(s, RFC3454.B3search, RFC3454.B3replace);

    if (Contains(s, RFC3454.C12) || Contains(s, RFC3454.C22) || Contains(s, RFC3454.C3) || Contains(s, RFC3454.C4) || Contains(s, RFC3454.C5) || Contains(s, RFC3454.C6) || Contains(s, RFC3454.C7) || Contains(s, RFC3454.C8))
    {
      // Table C.9 only contains code points > 0xFFFF which Java
      // doesn't handle
      throw new StringprepException(StringprepException.CONTAINS_PROHIBITED);
    }

    // Bidi handling
    bool r = Contains(s, RFC3454.D1);
    bool l = Contains(s, RFC3454.D2);

    // RFC 3454, section 6, requirement 1: already handled above (table C.8)

    // RFC 3454, section 6, requirement 2
    if (r && l)
    {
      throw new StringprepException(StringprepException.BIDI_BOTHRAL);
    }

    // RFC 3454, section 6, requirement 3
    if (r)
    {
      if (!Contains(s[0], RFC3454.D1) || !Contains(s[s.Length - 1], RFC3454.D1))
      {
        throw new StringprepException(StringprepException.BIDI_LTRAL);
      }
    }

    return s.ToString();
  }

(4) Überladen der Funktionen ToAscii und ToUnicode (IDNA.cs), um den Parameter
bool useIDNA2008 an Nameprep durchzureichen:


  public static string ToASCII(string input, bool allowUnassigned, bool useSTD3ASCIIRules, bool useIDNA2008)
  {
    // Step 1: Check if the string contains code points outside
    //         the ASCII range 0..0x7c.

    bool nonASCII = false;

    for (int i = 0; i < input.Length; i++)
    {
      int c = input[i];
      if (c > 0x7f)
      {
        nonASCII = true;
        break;
      }
    }

    // Step 2: Perform the nameprep operation.

    if (nonASCII)
    {
      try
      {
        input = Stringprep.NamePrep(input, allowUnassigned, useIDNA2008);
      }
      catch (StringprepException e)
      {
        // TODO
        throw new IDNAException(e);
      }
    }

    // Step 3: - Verify the absence of non-LDH ASCII code points
    //    (char) 0..0x2c, 0x2e..0x2f, 0x3a..0x40, 0x5b..0x60,
    //    (char) 0x7b..0x7f
    //         - Verify the absence of leading and trailing
    //           hyphen-minus

    if (useSTD3ASCIIRules)
    {
      for (int i = 0; i < input.Length; i++)
      {
        int c = input[i];
        if ((c <= 0x2c) || (c >= 0x2e && c <= 0x2f) || (c >= 0x3a && c <= 0x40) || (c >= 0x5b && c <= 0x60) || (c >= 0x7b && c <= 0x7f))
        {
          throw new IDNAException(IDNAException.CONTAINS_NON_LDH);
        }
      }

      if (input.StartsWith("-") || input.EndsWith("-"))
      {
        throw new IDNAException(IDNAException.CONTAINS_HYPHEN);
      }
    }

    // Step 4: If all code points are inside 0..0x7f, skip to step 8

    nonASCII = false;

    for (int i = 0; i < input.Length; i++)
    {
      int c = input[i];
      if (c > 0x7f)
      {
        nonASCII = true;
        break;
      }
    }

    string output = input;

    if (nonASCII)
    {

      // Step 5: Verify that the sequence does not begin with the ACE prefix.

      if (input.StartsWith(ACE_PREFIX))
      {
        throw new IDNAException(IDNAException.CONTAINS_ACE_PREFIX);
      }

      // Step 6: Punycode

      try
      {
        output = Punycode.Encode(input);
      }
      catch (PunycodeException e)
      {
        // TODO
        throw new IDNAException(e);
      }

      // Step 7: Prepend the ACE prefix.

      output = ACE_PREFIX + output;
    }

    // Step 8: Check that the length is inside 1..63.

    if (output.Length < 1 || output.Length > 63)
    {
      throw new IDNAException(IDNAException.TOO_LONG);
    }

    return output;
  }

  public static string ToUnicode(string input, bool allowUnassigned, bool useSTD3ASCIIRules, bool useIDNA2008)
  {
    string original = input;
    bool nonASCII = false;

    // Step 1: If all code points are inside 0..0x7f, skip to step 3.

    for (int i = 0; i < input.Length; i++)
    {
      int c = input[i];
      if (c > 0x7f)
      {
        nonASCII = true;
        break;
      }
    }

    // Step 2: Perform the Nameprep operation.

    if (nonASCII)
    {
      try
      {
        input = Stringprep.NamePrep(input, allowUnassigned, useIDNA2008);
      }
      catch (StringprepException e)
      {
        // ToUnicode never fails!
        return original;
      }
    }

    // Step 3: Verify the sequence starts with the ACE prefix.

    if (!input.StartsWith(ACE_PREFIX))
    {
      // ToUnicode never fails!
      return original;
    }

    string stored = input;

    // Step 4: Remove the ACE prefix.

    input = input.Substring(ACE_PREFIX.Length);

    // Step 5: Decode using punycode

    string output;

    try
    {
      output = Punycode.Decode(input);
    }
    catch (PunycodeException e)
    {
      // ToUnicode never fails!
      return original;
    }

    // Step 6: Apply toASCII

    string ascii;

    try
    {
      ascii = ToASCII(output, allowUnassigned, useSTD3ASCIIRules, useIDNA2008);
    }
    catch (IDNAException e)
    {
      // ToUnicode never fails!
      return original;
    }

    // Step 7: Compare case-insensitively.

    if (!ascii.ToUpper().Equals(stored.ToUpper()))
    {
      // ToUnicode never fails!
      return original;
    }

    // Step 8: Return the result.

    return output;
  }

(5) Testen von ToAscii und ToUnicode mit und ohne Anwendung von IDNA2008:


  [TestMethod()]
  public void Test030_EsZett_IDNA2003()
  {

    string u1 = "täßt";

    // Nameprep IDNA2203 should send "täßt" to "tässt"
    string u2 = Stringprep.NamePrep(u1, false, false);
    Assert.AreEqual("tässt", u2);

    // ToAscii IDNA2003 should send both "täßt" and "tässt" to "xn--tsst-loa"
    string a1 = IDNA.ToASCII(u1, false, true, false);
    Assert.AreEqual("xn--tsst-loa", a1);

    string a2 = IDNA.ToASCII(u2, false, true, false);
    Assert.AreEqual(a1, a2);

    // ToUnicode IDNA2003 should send "xn--tsst-loa" to "tässt"
    string u3 = IDNA.ToUnicode(a1, false, true, false);
    Assert.AreEqual(u2, u3);

  }

  [TestMethod()]
  public void Test040_EsZett_IDNA2008()
  {

    string u1 = "täßt";

    // Nameprep IDNA2208 should send "täßt" to "täßt"
    string u2 = Stringprep.NamePrep(u1, false, true);
    Assert.AreEqual(u1, u2);

    // ToAscii IDNA2008 should send "täßt" to "xn--tt-giat"
    string a1 = IDNA.ToASCII(u1, false, true, true);
    Assert.AreEqual("xn--tt-giat", a1);

    string a2 = IDNA.ToASCII(u2, false, true, true);
    Assert.AreEqual(a1, a2);

    // ToUnicode IDNA2003 should send "xn--tt-giat.de" to "täßt"
    string u3 = IDNA.ToUnicode(a1, false, true, true);
    Assert.AreEqual(u2, u3);

  }