//
// $Id: dnstracer.c, v 1.48 2004/07/08 11:15:17 mavetju Exp $
//

//
// Copyright (c) 2002 by Edwin Groothuis, edwin@mavetju.org.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY FATAL DIMENSIONS AND CONTRIBUTORS ``AS
// IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
// STAFF OF FATAL DIMENSIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//

#ifdef WIN32
    #include <winsock.h>
    #include <io.h>
    #include "getopt.h"
    // To reduce the amount of #ifdef's, these two are defined.
    // rand()/srand() is ISO C89 naming but it's obsoleted according
    // to the man-page.
    #define random	rand
    #define srandom	srand
#else
    #include <unistd.h>
    #include <sys/time.h>
    #include <sys/param.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <arpa/nameser.h>
    #include <netdb.h>
    #include <resolv.h>
#endif

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#include "dnstracer-missing.h"

#define DEFAULT_RETRIES			3
#define DEFAULT_CACHING			1
#define DEFAULT_NEGATIVE_CACHING	0
#define DEFAULT_OVERVIEW		0
#define DEFAULT_QUERYTYPE		ns_t_a
#define DEFAULT_NOIPV6			0
#define DEFAULT_EDNS0SIZE		1500

#ifdef NOIPV6
#define gethostbyname2(a, b) gethostbyname(a)
#endif

int	verbose = 0;
int	global_overview = DEFAULT_OVERVIEW;
int	global_retries = DEFAULT_RETRIES;
int	global_caching = DEFAULT_CACHING;
int	global_negative_caching = DEFAULT_NEGATIVE_CACHING;
int	global_querytype = DEFAULT_QUERYTYPE;
int	global_noipv6 = DEFAULT_NOIPV6;
int	global_timeout = 0;
int	global_edns0 = DEFAULT_EDNS0SIZE;
char *	global_source_address = NULL;

/*****************************************************************************/

struct arecord {
    char *		server_name;
    char *		server_ip;
    u_int16_t		type;
    char *		rr_name;
    char *		rr_data;
    struct arecord *	next;
};

struct busy {
    char *		server;
    struct busy *	next;
};

struct answer {
    char *		server;
    struct answer *	next;
};

struct dnssession {
    struct dnsheader *		send_header;	// The DNS header being sent
    struct dnsquestion *	send_question;	// The DNS question being sent
    struct dnsheader *		recv_header;	// The DNS header received
    struct dnsquestion *	recv_question;	// The DNS question received
    struct dnsrr *		send_additional;// First Additional RR send
    struct dnsrr *		answer;		// First Answer RR received
    struct dnsrr *		authority;	// First Authority RR received
    struct dnsrr *		recv_additional;// First Additional RR received
    struct dnssession *		next;

    char *			server;		// First server being queried
    char *			host;		// Hostname being queried

    int				recv_len;		// Raw packet with
    char *			recv_pkt;		// with pointers to...
    char *			recv_pkt_header;	// - header
    char *			recv_pkt_question;	// - question
    char *			recv_pkt_answer;	// - first answer RR
    char *			recv_pkt_authority;	// - first authority RR
    char *			recv_pkt_additional;	// - first additional RR

    int				socket;
    int				ipv6;
};

struct dnsheader {
	u_int16_t identification;
	union {
	    u_int16_t	flags;
	    struct bit {
#ifdef WORDS_BIGENDIAN
		u_char qr:1;
		u_char opcode:4;
		u_char aa:1;
		u_char tc:1;
		u_char rd:1;
		u_char ra:1;
		u_char zero:3;
		u_char rcode:4;
#else
		u_char rcode:4;
		u_char zero:3;
		u_char ra:1;
		u_char rd:1;
		u_char tc:1;
		u_char aa:1;
		u_char opcode:4;
		u_char qr:1;
#endif
	    } bit;
	} flags;
	u_int16_t	nquestions;
	u_int16_t	nanswerRR;
	u_int16_t	nauthorityRR;
	u_int16_t	nadditionalRR;
};

struct dnsquestion {
	u_int		querylength;
	u_char *	query;
	u_int16_t	type;
	u_int16_t	class;
};

struct dnsrr {
	u_char *	domainname;
	u_int16_t	type;
	u_int16_t	class;
	u_int32_t	ttl;
	u_int16_t	datalength;
	u_char *	data;

	u_char *	domainname_string; // uncompressed version of DN
	u_char *	data_string;	// parsed version of data if possible

	struct dnsrr *	next;
};

#define MAX_RR_TYPES	256
char *rr_types[MAX_RR_TYPES] = {
	"#0x00", "a",     "ns",    "#0x03", "#0x04", "cname", "soa",   "#0x07",
	"#0x08", "#0x09", "#0x0a", "#0x0b", "ptr",   "hinfo", "#0x0e", "mx",
	"txt",   "rp",    "#0x12", "#0x13", "#0x14", "#0x15", "#0x16", "#0x17",
	"#0x18", "#0x19", "#0x1a", "#0x1b", "aaaa",  "#0x1d", "#0x1e", "#0x1f",
	"#0x20", "srv",   "0x22",  "#0x23", "#0x24", "#0x25", "a6",    "#0x27",
	"#0x28", "opt",   "#0x2a", "#0x2b", "#0x2c", "#0x2d", "#0x2e", "#0x2f",
	"#0x30", "#0x31", "#0x32", "#0x33", "#0x34", "#0x35", "#0x36", "#0x37",
	"#0x38", "#0x39", "#0x3a", "#0x3b", "#0x3c", "#0x3d", "#0x3e", "#0x3f",
	"#0x40", "#0x41", "#0x42", "#0x43", "#0x44", "#0x45", "#0x46", "#0x47",
	"#0x48", "#0x49", "#0x4a", "#0x4b", "#0x4c", "#0x4d", "#0x4e", "#0x4f",
	"#0x50", "#0x51", "#0x52", "#0x53", "#0x54", "#0x55", "#0x56", "#0x57",
	"#0x58", "#0x59", "#0x5a", "#0x5b", "#0x5c", "#0x5d", "#0x5e", "#0x5f",
	"#0x60", "#0x61", "#0x62", "#0x63", "#0x64", "#0x65", "#0x66", "#0x67",
	"#0x68", "#0x69", "#0x6a", "#0x6b", "#0x6c", "#0x6d", "#0x6e", "#0x6f",
	"#0x70", "#0x71", "#0x72", "#0x73", "#0x74", "#0x75", "#0x76", "#0x77",
	"#0x78", "#0x79", "#0x7a", "#0x7b", "#0x7c", "#0x7d", "#0x7e", "#0x7f",
	"#0x80", "#0x81", "#0x82", "#0x83", "#0x84", "#0x85", "#0x86", "#0x87",
	"#0x88", "#0x89", "#0x8a", "#0x8b", "#0x8c", "#0x8d", "#0x8e", "#0x8f",
	"#0x90", "#0x91", "#0x92", "#0x93", "#0x94", "#0x95", "#0x96", "#0x97",
	"#0x98", "#0x99", "#0x9a", "#0x9b", "#0x9c", "#0x9d", "#0x9e", "#0x9f",
	"#0xa0", "#0xa1", "#0xa2", "#0xa3", "#0xa4", "#0xa5", "#0xa6", "#0xa7",
	"#0xa8", "#0xa9", "#0xaa", "#0xab", "#0xac", "#0xad", "#0xae", "#0xaf",
	"#0xb0", "#0xb1", "#0xb2", "#0xb3", "#0xb4", "#0xb5", "#0xb6", "#0xb7",
	"#0xb8", "#0xb9", "#0xba", "#0xbb", "#0xbc", "#0xbd", "#0xbe", "#0xbf",
	"#0xc0", "#0xc1", "#0xc2", "#0xc3", "#0xc4", "#0xc5", "#0xc6", "#0xc7",
	"#0xc8", "#0xc9", "#0xca", "#0xcb", "#0xcc", "#0xcd", "#0xce", "#0xcf",
	"#0xd0", "#0xd1", "#0xd2", "#0xd3", "#0xd4", "#0xd5", "#0xd6", "#0xd7",
	"#0xd8", "#0xd9", "#0xda", "#0xdb", "#0xdc", "#0xdd", "#0xde", "#0xdf",
	"#0xe0", "#0xe1", "#0xe2", "#0xe3", "#0xe4", "#0xe5", "#0xe6", "#0xe7",
	"#0xe8", "#0xe9", "#0xea", "#0xeb", "#0xec", "#0xed", "#0xee", "#0xef",
	"#0xf0", "#0xf1", "#0xf2", "#0xf3", "#0xf4", "#0xf5", "#0xf6", "#0xf7",
	"#0xf8", "#0xf9", "#0xfa", "#0xfb", "#0xfc", "#0xfd", "#0xfe", "any"
};

/*****************************************************************************/

char *get_resource(u_int16_t type, struct dnssession *session, char *buffer,
	int dots);
char *printablename(char *name, int withdots);

/*****************************************************************************/

//
// Extract information from received packets.
//

int
strnrcasecmp(const char *big, const char *little, size_t len)
{
    char *p;
    size_t lenl, lenb;
 
    lenl = strlen(little);
    lenb = strlen(big);

    if (lenl > lenb)
	return -1;
    if (len > lenl)
	return -1;

    p = (char *)big + lenb - len;
    return strncasecmp(p, little, len);
}

u_int32_t
get32bit(u_char *s)
{
    return 256*256*256*s[0] + 256*256*s[1] + 256*s[2] + s[3];
}

u_int16_t
get16bit(u_char *s)
{
    return 256*s[0] + s[1];
}

char *
getname(struct dnssession *session, char **thisname)
{
    int		compressing = 0;
    char *	p;
    static char	hostname[NS_MAXDNAME];

    //
    // Copy the name out of a received packet. It can be compressed.
    //

    //
    // If the name is empty, just return a dot.
    //
    if (*thisname[0] == 0) {
	strcpy(hostname, "\1.");
	(*thisname)++;
	return hostname;
    }

    p = *thisname;
    memset(hostname, 0, sizeof(hostname));
    while (p[0] != 0) {
	//
	// If a name is being compressed, set the pointer to the start of
	// the packet plus the offset. If this is the first compression,
	// move the end-of-this-name pointer two places further and stop
	// changing it from now.
	//
	if ((p[0]&0xc0) != 0) {
	    u_int16_t offset;
	    char oldp;

	    oldp = p[0];
	    p[0] &= 0x3f;
	    offset = get16bit(p);
	    p[0] = oldp;
	    p = session->recv_pkt + offset;
	    if (compressing == 0) *thisname += 2;

	    compressing = 1;
	    continue;
	}

	//
	// Just copy p[0]+1 bytes (+1 to save the length-byte). If
	// compression is not going on, move the end-of-this-name
	// pointer. The check for the next character being 0 is a
	// hack to make sure the end-of-name marked is skipped over
	// before returning the packet.
	// 
	//
	memcpy(hostname + strlen(hostname), p, p[0] + 1);
	if (compressing == 0) {
	    *thisname += p[0] + 1;
	    if (*thisname[0] == 0) *thisname += 1;
	}
	p += p[0] + 1;
    }

    return hostname;
}

char *
extract_rr(struct dnssession *session, char *thisrr, struct dnsrr **rr)
{
    struct dnsrr *	RR;
    char *		domainname;
    char *		p;

    RR = (struct dnsrr *)calloc(1, sizeof(struct dnsrr));
    RR->next = *rr;

    //
    // Extract just one resource-record.
    // If the record is a ns_t_ns or ns_t_cname, cache the name 
    // into data_string.
    //
    domainname = getname(session, &thisrr);
    RR->domainname = strdup(domainname);
    RR->domainname_string = strdup(printablename(domainname, 1));
    RR->type = get16bit(thisrr);
    RR->class = get16bit(thisrr + 2);
    RR->ttl = get32bit(thisrr + 4);
    RR->datalength = get16bit(thisrr + 8);
    RR->data = (u_char *)calloc(1, RR->datalength);
    memcpy(RR->data, thisrr + 10, RR->datalength);

    p = thisrr + 10;
    RR->data_string = strdup(get_resource(RR->type, session, p, 1));
    thisrr += 10 + RR->datalength;

    *rr = RR;
    return thisrr;
}

void
extract_data(struct dnssession *session)
{
    struct dnsheader *	header = NULL;
    struct dnsquestion *question = NULL;
    struct dnsrr *	answer = NULL;
    struct dnsrr *	authority = NULL;
    struct dnsrr *	additional = NULL;

    char *	pheader;
    char *	pquestion;
    char *	panswer;
    char *	pauthority;
    char *	padditional;

    char *pbuffer;
    int i;

    pbuffer = session->recv_pkt;

    //
    // Extract the header.
    //
    session->recv_pkt_header = pbuffer;
    pheader = pbuffer;
    header = (struct dnsheader *)calloc(1, sizeof(struct dnsheader));
    memcpy(header, session->recv_pkt, sizeof(struct dnsheader));
    header->identification = ntohs(header->identification);
    header->flags.flags = ntohs(header->flags.flags);
    header->nquestions = ntohs(header->nquestions);
    header->nanswerRR = ntohs(header->nanswerRR);
    header->nauthorityRR = ntohs(header->nauthorityRR);
    header->nadditionalRR = ntohs(header->nadditionalRR);
    pbuffer += sizeof(struct dnsheader);

    //
    // Extract the questions RR.
    //
    session->recv_pkt_question = pbuffer;
    pquestion = pbuffer;
    question = (struct dnsquestion *)calloc(1, sizeof(struct dnsquestion));
    question->query = strdup(getname(session, &pquestion));
    question->type = get16bit(pquestion);
    question->class = get16bit(pquestion + 2);
    pbuffer = pquestion + 4;

    //
    // Extract the answer RR
    //
    session->recv_pkt_answer = pbuffer;
    for (i = 0; i < header->nanswerRR; i++) {
	panswer = pbuffer;
	pbuffer = extract_rr(session, panswer, &answer);
    }

    //
    // Extract the authority RR
    //
    session->recv_pkt_authority = pbuffer;
    for (i = 0; i < header->nauthorityRR; i++) {
	pauthority = pbuffer;
	pbuffer = extract_rr(session, pauthority, &authority);
    }

    //
    // Extract the additional RR
    //
    session->recv_pkt_additional = pbuffer;
    for (i = 0; i < header->nadditionalRR; i++) {
	padditional = pbuffer;
	pbuffer = extract_rr(session, padditional, &additional);
    }

    session->recv_header = header;
    session->recv_question = question;
    session->answer = answer;
    session->recv_additional = additional;
    session->authority = authority;
}

/*****************************************************************************/

//
// Dump verbose data to the screen
//

char *
printablename(char *name, int withdots)
{
    static char hostname[NS_MAXDNAME];
    char *p, q;

    //
    // Convert the name from label-format into 'human' readable format,
    // either with dots or the size of the label as seperators.
    //
    if (name == NULL || name[0] == 0) {
	if (withdots == 0)
	    strcpy(hostname, "(0)root");
	else
	    strcpy(hostname, ".");
	return hostname;
    }

    hostname[0] = 0;
    p = name;
    while (p[0] != 0) {
	if (withdots == 0)
	    sprintf(hostname + strlen(hostname), "(%d)", p[0]);
	else
	    strcat(hostname, ".");
	q = p[p[0] + 1];
	p[p[0] + 1] = 0;
	sprintf(hostname + strlen(hostname), "%s", p + 1);
	p = p + p[0] + 1;
	p[0] = q;
    }

    if (withdots == 0)
	return hostname;
    else
	return hostname + 1;	// ignore the dot at the beginning of the string
}

char *
get_class(u_int16_t class)
{
    switch (class) {
    case ns_c_in:	return "Internet";
    case ns_c_chaos:	return "MIT Chaos-net";
    case ns_c_hs:	return "MIT Hesiod";
    case ns_c_none:	return "Pre-req in update";
    case ns_c_any:	return "Wildcard";
    default:		return "unknown";
    }
}

char *
get_type(u_int16_t type)
{
    switch (type) {
    case ns_t_a:	return "A";
    case ns_t_ns:	return "NS";
    case ns_t_cname:	return "CNAME";
    case ns_t_soa:	return "SOA";
    case ns_t_ptr:	return "PTR";
    case ns_t_any:	return "ANY";
    default:		return "unknown";
    }
}

char *
get_ttl(u_int32_t ttl)
{
    static char retval[NS_MAXDNAME];

    //
    // Return the TTL as a weeks/days/hours/minuts/seconds value
    //

    retval[0] = 0;
    if (ttl > 7*24*60*60) {
	sprintf(retval, "%dw", ttl/(7*24*60*60));
	ttl %= 7*24*60*60;
    }
    if (ttl > 24*60*60) {
	sprintf(retval + strlen(retval), "%dd", ttl/(24*60*60));
	ttl %= 24*60*60;
    }
    if (ttl > 60*60) {
	sprintf(retval + strlen(retval), "%dh", ttl/(60*60));
	ttl %= 60*60;
    }
    if (ttl > 60) {
	sprintf(retval + strlen(retval), "%dm", ttl/(60));
	ttl %= 60;
    }
    if (ttl > 0) {
	sprintf(retval + strlen(retval), "%ds", ttl);
    }

    return retval;
}

char *
get_resource(u_int16_t type, struct dnssession *session, char *buffer, int dots)
{
    static char retval[NS_MAXDNAME];

    //
    // Returns a parsed resource-data string. Only needed for
    // A, NS and CNAME records.
    //

    switch (type) {
    case  ns_t_a:
	sprintf(retval, "%hu.%hu.%hu.%hu",
		(u_char)buffer[0],
		(u_char)buffer[1],
		(u_char)buffer[2],
		(u_char)buffer[3]);
	return retval;

    case ns_t_aaaa:
	sprintf(retval,
		"%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
		"%02x%02x:%02x%02x:%02x%02x:%02x%02x",
		(u_char)buffer[ 0], (u_char)buffer[ 1],
		(u_char)buffer[ 2], (u_char)buffer[ 3],
		(u_char)buffer[ 4], (u_char)buffer[ 5],
		(u_char)buffer[ 6], (u_char)buffer[ 7],
		(u_char)buffer[ 8], (u_char)buffer[ 9],
		(u_char)buffer[10], (u_char)buffer[11],
		(u_char)buffer[12], (u_char)buffer[13],
		(u_char)buffer[14], (u_char)buffer[15]
		);
	return retval;

    case ns_t_hinfo:
    case ns_t_ns:
    case ns_t_ptr:
    case ns_t_cname:
    case ns_t_txt:
    case ns_t_rp:
	strcpy(retval, printablename(getname(session, &buffer), dots));
	return retval;

    case ns_t_srv:
    {
	u_int16_t prio, weight, port;

	prio = get16bit(buffer);
	buffer += 2;
	weight = get16bit(buffer);
	buffer += 2;
	port = get16bit(buffer);
	buffer += 2;
	sprintf(retval, "%hu %hu %hu %s", prio, weight, port,
	    printablename(getname(session, &buffer), dots));
	return retval;
    }

    case ns_t_mx:
    {
	u_int16_t us;

	us = get16bit(buffer);
	buffer += 2;
	sprintf(retval, "%hu %s", us,
	    printablename(getname(session, &buffer), dots));
	return retval;
    }

    case ns_t_soa:
    {
	static char retval[3*NS_MAXDNAME];
	char mname[NS_MAXDNAME];
	char rname[NS_MAXDNAME];
	u_int32_t ul;

	strcpy(mname, printablename(getname(session, &buffer), dots));
	strcpy(rname, printablename(getname(session, &buffer), dots));

	ul = get32bit(buffer);
	sprintf(retval, "serial: %ud mname: %s rname: %s", ul, mname, rname);
	return retval;
    }
    }
	
    sprintf(retval, "unknown type, see get_resource(0x%X)", type);
    return retval;
}

void
dump_question(struct dnsquestion *question)
{
    printf("- Queryname:            %s\n", printablename(question->query, 0));
    printf("- Type:                 %hu (%s)\n",
				    question->type, get_type(question->type));
    printf("- Class:                %hu (%s)\n",
				    question->class, get_class(question->class));
}

void
dump_header(struct dnsheader *header)
{
    printf("- Identifier:           0x%04hX\n", header->identification);
    printf("- Flags:                0x%02hX (", header->flags.flags);

    if (header->flags.bit.qr) printf("R "); else printf("Q ");
    if (header->flags.bit.aa) printf("AA ");
    if (header->flags.bit.tc) printf("TC ");
    if (header->flags.bit.rd) printf("RD ");
    if (header->flags.bit.ra) printf("RA ");
    printf(")\n");

    printf("- Opcode:               %hu ", header->flags.bit.opcode);
    switch (header->flags.bit.opcode) {
    case 0: printf("(Standard query)\n");break;
    case 1: printf("(Inverse query)\n");break;
    case 2: printf("(Server status request)\n");break;
    case 4: printf("(Notify)\n");break;
    case 5: printf("(Update)\n");break;
    case 14: printf("(Zone init)\n");break;
    case 15: printf("(Zone Ref)\n");break;
    default: printf("(unknown)\n");
    }

    printf("- Return code:          %hu ", header->flags.bit.rcode);
    switch (header->flags.bit.rcode) {
    case 0: printf("(No error)\n");break;
    case 1: printf("(Format error)\n");break;
    case 2: printf("(Server failure)\n");break;
    case 3: printf("(Name error)\n");break;
    case 4: printf("(Not implemented)\n");break;
    case 5: printf("(Refused)\n");break;
    case 6: printf("(Name exists)\n");break;
    case 7: printf("(RRset exists)\n");break;
    case 8: printf("(RRset does not exist)\n");break;
    case 9: printf("(Not authoritive)\n");break;
    case 10: printf("(Zone of record different from zone section)\n");break;
    default: printf("(unknown:%d)\n", header->flags.bit.rcode);break;
    }

    printf("- Number questions:     %hu\n", header->nquestions);
    printf("- Number answer RR:     %hu\n", header->nanswerRR);
    printf("- Number authority RR:  %hu\n", header->nauthorityRR);
    printf("- Number additional RR: %hu\n", header->nadditionalRR);
}

void
dump_rr(struct dnsrr *rr, struct dnssession *session)
{
    printf("- Domainname:           %s\n", printablename(rr->domainname, 0));
    printf("- Type:                 %hu (%s)\n",
				    rr->type, get_type(rr->type));
    printf("- Class:                %hu (%s)\n",
				    rr->class, get_class(rr->class));
    printf("- TTL:                  %u (%s)\n",
				    rr->ttl, get_ttl(rr->ttl));
    printf("- Resource length:      %hu\n", rr->datalength);
    printf("- Resource data:        %s\n",
				    get_resource(rr->type, session, rr->data, 0));
}

void
dump_data(struct sockaddr_in *dest4, struct sockaddr_in6 *dest6, struct dnssession *session)
{
    struct dnsrr *answerrr;
    struct dnsrr *authorityrr;
    struct dnsrr *additionalrr;

    if (verbose == 0) return;

    //
    // Dumps the output of session on the screen.
    //

    if (dest4 != NULL) {
	printf("IP HEADER\n");
	printf("- Destination address:  %s\n", inet_ntoa(dest4->sin_addr));
    }
    if (dest6 != NULL) {
	printf("IP HEADER\n");
	printf("- Destination address:  %s\n", "XXX");
    }

    if (session->send_header != NULL && session->recv_header == NULL) {
	printf("DNS HEADER (send)\n");
	dump_header(session->send_header);
    }

    if (session->recv_header != NULL) {
	printf("DNS HEADER (recv)\n");
	dump_header(session->recv_header);
    }

    if (session->send_question != NULL && session->recv_question == NULL) {
	printf("QUESTIONS (send)\n");
	dump_question(session->send_question);
    }
    if (session->send_additional != NULL && session->recv_question == NULL) {
	printf("ADDITIONAL (send)\n");
	dump_rr(session->send_additional, session);
    }
    if (session->recv_question != NULL) {
	printf("QUESTIONS (recv)\n");
	dump_question(session->recv_question);
    }

    answerrr = session->answer;
    while (answerrr != NULL) {
	printf("ANSWER RR\n");
	dump_rr(answerrr, session);
	answerrr = answerrr->next;
    }

    authorityrr = session->authority;
    while (authorityrr != NULL) {
	printf("AUTHORITY RR\n");
	dump_rr(authorityrr, session);
	authorityrr = authorityrr->next;
    }

    additionalrr = session->recv_additional;
    while (additionalrr != NULL) {
	printf("ADDITIONAL RR\n");
	dump_rr(additionalrr, session);
	additionalrr = additionalrr->next;
    }
}

/*****************************************************************************/

//
// Packet creation routines
//

u_char *
create_packet(struct dnssession *session, int len)
{
    u_char *	pkt;
    struct dnsheader	nheader;	// networked header
    struct dnsquestion	nquestion;	// networked question
    u_int32_t offset, l;
    u_int16_t s;

    //
    // Transform a "host" packet into a "network" packet.
    // In other words, copy everything and byte-swap some field.
    //

//    len = sizeof(struct dnsheader) + session->send_question->querylength + 4;
//    if (global_edns0) len += 11;

    pkt = (u_char *)calloc(1, len);

    memcpy(&nheader, session->send_header, sizeof(struct dnsheader));
    memcpy(&nquestion, session->send_question, sizeof(struct dnsquestion));

    nheader.identification = htons(session->send_header->identification);
    nheader.flags.flags = htons(session->send_header->flags.flags);
    nheader.nquestions = htons(session->send_header->nquestions);
    nheader.nanswerRR = htons(session->send_header->nanswerRR);
    nheader.nauthorityRR = htons(session->send_header->nauthorityRR);
    nheader.nadditionalRR = htons(session->send_header->nadditionalRR);

    nquestion.type = htons(session->send_question->type);
    nquestion.class = htons(session->send_question->class);

    memcpy(pkt, &nheader, sizeof(struct dnsheader));
    offset = sizeof(struct dnsheader);;
    memcpy(pkt + offset, nquestion.query, nquestion.querylength);
    offset += nquestion.querylength;
    memcpy(pkt + offset, &(nquestion.type), 4);
    offset += 4;

    if (global_edns0) {
	*(pkt + offset++) = '\0';

	s = htons(session->send_additional->type);
	memcpy(pkt + offset, &s, 2);
	offset += 2;

	s = htons(session->send_additional->class);
	memcpy(pkt + offset, &s, 2);
	offset += 2;

	l = htons(session->send_additional->ttl);
	memcpy(pkt + offset, &l, 4);
	offset += 4;

	s = htons(session->send_additional->datalength);
	memcpy(pkt + offset, &s, 2);
	offset += 2;
    }

    return pkt;
}

/*****************************************************************************/

//
// Network routines
//

int
create_socket(int PF)
{
    int	s;

    if ((s = socket(PF, SOCK_DGRAM, 0)) < 0) {
	perror("create_socket/socket");
	printf("If this is an IPv6 problem, run configure with --disable-ipv6\n");
	exit(1);
    }

    if (global_source_address != NULL) {
	struct addrinfo hints, *src_res;
	int error;

	// thanks to the src/usr.bin/telnet/commands.c of FreeBSD 4.7!
        memset(&hints, 0, sizeof(hints));
        hints.ai_flags = AI_NUMERICHOST;
        hints.ai_family = PF_INET;
        hints.ai_socktype = SOCK_DGRAM;
        error = getaddrinfo(global_source_address, 0, &hints, &src_res);
        if (error == EAI_NONAME) {
	    hints.ai_flags = 0;
	    error = getaddrinfo(global_source_address, 0, &hints, &src_res);
        }
        if (error != 0) {
	    perror(global_source_address);
	    if (error == EAI_SYSTEM)
		perror(global_source_address);
	    exit(1);
        }

	if (bind(s, src_res->ai_addr, src_res->ai_addrlen) < 0) {
	    perror("create_socket/bind");
	    exit(1);
	}
    }

    return s;
}

int
send_data(char *server, struct dnssession *session)
{
    int		cc;
    char *	pkt;
    int		len;
    struct sockaddr_in dest4;
#ifndef NOIPV6
    struct sockaddr_in6 dest6;
#endif

    len = sizeof(struct dnsheader) + session->send_question->querylength + 4;
    if (global_edns0) len += 11;

#ifndef NOIPV6
    if (session->ipv6) {
	memset(&dest6, 0, sizeof(struct sockaddr_in6));
	dest6.sin6_family = AF_INET6;
	dest6.sin6_port = htons(53);
	inet_pton(AF_INET6, server, &dest6.sin6_addr);

	dump_data(NULL, &dest6, session);

    } else
#endif
    {
	memset(&dest4, 0, sizeof(struct sockaddr_in));
	dest4.sin_family = AF_INET;
	dest4.sin_port = htons(53);
	dest4.sin_addr.s_addr = inet_addr(server);

	dump_data(&dest4, NULL, session);
    }

    pkt = create_packet(session, len);
    if ((cc = sendto(session->socket, pkt, len, 0,
#ifndef NOIPV6
	session->ipv6 ? (struct sockaddr *)&dest6 : (struct sockaddr *)&dest4,
	session->ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)
#else
	(struct sockaddr *)&dest4, sizeof(struct sockaddr_in)
#endif
	)) == -1) {
	    perror("send_data/sendto");
    }

    free(pkt);
    return cc;
}

int
receive_data(struct dnssession *session, int retry)
{
    char		buffer[2048];
    ssize_t		len;
    fd_set		in_set;
    struct timeval	timeout;

    timeout.tv_sec = 5 * (1 << retry);
    timeout.tv_usec = 0;
    if (global_timeout && timeout.tv_sec > global_timeout)
	timeout.tv_sec = global_timeout;

    FD_ZERO(&in_set);
    FD_SET(session->socket, &in_set);

    if (select(session->socket + 1, &in_set, NULL, NULL, &timeout) < 0)
	return 2;
    if (!FD_ISSET(session->socket, &in_set))
	return 3;
    if ((len = recv(session->socket, buffer, sizeof(buffer), 0)) == -1)
	return 1;

    if (get16bit(buffer) != session->send_header->identification) {
	fprintf(stderr, "Expected id: %hx, received id: %hx\n",
	    session->send_header->identification, get16bit(buffer));
	return 4;
    }

    session->recv_len = len;
    session->recv_pkt = (char *)calloc(1, len);
    memcpy(session->recv_pkt, buffer, len);
    extract_data(session);
    dump_data(NULL, NULL, session);
    return 0;
}

/*****************************************************************************/

//
// Record creation routines
//

void
create_header(struct dnssession *session)
{

    session->send_header = (struct dnsheader *)calloc(1, sizeof(struct dnsheader));

    //
    // Create a random identifier between 0 and 32675. It could be up to
    // 65535, but the high bit sometimes screws things up when comparing
    // the value received. Looks like it has something to do with
    // one-complement and two-complement, but don't know how to solve it.
    //
    session->send_header->identification	= random() & 0x7F7F;
    session->send_header->nquestions		= 1;
    if (global_edns0)
	session->send_header->nadditionalRR	= 1;
}

void
create_question(struct dnssession *session, char *name)
{
    char *p, *q;

    session->send_question =
	(struct dnsquestion *)calloc(1, sizeof(struct dnsquestion));

    session->send_question->querylength = strlen(name) + 2;
    session->send_question->query =
	(u_char *)calloc(1, session->send_question->querylength + 2);
    strcpy(session->send_question->query + 1, name);

    p = session->send_question->query + 1;
    q = session->send_question->query;
    while (p[0] != 0) {
	if (p[0] == '.') {
	    q[0] = p - q - 1;
	    q = p;
	}
	p++;
    }
    q[0] = p - q - 1;

    session->send_question->type = global_querytype;
    session->send_question->class = ns_c_in;
}

void
create_edns0(struct dnssession *session)
{
    if (!global_edns0) return;

    session->send_additional =
	(struct dnsrr *)calloc(1, sizeof(struct dnsrr));
    session->send_additional->domainname = NULL;
    session->send_additional->type = ns_t_opt;
    session->send_additional->class = global_edns0;
    session->send_additional->ttl = 0;
    session->send_additional->datalength = 0;
    session->send_additional->data = NULL;

    session->send_additional->next = NULL;

}

/*****************************************************************************/

//
// A record caching routines.
// It's just a linked list which hold a bunch of IP address from which
// we have gotten answers.
//

struct arecord *arecords = NULL;

void
add_arecord(struct dnssession *session, struct dnsrr *rr, char *server_name, char *server_ip)
{
    struct arecord *	arecord;

    arecord = (struct arecord *)calloc(1, sizeof(struct arecord));
    arecord->server_name = strdup(server_name);
    arecord->server_ip = strdup(server_ip);
    arecord->rr_name = strdup(printablename(rr->domainname, 1));

    if (rr->data_string == NULL)
	arecord->rr_data = NULL;
    else
	arecord->rr_data = strdup(rr->data_string);

    arecord->next = arecords;
    arecords = arecord;
}

void
display_arecords(void)
{
    struct arecord *	arecord;
    int			i;
    char		s[10];

    arecord = arecords;
    while (arecord != NULL) {
	printf("%s (%s)%n",
	    arecord->server_name, arecord->server_ip, &i);
	if (40 - i < 1)
	    printf(" ");
	else {
	    sprintf(s, "%%%ds", 40 - i);
	    printf(s, " ");
	}
	printf("%s -> %s\n", arecord->rr_name, arecord->rr_data);
	arecord = arecord->next;
    }
}

/*****************************************************************************/

//
// Answer caching routines.
// It's just a linked list which hold a bunch of IP address from which
// we have gotten answers.
//

struct answer *answers = NULL;

void
add_answer(char *server)
{
    struct answer *answer;

    answer = (struct answer *)calloc(1, sizeof(struct answer));
    answer->server = strdup(server);
    answer->next = answers;
    answers = answer;
}

int
has_answer(char *server)
{
    struct answer *answer;

    answer = answers;
    while (answer != NULL) {
	if (strcmp(answer->server, server) == 0)
	    return 1;
	answer = answer->next;
    }
    return 0;
}

/*****************************************************************************/

//
// Busy signal routines.
// It's just a linked list which hold a bunch of IP address of servers
// we are currently querying. Just to prevent that we query until we're
// out of memory.
//

struct busy *busies = NULL;

void
add_busy(char *server)
{
    struct busy *busy;

    busy = (struct busy *)calloc(1, sizeof(struct busy));
    busy->server = strdup(server);
    busy->next = busies;
    busies = busy;
}

void
remove_busy(char *server)
{
    struct busy *busy, *prev;

    if (strcmp(busies->server, server) == 0) {
	busy = busies;
	busies = busies->next;
	free(busy);
	return;
    }

    prev = busies;
    busy = prev->next;
    while (busy != NULL) {
	if (strcmp(busy->server, server) == 0) {
	    prev->next = busy->next;
	    free(busy);
	    return;
	}
    }
}

int
is_busy(char *server)
{
    struct busy *busy;

    busy = busies;
    while (busy != NULL) {
	if (strcmp(busy->server, server) == 0)
	    return 1;
	busy = busy->next;
    }
    return 0;
}

/*****************************************************************************/

//
// The core of this program
//

int
create_session(char *host, char *server_ip, int ipv6, char *server_name,
    char *server_authfor, int depth, char *prefix, int last)
{
    struct dnssession * session;
    struct dnsrr *	rrauth;
    struct dnsrr *	rradd;
    int			i, retval, errorcode, refersbackwards = 0;
    char		s[NS_MAXDNAME];

    //
    // Print the graphs in front of the servernames
    //
    if (depth != 0) {
	printf("%s%c", prefix, last == 1 ? ' ' : '|');
	printf("\\___ ");
    }

    printf("%s ", server_name);

    if (server_authfor != NULL)
	printf("[%s] ", server_authfor);

    if (server_ip[0] == 0) {
	printf("(No IP address)");
	return 1;
    }

    if (global_noipv6 && ipv6) {
	printf("(%s) Not queried", server_ip);
	return 1;
    }
#ifdef NOIPV6
    if (ipv6) {
	printf("(%s) Not queried", server_ip);
	return 1;
    }
#endif

    printf("(%s) ", server_ip);
    fflush(stdout);

    //
    // If this address is already being queried, ignore it to prevent
    // recursion. Do not add it as being cached, because one or more 
    // records in it might be unreachable.
    //
    if (is_busy(server_ip) == 1) {
	printf("Lame server ");
	fflush(stdout);
	return 0;
    }

    //
    // Ignore things we have already displayed
    //
    if (global_caching && has_answer(server_ip)) {
	printf("(cached)");
	return 0;
    }

    //
    // To prevent infinite recursion, mark this server as being probed
    //
    add_busy(server_ip);

    //
    // Create a nice DNS packet. Each session has its own port, so we
    // don't have to worry about packets received from previous sessions.
    //
    session = (struct dnssession *)calloc(1, sizeof(struct dnssession));
    session->socket = create_socket(ipv6 ? AF_INET6 : AF_INET);
    session->ipv6 = ipv6;
    session->server = strdup(server_ip);
    session->host = strdup(host);
    create_header(session);
    create_question(session, host);
    create_edns0(session);

    //
    // Send the packet and wait for an answer.
    //
    errorcode = 0;
    for (i = 0; i < global_retries; i++) {
	send_data(server_ip, session);
	if ((errorcode = receive_data(session, i)) == 0)
	    break;
	printf("* ");
	fflush(stdout);
    }
    close(session->socket);

    //
    // Timeouts and other weird stuff. Make sure we remove the busy-flag.
    //
    if (errorcode != 0) {
	remove_busy(server_ip);
	if (global_negative_caching) add_answer(server_ip);
	return 1;
    }

    //
    // We have an answerRR from this server, print a message.
    // Also cache it for later.
    //
    if (session->recv_header->nanswerRR != 0) {
	struct dnsrr *answer;

	if (session->recv_header->flags.bit.aa)
	    printf("Got authoritative answer ");
	else
	    printf("Got answer ");

	answer = session->answer;
	while (answer != NULL) {
	    if (answer->type != global_querytype)
		printf("[received type is %s] ", rr_types[answer->type]);
	    add_arecord(session, answer, server_name, server_ip);
	    answer = answer->next;
	}
	if (global_caching) add_answer(server_ip);
    }

    //
    // If the domainname in the authority section is the same as
    // the one from this server, mark it as lame.
    //
    if (session->authority != NULL && server_authfor != NULL) {
	if (session->recv_header->flags.bit.aa == 0 &&
	    strcasecmp(server_authfor,
		       session->authority->domainname_string) == 0) {
	    printf("Lame server ");
	    remove_busy(server_ip);
	    return 0;
	}
    }


    //
    // If the current server has an authoritative answer, don't go
    // further.
    //
    if (session->recv_header->flags.bit.aa) {
	remove_busy(server_ip);
	return 0;
    }

    //
    // When no answers were received, go through the list of authorities
    // and ask them for it. Maybe the IP address of the authority was
    // in the additional RRs, maybe there are two IP address in
    // the list of additional RRs, maybe there are none and a
    // gethostbyname() has to be done.
    //
    retval = 0;
    rrauth = session->authority;
    while (rrauth != NULL) {
	int	found = 0;
	char	nextserver_ip[NS_MAXDNAME];
	char	nextserver_name[NS_MAXDNAME];

	//
	// Only serve NS records
	//
	if (rrauth->type != ns_t_ns) {
	    rrauth = rrauth->next;
	    continue;
	}

	//
	// If the domainname in the authority section is not a postfix
	// of what we have, don't go there. This might happen if we are
	// looking through cnames from different domains.
	//
	if (strcmp(rrauth->domainname_string, ".") == 0) {
	    rrauth = rrauth->next;
	    if (!refersbackwards++)
		printf("Refers backwards ");
	    continue;
	}

	if (server_authfor != NULL && strcmp(server_authfor, ".") != 0 &&
	    strnrcasecmp(rrauth->domainname_string, server_authfor,
		strlen(server_authfor)) != 0) {
	    if (!refersbackwards++)
		printf("Refers backwards ");
	    rrauth = rrauth->next;
	    continue;
	}

	//
	// Count the number of IP addresses in the additionalRR
	// for this authorityRR
	//
	rradd = session->recv_additional;
	while (rradd != NULL) {
	   if (strcmp(printablename(rradd->domainname, 1),
		rrauth->data_string) == 0)
		found++;
	    rradd = rradd->next;
	}

	rradd = session->recv_additional;
	do {
	    //
	    // Find the first IP address
	    //
	    while (rradd != NULL) {
		if (strcmp(printablename(rradd->domainname, 1),
			   rrauth->data_string) == 0)
		    break;
		rradd = rradd->next;
	    }

	    //
	    // Prepare the graphs in front of the servers name
	    //
	    sprintf(s, "%s%c%s",
		prefix, last == 1 ? ' ' : '|', depth == 0 ? "" : "     ");

	    //
	    // Recurse into this server...
	    //
	    if (rradd != NULL) {
		// This is easy, we got the IP address in the additional
		// section. Don't worry about additional records with the
		// same name, they're done automaticly after this one.

		printf("\n");

		strcpy(nextserver_name, printablename(rradd->domainname, 1));
		strcpy(nextserver_ip, rradd->data_string);
		retval += create_session(host,
			    nextserver_ip, (rradd->type == ns_t_aaaa) ? 1 : 0,
			    nextserver_name, rrauth->domainname_string,
			    depth + 1, s,
			    (rrauth->next == NULL && found <= 1) ? 1 : 0);
	    } else {
		int ip, ipfound = 0;

		strcpy(nextserver_name, rrauth->data_string);

#ifdef NOIPV6
		for (ip = 0; ip < 1; ip++) {
#else
		for (ip = 0; ip < 2; ip++) {
#endif
		    int count, i;
		    struct hostent *h;
		    char **addr_list = NULL;

		    h = gethostbyname2(nextserver_name,
			    ip == 0 ? AF_INET : AF_INET6);
		    if (h == NULL) continue;

		    //
		    // One or more IP address were found. Go through all
		    // of them (make sure they're saved before calling
		    // gethostbyname() again).
		    //
		    count = 0;
		    while (h->h_addr_list[count] != NULL) count++;
		    addr_list = (char **)calloc(count, sizeof(char *));
		    for (i = 0; i < count; i++) {
			addr_list[i] = (char *)calloc(1, h->h_length);
			memcpy(addr_list[i], h->h_addr_list[i], h->h_length);
		    }

		    for (i = 0; i < count; i++) {
			if (ip == 0) {
			    u_char *s = addr_list[i];
			    sprintf(nextserver_ip,
				"%hu.%hu.%hu.%hu", s[0], s[1], s[2], s[3]);
			    ipv6 = 0;
			} else {
			    u_char *s = addr_list[i];
			    sprintf(nextserver_ip,
				"%02hx%02hx:%02hx%02hx:%02hx%02hx:%02hx%02hx:"
				"%02hx%02hx:%02hx%02hx:%02hx%02hx:%02hx%02hx",
				s[ 0], s[ 1], s[ 2], s[ 3], s[ 4], s[ 5], s[ 6], s[ 7],
				s[ 8], s[ 9], s[10], s[11], s[12], s[13], s[14], s[15]);
			    ipv6 = 1;
			}
			printf("\n");

			retval += create_session(host,
			    nextserver_ip, ip == 1,
			    nextserver_name, rrauth->domainname_string,
			    depth + 1, s,
			    (rrauth->next == NULL && found <= 1) ? 1 : 0);
			ipfound++;
		    }
		}

		if (ipfound == 0) {
		    //
		    // No IP address was found for this hostname.
		    // Just call the function and let them print
		    // an error.
		    //
		    printf("\n");
		    nextserver_ip[0] = 0;
		    retval += create_session(host,
			nextserver_ip, 0,
			nextserver_name, rrauth->domainname_string,
			depth + 1, s,
			(rrauth->next == NULL && found <= 1) ? 1 : 0);
		}
	    }

	    //
	    // If there are no more IP addresses, then do the next
	    // authorityRR.
	    //
	    if (--found <= 0)
		break;
	    if (rradd != NULL)
		rradd = rradd->next;
	} while (rradd != NULL);
	rrauth = rrauth->next;
    }

    //
    // Cache it for later if there were no servers which went wrong.
    // Also remove the busy flag.
    //
    if (global_caching && retval == 0) add_answer(server_ip);
    remove_busy(server_ip);

    return retval;
}

/*****************************************************************************/

//
// Startup, usage and win32-initialization
// Win32-initialization by Mike Black <mblack@csihq.com>
//

void
usage(void)
{
    char *version = "1.10";
    fprintf(stderr,
	"DNSTRACER version %s - (c) Edwin Groothuis - http://www.mavetju.org\n"
	"Usage: dnstracer [options] [host]\n"
	"\t-c: disable local caching, default enabled\n"
	"\t-C: enable negative caching, default disabled\n"
	"\t-e: disable EDNS0, default enabled\n"
	"\t-E <size>: set EDNS0 size, default 1500\n"
	"\t-o: enable overview of received answers, default disabled\n"
	"\t-q <querytype>: query-type to use for the DNS requests, default A\n"
	"\t-r <retries>: amount of retries for DNS requests, default 3\n"
	"\t-s <server>: use this server for the initial request, default localhost\n"
	"\t             If . is specified, A.ROOT-SERVERS.NET will be used.\n"
	"\t-t <maximum timeout>: Limit time to wait per try\n"
	"\t-v: verbose\n"
	"\t-S <ip address>: use this source address.\n",
	version
    );
#ifndef NOIPV6
    fprintf(stderr,
	"\t-4: don't query IPv6 servers\n"
    );
#endif
    exit(1);
}

#ifdef WIN32
int
wsockinit(void)
{
    WSADATA wsaData;		/* Structure for WinSock setup communication */

    if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) { /* Load Winsock 2.0 DLL */
	fprintf(stderr, "WSAStartup() failed");
	exit(1);
    }
    return 1;
}
#endif


int
main(int argc, char **argv)
{
    int		ch;
    char *	server_name = "127.0.0.1";
    char *	server_ip = "0000:0000:0000:0000:0000:0000:0000:0000";
    char	ipaddress[NS_MAXDNAME];
    char	argv0[NS_MAXDNAME];
    int		server_root = 0;
    int		ipv6 = 0;

#ifndef WIN32
    //
    // Get the first nameserver from /etc/resolv.conf
    //
    // This piece of code was donated by Moritz Barsnick. 
    //
    if (!(_res.options & RES_INIT))
	res_init();

    if (_res.nscount > 0) {
	server_ip = strdup(inet_ntoa(_res.nsaddr_list[0].sin_addr));
	server_name = strdup(server_ip);
    }
#endif

#ifdef WIN32
    wsockinit();
#endif

    while ((ch = getopt(argc, argv, "4cCeE:oq:r:S:s:t:v")) != -1) {
	switch (ch) {
	case '4':
#ifndef NOIPV6
	    global_noipv6 = 1;
#else
	    printf("Option -4 ignored\n");
#endif
	    break;

	case 'c':
	    global_caching = 0;
	    break;

	case 'C':
	    global_negative_caching = 1;
	    break;

	case 'e':
	    global_edns0 = 0;
	    break;

	case 'E':
	    global_edns0 = atoi(optarg);
	    break;

	case 'o':
	    global_overview = 1;
	    break;

	case 'q':
	    if ((global_querytype = atoi(optarg)) < 1) {
		int	i;
		for (i = 0; i < MAX_RR_TYPES; i++) {
			if (strcmp(rr_types[i], optarg) == 0) {
				global_querytype = i;
				break;
			}
		}

		if (global_querytype < 1) {
		    fprintf(stderr,
			"Strange querytype, setting to default\n");
		    global_querytype = DEFAULT_QUERYTYPE;
		}
	    }
	    break;

	case 'r':
	    if ((global_retries = atoi(optarg)) < 1) {
		fprintf(stderr,
		    "Strange amount of retries, setting to default\n");
		global_retries = DEFAULT_RETRIES;
	    }
	    break;

	case 'S':
	    global_source_address = optarg;
	    break;

	case 's':
	    server_name = optarg;
	    if (strcmp(server_name, ".") == 0) {
		server_name = strdup("A.ROOT-SERVERS.NET");
		server_root = 1;
	    }
	    break;

	case 't':
	    global_timeout = atoi(optarg);
	    break;

	case 'v':
	    verbose = 1;
	    break;

	default:
	    usage();
	}
    }
    argc -= optind;
    argv += optind;

    if (argv[0] == NULL) usage();

    // check for a trailing dot
    strcpy(argv0, argv[0]);
    if (argv0[strlen(argv[0]) - 1] == '.') argv0[strlen(argv[0]) - 1] = 0;

    printf("Tracing to %s[%s] via %s, maximum of %d retries\n",
	argv0, rr_types[global_querytype], server_name, global_retries);

    srandom(time(NULL));

    {
	struct hostent *h = NULL;

#ifndef NOIPV6
	h = gethostbyname2(server_name, AF_INET6);
#endif
	if (h == NULL || global_noipv6)
	    h = gethostbyname2(server_name, AF_INET);
	if (h == NULL) {
	    fprintf(stderr, "Cannot find IP address for %s\n", server_name);
	    return 1;
	}
	if (h->h_addrtype == AF_INET) {
	    u_char *s = h->h_addr_list[0];
	    sprintf(ipaddress, "%hu.%hu.%hu.%hu", s[0], s[1], s[2], s[3]);
	    ipv6 = 0;
	} else {
	    u_char *s = h->h_addr_list[0];
	    sprintf(ipaddress,
		"%02hx%02hx:%02hx%02hx:%02hx%02hx:%02hx%02hx:"
		"%02hx%02hx:%02hx%02hx:%02hx%02hx:%02hx%02hx",
		s[ 0], s[ 1], s[ 2], s[ 3], s[ 4], s[ 5], s[ 6], s[ 7],
		s[ 8], s[ 9], s[10], s[11], s[12], s[13], s[14], s[15]);
	    ipv6 = 1;
	}
    }

    create_session(argv0, ipaddress, ipv6, server_name,
	server_root == 0 ? NULL : ".", 0, "", 1);

    printf("\n");

    if (global_overview != 0) {
	printf("\n");
	display_arecords();
    }

    return 0;
}
