Home page

HOWTO build a DNS registry

Sheets


Credits

How to contribute

Legal notice

Whois service

Search:

Printable version    Version française

Setting up a DNS registry with XML and XSL

Stephane Bortzmeyer

AFNIC

Immeuble International 78181 Saint-Quentin-en-Yvelines France <bortzmeyer@nic.fr>

$Date: 2004/03/14 20:59:18 $

Abstract

This document describes how to set up a small DNS registry using a XML file as the database (both social and technical) and XSLT to produce the zone file.


General view

Every DNS registry will require a database. For large registries, a real DBMS (DataBase Management System) is mandatory. But for small registries, it can be overkill. These small registries (for instance ccTLDs in developing countries) typically use adhoc techniques that have serious drawbacks:

  • Some use only the zone file as a database. Social information is made of comments in that zone file. It prevents any whois-like service to be created. And it does not allow automatic processing of the data (for instance, finding all the domains hold by a given entity).

  • Some use office software like MS-Excel. Since these programs have very poor groupware capabilities, it makes difficult for several people to work on the database.

The solution we suggest is to use a XML file for the database:

  1. Since it is a text file, groupware tools like CVS will be usable to allow group work.

  2. XML is an open format so you do not depend on Microsoft for your data.

  3. XML is suitable for automatic processing: we will mostly use XSLT but many other tools are possible.

Warning

This document has been throughly checked but the ideas presented here have never been actually deployed with a real registry.

Brief reminder about XML

In French, you can see the AFNIC training.

Writing schemas

One of the funny things about XML is that you can use several different programming languages to write the schema. You have:

  1. DTD (Document Type Definition), a legacy from SGML, widely implemented and recognized. The simplest solution.

  2. XML Schemas, a very verbose language. [1]

  3. Relax, the least common

The schema

When you set up a registry, you have to decide on a schema. Wether it is in SQL or written for XML, this is a policy choice. Here, we will skip the political part and I propose the following schema, written using the DTD language.

<!-- A simple schema for an Internet DNS registry -->

<!ELEMENT domain (fqdn,nameservers)>
<!ATTLIST domain holder IDREF #REQUIRED>
<!ATTLIST domain tech_c IDREF #REQUIRED>
<!ATTLIST domain admin_c IDREF #REQUIRED>

<!ELEMENT nameserver (name,ipaddress?)>

<!ELEMENT contact (name,address+,city,country,phone,email)>
<!ATTLIST contact handle ID #REQUIRED>

<!ELEMENT nameservers (nameserver)+> 
<!ELEMENT zone (domain|contact)+> 

<!ELEMENT fqdn (#PCDATA)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT address (#PCDATA)>
<!ELEMENT city (#PCDATA)>
<!ELEMENT country (#PCDATA)>
<!ELEMENT phone (#PCDATA)>
<!ELEMENT email (#PCDATA)>
<!ELEMENT ipaddress (#PCDATA)>

<!-- $Id: schema.dtd,v 1.1 2003/03/18 13:55:38 bortzmeyer Exp $ -->

The data

With such a schema, you can write a zone file.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE zone SYSTEM "schema.dtd">

<!-- $Id: example-zone.xml,v 1.3 2003/04/22 15:33:27 bortzmeyer Exp $ -->

<zone>

  <contact handle="SB1">
    <name>Bortzmeyer</name>
    <address>Abbey Road</address>
    <city>London</city>
    <country>uk</country>
    <phone>1234</phone>
    <email>bortzmeyer@hotmail.com</email>
  </contact>

  <domain holder="PL1" tech_c="CD1" admin_c="SB1">
    <fqdn>foobar.tld</fqdn>
    <nameservers>
      <nameserver>
	<name>ns1.example.com</name>
      </nameserver>
      <nameserver>
	<name>ns2.example.com</name>
      </nameserver>
    </nameservers>
  </domain>


  <contact handle="CD1">
    <name>Delacourt</name>
    <address>Chateau</address>
    <city>Cersailles</city>
    <country>fr</country>
    <phone>1234</phone>
    <email>celine@nic.fr</email>
  </contact>


  <contact handle="PL1">
    <name>Lubrano</name>
    <address>Immeuble International</address>
    <city>Saint-Quentin</city>
    <country>fr</country>
    <phone>1234</phone>
    <email>lulu@nic.fr</email>
  </contact>


  <domain holder="SB1" tech_c="SB1" admin_c="SB1">
    <fqdn>barfoo.tld</fqdn>
    <nameservers>
      <nameserver>
	<name>ns1.barfoo.tld</name>
	<ipaddress>10.20.30.40</ipaddress>
      </nameserver>
    </nameservers>
  </domain>

  <domain holder="SB1" tech_c="SB1" admin_c="PL1">
    <fqdn>dummy.tld</fqdn>
    <nameservers>
      <nameserver>
	<name>ns2.barfoo.tld</name>
	<ipaddress>10.20.30.41</ipaddress>
      </nameserver>
      <nameserver>
	<name>ns2.nic.fr</name>
      </nameserver>
    </nameservers>
  </domain>

</zone>



The zone is a simple text file. You can use any editor you want (although I would recommand Emacs with its psgml package, which simplifies XML edition a lot).

The processing

To create a zone file suitable for your nameserver (we will use the syntax of BIND and nsd), a XSL stylesheet can be as simple as:

<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:date="http://exslt.org/dates-and-times"
                version='1.0'>

<!-- $Id: zone2bind.xslt,v 1.2 2003/04/22 15:33:27 bortzmeyer Exp $ -->

  <xsl:output method="text"/>

  <xsl:preserve-space elements="*"/>

  <xsl:param name="tld">tld</xsl:param>

  <xsl:param name="soa">1</xsl:param> <!-- The SOA must be provided by the
  calling program or we default to 1 -->

  <xsl:param name="current_date">(UNSPECIFIED)</xsl:param>

  <xsl:param name="current_dir">(UNSPECIFIED)</xsl:param>

  <xsl:param name="current_host">(UNSPECIFIED)</xsl:param>

  <xsl:param name="zone_file">(UNSPECIFIED)</xsl:param>

  <xsl:template match="/">
    <xsl:text>; Zone file for the '.</xsl:text><xsl:value-of select="$tld"/><xsl:text>' domain
; Automatically produced by </xsl:text> 
      <xsl:value-of select="system-property('xsl:vendor')"/><xsl:text> (</xsl:text>
    <xsl:value-of select="system-property('xsl:vendor-url')"/><xsl:text>)
:   on </xsl:text>
    <xsl:choose>
      <xsl:when test="function-available('date:date-time')">
        <xsl:variable name="now" select="date:date-time()"/>
        <xsl:value-of select="date:year($now)"/>
        <xsl:text>/</xsl:text>
        <xsl:value-of select="date:month-in-year($now)"/>
        <xsl:text>/</xsl:text>
        <xsl:value-of select="date:day-in-month($now)"/>
	<xsl:text> (date obtained by the XSL processor)</xsl:text>
      </xsl:when>
      <xsl:otherwise>
	<xsl:value-of select="$current_date"/>
	<xsl:text> (date obtained from the calling program)</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:text>
; Generated in </xsl:text>    
    <xsl:value-of select="$current_host"/>
    <xsl:text>:</xsl:text>
    <xsl:value-of select="$current_dir"/>
<xsl:text>
;    from </xsl:text>
    <xsl:value-of select="$zone_file"/>
<xsl:text>
; Do not edit by hand
    </xsl:text>

$TTL    86400
@       IN      SOA     ns1.nic.tld. hostmaster.nic.tld. (
                        <xsl:value-of select="$soa"/>               ; Serial 
                          86400         ; Refresh
                          21600         ; Retry
                         604800       ; Expire
                         7200 )       ; Negative Cache TTL
;
@       IN      NS      ns1
@       IN      NS      ns2
    <xsl:text>&#10;&#10;</xsl:text>
    <xsl:apply-templates select="zone/domain"/>
  </xsl:template>

  <xsl:template match="/zone/domain">
    <!-- TODO: better handling of whitespace to have a prettier zone file -->
    <xsl:for-each select="nameservers/nameserver">
      <xsl:value-of select="../../fqdn"/>.  IN    NS   <xsl:value-of select="name"/>.
      <xsl:variable name="name"><xsl:value-of select='name'/></xsl:variable>
      <!-- Add the glue records, if necessary -->
      <xsl:if test="substring($name, 2 + string-length($name) - string-length('$tld')) = $tld">
	<xsl:text>&#10;</xsl:text>
	<xsl:value-of select="$name"/>.  IN    A   <xsl:value-of select="ipaddress"/>
      </xsl:if>
      <xsl:text>&#10;</xsl:text>
    </xsl:for-each>
    <xsl:text>&#10;</xsl:text>
  </xsl:template>


</xsl:stylesheet>




You can apply this stylesheet and automatically get the zone file in the BIND format. Here is an example with libxslt:

% xsltproc -o db.tld zone2bind.xslt example-zone.xml
      

But XML allows you much more. Suppose we want to get the information about a contact. we will use the xpath tool, part of Perl XML::XPath module[2]. xpath allows us to use Xpath queries against our data:

% xpath -e '//contact[@handle="SB1"]' ./example-zone.xml       
Found 1 nodes in ./example-zone.xml:
-- NODE --

<contact handle="SB1">
    <name>Bortzmeyer</name>
    <address>Abbey Road</address>
    <city>London</city>
    <country>uk</country>
    <phone>1234</phone>
    <email>bortzmeyer@hotmail.com</email>
  </contact>

% xpath -e '//contact[city="Saint-Quentin"]' ./example-zone.xml 
Found 1 nodes in ./example-zone.xml:
-- NODE --

<contact handle="PL1">
    <name>Lubrano</name>
    <address>Immeuble International</address>
    <city>Saint-Quentin</city>
    <country>fr</country>
    <phone>1234</phone>
    <email>lulu@nic.fr</email>
  </contact>

    

The whois server

The whois protocol is defined in RFC 954 (RFC means Request For Comments. The RFC are available on the IETF server.). You use it to find out social information about domain registrants, or contacts (technical or administrative). An example of use, with the whois program:

% whois -h whois.example.tld linux.tld
All rights reserved.
Copyright TLD-NIC

Domain: linux.tld

Registrant name: Torvalds
...
      

We present here a very simple whois server, but which is sufficient for our purposes. It opens the XML file, parse it into a DOM object, then waits for requests. When one arrives, it uses Xpath to query the data.

#!/usr/bin/python

# $Id: whoisd.py,v 1.3 2003/04/15 14:28:02 bortzmeyer Exp $

import SocketServer, time, re, sys, os, pwd, syslog, signal, string
from xml.dom import ext
from xml.dom.ext.reader import PyExpat
from xml.xpath import Evaluate

PORT = 43
user = "nobody"
db_file = "example-zone.xml"
InvalidDomain = "This domain name is not a legal one"
legal = """All rights reserved.
Copyright <Your organization>.

"""

def hup_received (signum, frame):
    global xml_dom_object
    syslog.syslog (syslog.LOG_INFO | syslog.LOG_DAEMON, \
                   "Reloading database")
    xml_dom_object = reader.fromStream(open(db_file))
    
class ThreadingServer (SocketServer.ThreadingMixIn,SocketServer.TCPServer):
    pass

class WhoisRequestHandler (SocketServer.StreamRequestHandler):

    def write_contact (self, handle, label):
        result = Evaluate("/zone/contact[@handle=\"" + \
                          handle + "\"]",
                          xml_dom_object.documentElement)
        contact_node = result[0]
        contact_name = Evaluate("name/text()", contact_node)[0].nodeValue
        self.wfile.write (label + " name: " +  contact_name + "\n")
        contact_address = Evaluate("address/text()", contact_node)[0].nodeValue
        self.wfile.write (label + " address: " +  contact_address + "\n")
        contact_city = Evaluate("city/text()", contact_node)[0].nodeValue
        self.wfile.write (label + " city: " +  contact_city + "\n")
        contact_email = Evaluate("email/text()", contact_node)[0].nodeValue
        self.wfile.write (label + " email: " +  contact_email + "\n")
        self.wfile.write ("\n")
        
    def handle (self):
        domain = string.strip(self.rfile.readline())
        # TODO: allow the search of a contact by its handle
        syslog.syslog (syslog.LOG_INFO | syslog.LOG_DAEMON, \
                       "Request for domain \"" + str(domain) + "\" from " +
                       str(self.client_address))
        if domain == "":
            raise InvalidDomain
        if not re.match('^[A-Za-z0-9\.\-]+$', domain):
            raise InvalidDomain
        # Use XPath to find the domain
        result = Evaluate("/zone/domain[fqdn=\"" + \
                          string.lower(domain) + "\"]",
                          xml_dom_object.documentElement)
        if not len(result):
            self.wfile.write ("No such domain \"" + domain + "\"\n")
            return
        domain_node = result[0]
        domain_name = Evaluate("fqdn/text()", domain_node)[0].nodeValue
        self.wfile.write (legal)
        self.wfile.write ("Domain: " +  domain_name + "\n\n")
        domain_holder = Evaluate("@holder", domain_node)[0].nodeValue
        self.write_contact(domain_holder, "Registrant")
        domain_admin = Evaluate("@admin_c", domain_node)[0].nodeValue
        self.write_contact(domain_admin, "Admin. contact")
        domain_tech = Evaluate("@tech_c", domain_node)[0].nodeValue
        self.write_contact(domain_tech, "Tech. contact")
        # TODO: display the nameservers
        
server = ThreadingServer (("", PORT),
                          WhoisRequestHandler)
server.allow_reuse_address = 1
if (os.getuid() == 0):
    uid = pwd.getpwnam(user)[2]
    os.setreuid(uid,uid)
syslog.openlog('whoisd')
reader = PyExpat.Reader()
xml_dom_object = reader.fromStream(open(db_file))
signal.signal(signal.SIGHUP, hup_received)
try:
    server.serve_forever()
except KeyboardInterrupt:
    syslog.syslog (syslog.LOG_INFO | syslog.LOG_DAEMON, \
                   "Terminating")


      



[1] "Schemas" has two meanings, the generic one, which include DTD, for instance and the specific one used in this paragraph.

[2] Such a tool is trivial to write with any Xpath library: I have a similar program in Python

Last news
Changing the IP address of the TLD name server

The whois service

Anycast, une nouvelle technique de gestion d'un parc de serveur de noms

Setting up a DNS registry with XML and XSL

The choices for a nameserver

Checking your domaine: why and how

Modélisation de données

Should you publish social information about you registrants?

The zone file generator

HOWTO setup a domain registry

IDN (Internationalized Domain Names)

DocBook/XML source of this page

For every question about generic NIC, please ask info@generic-nic.net.

(last rebuild by WML 2.0.8 (30-Oct-2001): Wednesday 9 March 2005)