More often than not, an application has dependecies to a backend: whether it is a frontend to a relational database system on which the schema might change, or be it a program that retrieves data from an LDAP directory server on which object classes may be modified, it is important to keep the frontend of the "fat client" in sync with the backend. A ubiquitious way of doing that is to compare some sort of version or release number to ensure that the frontend and the backend are compatible.

Several methods can be used to ensure release correlation between the front and backends; an HTTP GET request to a CGI script might retrieve version information which is then compared, a SELECT current_version FROM table can do similar on a relational database backend, or even a setting in a configuration store on the client machine, such as the registry on Windows clients and servers.

A method which is seldom used is DNS. The Domain Name System is a huge distributed database (arguably the World's largest distributed information database), which is generally used for translating hostnames such as www.example.com to their current TCP/IP address such as 10.254.138.203. The DNS orders the information it holds in so-called resource records (RR) of which there are many types: the A resource record holds an IP address, the MX RR stores a mail-exchanger name with its priority, the NS RR documents which Name Server(s) are authoritative for a domain, etc. One seldom used resource record is the TXT RR, which can hold an arbitrary string of up to 65535 octets.

In standard BIND notation, a DNS file (called a zone) for these four types of records looks like this:

$ORIGIN example.com.
.               IN NS  ns.example.com.
www             IN A   10.254.138.203
mail            IN MX  10 mail.example.com.
vers            IN TXT "Hello World"

Note how each line is formatted according to the type of information the resource record is allow to contain, and notes once again, that the TXT RR may contain an arbitrary string. To read up on DNS, I reccomend the WikiPedia article and for a good online guide to the working of the BIND (the Berkeley Internet Name Daemon) DNS for Rocket Scientists. One of the best books is Pro DNS and BIND.

The resource record I am interested in is the TXT RR. If I query this resource record with a tool able to retrieve different types of RR (ping is not a suitable utility for doing this) I would get something like this displayed:

$ host -t txt vers.example.com
vers.example.com text "Hello World"

$ dig vers.example.com txt
;; QUESTION SECTION:
;vers.example.com. IN    TXT

;; ANSWER SECTION:
vers.example.com. 60 IN  TXT     "Hello World"

How about using such a resource record to distribute the current application version? This technique will work over the Internet accross continents and within organizations that maintain their own Domain Name System servers. A query to the DNS is a very lightweight operation (a single UDP datagram will often suffice in returning the desired data) and it is cached by intermediary name servers (as long as the time to live (TTL) of the resource record hasn't expired) which reduces the load on the origin DNS server. A TXT RR is a good place to store our information, even though there are valid reasons against doing so.

The DNS administrator of your domain (be it on an internal corporate network or on the Internet) will have to update the record for you, unless she or he allows you to perform dynamic DNS updates.

Choose a hostname for the TXT RR which will uniquely identify your application. Since hostnames can be quite long (254 characters), and since nobody will have to type in this hostname, you can allocate almost anything. There is no danger in having names clash in the future either. By that I mean, suppose you wanted a TXT record called supercool.example.com and later you need a host with the same name, the DNS administrator would later simply add an A RR to supercool.example.com with the address of the host.

To avoid TXT RR other than those that contain our release/version information from "polluting" us, I have decided to have the resource record contain the string version_ in its value. This will allow constructs such as the following:

$ORIGIN example.com.
.               IN NS   ns.example.com.
www             IN A    10.254.138.203
mail            IN MX   10 mail.example.com.
vers            IN TXT  "Hello World"
vers            IN TXT  "maintainer is JP"
vers            IN TXT  "version 1.8"

Note how there are three TXT RR for the domain name vers.example.com. Oh, and do note, that the Domain Name System is designed to return the three values in arbitrary, usually round-robin type, order to the program that issues a query for the resource record. For this reason, and to allow entries that don't specify a release number for our software, I've used the notation on the last line in the example above; the program will simply remove the "version_" from the string before interpreting it.

Here is a small C function which has been tested on Windows XP, Windows 2003, as well as several flavors of Linux and Unix of course. Compilation instructions are located at the top of the program for both Linux/Unix and Windows. The latter was compiled with Microsoft Visual C++ 6.0 and the Microsoft Platform SDK.

/*
 * dns-version-txt.c (C)2006 by Jan-Piet Mens <jpmens at gmail.com>
 * The function getversionfromdns() returns a version string contained
 * in a TXT RR. 
 * This function works on Win32 as well as Unix/Linux, whereby it
 * requires the resolve.[ch] stubs which are (C)Kungliga Tekniska Hygkolan,
 * Sweden.
 *
 * To compile on Linux/Unix:
 *
 *  gcc -DUNIX=1 -o dns dns-version-txt.c resolve.c -lresolv
 *
 * To compile on Windows with MS Platform SDK:
 *
 *  cl -nologo -DWIN32=1 -o dns.exe dns-version-txt.c dnsapi.lib wsock32.lib
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
# include <windows.h>
# include <winerror.h>
# include <windns.h>
#else
# include <stdio.h>
# include "resolve.h"
#endif

#define DNSVNAME    "fuppsver.example.com"
#define RRprefix    "version "    /* The string before the release/version
                   * number in the TXT RR. If this is set
                   * to "version-", the TXT RR must contain
                   * "version-1.8"
                   */

/*
 * int getversionfromdns(char **versionstring)
 *   retrieve the version information from a TXT RR from DNS
 *   for the domain DNSVNAME. If version information cannot
 *   be retrieved, a non-zero value is returned. Otherwise,
 *   the pointer at `versionstring' contains the version
 *   string sans the prefix RRprefix. This pointer is overwritten
 *   at each invocation and must be freed by the caller.
 */

#ifdef WIN32
int getversionfromdns(char **versionstring)
{
    PDNS_RECORD qr, rp;
    DWORD rc;

    rc = DnsQuery(DNSVNAME, DNS_TYPE_TEXT, DNS_QUERY_STANDARD,
        NULL, &qr, NULL);
    if (rc != ERROR_SUCCESS) {
        return (-1);
    }

    *versionstring = NULL;

    for (rp = qr; rp != NULL; rp = rp->pNext) {
        if( rp->wType == DNS_TYPE_TEXT ) {
            int n;

            for (n = 0; n < rp->Data.Txt.dwStringCount; n++) {
                if (!strncmp(rp->Data.Txt.pStringArray[n], RRprefix, strlen(RRprefix))) {
                    *versionstring = strdup(rp->Data.Txt.pStringArray[n] + strlen(RRprefix));
                    break;
                }
            }
        }
    }

    DnsRecordListFree(qr, DnsFreeRecordList );

    return (0);
}

#else /* Unix */

#define TXT_RR    16

int getversionfromdns(char **versionstring)
{
    struct dns_reply *r;
    struct resource_record *rr;

    if ((r = dns_lookup (DNSVNAME, "TXT")) == NULL) {
        return (-1);
    }

    for (rr = r->head; rr; rr = rr->next) {
        if (rr->type == TXT_RR) {
            if (!strncmp(rr->u.txt, RRprefix, strlen(RRprefix))) {
                *versionstring = strdup(rr->u.txt + strlen(RRprefix));
                break;
            }
        }
    }

    dns_free_data(r);
    return (0);
}

#endif

int main()
{
    char *version;
    int rc;
    
    rc = getversionfromdns(&version);
    if (rc == 0) {
        printf("Version returned from DNS: %s\n", version);
        free(version);
    }
    return (0);
}

The Unix/Linux version of the code utilitzes the resolver stubs resolve.c and resolve.h, which are Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Hvgskolan (Royal Institute of Technology, Stockholm, Sweden).

What should the program do if the release/version information returned from the DNS doesn't match what is expected? Well, that very strongly depends on whether the application can continue to work or not. Some programmers might prefer to have the program abort with a diagnostic message and force the user to install a newer version. Others might be able to perform a remote software installation for the new program version. Yet again others, might decide to let the program continue, but instruct the "backend" to use a previous data version. There are innumerable scenarios, and I can only hint at some of the probable ones.

The only package I know of to use a similar technology is the excellent ClamAV scanning toolkit for Linux/Unix; its freshclam tool checks the value of a TXT RR to determine whether new signatures are available. The TXT resource record also contains a timestamp.

$ host -t txt current.cvd.clamav.net
current.cvd.clamav.net text "0.88.4:40:1891:1158557342:1"

Resources

Comments

blog comments powered by Disqus