View Issue Details

IDProjectCategoryView StatusLast Update
0037906FPCRTLpublic2021-04-17 05:54
ReporterNoel Duffy Assigned ToMichael Van Canneyt  
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
Product Version3.2.0 
Fixed in Version3.3.1 
Summary0037906: Add support for TCP dns queries and queries for other RR types like SOA, Text, NS to netdb
DescriptionThe netdb unit supports DNS queries over UDP only, and has methods only for querying A, AAAA, and PTR records. DNS client libraries are expected to support TCP for long resource records like Text that won't fit inside a UDP packet.

This issue is to track the progress of adding this support.

At a minimum we need:

1. A method for making DNS queries over TCP
2. The ability for public query methods to detect truncation and switch to TCP
3. Convenience methods for querying Text, SOA, and NS records.
Steps To ReproduceN/A. Problem is that the unit provides no functions or procedures to do what we want.
TagsNo tags attached.
Fixed in Revision48455
FPCOldBugId
FPCTarget3.2.2
Attached Files

Activities

Noel Duffy

2020-10-11 00:33

reporter   ~0126231

Some additional nice-to-haves include:

1. Querying over IPv6
2. Query api accepts IP of DNS server to query, rather than just using the local resolver. This is useful in a number of cases, for instance, when a DNS cache is being used for testing purposes.

Noel Duffy

2020-10-11 00:51

reporter   ~0126232

Attached is an updated netdb.pp (named newnetdb.pp) and a small program that exercises it to make DNS TCP queries for any domain names passed on the command line.

It can be run like this:

$ ./dnstest <domain1> <domain2> .. <domainN>

and it will query text records for all the domains.

This is a crude proof-of-concept. The code hasn't been rigorously tested yet.

Key points about this code:

1. I created new data types for storing the TCP payload. This in turn means that I had to resort to creating copies of methods like BuildPayload, NextRR, and SkipAnsQueries that accepted a TCP payload.
2. The on-the-wire protocol for DNS over TCP has a two-octet length field at the start. This field is not present in the protocol for DNS over UDP. This requires a new record for making queries.

The resulting code is, IMO, not ideal. We go to great lengths to avoid additional buffer copies, but the cost of this is duplicated methods and three record types for essentially the same information.

An alternative approach then, is to use a single header record and a dynamic buffer for queries and replies. At the time of writing to a socket, we would then need to write a final byte buffer for transmitting over the wire, and after reading the response, we'd need to copy its contents into a record and a dynamic buffer. These additional moves would not be overly onerous, I think.
dnstest.lpr (4,857 bytes)   
program dnstest;

{$mode objfpc}
{$h+}

uses
  newnetdb, sockets, sysutils, math;

type
  TTxtRecordArr = Array of String;

function GetFixlenStr(pl: TPayLoadTCP; startidx: Cardinal; len: Byte;
  out res: AnsiString): Byte;
begin
  Result := 0;
  res := '';
  if (startidx + len) > Length(pl) then exit;
  SetLength(res, len);
  Move(pl[startidx], res[1], len);
  Result := len;
end;

procedure DumpRR(rr: TRRData; const pl: TPayLoadTCP; const idx: Cardinal );
var
  ac: Word;
  s,s2: AnsiString;
  i, size1: Cardinal;
  size2, count: Byte;
begin
  s := 'Unknown';
  case NToHs(rr.AType) of
    DNSQRY_TXT:
        s := 'TXT';
    DNSQRY_A:
        s := 'A';
    DNSQRY_MX:
        s := 'MX';
    DNSQRY_PTR:
        s := 'PTR';
    DNSQRY_CNAME:
      s := 'CNAME';
    DNSQRY_AAAA:
      s := 'AAAA';
  end;

  s2 := '';
  ac := NToHs(rr.AClass);

  writeln('aclass='+inttostr(ac));
  writeln('atype='+inttostr(NToHs(rr.Atype)));
  writeln('ttl='+inttostr(NToHl(rr.TTL)));
  size1 := NToHs(rr.RDLength);
  writeln('length='+inttostr(size1));
  writeln('kind:'+s);

  i := idx;
  s2 := '';
  size2 := pl[i];
  count := GetFixlenStr(pl, i+1, size2, s2);
  writeln('txt['+inttostr(i)+','+inttostr(count)+']:'+s2);
end;

function GetStrFromTextRR(const rr: TRRData; const pl: TPayLoadTCP;
  ps: Cardinal): String;
var
  s2: AnsiString;
  count: Byte;
begin
  if NToHs(rr.AType) = DNSQRY_TXT then
  begin
    count := GetFixlenStr(pl, ps+1, pl[ps], s2);
    if count > 0 then
      Result := s2;
  end;
end;

procedure lookup_txt_debug(hn: AnsiString);
var
  Qry             : TQueryDataLength;
  Ans: TQueryDataLengthTCP;

  MaxAnswer,I,QryLen,
  AnsLen,AnsStart     : Longint;
  RR                  : TRRData;
  B: Boolean;
  r: TDNSRcode;

begin
  CheckResolveFile;
  if Length(DNSServers) = 0 then
  begin
    writeln('No dns servers configured.');
    exit;
  end;

  QryLen:=BuildPayLoadTCP(Qry,hn,DNSQRY_TXT,1);
  DumpPayLoad(Qry.hpl, QryLen);
  if not QueryTCP(0,Qry,Ans,QryLen,AnsLen) then
  begin
    writeln('Failed');
    r := GetRcode(Ans.h);
    case r of
      rcInvalid: writeln('Invalid response');
      rcNoError: writeln('No error');
      rcRefused: writeln('Refused');
      rcNotImpl: writeln('Not implemented');
      rcNXDomain: writeln('NXDomain');
      rcServFail: writeln('ServFail');
      rcFormatError: writeln('Format error');
      rcReserved: writeln('Reserved code');
    end;
    exit;
  end
  else
  begin
    AnsStart:=SkipAnsQueriesTCP(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount-1;

    writeln('Resp len:'+inttostr(AnsLen)+',arcount: ' +
      inttostr(Ans.h.arcount)+', ancount:'+inttostr(Ans.h.ancount));
    writeln('AnsStart='+inttostr(AnsStart));
    I:=0;

    B := NextRRTCP(Ans.Payload,AnsStart,AnsLen,RR);
    While (I<=MaxAnswer) and B do
    begin
      writeln('rrdata '+inttostr(I)+' AnsStart:'+inttostr(AnsStart));
      DumpRR(rr,Ans.Payload,AnsStart);
      Inc(I);
      Inc(AnsStart, NToHs(RR.RDLength));
      B := NextRRTCP(Ans.Payload,AnsStart,AnsLen,RR);
    end;
    writeln('Good.');
  end;
end;

function QueryText(dn: String):TTxtRecordArr;
var
  Qry: TQueryDataLength;
  Ans: TQueryDataLengthTCP;
  MaxAnswer,I,QryLen,
  AnsLen,AnsStart: Longint;
  RR: TRRData;
  B: Boolean;
  r: TDNSRcode;
  s: String;

begin
  SetLength(Result,0);

  CheckResolveFile;
  if Length(DNSServers) = 0 then
  begin
    writeln('No dns servers configured.');
    exit;
  end;

  QryLen:=BuildPayLoadTCP(Qry,dn,DNSQRY_TXT,1);
  if not QueryTCP(0,Qry,Ans,QryLen,AnsLen) then
  begin
    r := GetRcode(Ans.h);
    case r of
      rcInvalid: writeln('Invalid response');
      rcNoError: writeln('No error');
      rcRefused: writeln('Refused');
      rcNotImpl: writeln('Not implemented');
      rcNXDomain: writeln('NXDOMAIN');
      rcServFail: writeln('SERVFAIL');
      rcFormatError: writeln('Format error');
      rcReserved: writeln('Reserved code');
    end;
    exit;
  end
  else
  begin
    AnsStart:=SkipAnsQueriesTCP(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount-1;

    I:=0;
    B := NextRRTCP(Ans.Payload,AnsStart,AnsLen,RR);
    While (I<=MaxAnswer) and B do
    begin
      s := GetStrFromTextRR(RR, Ans.Payload, AnsStart);
      if (Length(s) > 0) then
      begin
        SetLength(Result, Length(Result)+1);
        Result[Length(Result)-1] := s;
      end;
      Inc(I);
      Inc(AnsStart, NToHs(RR.RDLength));
      B := NextRRTCP(Ans.Payload,AnsStart,AnsLen,RR);
    end;
  end;
end;

var
  res: TTxtRecordArr;
  S: String;
  idx: Integer;
begin
  if ParamCount = 0 then
  begin
    WriteLn(ParamStr(0)+' domain1 domain2 .. domainN');
    WriteLn('Text records will be queried for each domain.');
    exit;
  end;
  for idx := 1 to ParamCount do
  begin
    WriteLn(' - ' + ParamStr(idx));
    res := QueryText(ParamStr(idx));
    for S in res do
      writeln('    "' + S + '"');
  end;
end.

dnstest.lpr (4,857 bytes)   
newnetdb.pp (44,609 bytes)   
{
    This file is part of the Free Pascal run time library.
    Copyright (c) 2003 by the Free Pascal development team

    Implement networking routines.
    
    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}
{$mode objfpc}
{$h+}

unit newnetdb;
{
  WARNING
  This unit hardly does any error checking. For example, stringfromlabel
  could easily be exploited by  someone sending malicious UDP packets in
  order to crash  your program.  So if you really want to depend on this
  in critical programs then you'd better fix a lot of code in here.
  Otherwise, it appears to work pretty well.
}

Interface

Uses Sockets;

{$IFDEF OS2}
(* ETC directory location determined by environment variable ETC *)
 {$DEFINE ETC_BY_ENV}
(* Use names supported also on non-LFN drives like plain FAT-16. *)
 {$DEFINE SFN_VERSION}
{$ENDIF OS2}
{$IFDEF GO32V2}
 {$DEFINE ETC_BY_ENV}
 {$DEFINE SFN_VERSION}
{$ENDIF GO32V2}
{$IFDEF WATCOM}
 {$DEFINE ETC_BY_ENV}
 {$DEFINE SFN_VERSION}
{$ENDIF WATCOM}

{$IFDEF UNIX}
(* ETC directory location hardcoded to /etc/ *)
 {$DEFINE UNIX_ETC}
{$ENDIF UNIX}

Type
  THostAddr = in_addr;		// historical aliases for these.
  THostAddr6= Tin6_addr;
  TNetAddr  = THostAddr;	// but in net order.

Const
  DNSPort        = 53;
  MaxResolveAddr = 10;
  SServicesFile  = 'services'; 
  SHostsFile     = 'hosts';
  SNetworksFile  = 'networks';
  MAX_UDP_PAYLOAD = 512;
  MAX_TCP_PAYLOAD = 65535;

  { from http://www.iana.org/assignments/dns-parameters }
  DNSQRY_A     = 1;                     // name to IP address
  DNSQRY_AAAA  = 28;                    // name to IP6 address
  DNSQRY_A6    = 38;                    // name to IP6 (new)
  DNSQRY_PTR   = 12;                    // IP address to name
  DNSQRY_MX    = 15;                    // name to MX
  DNSQRY_TXT   = 16;                    // name to TXT
  DNSQRY_CNAME = 5;

  // Flags 1
  QF_QR     = $80;
  QF_OPCODE = $78;
  QF_AA     = $04;
  QF_TC     = $02;  // Truncated.
  QF_RD     = $01;

  // Flags 2
  QF_RA     = $80;
  QF_Z      = $70;
  QF_RCODE  = $0F;

{$IFDEF SFN_VERSION}
  SProtocolFile  = 'protocol';
  SResolveFile   = 'resolv';
 {$IFDEF OS2}
(* Peculiarity of OS/2 - depending on the used TCP/IP version, *)
(* the file differs slightly in name and partly also content.  *)
   SResolveFile2  = 'resolv2';
 {$ENDIF OS2}
{$ELSE SFN_VERSION}
  SProtocolFile  = 'protocols';
  SResolveFile   = 'resolv.conf';
{$ENDIF SFN_VERSION}

  MaxRecursion = 10;
  MaxIP4Mapped = 10;

var
  EtcPath: string;

Type
  TDNSRcode = (rcNoError, rcFormatError,rcServFail,rcNXDomain,
    rcNotImpl,rcRefused,rcReserved,rcInvalid);

  TDNSServerArray = Array of THostAddr;
  TServiceEntry = record
    Name     : String;
    Protocol : String;
    Port     : Word;
    Aliases  : String;
  end;
     
  THostEntry = record
    Name : String;
    Addr : THostAddr;
    Aliases : String;
  end;
  PHostEntry = ^THostEntry;
  THostEntryArray = Array of THostEntry;

  THostEntry6 = record
    Name : String;
    Addr : THostAddr6;
    Aliases : String;
  end;
  PHostEntry6 = ^THostEntry6;
  THostEntry6Array = Array of THostEntry6;
  
  TNetworkEntry = Record
    Name : String;
    Addr : TNetAddr;
    Aliases : String;
  end;  
  PNetworkEntry = ^TNetworkEntry;

  TProtocolEntry = Record
    Name : String;
    Number : integer;
    Aliases : String;
  end;  
  PProtocolEntry = ^TProtocolEntry;

  PHostListEntry = ^THostListEntry;
  THostListEntry = Record
    Entry : THostEntry;
    Next : PHostListEntry;
  end;

  TPayLoad  = Array[0..511] of Byte;
  TPayLoadTCP = Array[0 .. 65535] of Byte;

  TDNSHeader = packed Record
    id      : Array[0..1] of Byte;
    flags1  : Byte;
    flags2  : Byte;
    qdcount : word;
    ancount : word;
    nscount : word;
    arcount : word;
  end;

  TQueryData = packed Record
    h: TDNSHeader;
    Payload : TPayLoad;
  end;

  TQueryDataLength = packed record
    length: Word;
    hpl: TQueryData;
  end;

  TQueryDataLengthTCP = packed Record
    length: Word;
    h: TDNSHeader;
    Payload : TPayLoadTCP;
  end;

  PRRData = ^TRRData;
  TRRData = Packed record       // RR record
    Atype    : Word;            // Answer type
    AClass   : Word;
    TTL      : Cardinal;
    RDLength : Word;
  end;

Var  
  DNSServers            : TDNSServerArray;
  DNSOptions            : String;
  DefaultDomainList     : String;
  CheckResolveFileAge   : Boolean; 
  CheckHostsFileAge     : Boolean; 
  TimeOutS,TimeOutMS    : Longint;
  
{$ifdef android}
Function GetDNSServers : Integer;
{$else}
Function GetDNSServers(FN : String) : Integer;
{$endif android}

Function ResolveName(HostName : String; Var Addresses : Array of THostAddr) : Integer;
Function ResolveName6(HostName : String; Var Addresses : Array of THostAddr6) : Integer;


Function ResolveAddress(HostAddr : THostAddr; Var Addresses : Array of String) : Integer;
Function ResolveAddress6(HostAddr: THostAddr6; var Addresses: Array of string) : Integer;

function IN6_IS_ADDR_V4MAPPED(HostAddr: THostAddr6): boolean;

Function ResolveHostByName(HostName : String; Var H : THostEntry) : Boolean;
Function ResolveHostByAddr(HostAddr : THostAddr; Var H : THostEntry) : Boolean;

Function ResolveHostByName6(Hostname : String; Var H : THostEntry6) : Boolean;
Function ResolveHostByAddr6(HostAddr : THostAddr6; Var H : THostEntry6) : Boolean;

Function GetHostByName(HostName: String;  Var H : THostEntry) : boolean;
Function GetHostByAddr(Addr: THostAddr;  Var H : THostEntry) : boolean;

Function GetNetworkByName(NetName: String; Var N : TNetworkEntry) : boolean;
Function GetNetworkByAddr(Addr: THostAddr; Var N : TNetworkEntry) : boolean;

Function GetServiceByName(Const Name,Proto : String; Var E : TServiceEntry) : Boolean;
Function GetServiceByPort(Port : Word;Const Proto : String; Var E : TServiceEntry) : Boolean;

Function GetProtocolByName(ProtoName: String;  Var H : TProtocolEntry) : boolean;
Function GetProtocolByNumber(proto: Integer;  Var H : TProtocolEntry) : boolean;

Procedure DumpPayLoad(Q : TQueryData; L : Integer);

function IsTruncated(R: TDNSHeader): Boolean;
function GetRcode(R: TDNSHeader): TDNSRcode;

Function ProcessHosts(FileName : String) : PHostListEntry;
Function FreeHostsList(var List : PHostListEntry) : Integer;
Procedure HostsListToArray(var List : PHostListEntry; Var Hosts : THostEntryArray; FreeList : Boolean);
Procedure CheckResolveFile;
Function Query(Resolver : Integer; Var Qry,Ans : TQueryData; QryLen : Integer; Var AnsLen : Integer) : Boolean;
function QueryTCP(Resolver : Integer; Var Qry: TQueryDataLength;
  var Ans: TQueryDataLengthTCP; QryLen : Integer; Var AnsLen : Integer) : Boolean;
Function BuildPayLoad(Var Q : TQueryData; Name : String; RR : Word; QClass : Word) : Integer;
Function BuildPayLoadTCP(Var Q : TQueryDataLength; Name : String; RR : Word; QClass : Word) : Integer;

Function SkipAnsQueries(Var Ans : TQueryData; L : Integer) : integer;
Function SkipAnsQueriesTCP(Var Ans : TQueryDataLengthTCP; L : Integer) : integer;

Function NextRR(Const PayLoad : TPayLoad;Var Start : LongInt; AnsLen : LongInt; Var RR : TRRData) : Boolean;
Function NextRRTCP(Const PayLoad : TPayLoadTCP;Var Start : LongInt;
  AnsLen : LongInt; Var RR : TRRData) : Boolean;
function stringfromlabel(pl: TPayLoad; start: integer): string;
Function CheckAnswer(Const Qry : TDNSHeader; Var Ans : TDNSHeader) : Boolean;

Implementation

uses 
   BaseUnix,
   sysutils;

var
  DefaultDomainListArr : array of string;
  NDots: Integer;

{ ---------------------------------------------------------------------
    Some Parsing routines
  ---------------------------------------------------------------------}

Const 
  Whitespace = [' ',#9];

Function NextWord(Var Line : String) : String;

Var 
  I,J : Integer;

begin
  I:=1;
  While (I<=Length(Line)) and (Line[i] in Whitespace) do
    inc(I);
  J:=I;
  While (J<=Length(Line)) and Not (Line[J] in WhiteSpace) do
    inc(j);
  Result:=Copy(Line,I,J-I);  
  Delete(Line,1,J);  
end;
  
Function StripComment(var L : String) : Boolean;

Var
  i : Integer;

begin
  I:=Pos('#',L);
  If (I<>0) then
    L:=Copy(L,1,I-1)
  else
    begin
      I:=Pos(';',L);
      If (I<>0) then
        L:=Copy(L,1,I-1)
    end;
  Result:=Length(L)>0;
end;

Function MatchNameOrAlias(Const Entry,Name: String; Aliases : String) : Boolean;

Var
  P : Integer;
  A : String;

begin
  Result:=CompareText(Entry,Name)=0;
  If Not Result then
    While (Not Result) and (Length(Aliases)>0) do
      begin
      P:=Pos(',',Aliases);
      If (P=0) then
        P:=Length(Aliases)+1;
      A:=Copy(Aliases,1,P-1);
      Delete(Aliases,1,P);
      Result:=CompareText(A,Entry)=0;
      end;
end;

{ ---------------------------------------------------------------------
    hosts processing
  ---------------------------------------------------------------------}

Function GetAddr(Var L : String; Var Addr : THostAddr) : Boolean;

Var
  S : String;
//  i,p,a : Integer;
  
begin
  Result:=True;
  S:=NextWord(L);
  Addr:=StrToNetAddr(S);
//  Writeln(s,'->',Addr.s_bytes[1],'.',Addr.s_bytes[2],'.',Addr.s_bytes[3],'.',Addr.s_bytes[4]);
  Result:=Addr.s_bytes[1]<>0;
end;


Function FillHostEntry (Var Entry : THostEntry; L: String) : boolean;

Var
  H : String;

begin
  Result := False;
  Repeat
  H:=NextWord(L);
    If (H<>'') then begin
      if (Entry.Name='') then
        Entry.Name:=H
      else  
        begin
        If (Entry.Aliases<>'') then
          Entry.Aliases:=Entry.Aliases+',';
        Entry.Aliases:=Entry.Aliases+H;
        end;
      Result := True;
    end;
  until (H='');
end;

function IsTruncated(R: TDNSHeader): Boolean;
begin
  Result := ((R.flags1 and QF_TC) > 0);
end;

function GetRcode(R: TDNSHeader): TDNSRcode;
var
  rcode_n: Byte;
begin
  rcode_n := (R.flags2 and QF_RCODE);
  case rcode_n of
    0: Result := rcNoError;
    1: Result := rcFormatError;
    2: Result := rcServFail;
    3: Result := rcNXDomain;
    4: Result := rcNotImpl;
    5: Result := rcRefused;
    6 .. 15: Result := rcReserved;
  else
    Result := rcInvalid;
  end;
end;

function ProcessHosts(FileName: String): PHostListEntry;

Var
  F : Text;
  L : String;
  A : THostAddr;
  T : PHostListEntry;
  B : Array of byte;
  FS : Int64;
  
begin
  Result:=Nil;
  Assign(F,FileName);
  {$push}{$I-}
  Reset(F);
  SetLength(B,65355);
  SetTextBuf(F,B[0],65355);
  {$pop};
  If (IOResult<>0) then
    Exit;
  Try  
    While Not EOF(F) do
      begin
      Readln(F,L);
      If StripComment(L) then
        begin
        If GetAddr(L,A) then
          begin
          T:=New(PHostListEntry);
          T^.Entry.Addr:=A;
          FillHostEntry(T^.Entry,L);
          T^.Next:=Result;
          Result:=T;
          end;
        end;
      end;
  Finally  
    Close(F);
  end;
end;

{ Internal lookup, used in GetHostByName and friends. }

Var
  HostsList : PHostListEntry = Nil;  
  HostsFileAge  : Longint;
//  HostsFileName : String;

function FreeHostsList(var List: PHostListEntry): Integer;

Var
  P : PHostListEntry;

begin
  Result:=0;
  While (List<>Nil) do
    begin
    Inc(Result);
    P:=List^.Next;
    Dispose(List);
    List:=P;
    end;
end;

procedure HostsListToArray(var List: PHostListEntry;
  var Hosts: THostEntryArray; FreeList: Boolean);

Var
  P : PHostListEntry;
  Len : Integer;

begin
  Len:=0;
  P:=List;
  While P<> Nil do
    begin
    Inc(Len);
    P:=P^.Next;
    end;
  SetLength(Hosts,Len);
  If (Len>0) then
    begin
    Len:=0;
    P:=List;
    While (P<>Nil) do
      begin
      Hosts[Len]:=P^.Entry;
      P:=P^.Next;
      Inc(Len);
      end;
    end;
  If FreeList then
    FreeHostsList(List);
end;

Procedure CheckHostsFile;

Var
  F : Integer;

begin
  If CheckHostsFileAge then
    begin
    F:=FileAge (EtcPath + SHostsFile);
    If HostsFileAge<F then
      begin
      // Rescan.
      FreeHostsList(HostsList);
      HostsList:=ProcessHosts (EtcPath + SHostsFile);
      HostsFileAge:=F;
      end;
    end;  
end;

Function FindHostEntryInHostsFile(N: String; Addr: THostAddr; Var H : THostEntry) : boolean;

Var
//  F : Text;
  HE : THostEntry;
  P : PHostListEntry;
  
begin
  Result:=False;
  CheckHostsFile;
  P:=HostsList;
  While (Not Result) and (P<>Nil) do
    begin
    HE:=P^.Entry;
    If (N<>'') then
      Result:=MatchNameOrAlias(N,HE.Name,HE.Aliases)
    else
      Result:=Cardinal(hosttonet(Addr))=Cardinal(HE.Addr);
    P:=P^.Next;  
    end; 
 If Result then
   begin
   H.Name:=HE.Name;
   H.Addr:=nettohost(HE.Addr);
   H.Aliases:=HE.Aliases;
   end;
end;

{ ---------------------------------------------------------------------
   Resolve.conf handling
  ---------------------------------------------------------------------}

{$ifdef android}

Function GetDNSServers: Integer;
var
  i: integer;
  s: string;
  H : THostAddr;
begin
  if SystemApiLevel >= 26 then
    begin
      // Since Android 8 the net.dnsX properties can't be read.
      // Use Google Public DNS servers
      Result:=2;
      SetLength(DNSServers, Result);
      DNSServers[0]:=StrToNetAddr('8.8.8.8');
      DNSServers[1]:=StrToNetAddr('8.8.4.4');
      exit;
    end;

  Result:=0;
  SetLength(DNSServers, 9);
  for i:=1 to 9 do
    begin
      s:=GetSystemProperty(PAnsiChar('net.dns' + IntToStr(i)));
      if s = '' then
        break;
      H:=StrToNetAddr(s);
      if H.s_bytes[1] <> 0 then
        begin
          DNSServers[Result]:=H;
          Inc(Result);
        end;
    end;
  SetLength(DNSServers, Result);
end;

var
  LastChangeProp: string;

Procedure CheckResolveFile;
var
  n, v: string;
begin
  if not CheckResolveFileAge then
    exit;

  if (Length(DNSServers) = 0) and (SystemApiLevel >= 26) then
    begin
      GetDNSServers;
      exit;
    end;

  n:=GetSystemProperty('net.change');
  if n <> '' then
    v:=GetSystemProperty(PAnsiChar(n))
  else
    v:='';
  n:=n + '=' + v;
  if LastChangeProp = n then
    exit;
  LastChangeProp:=n;
  GetDNSServers;
end;

{$else}

Var
  ResolveFileAge  : Longint;
  ResolveFileName : String;
  
function GetDNSServers(FN: String): Integer;

Var
  R : Text;
  L : String;
//  I : Integer;
  H : THostAddr;
  E : THostEntry;
  
  Function CheckDirective(Dir : String) : Boolean;
  
  Var
    P : Integer;
  
  begin
    P:=Pos(Dir,L);
    Result:=(P<>0);
    If Result then
      begin
      Delete(L,1,P+Length(Dir));
      L:=Trim(L);
      end;
  end;
   
begin
  Result:=0;
  ResolveFileName:=Fn;
  ResolveFileAge:=FileAge(FN);
  DefaultDomainListArr:=[];
  NDots:=1;
  {$push}{$i-}
  Assign(R,FN);
  Reset(R);
  {$pop}
  If (IOResult<>0) then 
    exit;
  Try  
    While not EOF(R) do
      begin
      Readln(R,L);
      if StripComment(L) then
        If CheckDirective('nameserver') then
          begin
          H:=HostToNet(StrToHostAddr(L));
          If (H.s_bytes[1]<>0) then
            begin
            setlength(DNSServers,Result+1);
            DNSServers[Result]:=H;
            Inc(Result);
            end
          else if FindHostEntryInHostsFile(L,H,E) then
            begin
            setlength(DNSServers,Result+1);
            DNSServers[Result]:=E.Addr;
            Inc(Result);
            end;
          end
        else if CheckDirective('domain') then
          DefaultDomainList:=L
        else if CheckDirective('search') then
          DefaultDomainList:=L
        else if CheckDirective('options') then
          DNSOptions:=L;
      end;
  Finally
    Close(R);
  end;
  L := GetEnvironmentVariable('LOCALDOMAIN');
  if L <> '' then
    DefaultDomainList := L;
end;

procedure CheckResolveFile;

Var
  F : Integer;
  N : String;

begin
  If CheckResolveFileAge then
    begin
    N:=ResolveFileName;
    if (N='') then
      N:=EtcPath + SResolveFile;
    F:=FileAge(N);
    If ResolveFileAge<F then
      GetDnsServers(N);
    end;  
end;

{$endif android}

{ ---------------------------------------------------------------------
    Payload handling functions.
  ---------------------------------------------------------------------}
  

procedure DumpPayLoad(Q: TQueryData; L: Integer);

Var 
  i : Integer;

begin
  Writeln('Payload : ',l);
  For I:=0 to L-1 do
    Write(Q.Payload[i],' ');
  Writeln;  
end;

function QueryTCP(Resolver: Integer; var Qry: TQueryDataLength;
  var Ans: TQueryDataLengthTCP; QryLen: Integer; var AnsLen: Integer): Boolean;
Var
  SA : TInetSockAddr;
  Sock : cint;
  L: ssize_t;
  RTO : Longint;
  ReadFDS : TFDSet;
  count: Integer;
  sendsize: ssize_t;
  respsize: Word;

begin
  Result:=False;
  With Qry.hpl.h do
  begin
    ID[0]:=Random(256);
    ID[1]:=Random(256);
    Flags1:=QF_RD;
    Flags2:=0;
    qdcount:=htons(1); // was 1 shl 8;
    ancount:=0;
    nscount:=0;
    arcount:=0;
  end;
  Sock:=FpSocket(AF_INET,SOCK_STREAM,0);
  If Sock=-1 then
    exit;
  With SA do
  begin
    sin_family:=AF_INET;
    sin_port:=htons(DNSport);
    //sin_port:=45333;
    sin_addr.s_addr:=cardinal(DNSServers[Resolver]); // octets already in net order
    //sin_addr.s_addr := 16777343; // 127.0.0.1
    //sin_addr.s_addr := 16843009; // 1.1.1.1
  end;
  if (fpconnect(Sock, @SA, SizeOf(SA)) <> 0) then
    exit;
  sendsize := QryLen + SizeOf(Qry.hpl.h) + SizeOf(Qry.length);
  count := fpsend(Sock,@Qry,sendsize,0);
  if count < sendsize then
  begin
    fpclose(Sock);
    exit;
  end;

  // tell other side we'll write no more.
  fpshutdown(Sock, SHUT_WR);
  // Wait for answer.
  RTO:=TimeOutS*1000+TimeOutMS;
  fpFD_ZERO(ReadFDS);
  fpFD_Set(Sock,ReadFDS);

  if fpSelect(Sock+1,@ReadFDS,Nil,Nil,RTO)<=0 then
  begin
    fpclose(Sock);
    exit;
  end;

  L:=fprecv(Sock,@Ans,SizeOf(Ans),0);
  if L <= 0 then
  begin
    fpclose(Sock);
    exit;
  end;

  // get the payload size, which doesn't include the 2 byte length field.
  // I.e, num bytes read  should be sizeof(word) (2 bytes) greater than the
  // payload size.
  respsize := NToHs(Ans.length);
  if L < (respsize + SizeOf(Ans.length)) then
  begin
    // we haven't received the full payload. Unsure in what circumstances
    // this might happen.
    fpclose(Sock);
    exit;
  end;

  If (L < SizeOf(Qry.hpl.h)) or not CheckAnswer(Qry.hpl.h,Ans.h) then
    exit;
  // Return Payload length.
  Anslen:=respsize-SizeOf(Qry.hpl.h);
  Result:=True;

end;

function BuildPayLoad(var Q: TQueryData; Name: String; RR: Word; QClass: Word
  ): Integer;

Var
  P : PByte;
  l,S : Integer;
  
begin
  Result:=-1;
  If length(Name)>506 then
    Exit;
  Result:=0;  
  P:=@Q.Payload[0];
  Repeat
    L:=Pos('.',Name);
    If (L=0) then
      S:=Length(Name)
    else
      S:=L-1;
    P[Result]:=S;
    Move(Name[1],P[Result+1],S);
    Inc(Result,S+1);
    If (L>0) then
      Delete(Name,1,L);
  Until (L=0);
  P[Result]:=0;
  rr := htons(rr);
  Move(rr,P[Result+1],2);
  Inc(Result,3);
  QClass := htons(QClass);
  Move(qclass,P[Result],2);
  Inc(Result,2);
end;

{Construct a TCP query payload from the given name, rr and qclass. The
 principal difference between the TCP and UDP payloads is the two-octet
 length field in the TCP payload. The UDP payload has no length field.

 See RFC-1035, section 4.2.2.

 Returns the length of the constructed payload, which doesn't include
 the header or the length field.}
function BuildPayLoadTCP(var Q: TQueryDataLength; Name: String; RR: Word;
  QClass: Word): Integer;
var
  l: Word;
begin
  l := BuildPayLoad(Q.hpl, Name, RR, QClass);
  Q.length := htons(l + SizeOf(Q.hpl.h));
  Result := l;
end;

function NextRR(const PayLoad: TPayLoad; var Start: LongInt; AnsLen: LongInt;
  var RR: TRRData): Boolean;

Var
  I : Integer;
  HaveName : Boolean;
  PA : PRRData;
  
begin
  Result:=False;
  I:=Start;
  // Skip labels and pointers. At least 1 label or pointer is present.
  Repeat
    HaveName:=True;
    If (Payload[i]>63) then // Pointer, skip
    begin
      Inc(I,2);
    end
    else If Payload[i]=0 then // Null termination of label, skip.
      Inc(i)
    else  
      begin
      Inc(I,Payload[i]+1); // Label, continue scan.
      HaveName:=False;
      end;
  Until HaveName or (I>(AnsLen-SizeOf(TRRData)));
  Result:=(I<=(AnsLen-SizeOf(TRRData)));
  // Check RR record.
  PA:=PRRData(@Payload[i]);
  RR:=PA^;
  Start:=I+SizeOf(TRRData);
end;


Function BuildName (Const PayLoad : TPayLoad; Start,len : Integer) : String;

Const
  FIREDNS_POINTER_VALUE = $C000;
  
Var
  I,O : Integer;
  P : Word;
  
begin
  SetLength(Result,512);
  I:=Start;
  O:=1;
  // Copy labels and pointers. At least 1 label or pointer is present.
  Repeat
    If (Payload[i]>63) then // Pointer, move.
      begin
      Move(Payload[i],P,2);
      I:=ntohs(p)-FIREDNS_POINTER_VALUE-12;
      end
    else if Payload[i]<>0 then // Label, copy
      begin
      If O<>1 then
        begin
        Result[O]:='.';
        Inc(O);
        end;
      P:=Payload[i];  
      Move(Payload[i+1],Result[o],P);
      Inc(I,P+1);
      Inc(O,P);
      end;
   Until (Payload[I]=0);
   setlength(result,o-1);
end;


{ ---------------------------------------------------------------------
    QueryData handling functions
  ---------------------------------------------------------------------}
  
function CheckAnswer(const Qry: TDNSHeader; var Ans: TDNSHeader): Boolean;
var
  tmp: Byte;
begin
  Result:=False;
  With Ans do
    begin
    // Check ID.
    If (ID[1]<>QRY.ID[1]) or (ID[0]<>Qry.ID[0]) then
      exit;  
    // Flags ?
    If (Flags1 and QF_QR)=0 then
      exit;
    if (Flags1 and QF_OPCODE)<>0 then 
      exit;
    tmp := (Flags2 and QF_RCODE);
    if (Flags2 and QF_RCODE)<>0 then
      exit;  
    // Number of answers ?  
    AnCount := htons(Ancount);
    If Ancount<1 then
      Exit;
    Result:=True;
    end;
end;

function SkipAnsQueries(var Ans: TQueryData; L: Integer): integer;

Var
  Q,I : Integer;

begin
  Result:=0;
  With Ans do
    begin
    h.qdcount := htons(h.qdcount);
    i:=0;
    q:=0;
    While (Q<h.qdcount) and (i<l) do  
      begin
      If Payload[i]>63 then
        begin
        Inc(I,6);
        Inc(Q);
        end
      else
        begin
        If Payload[i]=0 then
          begin
          inc(q);
          Inc(I,5);
          end
        else
          Inc(I,Payload[i]+1);  
        end;  
      end;
    Result:=I;  
    end;  
end;

function SkipAnsQueriesTCP(var Ans: TQueryDataLengthTCP; L: Integer): integer;
var
  Q,I : Integer;

begin
  Result:=0;
  With Ans do
  begin
    h.qdcount := htons(h.qdcount);
    i:=0;
    q:=0;
    While (Q<h.qdcount) and (i<l) do
    begin
      If Payload[i]>63 then
      begin
        Inc(I,6);
        Inc(Q);
      end
      else
      begin
        If Payload[i]=0 then
        begin
          inc(q);
          Inc(I,5);
        end
        else
          Inc(I,Payload[i]+1);
      end;
    end;
    Result:=I;
  end;
end;

{ ---------------------------------------------------------------------
    DNS Query functions.
  ---------------------------------------------------------------------}
  

function Query(Resolver: Integer; var Qry, Ans: TQueryData; QryLen: Integer;
  var AnsLen: Integer): Boolean;

Var
  SA : TInetSockAddr;
  Sock,L : Longint;
  Al,RTO : Longint;
  ReadFDS : TFDSet;
  
begin
  Result:=False;
  With Qry.h do
    begin
    ID[0]:=Random(256);
    ID[1]:=Random(256);
    Flags1:=QF_RD;
    Flags2:=0;
    qdcount:=htons(1); // was 1 shl 8;
    ancount:=0;
    nscount:=0;
    arcount:=0;
    end;
  Sock:=FpSocket(PF_INET,SOCK_DGRAM,0);
  If Sock=-1 then 
    exit;
  With SA do
    begin
    sin_family:=AF_INET;
    sin_port:=htons(DNSport);
    sin_addr.s_addr:=cardinal(DNSServers[Resolver]); // dnsservers already in net order
    end;
  fpsendto(sock,@qry,qrylen+12,0,@SA,SizeOf(SA));
  // Wait for answer.
  RTO:=TimeOutS*1000+TimeOutMS;
  fpFD_ZERO(ReadFDS);
  fpFD_Set(sock,readfds);
  if fpSelect(Sock+1,@readfds,Nil,Nil,RTO)<=0 then
    begin
    fpclose(Sock);
    exit;
    end;
  AL:=SizeOf(SA);
  L:=fprecvfrom(Sock,@ans,SizeOf(Ans),0,@SA,@AL);
  fpclose(Sock);

  If (L<12) or not CheckAnswer(Qry.h,Ans.h) Then
    exit;
  // Return Payload length.
  Anslen:=L-12;
  Result:=True;
  //end;
end;

function NextRRTCP(const PayLoad: TPayLoadTCP; var Start: LongInt;
  AnsLen: LongInt; var RR: TRRData): Boolean;
var
  I : Integer;
  HaveName : Boolean;
  PA : PRRData;

begin
  Result:=False;
  I:=Start;
  // Skip labels and pointers. At least 1 label or pointer is present.
  Repeat
    HaveName:=True;
    If (Payload[i]>63) then // Pointer, skip
    begin
      Inc(I,2);
    end
    else If Payload[i]=0 then // Null termination of label, skip.
      Inc(i)
    else
      begin
      Inc(I,Payload[i]+1); // Label, continue scan.
      HaveName:=False;
      end;
  Until HaveName or (I>(AnsLen-SizeOf(TRRData)));
  Result:=(I<=(AnsLen-SizeOf(TRRData)));
  // Check RR record.
  PA:=PRRData(@Payload[i]);
  RR:=PA^;
  Start:=I+SizeOf(TRRData);

end;

function stringfromlabel(pl: TPayLoad; start: integer): string;
var
  l,i: integer;
begin
  result := '';
  l := 0;
  i := 0;
  repeat
    l := ord(pl[start]);
    { compressed reply }
    while (l >= 192) do
      begin
        { the -12 is because of the reply header length }
        start := (l and not(192)) shl 8 + ord(pl[start+1]) - 12;
        l := ord(pl[start]);
      end;
    if l <> 0 then begin
      setlength(result,length(result)+l);
      move(pl[start+1],result[i+1],l);
      result := result + '.';
      inc(start,l); inc(start);
      inc(i,l); inc(i);
    end;
  until l = 0;
  if result[length(result)] = '.' then setlength(result,length(result)-1);
end;

Function ResolveNameAt(Resolver : Integer; HostName : String; Var Addresses : Array of THostAddr; Recurse: Integer) : Integer;

Var
  Qry, Ans            : TQueryData;
  MaxAnswer,I,QryLen,
  AnsLen,AnsStart     : Longint;
  RR                  : TRRData;
  cname               : string;
begin
  Result:=0;
  QryLen:=BuildPayLoad(Qry,HostName,DNSQRY_A,1);
  If Not Query(Resolver,Qry,Ans,QryLen,AnsLen) then
    Result:=-1
  else  
    begin
    AnsStart:=SkipAnsQueries(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount-1;
    If MaxAnswer>High(Addresses) then
      MaxAnswer:=High(Addresses);
    I:=0;
    While (I<=MaxAnswer) and NextRR(Ans.Payload,AnsStart,AnsLen,RR) do
      begin
      if htons(rr.AClass) = 1 then
        case ntohs(rr.AType) of
          DNSQRY_A: begin
            Move(Ans.PayLoad[AnsStart],Addresses[i],SizeOf(THostAddr));
            inc(Result);
            Inc(AnsStart,htons(RR.RDLength));
          end;
          DNSQRY_CNAME: begin
            if Recurse >= MaxRecursion then begin
              Result := -1;
              exit;
            end;
            rr.rdlength := ntohs(rr.rdlength);
            setlength(cname, rr.rdlength);
            cname := stringfromlabel(ans.payload, ansstart);
            Result := ResolveNameAt(Resolver, cname, Addresses, Recurse+1);
            exit; // FIXME: what about other servers?!
          end;
        end;
        Inc(I);
      end;  
    end;
end;

function ResolveName(HostName: String; var Addresses: array of THostAddr
  ): Integer;

Var
  I : Integer;

begin
  CheckResolveFile;
  I:=0;
  Result:=0;
  While (Result<=0) and (I<=high(DNSServers)) do
    begin
    Result:=ResolveNameAt(I,HostName,Addresses,0);
    Inc(I);
    end;
end;

//const NoAddress6 : array[0..7] of word = (0,0,0,0,0,0,0,0);

Function ResolveNameAt6(Resolver : Integer; HostName : String; Var Addresses : Array of THostAddr6; Recurse: Integer) : Integer;
                                                                                                                                        
Var
  Qry, Ans            : TQueryData;
  MaxAnswer,I,QryLen,
  AnsLen,AnsStart     : Longint;
  RR                  : TRRData;
  cname               : string;
  LIP4mapped: array[0..MaxIP4Mapped-1] of THostAddr;
  LIP4count: Longint;
                                                                                                                                        
begin
  Result:=0;
  QryLen:=BuildPayLoad(Qry,HostName,DNSQRY_AAAA,1);
  If Not Query(Resolver,Qry,Ans,QryLen,AnsLen) then begin
    // no answer? try IPv4 mapped addresses, maybe that will generate one
    LIP4Count := ResolveName(HostName, LIP4Mapped);
    if LIP4Count > 0 then begin
      inc(LIP4Count); // we loop to LIP4Count-1 later
      if LIP4Count > MaxIP4Mapped then LIP4Count := MaxIP4Mapped;
      if LIP4Count > Length(Addresses) then LIP4Count := Length(Addresses);
      for i := 0 to LIP4Count-2 do begin
        Addresses[i] := NoAddress6;
        Addresses[i].u6_addr16[5] := $FFFF;
        Move(LIP4Mapped[i], Addresses[i].u6_addr16[6], 4);
      end;
      Result := LIP4Count;
    end else begin
      Result:=-1
    end;
  end else
    begin
    AnsStart:=SkipAnsQueries(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount-1;
    If MaxAnswer>High(Addresses) then
      MaxAnswer:=High(Addresses);
    I:=0;
    While (I<=MaxAnswer) and NextRR(Ans.Payload,AnsStart,AnsLen,RR) do
      begin
      if (1=NtoHS(RR.AClass)) then
      case ntohs(rr.atype) of
        DNSQRY_AAAA: begin
            Move(Ans.PayLoad[AnsStart],Addresses[i],SizeOf(THostAddr6));
            inc(Result);
            rr.rdlength := ntohs(rr.rdlength);
            Inc(AnsStart,RR.RDLength);
          end;
        DNSQRY_CNAME: begin
          if Recurse >= MaxRecursion then begin
            Result := -1;
            exit;
          end;
          rr.rdlength := ntohs(rr.rdlength);
          setlength(cname, rr.rdlength);
          cname := stringfromlabel(ans.payload, ansstart);
          Result := ResolveNameAt6(Resolver, cname, Addresses, Recurse+1);
          exit; // FIXME: what about other servers?!
        end;
      end;
      Inc(I);
      end;
    end;
end;
                                                                                                                                        


function ResolveName6(HostName: String; var Addresses: array of THostAddr6
  ): Integer;
var
  i: Integer;
begin
  CheckResolveFile;
  i := 0;
  Result := 0;
  while (Result <= 0) and (I<= high(DNSServers)) do begin
    Result := ResolveNameAt6(I, Hostname, Addresses, 0);
    Inc(i);
  end;
end;

Function ResolveAddressAt(Resolver : Integer; Address : String; Var Names : Array of String; Recurse: Integer) : Integer;


Var
  Qry, Ans            : TQueryData;
  MaxAnswer,I,QryLen,
  AnsLen,AnsStart     : Longint;
  RR                  : TRRData;

begin
  Result:=0;
  QryLen:=BuildPayLoad(Qry,Address,DNSQRY_PTR,1);
  If Not Query(Resolver,Qry,Ans,QryLen,AnsLen) then
    Result:=-1
  else  
    begin
    AnsStart:=SkipAnsQueries(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount-1;
    If MaxAnswer>High(Names) then
      MaxAnswer:=High(Names);
    I:=0;
    While (I<=MaxAnswer) and NextRR(Ans.Payload,AnsStart,AnsLen,RR) do
      begin
      Case Ntohs(RR.AType) of
        DNSQRY_PTR:
          if (1=NtoHS(RR.AClass)) then
            begin
            Names[i]:=BuildName(Ans.Payload,AnsStart,AnsLen);
            inc(Result);
            RR.RDLength := ntohs(RR.RDLength);
            Inc(AnsStart,RR.RDLength);
            end;
        DNSQRY_CNAME:
          begin
          if Recurse >= MaxRecursion then
            begin
            Result := -1;
            exit;
            end;
          rr.rdlength := ntohs(rr.rdlength);
          setlength(Address, rr.rdlength);
          address := stringfromlabel(ans.payload, ansstart);
          Result := ResolveAddressAt(Resolver, Address, Names, Recurse+1);
          exit;
          end;
      end;
      Inc(I);
      end;  
    end;
end;


function ResolveAddress(HostAddr: THostAddr; var Addresses: array of String
  ): Integer;

Var
  I : Integer;
  S : String;
  nt : tnetaddr;
  
begin
  CheckResolveFile;
  I:=0;
  Result:=0;
  nt:=hosttonet(hostaddr);
  S:=Format('%d.%d.%d.%d.in-addr.arpa',[nt.s_bytes[4],nt.s_bytes[3],nt.s_bytes[2],nt.s_bytes[1]]);
  While (Result=0) and (I<=high(DNSServers)) do
    begin
    Result:=ResolveAddressAt(I,S,Addresses,1);
    Inc(I);
    end;
end;

function ResolveAddress6(HostAddr: THostAddr6; var Addresses: array of string
  ): Integer;

const
  hexdig: string[16] = '0123456789abcdef';
                                                                                
Var
  I : Integer;
  S : ShortString;
                                                                                
begin
  CheckResolveFile;
  Result:=0;
  S := '0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.int';
  for i := 7 downto 0 do begin
    S[5+(7-i)*8] := hexdig[1+(HostAddr.u6_addr16[i] and $000F) shr 00];
    S[7+(7-i)*8] := hexdig[1+(HostAddr.u6_addr16[i] and $00F0) shr 04];
    S[1+(7-i)*8] := hexdig[1+(HostAddr.u6_addr16[i] and $0F00) shr 08];
    S[3+(7-i)*8] := hexdig[1+(HostAddr.u6_addr16[i] and $F000) shr 12];
  end;
  I := 0;
  While (Result=0) and (I<=high(DNSServers)) do
    begin
    Result:=ResolveAddressAt(I,S,Addresses,1);
    Inc(I);
    end;
end;

function IN6_IS_ADDR_V4MAPPED(HostAddr: THostAddr6): boolean;
begin
  Result := 
   (HostAddr.u6_addr16[0] = 0) and
   (HostAddr.u6_addr16[1] = 0) and
   (HostAddr.u6_addr16[2] = 0) and
   (HostAddr.u6_addr16[3] = 0) and
   (HostAddr.u6_addr16[4] = 0) and
   (HostAddr.u6_addr16[5] = $FFFF);
end;

Function HandleAsFullyQualifiedName(const HostName: String) : Boolean;
var
  I,J : Integer;
begin
  Result := False;
  J := 0;
  for I := 1 to Length(HostName) do
    if HostName[I] = '.' then
      begin
      Inc(J);
      if J >= NDots then
        begin
        Result := True;
        Break;
        end;
      end;
end;

function ResolveHostByName(HostName: String; var H: THostEntry): Boolean;

Var
  Address : Array[1..MaxResolveAddr] of THostAddr;
  AbsoluteQueryFirst : Boolean;
  L : Integer;
  K : Integer;

begin
  // Use domain or search-list to append to the searched hostname.
  // When the amount of dots in hostname is higher or equal to ndots,
  // do the query without adding any search-domain first.
  // See the resolv.conf manual for more info.
  if (DefaultDomainList<>'') then
    begin
    // Fill the cached DefaultDomainListArr and NDots
    if (Length(DefaultDomainListArr) = 0) then
      begin
      DefaultDomainListArr := DefaultDomainList.Split(' ',Char(9));
      L := Pos('ndots:', DNSOptions);
      if L > 0 then
        NDots := StrToIntDef(Trim(Copy(DNSOptions, L+6, 2)), 1);
      end;

    AbsoluteQueryFirst := HandleAsFullyQualifiedName(HostName);
    if AbsoluteQueryFirst then
      L:=ResolveName(HostName,Address)
    else
      L := -1;

    K := 0;
    while (L < 1) and (K < Length(DefaultDomainListArr)) do
      begin
      L:=ResolveName(HostName + '.' + DefaultDomainListArr[K],Address);
      Inc(K);
      end;
    end
  else
    begin
    AbsoluteQueryFirst := False;
    L := -1;
    end;

  if (L<1) and not AbsoluteQueryFirst then
    L:=ResolveName(HostName,Address);

  Result:=(L>0);
  If Result then
    begin
    // We could add a reverse call here to get the real name and aliases.
    H.Name:=HostName;
    H.Addr:=Address[1];
    H.aliases:='';
    end;
end;

function ResolveHostByName6(Hostname: String; var H: THostEntry6): Boolean;

Var
  Address : Array[1..MaxResolveAddr] of THostAddr6;
  L : Integer;
  
begin
  L:=ResolveName6(HostName,Address);
  Result:=(L>0);
  If Result then
    begin
    // We could add a reverse call here to get the real name and aliases.
    H.Name:=HostName;
    H.Addr:=Address[1];
    H.aliases:='';
    end;
end;


function ResolveHostByAddr(HostAddr: THostAddr; var H: THostEntry): Boolean;

Var
  Names : Array[1..MaxResolveAddr] of String;
  I,L : Integer;
  
begin
  L:=ResolveAddress(HostAddr,Names);
  Result:=(L>0);
  If Result then
    begin
    H.Name:=Names[1];
    H.Addr:=HostAddr;
    H.Aliases:='';
    If (L>1) then
      For I:=2 to L do
        If (I=2) then
          H.Aliases:=Names[i]
        else  
          H.Aliases:=H.Aliases+','+Names[i];
    end;
end;

function ResolveHostByAddr6(HostAddr: THostAddr6; var H: THostEntry6): Boolean;

Var
  Names : Array[1..MaxResolveAddr] of String;
  I,L : Integer;
  
begin
  L:=ResolveAddress6(HostAddr,Names);
  Result:=(L>0);
  If Result then
    begin
    H.Name:=Names[1];
    H.Addr:=HostAddr;
    H.Aliases:='';
    If (L>1) then
      For I:=2 to L do
        If (I=2) then
          H.Aliases:=Names[i]
        else  
          H.Aliases:=H.Aliases+','+Names[i];
    end;
end;




//const NoAddress : in_addr = (s_addr: 0);

function GetHostByName(HostName: String; var H: THostEntry): boolean;

begin
  Result:=FindHostEntryInHostsFile(HostName,NoAddress,H);
end;


function GetHostByAddr(Addr: THostAddr; var H: THostEntry): boolean;

begin
  Result:=FindHostEntryInHostsFile('',Addr,H);
end;


{ ---------------------------------------------------------------------
    /etc/protocols handling.
  ---------------------------------------------------------------------}

Function GetNextProtoEntry(var F : Text; Var H : TProtocolEntry): boolean;

Var
  Line,S : String;
  I      : integer;
  
begin
  Result:=False;
  Repeat
    ReadLn(F,Line);
    StripComment(Line);
    S:=NextWord(Line);
    If (S<>'') then
      begin
        H.Name:=S;	
        S:=NextWord(Line);
	i:=strtointdef(s,-1);
        If (i<>-1) then
          begin
          H.number:=i;
          Result:=True;
          H.Aliases:='';
          Repeat
            S:=NextWord(line);
            If (S<>'') then
              If (H.Aliases='') then
                H.Aliases:=S
              else
                H.Aliases:=H.Aliases+','+S;  
          until (S='');
          end;
      end;
  until Result or EOF(F);
end;  

Function FindProtoEntryInProtoFile(N: String; prot: integer; Var H : TProtocolEntry) : boolean;

Var
  F : Text;
  HE : TProtocolEntry;
  
begin
  Result:=False;
  If FileExists (EtcPath + SProtocolFile) then
    begin
    Assign (F, EtcPath + SProtocolFile);
    {$push}{$i-}
    Reset(F);
    {$pop}
    If (IOResult=0) then
      begin
      While Not Result and GetNextProtoEntry(F,HE) do
        begin
        If (N<>'') then
          Result:=MatchNameOrAlias(N,HE.Name,HE.Aliases)
        else
          Result:=prot=he.number;
        end; 
      Close(f);
      If Result then
        begin
        H.Name:=HE.Name;
        H.number:=he.number;
        H.Aliases:=HE.Aliases;
        end;
      end;  
    end;
end;

function GetProtocolByName(ProtoName: String; var H: TProtocolEntry): boolean;

begin
  Result:=FindProtoEntryInProtoFile(ProtoName,0,H);
end;


function GetProtocolByNumber(proto: Integer; var H: TProtocolEntry): boolean;

begin
  Result:=FindProtoEntryInProtoFile('',Proto,H);
end;

{ ---------------------------------------------------------------------
    /etc/networks handling
  ---------------------------------------------------------------------}

function StrTonetpartial( IP : AnsiString) : in_addr ;

Var
    Dummy : AnsiString;
    I,j,k     : Longint;
//    Temp : in_addr;

begin
  strtonetpartial.s_addr:=0;              //:=NoAddress;
  i:=0; j:=0;
  while (i<4) and (j=0) do
   begin
     J:=Pos('.',IP);
     if j=0 then j:=length(ip)+1;
     Dummy:=Copy(IP,1,J-1);
     Delete (IP,1,J);
     Val (Dummy,k,J);
     if j=0 then
      strtonetpartial.s_bytes[i+1]:=k;
     inc(i);
   end;
   if (i=0) then strtonetpartial.s_addr:=0;
end;

Function GetNextNetworkEntry(var F : Text; Var N : TNetworkEntry): boolean;

Var
  NN,Line,S : String;
  A : TNetAddr;
  
begin
  Result:=False;
  Repeat
    ReadLn(F,Line);
    StripComment(Line);
    S:=NextWord(Line);
    If (S<>'') then
      begin
      NN:=S;
      A:=StrTonetpartial(NextWord(Line));
      Result:=(NN<>'') and (A.s_bytes[1]<>0); // Valid addr.
      If result then
        begin
        N.Addr.s_addr:=A.s_addr; // keep it host.
        N.Name:=NN;
        N.Aliases:='';
        end;      
      end;
  until Result or EOF(F);
end;  

Function FindNetworkEntryInNetworksFile(Net: String; Addr: TNetAddr; Var N : TNetworkEntry) : boolean;

Var
  F : Text;
  NE : TNetworkEntry;
  
begin
  Result:=False;
  If FileExists (EtcPath + SNetworksFile) then
    begin
    Assign (F, EtcPath + SNetworksFile);
    {$push}{$i-}
    Reset(F);
    {$pop}
    If (IOResult=0) then
      begin
      While Not Result and GetNextNetworkEntry(F,NE) do
        begin
        If (Net<>'') then
          Result:=MatchNameOrAlias(Net,NE.Name,NE.Aliases)
        else
          Result:=Cardinal(Addr)=Cardinal(NE.Addr);
        end; 
      Close(f);
      If Result then
        begin
        N.Name:=NE.Name;
        N.Addr:=nettohost(NE.Addr);
        N.Aliases:=NE.Aliases;
        end;
      end;  
    end;
end;

Const NoNet : in_addr = (s_addr:0);
  
function GetNetworkByName(NetName: String; var N: TNetworkEntry): boolean;

begin
  Result:=FindNetworkEntryInNetworksFile(NetName,NoNet,N);
end;

function GetNetworkByAddr(Addr: THostAddr; var N: TNetworkEntry): boolean;

begin
  Result:=FindNetworkEntryInNetworksFile('',Addr,N);
end;

{ ---------------------------------------------------------------------
    /etc/services section
  ---------------------------------------------------------------------}

Function GetNextServiceEntry(Var F : Text; Var E : TServiceEntry) : Boolean;


Var
  Line,S : String;
  P : INteger;
  
begin
  Result:=False;
  Repeat
    ReadLn(F,Line);
    StripComment(Line);
    S:=NextWord(Line);
    If (S<>'') then
      begin
      E.Name:=S;
      S:=NextWord(Line);
      P:=Pos('/',S);
      If (P<>0) then
        begin
        E.Port:=StrToIntDef(Copy(S,1,P-1),0);
        If (E.Port<>0) then
          begin
          E.Protocol:=Copy(S,P+1,Length(S)-P);
          Result:=length(E.Protocol)>0;
          E.Aliases:='';
          Repeat
            S:=NextWord(Line);
            If (S<>'') then
              If (Length(E.Aliases)=0) then
                E.aliases:=S
              else  
                E.Aliases:=E.Aliases+','+S;
          until (S='');
          end;
        end;
      end;
  until Result or EOF(F);
end;


Function FindServiceEntryInFile(Const Name,Proto : String; Port : Integer; Var E : TServiceEntry) : Boolean;

Var
  F : Text;
  TE : TServiceEntry;
  
begin
  Result:=False;
  If FileExists (EtcPath + SServicesFile) then
    begin
    Assign (F, EtcPath + SServicesFile);
    {$push}{$i-}
    Reset(F);
    {$pop}
    If (IOResult=0) then
      begin
      While Not Result and GetNextServiceEntry(F,TE) do
        begin
        If (Port=-1) then
          Result:=MatchNameOrAlias(Name,TE.Name,TE.Aliases)
        else 
          Result:=(Port=TE.Port);
        If Result and (Proto<>'') then
          Result:=(Proto=TE.Protocol);
        end; 
      Close(f);
      If Result then
        begin
        E.Name:=TE.Name;
        E.Port:=TE.Port;
        E.Protocol:=TE.Protocol;
        E.Aliases:=TE.Aliases;
        end;
      end;  
    end;
end;

function GetServiceByName(const Name, Proto: String; var E: TServiceEntry
  ): Boolean;

begin
  Result:=FindServiceEntryInFile(Name,Proto,-1,E);  
end;

function GetServiceByPort(Port: Word; const Proto: String; var E: TServiceEntry
  ): Boolean;

begin
  Result:=FindServiceEntryInFile('',Proto,Port,E);  
end;

{ ---------------------------------------------------------------------
    Initialization section
  ---------------------------------------------------------------------}

Procedure InitResolver;

begin
  TimeOutS :=5;
  TimeOutMS:=0;
  CheckHostsFileAge:=False;
{$IFDEF UNIX_ETC}
  EtcPath := '/etc/';
{$ELSE UNIX_ETC}
 {$IFDEF ETC_BY_ENV}
  EtcPath := GetEnvironmentVariable ('ETC');
  if (EtcPath <> '') and (EtcPath [Length (EtcPath)] <> DirectorySeparator) then
   EtcPath := EtcPath + DirectorySeparator;
 {$ELSE ETC_BY_ENV}
{$WARNING Support for finding /etc/ directory not implemented for this platform!}

 {$ENDIF ETC_BY_ENV}
{$ENDIF UNIX_ETC}
  If FileExists (EtcPath + SHostsFile) then
    HostsList := ProcessHosts (EtcPath + SHostsFile);
{$ifdef android}
  CheckResolveFileAge:=True;
  CheckResolveFile;
{$else}
  CheckResolveFileAge:=False;
  If FileExists(EtcPath + SResolveFile) then
    GetDNsservers(EtcPath + SResolveFile)
{$endif android}
{$IFDEF OS2}
  else if FileExists(EtcPath + SResolveFile2) then
    GetDNsservers(EtcPath + SResolveFile2)
{$ENDIF OS2}
                                         ;
end;

Procedure DoneResolver;

begin
  FreeHostsList(HostsList);
end;


Initialization
  InitResolver;
Finalization
  DoneResolver;  
end.
newnetdb.pp (44,609 bytes)   

Noel Duffy

2020-12-06 03:02

reporter   ~0127368

This note has attached to it a more functional example program that demonstrates the intended API more fully. The dnsq program operates like the Unix utility dig, accepting a query type, which is one of a, aaaa, mx, ns, ptr, txt or soa, and a dns domain name. The program queries the configured resolver and outputs all the DNS resource records returned, if any. It shows not only the main answers but also additional and authoritative answers as well. Additionally, it detects truncation and falls back to TCP in that case.

Example usage,

$ ./dnsq a freepascal.org
 - freepascal.org (A)
Answers: 1 NS: 0 Additional: 0 Length: 36 AnsStart: 20

-- Answers
freepascal.org 172800 A 85.222.228.11

$ ./dnsq ptr 11.228.222.85.in-addr.arpa
 - 11.228.222.85.in-addr.arpa (PTR)
Answers: 1 NS: 0 Additional: 0 Length: 69 AnsStart: 32

-- Answers
11.228.222.85.in-addr.arpa 3582 PTR vps46799.freepascal.org

$ ./dnsq ns freepascal.org
 - freepascal.org (NS)
Answers: 3 NS: 0 Additional: 4 Length: 185 AnsStart: 20

-- Answers
freepascal.org 172800 NS dns1.easydns.com
freepascal.org 172800 NS dns3.easydns.ca
freepascal.org 172800 NS dns2.easydns.net

-- Additional Section
dns2.easydns.net 183 A 198.41.222.254
dns1.easydns.com 289 A 64.68.192.10
dns1.easydns.com 216 AAAA 2400:CB00:2049:0001::A29F:1835
dns3.easydns.ca 86 A 64.68.196.10
 
$ ./dnsq txt freepascal.org
 - freepascal.org (TXT)
Answers: 1 NS: 0 Additional: 0 Length: 87 AnsStart: 20

-- Answers
freepascal.org 170448 TXT v=spf1 mx a:lists.freepascal.org a:mail.freepascal.org

$ ./dnsq soa freepascal.org
 - freepascal.org (SOA)
Answers: 1 NS: 0 Additional: 0 Length: 77 AnsStart: 20

-- Answers
freepascal.org 170450 SOA dns1.easydns.com zone.easydns.com, serial:1598197381, refresh:3600, retry:600, min:300, expire:604800

$ ./dnsq mx freepascal.org
 - freepascal.org (MX)
Answers: 1 NS: 0 Additional: 0 Length: 41 AnsStart: 20

-- Answers
freepascal.org 172710 MX 10 mail.freepascal.org

$ ./dnsq aaaa www.microsoft.com
 - www.microsoft.com (AAAA)
Answers: 5 NS: 0 Additional: 0 Length: 230 AnsStart: 23

-- Answers
www.microsoft.com 2596 CNAME www.microsoft.com-c-3.edgekey.net
www.microsoft.com-c-3.edgekey.net 19547 CNAME www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net
www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net 383 CNAME e13678.dspb.akamaiedge.net
e13678.dspb.akamaiedge.net 20 AAAA 2600:1415:0005:018D::356E
e13678.dspb.akamaiedge.net 20 AAAA 2600:1415:0005:0197::356E

And lastly, a huge txt record:

$ ./dnsq txt sony.com
 - sony.com (TXT)
Response truncated ... retrying as TCP
Answers: 22 NS: 0 Additional: 0 Length: 2260 AnsStart: 14

-- Answers
sony.com 600 TXT stripe-verification=35ba23934a707a07c4c9be6e43adc627d3cb801a293fdb8ca7bc5a940d9c853d
sony.com 600 TXT stripe-verification=e3c5cc73ce14364162038aa39a921d6ad8cd17b95c69d1e35ebe7d776f416c27
sony.com 600 TXT v=spf1 include:spfa.sony.com include:spf.protection.outlook.com include:amazonses.com include:servers.mcsv.net ip4:160.33.194.224/28 ip4:83.138.165.68/31 ip4:212.100.250.11 ip4:37.188.101.80/28 ip4:212.100.250.16/29 ip4:46.19.168.0/23 ip4:208.74.204.0/22 ip4:139.60.152.0/22 ip4:54.186.193.102/32 ip4:52.222.73.120/32 ip4:52.222.73.83/32 ip4:52.222.62.51/32 ip4:52.222.75.85/32 ip4:5.61.115.80/28 ip4:5.61.115.96/28 ip4:5.61.115.112/28 ip4:5.61.117.80/28 ip4:5.61.117.96/28 ip4:5.61.117.112/28 ip4:185.132.183.11 ip4:185.183.30.70 -all
sony.com 600 TXT webexdomainverification.ELPM=7682f227-dbc9-4df9-ae72-7649e05b521f
sony.com 600 TXT 0ed1fe018ab8b5050c7c8341b7b8894557d2554815
sony.com 600 TXT MS=ms30214679
sony.com 600 TXT YzcX/ANAcVb1c6oLNOXQzniFpgGypdlowJHvEvmRuqyhkvEsdv/zFQuiZuYakJL3xpHMmttCjOvNqxz3g+LFeg==
sony.com 600 TXT adobe-idp-site-verification=cff4ddad-a01e-4226-a77f-8c081cde0aee
sony.com 600 TXT adobe-sign-verification=b4a30c4f74bb611dce0e5d515054481c
sony.com 600 TXT amazonses:OfVkq/yn1d+o09tdXhxkoHbIGCNeP8aYj3amzwACQ3c=
sony.com 600 TXT cisco-ci-domain-verification=221d305d2b1221f9d96ea9cde0d89df2a2ddc44fef8454a724e1a22dd27bd782
sony.com 600 TXT cloudhealth=ef6859d5-232b-4ff0-8811-ded26d79e7ee
sony.com 600 TXT docusign=3122656f-b5f7-497b-8782-4907222b538a
sony.com 600 TXT docusign=877ac654-f0e6-4bc6-a293-49c26778da82
sony.com 600 TXT facebook-domain-verification=rn9nh6m7g7sxesufnk7gufxr7pht73
sony.com 600 TXT google-site-verification=j1FfNnOllL0QdFSzHNHnHAcWV_54Kbd_bURGKTK3y4s
sony.com 600 TXT google-site-verification=zfXryc1xAcIFps86mXmIJrDtpsur406Wn-pOMSS0i5w
sony.com 600 TXT k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBfF8XiPmS/aLbBcNnixpRclWpr1Z0MY4Hy9h3oW4VF6XDJaKhmTWkaOvKIv3ZMQyjIrbpmBwL0xiyy3F88HwPi9tA7POXgpsl12W3EXu2qzOHhMvpT7VZC0vFArz3H1djX3+4UGixZyt14lrXEvgd9TE9cJs2/RXdF0Joosx74QIDAQAB
sony.com 600 TXT onetrust-domain-verification=07d2af6be3aa4cdc99ebe26e053cdd18
sony.com 600 TXT smartsheet-site-validation=6_otSYK33LBHB3hGD4yvBCRC3K36fKfy
sony.com 600 TXT smartsheet-site-validation=bKd1dTuQ8acbZh57Q5DDRrTEaI0qI2SW
sony.com 600 TXT status-page-domain-verification=t7crx8w5wb4b
dnsq.lpr (11,500 bytes)   
program dnsq;

{$mode objfpc}
{$h+}

uses
  newnetdb, sockets, sysutils, math;

procedure dump_payload(const pl: TPayLoad; l: Word);
var
  idx,llen: Cardinal;
begin
  idx := 0;
  llen := 0;
  for idx := 0 to l do
  begin
    write('['+inttostr(idx)+'] '+IntToHex(pl[idx],2));
    if (pl[idx] > 48) and (pl[idx] < 123) then
      write(' ' + chr(pl[idx]))
    else
      write(' .');
    write(' ');
    Inc(llen);
    if llen >= 6 then
    begin
      llen := 0;
      writeln();
    end;
  end;
  if llen > 0 then
  begin
    writeln();
  end;
end;

procedure dump_payload(const pl: TPayLoadTCP; l: Word);
var
  idx,llen: Cardinal;
begin
  idx := 0;
  llen := 0;
  for idx := 0 to l do
  begin
    write('['+inttostr(idx)+'] '+IntToHex(pl[idx],2));
    if (pl[idx] > 48) and (pl[idx] < 123) then
      write(' ' + chr(pl[idx]))
    else
      write(' .');
    write(' ');
    Inc(llen);
    if llen >= 6 then
    begin
      llen := 0;
      writeln();
    end;
  end;
  if llen > 0 then
  begin
    writeln();
  end;
end;

procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayloadTCP);
var
  s: AnsiString;
  ss: ShortString;
  b: Boolean;
  ip: THostAddr;
  ip6: THostAddr6;
  mx: TDNSRR_MX;
  soa: TDNSRR_SOA;
begin
  s := 'Unknown';
  case RRN.RRMeta.Atype of
    DNSQRY_TXT:
        s := 'TXT';
    DNSQRY_A:
        s := 'A';
    DNSQRY_MX:
        s := 'MX';
    DNSQRY_PTR:
        s := 'PTR';
    DNSQRY_CNAME:
      s := 'CNAME';
    DNSQRY_AAAA:
      s := 'AAAA';
    DNSQRY_SOA:
      s := 'SOA';
    DNSQRY_NS:
      s := 'NS';
  end;

  write(RRN.RRName);
  //write(' aclass='+inttostr(RRN.RRMeta.AClass));
  write(' '+inttostr(RRN.RRMeta.TTL));
  write(' '+s+' ');
  {write(' start='+inttostr(RRN.RDataSt));
  writeln(' length='+inttostr(RRN.RRMeta.RDLength));}
  case RRN.RRMeta.Atype of
    DNSQRY_A:
      begin
        b := DNSRRGetA(RRN, pl, ip);
        if b then WriteLn(HostAddrToStr(ip)) else WriteLn('Failed');
      end;
    DNSQRY_CNAME:
      begin
        b := DNSRRGetCNAME(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
    DNSQRY_AAAA:
      begin
        b := DNSRRGetAAAA(RRN, pl, ip6);
        if b then WriteLn(HostAddrToStr6(ip6)) else WriteLn('Failed');
      end;
    DNSQRY_NS:
      begin
        b := DNSRRGetNS(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
    DNSQRY_SOA:
      begin
        b := DNSRRGetSOA(RRN, pl, soa);
        if b then
          WriteLn(
            soa.mname+
            ' '+soa.rname+
            ', serial:'+inttostr(soa.serial)+
            ', refresh:'+inttostr(soa.refresh)+
            ', retry:'+inttostr(soa.retry)+
            ', min:'+inttostr(soa.min)+
            ', expire:'+inttostr(soa.expire))
        else WriteLn('Failed');
      end;
    DNSQRY_TXT:
      begin
        b := DNSRRGetText(RRN, pl, s);
        if b then WriteLn(s) else WriteLn('Failed');
      end;
    DNSQRY_MX:
      begin
        b := DNSRRGetMX(RRN, pl, mx);
        if b then WriteLn(inttostr(mx.preference)+' '+mx.exchange)
        else WriteLn('Failed');
      end;
    DNSQRY_PTR:
      begin
        b := DNSRRGetPTR(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
  end;
end;

procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
var
  s: AnsiString;
  ss: ShortString;
  b: Boolean;
  ip: THostAddr;
  ip6: THostAddr6;
  mx: TDNSRR_MX;
  soa: TDNSRR_SOA;
begin
  s := 'Unknown';
  case RRN.RRMeta.Atype of
    DNSQRY_TXT:
        s := 'TXT';
    DNSQRY_A:
        s := 'A';
    DNSQRY_MX:
        s := 'MX';
    DNSQRY_PTR:
        s := 'PTR';
    DNSQRY_CNAME:
      s := 'CNAME';
    DNSQRY_AAAA:
      s := 'AAAA';
    DNSQRY_SOA:
      s := 'SOA';
    DNSQRY_NS:
      s := 'NS';
  end;

  write(RRN.RRName);
  //write(' aclass='+inttostr(RRN.RRMeta.AClass));
  write(' '+inttostr(RRN.RRMeta.TTL)+' ');
  write(s+' ');
  {write(' start='+inttostr(RRN.RDataSt));
  writeln(' length='+inttostr(RRN.RRMeta.RDLength));}
  case RRN.RRMeta.Atype of
    DNSQRY_A:
      begin
        b := DNSRRGetA(RRN, pl, ip);
        if b then WriteLn(HostAddrToStr(ip)) else WriteLn('Failed');
      end;
    DNSQRY_CNAME:
      begin
        b := DNSRRGetCNAME(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
    DNSQRY_AAAA:
      begin
        b := DNSRRGetAAAA(RRN, pl, ip6);
        if b then WriteLn(HostAddrToStr6(ip6)) else WriteLn('Failed');
      end;
    DNSQRY_NS:
      begin
        b := DNSRRGetNS(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
    DNSQRY_SOA:
      begin
        b := DNSRRGetSOA(RRN, pl, soa);
        if b then
          WriteLn(
            soa.mname+
            ' '+soa.rname+
            ', serial:'+inttostr(soa.serial)+
            ', refresh:'+inttostr(soa.refresh)+
            ', retry:'+inttostr(soa.retry)+
            ', min:'+inttostr(soa.min)+
            ', expire:'+inttostr(soa.expire))
        else WriteLn('Failed');
      end;
    DNSQRY_TXT:
      begin
        b := DNSRRGetText(RRN, pl, s);
        if b then WriteLn(s) else WriteLn('Failed');
      end;
    DNSQRY_MX:
      begin
        b := DNSRRGetMX(RRN, pl, mx);
        if b then WriteLn(inttostr(mx.preference)+' '+mx.exchange)
        else WriteLn('Failed');
      end;
    DNSQRY_PTR:
      begin
        b := DNSRRGetPTR(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
  end;
end;

procedure QueryDebugTCP(dn: AnsiString; qtype: Word);
var
  Ans: TQueryDataLengthTCP;
  MaxAnswer, AnsLen: Longint;
  AnsStart: Word;
  RRN: TRRNameData;
  r: TDNSRcode;
  RRArr: TRRNameDataArray;
  qr: Boolean;

begin
  qr := DnsLookup(dn, qtype, Ans, AnsLen);
  if not qr then
  begin
    if AnsLen = -1 then
    begin
      writeln('No DNS server could be reached.');
      exit;
    end;
    r := GetRcode(Ans.h);
    case r of
      rcInvalid: writeln('Invalid response');
      rcNoError:
        begin
          writeln('Server returned no records.');
        end;
      rcRefused: writeln('Refused');
      rcNotImpl: writeln('Not implemented');
      rcNXDomain: writeln('NXDOMAIN');
      rcServFail: writeln('SERVFAIL');
      rcFormatError: writeln('Format error');
      rcReserved: writeln('Reserved code');
    end;
    exit;
  end
  else
  begin
    AnsStart:=SkipAnsQueriesTCP(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount;
    //dump_payload(Ans.Payload, AnsLen);
    Write('Answers: '+inttostr(Ans.h.ancount)+' ');
    Write('NS: '+inttostr(NToHS(Ans.h.nscount))+' ');
    Write('Additional: '+inttostr(NToHS(Ans.h.arcount))+' ');
    Write('Length: '+inttostr(AnsLen)+' ');
    WriteLn('AnsStart: '+inttostr(AnsStart));

    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- Answers');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;

    // dump any ns records
    // if there are authority RRs, we're already pointed at the
    // first one.
    MaxAnswer := NToHS(Ans.h.nscount);
    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- NS AUTHORITY Section');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;

    // dump any additional records
    // if there are additional RRs, we're already pointed at the
    // first one.
    MaxAnswer := NToHS(Ans.h.arcount);
    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- Additional section');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;
  end;
end;

procedure QueryDebugUDP(dn: AnsiString; qtype: Word);
var
  Ans: TQueryData;
  MaxAnswer, AnsLen: Longint;
  AnsStart: Word;
  r: TDNSRcode;
  RRN: TRRNameData;
  RRArr: TRRNameDataArray;
  qr: Boolean;

begin
  qr := DnsLookup(dn, qtype, Ans, AnsLen);
  if not qr then
  begin
    if AnsLen = -1 then
    begin
      writeln('No DNS server could be reached.');
      exit;
    end;
    r := GetRcode(Ans.h);
    case r of
      rcInvalid: writeln('Invalid response');
      rcNoError:
        begin
          writeln('Server returned no records.');
        end;
      rcRefused: writeln('Refused');
      rcNotImpl: writeln('Not implemented');
      rcNXDomain: writeln('NXDOMAIN');
      rcServFail: writeln('SERVFAIL');
      rcFormatError: writeln('Format error');
      rcReserved: writeln('Reserved code');
    end;
    exit;
  end
  else
  begin
    if IsTruncated(Ans.h) then
    begin
      writeln('Response truncated ... retrying as TCP');
      QueryDebugTCP(dn, qtype);
      exit;
    end;
    //dump_payload(Ans.Payload, AnsLen);
    AnsStart:=SkipAnsQueries(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount;

    Write('Answers: '+inttostr(Ans.h.ancount)+' ');
    Write('NS: '+inttostr(NToHS(Ans.h.nscount))+' ');
    Write('Additional: '+inttostr(NToHS(Ans.h.arcount))+' ');
    Write('Length: '+inttostr(AnsLen)+' ');
    WriteLn('AnsStart: '+inttostr(AnsStart));

    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- Answers');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;

    // dump any ns records
    // if there are authority RRs, we're already pointed at the
    // first one.
    MaxAnswer := NToHS(Ans.h.nscount);
    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- NS AUTHORITY Section');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;

    // dump any additional records
    // if there are additional RRs, we're already pointed at the
    // first one.
    MaxAnswer := NToHS(Ans.h.arcount);
    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- Additional Section');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;
  end;
end;

function QTypeToStr(qtype: Word): String;
begin
  Result := 'Unknown';
  case qtype of
    DNSQRY_A: Result := 'A';
    DNSQRY_AAAA: Result := 'AAAA';
    DNSQRY_NS: Result := 'NS';
    DNSQRY_SOA: Result := 'SOA';
    DNSQRY_MX: Result := 'MX';
    DNSQRY_CNAME: Result := 'CNAME';
    DNSQRY_PTR: Result := 'PTR';
    DNSQRY_TXT: Result := 'TXT';
  end;
end;

var
  S,S1: String;
  qtype: Word;
begin
  if ParamCount <> 2 then
  begin
    WriteLn(ParamStr(0)+' qtype domain');
    WriteLn('Query DNS records for domain <domain>. Parameter qtype is one of:');
    WriteLn(' - A (A host address)');
    WriteLn(' - MX (Mail Exchanger)');
    WriteLn(' - SOA (Start of Authority)');
    WriteLn(' - NS (Authoritative Name Server)');
    WriteLn(' - AAAA (IPv6 hostname)');
    WriteLn(' - CNAME (Canonical Name)');
    WriteLn(' - PTR (reverse domain lookup for ip using pseudo domain IN-ADDR.ARPA');
    WriteLn(' - TXT (Free-form text strings. May exceed UDP 512 byte limit.)');
    exit;
  end;

  S := ParamStr(2);
  S1 := LowerCase(ParamStr(1));
  case S1 of
    'ns': qtype := DNSQRY_NS;
    'mx': qtype := DNSQRY_MX;
    'soa': qtype := DNSQRY_SOA;
    'a': qtype := DNSQRY_A;
    'aaaa': qtype := DNSQRY_AAAA;
    'cname': qtype := DNSQRY_CNAME;
    'ptr': qtype := DNSQRY_PTR;
    'txt': qtype := DNSQRY_TXT;
  else
    qtype := DNSQRY_A;
  end;

  WriteLn(' - ' + S +' ('+QTypeToStr(qtype)+')');
  QueryDebugUDP(S, qtype);
end.

dnsq.lpr (11,500 bytes)   
newnetdb-2.pp (61,826 bytes)   
{
    This file is part of the Free Pascal run time library.
    Copyright (c) 2003 by the Free Pascal development team

    Implement networking routines.
    
    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}
{$mode objfpc}
{$h+}

unit newnetdb;
{
  WARNING
  This unit hardly does any error checking. For example, stringfromlabel
  could easily be exploited by  someone sending malicious UDP packets in
  order to crash  your program.  So if you really want to depend on this
  in critical programs then you'd better fix a lot of code in here.
  Otherwise, it appears to work pretty well.
}

Interface

Uses Sockets;

{$IFDEF OS2}
(* ETC directory location determined by environment variable ETC *)
 {$DEFINE ETC_BY_ENV}
(* Use names supported also on non-LFN drives like plain FAT-16. *)
 {$DEFINE SFN_VERSION}
{$ENDIF OS2}
{$IFDEF GO32V2}
 {$DEFINE ETC_BY_ENV}
 {$DEFINE SFN_VERSION}
{$ENDIF GO32V2}
{$IFDEF WATCOM}
 {$DEFINE ETC_BY_ENV}
 {$DEFINE SFN_VERSION}
{$ENDIF WATCOM}

{$IFDEF UNIX}
(* ETC directory location hardcoded to /etc/ *)
 {$DEFINE UNIX_ETC}
{$ENDIF UNIX}

Type
  THostAddr = in_addr;		// historical aliases for these.
  THostAddr6= Tin6_addr;
  TNetAddr  = THostAddr;	// but in net order.

Const
  DNSPort        = 53;
  MaxResolveAddr = 10;
  SServicesFile  = 'services'; 
  SHostsFile     = 'hosts';
  SNetworksFile  = 'networks';
  MAX_UDP_PAYLOAD = 512;
  MAX_TCP_PAYLOAD = 65535;

  { from http://www.iana.org/assignments/dns-parameters }
  DNSQRY_A     = 1;                     // name to IP address
  DNSQRY_AAAA  = 28;                    // name to IP6 address
  DNSQRY_A6    = 38;                    // name to IP6 (new)
  DNSQRY_PTR   = 12;                    // IP address to name
  DNSQRY_MX    = 15;                    // name to MX
  DNSQRY_TXT   = 16;                    // name to TXT
  DNSQRY_CNAME = 5;
  DNSQRY_SOA   = 6;
  DNSQRY_NS    = 2;

  // Flags 1
  QF_QR     = $80;
  QF_OPCODE = $78;
  QF_AA     = $04;
  QF_TC     = $02;  // Truncated.
  QF_RD     = $01;

  // Flags 2
  QF_RA     = $80;
  QF_Z      = $70;
  QF_RCODE  = $0F;

{$IFDEF SFN_VERSION}
  SProtocolFile  = 'protocol';
  SResolveFile   = 'resolv';
 {$IFDEF OS2}
(* Peculiarity of OS/2 - depending on the used TCP/IP version, *)
(* the file differs slightly in name and partly also content.  *)
   SResolveFile2  = 'resolv2';
 {$ENDIF OS2}
{$ELSE SFN_VERSION}
  SProtocolFile  = 'protocols';
  SResolveFile   = 'resolv.conf';
{$ENDIF SFN_VERSION}

  MaxRecursion = 10;
  MaxIP4Mapped = 10;

var
  EtcPath: string;

Type
  TDNSRcode = (rcNoError, rcFormatError,rcServFail,rcNXDomain,
    rcNotImpl,rcRefused,rcReserved,rcInvalid);

  TDNSServerArray = Array of THostAddr;
  TServiceEntry = record
    Name     : String;
    Protocol : String;
    Port     : Word;
    Aliases  : String;
  end;
     
  THostEntry = record
    Name : String;
    Addr : THostAddr;
    Aliases : String;
  end;
  PHostEntry = ^THostEntry;
  THostEntryArray = Array of THostEntry;

  THostEntry6 = record
    Name : String;
    Addr : THostAddr6;
    Aliases : String;
  end;
  PHostEntry6 = ^THostEntry6;
  THostEntry6Array = Array of THostEntry6;
  
  TNetworkEntry = Record
    Name : String;
    Addr : TNetAddr;
    Aliases : String;
  end;  
  PNetworkEntry = ^TNetworkEntry;

  TProtocolEntry = Record
    Name : String;
    Number : integer;
    Aliases : String;
  end;  
  PProtocolEntry = ^TProtocolEntry;

  PHostListEntry = ^THostListEntry;
  THostListEntry = Record
    Entry : THostEntry;
    Next : PHostListEntry;
  end;

  TPayLoad  = Array[0..511] of Byte;
  TPayLoadTCP = Array[0 .. 65535] of Byte;

  TDNSHeader = packed Record
    id      : Array[0..1] of Byte;
    flags1  : Byte;
    flags2  : Byte;
    qdcount : word;
    ancount : word;
    nscount : word;
    arcount : word;
  end;

  TQueryData = packed Record
    h: TDNSHeader;
    Payload : TPayLoad;
  end;

  TQueryDataLength = packed record
    length: Word;
    hpl: TQueryData;
  end;

  TQueryDataLengthTCP = packed Record
    length: Word;
    h: TDNSHeader;
    Payload : TPayLoadTCP;
  end;

  PRRData = ^TRRData;
  TRRData = Packed record       // RR record
    Atype    : Word;            // Answer type
    AClass   : Word;
    TTL      : Cardinal;
    RDLength : Word;
  end;

  TRRNameData = packed record
    RRName   : ShortString;
    RRMeta   : TRRData;
    RDataSt  : Word;
  end;
  TRRNameDataArray = array of TRRNameData;

  TDNSDomainName = ShortString;
  TDNSRR_SOA = packed record
    mname, rname: TDNSDomainName;
    serial,refresh,retry,expire,min: Cardinal;
  end;
  TDNSRR_MX = packed record
    preference: Word;
    exchange: TDNSDomainName;
  end;

Var  
  DNSServers            : TDNSServerArray;
  DNSOptions            : String;
  DefaultDomainList     : String;
  CheckResolveFileAge   : Boolean; 
  CheckHostsFileAge     : Boolean; 
  TimeOutS,TimeOutMS    : Longint;
  
{$ifdef android}
Function GetDNSServers : Integer;
{$else}
Function GetDNSServers(FN : String) : Integer;
{$endif android}

Function ResolveName(HostName : String; Var Addresses : Array of THostAddr) : Integer;
Function ResolveName6(HostName : String; Var Addresses : Array of THostAddr6) : Integer;

function DnsLookup(dn: String; qtype: Word; out Ans: TQueryData;
  out AnsLen: Longint): Boolean;
function DnsLookup(dn: String; qtype: Word; out Ans: TQueryDataLengthTCP;
  out AnsLen: Longint): Boolean;

Function ResolveAddress(HostAddr : THostAddr; Var Addresses : Array of String) : Integer;
Function ResolveAddress6(HostAddr: THostAddr6; var Addresses: Array of string) : Integer;

function IN6_IS_ADDR_V4MAPPED(HostAddr: THostAddr6): boolean;

Function ResolveHostByName(HostName : String; Var H : THostEntry) : Boolean;
Function ResolveHostByAddr(HostAddr : THostAddr; Var H : THostEntry) : Boolean;

Function ResolveHostByName6(Hostname : String; Var H : THostEntry6) : Boolean;
Function ResolveHostByAddr6(HostAddr : THostAddr6; Var H : THostEntry6) : Boolean;

Function GetHostByName(HostName: String;  Var H : THostEntry) : boolean;
Function GetHostByAddr(Addr: THostAddr;  Var H : THostEntry) : boolean;

Function GetNetworkByName(NetName: String; Var N : TNetworkEntry) : boolean;
Function GetNetworkByAddr(Addr: THostAddr; Var N : TNetworkEntry) : boolean;

Function GetServiceByName(Const Name,Proto : String; Var E : TServiceEntry) : Boolean;
Function GetServiceByPort(Port : Word;Const Proto : String; Var E : TServiceEntry) : Boolean;

Function GetProtocolByName(ProtoName: String;  Var H : TProtocolEntry) : boolean;
Function GetProtocolByNumber(proto: Integer;  Var H : TProtocolEntry) : boolean;

Procedure DumpPayLoad(Q : TQueryData; L : Integer);

function IsTruncated(R: TDNSHeader): Boolean;
function GetRcode(R: TDNSHeader): TDNSRcode;

function GetFixlenStr(pl: TPayLoad; startidx: Cardinal; len: Byte;
  out res: ShortString): Byte;
function GetFixlenStr(pl: TPayLoadTCP; startidx: Cardinal; len: Byte;
  out res: ShortString): Byte;
Function ProcessHosts(FileName : String) : PHostListEntry;
Function FreeHostsList(var List : PHostListEntry) : Integer;
Procedure HostsListToArray(var List : PHostListEntry; Var Hosts : THostEntryArray; FreeList : Boolean);
Procedure CheckResolveFile;
Function Query(Resolver : Integer; Var Qry,Ans : TQueryData; QryLen : Integer; Var AnsLen : Integer) : Boolean;
function QueryTCP(Resolver : Integer; Var Qry: TQueryDataLength;
  var Ans: TQueryDataLengthTCP; QryLen : Integer; Var AnsLen : Integer) : Boolean;
Function BuildPayLoad(Var Q : TQueryData; Name : String; RR : Word; QClass : Word) : Integer;
Function BuildPayLoadTCP(Var Q : TQueryDataLength; Name : String; RR : Word; QClass : Word) : Integer;

Function SkipAnsQueries(Var Ans : TQueryData; L : Integer) : integer;
Function SkipAnsQueriesTCP(Var Ans : TQueryDataLengthTCP; L : Integer) : integer;

Function NextRR(Const PayLoad : TPayLoad;Var Start : LongInt; AnsLen : LongInt; Var RR : TRRData) : Boolean;
Function NextRR(Const PayLoad : TPayLoadTCP;Var Start : LongInt;
  AnsLen : LongInt; Var RR : TRRData) : Boolean;
function stringfromlabel(pl: TPayLoad; var start: Integer): string;
function stringfromlabel(pl: TPayLoadTCP; var start: Integer): string;
Function CheckAnswer(Const Qry : TDNSHeader; Var Ans : TDNSHeader) : Boolean;

function NextNameRR(const pl: TPayLoadTCP; start: Word;
  out RRName: TRRNameData): Boolean;
function NextNameRR(const pl: TPayLoad; start: Word;
  out RRName: TRRNameData): Boolean;

function GetRRrecords(const pl: TPayloadTCP; var Start: Word; Count: Word):
  TRRNameDataArray;
function GetRRrecords(const pl: TPayload; var Start: Word; Count: Word):
  TRRNameDataArray;

function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoadTCP;
  out IP: THostAddr): Boolean;
function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoad;
  out IP: THostAddr): Boolean;
function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoad;
  out cn: TDNSDomainName): Boolean;
function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoadTCP;
  out cn: TDNSDomainName): Boolean;
function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoadTCP;
  out IP: THostAddr6): Boolean;
function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoad;
  out IP: THostAddr6): Boolean;
function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoadTCP;
  out NSName: TDNSDomainName): Boolean;
function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoad;
  out NSName: TDNSDomainName): Boolean;
function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoadTCP;
  out dnssoa: TDNSRR_SOA): Boolean;
function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoad;
  out dnssoa: TDNSRR_SOA): Boolean;
function  DNSRRGetText(const RR: TRRNameData; const pl: TPayLoad;
  out dnstext: AnsiString): Boolean;
function  DNSRRGetText(const RR: TRRNameData; const pl: TPayLoadTCP;
  out dnstext: AnsiString): Boolean;
function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoadTCP;
  out MX: TDNSRR_MX): Boolean;
function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoad;
  out MX: TDNSRR_MX): Boolean;
function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoadTCP;
  out ptr: TDNSDomainName): Boolean;
function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoad;
  out ptr: TDNSDomainName): Boolean;

Implementation

uses 
   BaseUnix,
   sysutils;

var
  DefaultDomainListArr : array of string;
  NDots: Integer;

{ ---------------------------------------------------------------------
    Some Parsing routines
  ---------------------------------------------------------------------}

Const 
  Whitespace = [' ',#9];

Function NextWord(Var Line : String) : String;

Var 
  I,J : Integer;

begin
  I:=1;
  While (I<=Length(Line)) and (Line[i] in Whitespace) do
    inc(I);
  J:=I;
  While (J<=Length(Line)) and Not (Line[J] in WhiteSpace) do
    inc(j);
  Result:=Copy(Line,I,J-I);  
  Delete(Line,1,J);  
end;
  
Function StripComment(var L : String) : Boolean;

Var
  i : Integer;

begin
  I:=Pos('#',L);
  If (I<>0) then
    L:=Copy(L,1,I-1)
  else
    begin
      I:=Pos(';',L);
      If (I<>0) then
        L:=Copy(L,1,I-1)
    end;
  Result:=Length(L)>0;
end;

Function MatchNameOrAlias(Const Entry,Name: String; Aliases : String) : Boolean;

Var
  P : Integer;
  A : String;

begin
  Result:=CompareText(Entry,Name)=0;
  If Not Result then
    While (Not Result) and (Length(Aliases)>0) do
      begin
      P:=Pos(',',Aliases);
      If (P=0) then
        P:=Length(Aliases)+1;
      A:=Copy(Aliases,1,P-1);
      Delete(Aliases,1,P);
      Result:=CompareText(A,Entry)=0;
      end;
end;

{ ---------------------------------------------------------------------
    hosts processing
  ---------------------------------------------------------------------}

Function GetAddr(Var L : String; Var Addr : THostAddr) : Boolean;

Var
  S : String;
//  i,p,a : Integer;
  
begin
  Result:=True;
  S:=NextWord(L);
  Addr:=StrToNetAddr(S);
//  Writeln(s,'->',Addr.s_bytes[1],'.',Addr.s_bytes[2],'.',Addr.s_bytes[3],'.',Addr.s_bytes[4]);
  Result:=Addr.s_bytes[1]<>0;
end;


Function FillHostEntry (Var Entry : THostEntry; L: String) : boolean;

Var
  H : String;

begin
  Result := False;
  Repeat
  H:=NextWord(L);
    If (H<>'') then begin
      if (Entry.Name='') then
        Entry.Name:=H
      else  
        begin
        If (Entry.Aliases<>'') then
          Entry.Aliases:=Entry.Aliases+',';
        Entry.Aliases:=Entry.Aliases+H;
        end;
      Result := True;
    end;
  until (H='');
end;

function IsTruncated(R: TDNSHeader): Boolean;
begin
  Result := ((R.flags1 and QF_TC) > 0);
end;

function GetRcode(R: TDNSHeader): TDNSRcode;
var
  rcode_n: Byte;
begin
  rcode_n := (R.flags2 and QF_RCODE);
  case rcode_n of
    0: Result := rcNoError;
    1: Result := rcFormatError;
    2: Result := rcServFail;
    3: Result := rcNXDomain;
    4: Result := rcNotImpl;
    5: Result := rcRefused;
    6 .. 15: Result := rcReserved;
  else
    Result := rcInvalid;
  end;
end;

function ProcessHosts(FileName: String): PHostListEntry;

Var
  F : Text;
  L : String;
  A : THostAddr;
  T : PHostListEntry;
  B : Array of byte;
  FS : Int64;
  
begin
  Result:=Nil;
  Assign(F,FileName);
  {$push}{$I-}
  Reset(F);
  SetLength(B,65355);
  SetTextBuf(F,B[0],65355);
  {$pop};
  If (IOResult<>0) then
    Exit;
  Try  
    While Not EOF(F) do
      begin
      Readln(F,L);
      If StripComment(L) then
        begin
        If GetAddr(L,A) then
          begin
          T:=New(PHostListEntry);
          T^.Entry.Addr:=A;
          FillHostEntry(T^.Entry,L);
          T^.Next:=Result;
          Result:=T;
          end;
        end;
      end;
  Finally  
    Close(F);
  end;
end;

{ Internal lookup, used in GetHostByName and friends. }

Var
  HostsList : PHostListEntry = Nil;  
  HostsFileAge  : Longint;
//  HostsFileName : String;

function FreeHostsList(var List: PHostListEntry): Integer;

Var
  P : PHostListEntry;

begin
  Result:=0;
  While (List<>Nil) do
    begin
    Inc(Result);
    P:=List^.Next;
    Dispose(List);
    List:=P;
    end;
end;

procedure HostsListToArray(var List: PHostListEntry;
  var Hosts: THostEntryArray; FreeList: Boolean);

Var
  P : PHostListEntry;
  Len : Integer;

begin
  Len:=0;
  P:=List;
  While P<> Nil do
    begin
    Inc(Len);
    P:=P^.Next;
    end;
  SetLength(Hosts,Len);
  If (Len>0) then
    begin
    Len:=0;
    P:=List;
    While (P<>Nil) do
      begin
      Hosts[Len]:=P^.Entry;
      P:=P^.Next;
      Inc(Len);
      end;
    end;
  If FreeList then
    FreeHostsList(List);
end;

Procedure CheckHostsFile;

Var
  F : Integer;

begin
  If CheckHostsFileAge then
    begin
    F:=FileAge (EtcPath + SHostsFile);
    If HostsFileAge<F then
      begin
      // Rescan.
      FreeHostsList(HostsList);
      HostsList:=ProcessHosts (EtcPath + SHostsFile);
      HostsFileAge:=F;
      end;
    end;  
end;

Function FindHostEntryInHostsFile(N: String; Addr: THostAddr; Var H : THostEntry) : boolean;

Var
//  F : Text;
  HE : THostEntry;
  P : PHostListEntry;
  
begin
  Result:=False;
  CheckHostsFile;
  P:=HostsList;
  While (Not Result) and (P<>Nil) do
    begin
    HE:=P^.Entry;
    If (N<>'') then
      Result:=MatchNameOrAlias(N,HE.Name,HE.Aliases)
    else
      Result:=Cardinal(hosttonet(Addr))=Cardinal(HE.Addr);
    P:=P^.Next;  
    end; 
 If Result then
   begin
   H.Name:=HE.Name;
   H.Addr:=nettohost(HE.Addr);
   H.Aliases:=HE.Aliases;
   end;
end;

{ ---------------------------------------------------------------------
   Resolve.conf handling
  ---------------------------------------------------------------------}

{$ifdef android}

Function GetDNSServers: Integer;
var
  i: integer;
  s: string;
  H : THostAddr;
begin
  if SystemApiLevel >= 26 then
    begin
      // Since Android 8 the net.dnsX properties can't be read.
      // Use Google Public DNS servers
      Result:=2;
      SetLength(DNSServers, Result);
      DNSServers[0]:=StrToNetAddr('8.8.8.8');
      DNSServers[1]:=StrToNetAddr('8.8.4.4');
      exit;
    end;

  Result:=0;
  SetLength(DNSServers, 9);
  for i:=1 to 9 do
    begin
      s:=GetSystemProperty(PAnsiChar('net.dns' + IntToStr(i)));
      if s = '' then
        break;
      H:=StrToNetAddr(s);
      if H.s_bytes[1] <> 0 then
        begin
          DNSServers[Result]:=H;
          Inc(Result);
        end;
    end;
  SetLength(DNSServers, Result);
end;

var
  LastChangeProp: string;

Procedure CheckResolveFile;
var
  n, v: string;
begin
  if not CheckResolveFileAge then
    exit;

  if (Length(DNSServers) = 0) and (SystemApiLevel >= 26) then
    begin
      GetDNSServers;
      exit;
    end;

  n:=GetSystemProperty('net.change');
  if n <> '' then
    v:=GetSystemProperty(PAnsiChar(n))
  else
    v:='';
  n:=n + '=' + v;
  if LastChangeProp = n then
    exit;
  LastChangeProp:=n;
  GetDNSServers;
end;

{$else}

Var
  ResolveFileAge  : Longint;
  ResolveFileName : String;
  
function GetDNSServers(FN: String): Integer;

Var
  R : Text;
  L : String;
//  I : Integer;
  H : THostAddr;
  E : THostEntry;
  
  Function CheckDirective(Dir : String) : Boolean;
  
  Var
    P : Integer;
  
  begin
    P:=Pos(Dir,L);
    Result:=(P<>0);
    If Result then
      begin
      Delete(L,1,P+Length(Dir));
      L:=Trim(L);
      end;
  end;
   
begin
  Result:=0;
  ResolveFileName:=Fn;
  ResolveFileAge:=FileAge(FN);
  DefaultDomainListArr:=[];
  NDots:=1;
  {$push}{$i-}
  Assign(R,FN);
  Reset(R);
  {$pop}
  If (IOResult<>0) then 
    exit;
  Try  
    While not EOF(R) do
      begin
      Readln(R,L);
      if StripComment(L) then
        If CheckDirective('nameserver') then
          begin
          H:=HostToNet(StrToHostAddr(L));
          If (H.s_bytes[1]<>0) then
            begin
            setlength(DNSServers,Result+1);
            DNSServers[Result]:=H;
            Inc(Result);
            end
          else if FindHostEntryInHostsFile(L,H,E) then
            begin
            setlength(DNSServers,Result+1);
            DNSServers[Result]:=E.Addr;
            Inc(Result);
            end;
          end
        else if CheckDirective('domain') then
          DefaultDomainList:=L
        else if CheckDirective('search') then
          DefaultDomainList:=L
        else if CheckDirective('options') then
          DNSOptions:=L;
      end;
  Finally
    Close(R);
  end;
  L := GetEnvironmentVariable('LOCALDOMAIN');
  if L <> '' then
    DefaultDomainList := L;
end;

procedure CheckResolveFile;

Var
  F : Integer;
  N : String;

begin
  If CheckResolveFileAge then
    begin
    N:=ResolveFileName;
    if (N='') then
      N:=EtcPath + SResolveFile;
    F:=FileAge(N);
    If ResolveFileAge<F then
      GetDnsServers(N);
    end;  
end;

{$endif android}

{ ---------------------------------------------------------------------
    Payload handling functions.
  ---------------------------------------------------------------------}

function GetFixlenStr(pl: TPayLoad; startidx: Cardinal; len: Byte; out
  res: ShortString): Byte;
begin
  Result := 0;
  res := '';
  if (startidx + len) > Length(pl) then exit;
  SetLength(res, len);
  Move(pl[startidx], res[1], len);
  Result := len;
end;

function GetFixlenStr(pl: TPayLoadTCP; startidx: Cardinal; len: Byte;
  out res: ShortString): Byte;
begin
  Result := 0;
  res := '';
  if (startidx + len) > Length(pl) then exit;
  SetLength(res, len);
  Move(pl[startidx], res[1], len);
  Result := len;
end;

procedure DumpPayLoad(Q: TQueryData; L: Integer);

Var 
  i : Integer;

begin
  Writeln('Payload : ',l);
  For I:=0 to L-1 do
    Write(Q.Payload[i],' ');
  Writeln;  
end;

function QueryTCP(Resolver: Integer; var Qry: TQueryDataLength;
  var Ans: TQueryDataLengthTCP; QryLen: Integer; var AnsLen: Integer): Boolean;
Var
  SA : TInetSockAddr;
  Sock : cint;
  L: ssize_t;
  RTO : Longint;
  ReadFDS : TFDSet;
  count: Integer;
  sendsize: ssize_t;
  respsize: Word;

begin
  Result:=False;
  With Qry.hpl.h do
  begin
    ID[0]:=Random(256);
    ID[1]:=Random(256);
    Flags1:=QF_RD;
    Flags2:=0;
    qdcount:=htons(1); // was 1 shl 8;
    ancount:=0;
    nscount:=0;
    arcount:=0;
  end;
  Sock:=FpSocket(AF_INET,SOCK_STREAM,0);
  If Sock=-1 then
    exit;
  With SA do
  begin
    sin_family:=AF_INET;
    sin_port:=htons(DNSport);
    sin_addr.s_addr:=cardinal(DNSServers[Resolver]); // octets already in net order
  end;
  if (fpconnect(Sock, @SA, SizeOf(SA)) <> 0) then
    exit;
  sendsize := QryLen + SizeOf(Qry.hpl.h) + SizeOf(Qry.length);
  count := fpsend(Sock,@Qry,sendsize,0);
  if count < sendsize then
  begin
    fpclose(Sock);
    exit;
  end;

  // tell other side we'll write no more.
  fpshutdown(Sock, SHUT_WR);
  // Wait for answer.
  RTO:=TimeOutS*1000+TimeOutMS;
  fpFD_ZERO(ReadFDS);
  fpFD_Set(Sock,ReadFDS);

  if fpSelect(Sock+1,@ReadFDS,Nil,Nil,RTO)<=0 then
  begin
    fpclose(Sock);
    exit;
  end;

  L:=fprecv(Sock,@Ans,SizeOf(Ans),0);
  if L <= 1 then
  begin
    fpclose(Sock);
    exit;
  end;

  // get the payload size, which doesn't include the 2 byte length field.
  // I.e, num bytes read  should be sizeof(word) (2 bytes) greater than the
  // payload size.
  respsize := NToHs(Ans.length);
  if L < (respsize + SizeOf(Ans.length)) then
  begin
    // we haven't received the full payload. Unsure in what circumstances
    // this might happen.
    fpclose(Sock);
    exit;
  end;

  if L < SizeOf(Qry.hpl.h) then exit;

  // even though we return false to indicate no answer, AnsLen is still
  // >= 0 and lets the caller know we got an answer from the dns server/
  Anslen:=respsize-SizeOf(Qry.hpl.h);
  If not CheckAnswer(Qry.hpl.h,Ans.h) then
    exit;
  Result:=True;

end;

function BuildPayLoad(var Q: TQueryData; Name: String; RR: Word; QClass: Word
  ): Integer;

Var
  P : PByte;
  l,S : Integer;
  
begin
  Result:=-1;
  If length(Name)>506 then
    Exit;
  Result:=0;  
  P:=@Q.Payload[0];
  Repeat
    L:=Pos('.',Name);
    If (L=0) then
      S:=Length(Name)
    else
      S:=L-1;
    P[Result]:=S;
    Move(Name[1],P[Result+1],S);
    Inc(Result,S+1);
    If (L>0) then
      Delete(Name,1,L);
  Until (L=0);
  P[Result]:=0;
  rr := htons(rr);
  Move(rr,P[Result+1],2);
  Inc(Result,3);
  QClass := htons(QClass);
  Move(qclass,P[Result],2);
  Inc(Result,2);
end;

{Construct a TCP query payload from the given name, rr and qclass. The
 principal difference between the TCP and UDP payloads is the two-octet
 length field in the TCP payload. The UDP payload has no length field.

 See RFC-1035, section 4.2.2.

 Returns the length of the constructed payload, which doesn't include
 the header or the length field.}
function BuildPayLoadTCP(var Q: TQueryDataLength; Name: String; RR: Word;
  QClass: Word): Integer;
var
  l: Word;
begin
  l := BuildPayLoad(Q.hpl, Name, RR, QClass);
  Q.length := htons(l + SizeOf(Q.hpl.h));
  Result := l;
end;

function NextRR(const PayLoad: TPayLoad; var Start: LongInt; AnsLen: LongInt;
  var RR: TRRData): Boolean;

Var
  I : Integer;
  HaveName : Boolean;
  PA : PRRData;
  
begin
  Result:=False;
  I:=Start;
  // Skip labels and pointers. At least 1 label or pointer is present.
  Repeat
    HaveName:=True;
    If (Payload[i]>63) then // Pointer, skip
    begin
      Inc(I,2);
    end
    else If Payload[i]=0 then // Null termination of label, skip.
      Inc(i)
    else  
      begin
      Inc(I,Payload[i]+1); // Label, continue scan.
      HaveName:=False;
      end;
  Until HaveName or (I>(AnsLen-SizeOf(TRRData)));
  Result:=(I<=(AnsLen-SizeOf(TRRData)));
  // Check RR record.
  PA:=PRRData(@Payload[i]);
  RR:=PA^;
  Start:=I+SizeOf(TRRData);
end;


Function BuildName (Const PayLoad : TPayLoad; Start,len : Integer) : String;

Const
  FIREDNS_POINTER_VALUE = $C000;
  
Var
  I,O : Integer;
  P : Word;
  
begin
  SetLength(Result,512);
  I:=Start;
  O:=1;
  // Copy labels and pointers. At least 1 label or pointer is present.
  Repeat
    If (Payload[i]>63) then // Pointer, move.
      begin
      Move(Payload[i],P,2);
      I:=ntohs(p)-FIREDNS_POINTER_VALUE-12;
      end
    else if Payload[i]<>0 then // Label, copy
      begin
      If O<>1 then
        begin
        Result[O]:='.';
        Inc(O);
        end;
      P:=Payload[i];  
      Move(Payload[i+1],Result[o],P);
      Inc(I,P+1);
      Inc(O,P);
      end;
   Until (Payload[I]=0);
   setlength(result,o-1);
end;


{ ---------------------------------------------------------------------
    QueryData handling functions
  ---------------------------------------------------------------------}

{
Read a string from the payload buffer. Handles compressed as well as
regular labels. On termination start points to the character after the
end of the str.
}
function stringfromlabel(pl: TPayLoadTCP; var start: Integer): string;
var
  l,i,n: integer;
  ptrseen: Boolean = False;
begin
  result := '';
  l := 0;
  i := 0;
  n := start;
  repeat
    if (Length(pl) - n) < 2 then exit;
    l := ord(pl[n]);
    { compressed reply }
    while (l >= 192) do
      begin
        if not ptrseen then start := n + 2;
        ptrseen := True;
        { the -12 is because of the reply header length }
        n := (l and not(192)) shl 8 + ord(pl[n+1]) - 12;
        if (Length(pl) - n) < 2 then exit;
        l := ord(pl[n]);
      end;
    if (n+l) >= (Length(pl)-1) then l := 0;
    if l <> 0 then begin
      setlength(result,length(result)+l);
      move(pl[n+1],result[i+1],l);
      result := result + '.';
      inc(n,l); inc(n);
      inc(i,l); inc(i);
      if n > start then start := n;
    end;
  until l = 0;
  // per rfc1035, section 4.1.4, a domain name may be represented by
  // either a sequence of labels followed by 0, or a pointer, or a series
  // of labels followed by a pointer. If there's a pointer there's no 0.
  if not ptrseen then Inc(start); // jump past the 0.
  if result[length(result)] = '.' then setlength(result,length(result)-1);
end;

function CheckAnswer(const Qry: TDNSHeader; var Ans: TDNSHeader): Boolean;
begin
  Result:=False;
  With Ans do
    begin
    // Check ID.
    If (ID[1]<>QRY.ID[1]) or (ID[0]<>Qry.ID[0]) then
      exit;  
    // Flags ?
    If (Flags1 and QF_QR)=0 then
      exit;
    if (Flags1 and QF_OPCODE)<>0 then 
      exit;
    if (Flags2 and QF_RCODE)<>0 then
      exit;  
    // Number of answers ?  
    AnCount := htons(Ancount);
    If Ancount<1 then
      Exit;
    Result:=True;
    end;
end;

function NextNameRR(const pl: TPayLoadTCP; start: Word; out RRName: TRRNameData
  ): Boolean;
var
  I : Integer;
  PA : PRRData;

begin
  Result:=False;
  I:=Start;
  if (Length(pl) - I) < (SizeOf(TRRData)+2) then exit;
  RRName.RRName := stringfromlabel(pl, I);
  if (Length(pl) - I) < (SizeOf(TRRData)) then exit;

  PA:=PRRData(@pl[I]);
  RRName.RRMeta := PA^;
  RRName.RRMeta.AClass := NToHs(RRName.RRMeta.AClass);
  RRName.RRMeta.Atype := NToHs(RRName.RRMeta.Atype);
  RRName.RRMeta.RDLength := NToHs(RRName.RRMeta.RDLength);
  RRName.RRMeta.TTL := NToHl(RRName.RRMeta.TTL);
  RRName.RDataSt := I+SizeOf(TRRData);
  Result := True;
end;

function NextNameRR(const pl: TPayLoad; start: Word; out RRName: TRRNameData
  ): Boolean;
var
  I : Integer;
  PA : PRRData;

begin
  Result:=False;
  I:=Start;
  if (Length(pl) - I) < (SizeOf(TRRData)+2) then exit;
  RRName.RRName := stringfromlabel(pl, I);
  if (Length(pl) - I) < (SizeOf(TRRData)) then exit;

  PA:=PRRData(@pl[I]);
  RRName.RRMeta := PA^;
  RRName.RRMeta.AClass := NToHs(RRName.RRMeta.AClass);
  RRName.RRMeta.Atype := NToHs(RRName.RRMeta.Atype);
  RRName.RRMeta.RDLength := NToHs(RRName.RRMeta.RDLength);
  RRName.RRMeta.TTL := NToHl(RRName.RRMeta.TTL);
  RRName.RDataSt := I+SizeOf(TRRData);
  Result := True;
end;

function GetRRrecords(const pl: TPayloadTCP; var Start: Word; Count: Word
  ): TRRNameDataArray;
var
  I, Total: Word;
  B: Boolean;
  RRN: TRRNameData;

begin
  I:=0;
  Total := 0;
  SetLength(Result,Count);
  while (I < Count) do
  begin
    B := NextNameRR(pl, Start, RRN);
    if not B then break;
    Inc(Total);
    Result[I] := RRN;
    Inc(I);
    Start := RRN.RDataSt+RRN.RRMeta.RDLength;
  end;
  if Total < Count then SetLength(Result,Total);
end;

function GetRRrecords(const pl: TPayload; var Start: Word; Count: Word
  ): TRRNameDataArray;
var
  I, Total: Word;
  B: Boolean;
  RRN: TRRNameData;

begin
  I:=0;
  Total := 0;
  SetLength(Result,Count);
  while (I < Count) do
  begin
    B := NextNameRR(pl, Start, RRN);
    if not B then break;
    Inc(Total);
    Result[I] := RRN;
    Inc(I);
    Start := RRN.RDataSt+RRN.RRMeta.RDLength;
  end;
  if Total < Count then SetLength(Result,Total);
end;

function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoadTCP; out
  IP: THostAddr): Boolean;
begin
  IP.s_addr := 0;
  Result := False;
  if RR.RRMeta.Atype <> DNSQRY_A then exit;
  if (Length(pl) - RR.RDataSt) < 4 then exit;
  Move(pl[RR.RDataSt], IP, SizeOf(THostAddr));
  IP.s_addr := NToHl(IP.s_addr);
  Result := True;
end;

function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoad; out IP: THostAddr
  ): Boolean;
begin
  IP.s_addr := 0;
  Result := False;
  if RR.RRMeta.Atype <> DNSQRY_A then exit;
  if (Length(pl) - RR.RDataSt) < 4 then exit;
  Move(pl[RR.RDataSt], IP, SizeOf(THostAddr));
  IP.s_addr := NToHl(IP.s_addr);
  Result := True;
end;

function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoad; out
  cn: TDNSDomainName): Boolean;
var
  n: Integer;
begin
  Result := False;
  cn := '';
  if RR.RRMeta.Atype <> DNSQRY_CNAME then exit;
  n := RR.RDataSt;
  if (n + RR.RRMeta.RDLength) > Length(pl) then exit;
  cn := stringfromlabel(pl, n);
  Result := True;
end;

function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoadTCP; out
  cn: TDNSDomainName): Boolean;
var
  n: Integer;
begin
  Result := False;
  cn := '';
  if RR.RRMeta.Atype <> DNSQRY_CNAME then exit;
  n := RR.RDataSt;
  if (n + RR.RRMeta.rdlength) > Length(pl) then exit;
  cn := stringfromlabel(pl, n);
  Result := True;
end;

function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoadTCP; out
  IP: THostAddr6): Boolean;
begin
  IP.s6_addr32[0] := 0;
  IP.s6_addr32[1] := 0;
  IP.s6_addr32[2] := 0;
  IP.s6_addr32[3] := 0;
  Result := False;
  if RR.RRMeta.Atype <> DNSQRY_AAAA then exit;
  if (RR.RDataSt + SizeOf(THostAddr6)) > Length(pl) then exit;
  Move(pl[RR.RDataSt],IP,SizeOf(THostAddr6));
  Result := True;
end;

function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoad; out
  IP: THostAddr6): Boolean;
begin
  IP.s6_addr32[0] := 0;
  IP.s6_addr32[1] := 0;
  IP.s6_addr32[2] := 0;
  IP.s6_addr32[3] := 0;
  Result := False;
  if RR.RRMeta.Atype <> DNSQRY_AAAA then exit;
  if (RR.RDataSt + SizeOf(THostAddr6)) > Length(pl) then exit;
  Move(pl[RR.RDataSt],IP,SizeOf(THostAddr6));
  Result := True;
end;

function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoadTCP; out
  NSName: TDNSDomainName): Boolean;
var
  n: LongInt;
begin
  NSName := '';
  Result := False;
  if RR.RRMeta.Atype <> DNSQRY_NS then exit;
  if (RR.RDataSt + RR.RRMeta.RDLength) > Length(pl) then exit;
  n := RR.RDataSt;
  NSName := stringfromlabel(pl, n);
  Result := True;
end;

function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoad; out
  NSName: TDNSDomainName): Boolean;
var
  n: LongInt;
begin
  NSName := '';
  Result := False;
  if RR.RRMeta.Atype <> DNSQRY_NS then exit;
  if (RR.RDataSt + RR.RRMeta.RDLength) > Length(pl) then exit;
  n := RR.RDataSt;
  NSName := stringfromlabel(pl, n);
  Result := True;
end;

function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoadTCP; out
  dnssoa: TDNSRR_SOA): Boolean;
var
  idx: Integer;
begin
  // can't trust the counts we've been given, so check that we never
  // exceed the end of the payload buffer.
  idx := RR.RDataSt;
  Result := False;
  if RR.RRMeta.Atype <> DNSQRY_SOA then exit;
  dnssoa.mname := stringfromlabel(pl, idx);
  if idx >= Length(pl) then exit;

  dnssoa.rname := stringfromlabel(pl, idx);

  if (idx + (SizeOf(Cardinal) * 5)) > Length(pl) then exit;
  Move(pl[idx],dnssoa.serial,SizeOf(Cardinal));
  Inc(idx, SizeOf(Cardinal));
  Move(pl[idx], dnssoa.refresh, SizeOf(Cardinal));
  Inc(idx, SizeOf(Cardinal));
  Move(pl[idx], dnssoa.retry, SizeOf(Cardinal));
  Inc(idx, SizeOf(Cardinal));
  Move(pl[idx], dnssoa.expire, SizeOf(Cardinal));
  Inc(idx, SizeOf(Cardinal));
  Move(pl[idx], dnssoa.min, SizeOf(Cardinal));
  Result := True;
  dnssoa.serial := NToHl(dnssoa.serial);
  dnssoa.min := NToHl(dnssoa.min);
  dnssoa.expire := NToHl(dnssoa.expire);
  dnssoa.refresh := NToHl(dnssoa.refresh);
  dnssoa.retry := NToHl(dnssoa.retry);
end;

function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoad; out
  dnssoa: TDNSRR_SOA): Boolean;
var
  idx: Integer;
begin
  // can't trust the counts we've been given, so check that we never
  // exceed the end of the payload buffer.
  idx := RR.RDataSt;
  Result := False;
  if RR.RRMeta.Atype <> DNSQRY_SOA then exit;
  dnssoa.mname := stringfromlabel(pl, idx);
  if idx >= Length(pl) then exit;

  dnssoa.rname := stringfromlabel(pl, idx);

  if (idx + (SizeOf(Cardinal) * 5)) > Length(pl) then exit;
  Move(pl[idx],dnssoa.serial,SizeOf(Cardinal));
  Inc(idx, SizeOf(Cardinal));
  Move(pl[idx], dnssoa.refresh, SizeOf(Cardinal));
  Inc(idx, SizeOf(Cardinal));
  Move(pl[idx], dnssoa.retry, SizeOf(Cardinal));
  Inc(idx, SizeOf(Cardinal));
  Move(pl[idx], dnssoa.expire, SizeOf(Cardinal));
  Inc(idx, SizeOf(Cardinal));
  Move(pl[idx], dnssoa.min, SizeOf(Cardinal));
  Result := True;
  dnssoa.serial := NToHl(dnssoa.serial);
  dnssoa.min := NToHl(dnssoa.min);
  dnssoa.expire := NToHl(dnssoa.expire);
  dnssoa.refresh := NToHl(dnssoa.refresh);
  dnssoa.retry := NToHl(dnssoa.retry);
end;

function DNSRRGetText(const RR: TRRNameData; const pl: TPayLoad; out
  dnstext: AnsiString): Boolean;
var
  total: Word;
  wrk: ShortString;
  idx: LongInt;
  l: Byte;
begin
  Result := False;
  dnstext := '';
  if RR.RRMeta.Atype <> DNSQRY_TXT then exit;
  wrk := '';
  total := RR.RRMeta.RDLength;

  idx := RR.RDataSt;
  if (Length(pl) - idx)  < 2 then exit;

  repeat
    l := GetFixlenStr(pl, idx+1, pl[idx], wrk);
    dnstext := dnstext + wrk;
    Inc(idx, l+1);
  until (idx >= (RR.RDataSt + total)) or ((Length(pl) - idx) < 2);
  Result := True;
end;

function DNSRRGetText(const RR: TRRNameData; const pl: TPayLoadTCP; out
  dnstext: AnsiString): Boolean;
var
  total: Word;
  wrk: ShortString;
  idx: LongInt;
  l: Byte;
begin
  Result := False;
  dnstext := '';
  if RR.RRMeta.Atype <> DNSQRY_TXT then exit;
  wrk := '';
  total := RR.RRMeta.RDLength;

  idx := RR.RDataSt;
  if (Length(pl) - idx)  < 2 then exit;

  repeat
    l := GetFixlenStr(pl, idx+1, pl[idx], wrk);
    dnstext := dnstext + wrk;
    Inc(idx, l+1);
    until (idx >= (RR.RDataSt + total)) or ((Length(pl) - idx) < 2);
  Result := True;
end;

function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoadTCP; out
  MX: TDNSRR_MX): Boolean;
var
  idx: Integer;
begin
  Result := False;
  MX.preference := 0;
  MX.exchange := '';
  if RR.RRMeta.Atype <> DNSQRY_MX then exit;
  idx := RR.RDataSt;
  if idx + SizeOf(Word) >= Length(pl) then exit;
  Move(pl[idx],MX.preference, SizeOf(Word));
  Inc(idx, SizeOf(Word));
  if (Length(pl) - idx) < 2 then exit;
  MX.exchange := stringfromlabel(pl, idx);
  MX.preference := NToHs(MX.preference);
  Result := True;
end;

function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoad; out MX: TDNSRR_MX
  ): Boolean;
var
  idx: Integer;
begin
  Result := False;
  MX.preference := 0;
  MX.exchange := '';
  if RR.RRMeta.Atype <> DNSQRY_MX then exit;
  idx := RR.RDataSt;
  if idx + SizeOf(Word) >= Length(pl) then exit;
  Move(pl[idx],MX.preference, SizeOf(Word));
  Inc(idx, SizeOf(Word));
  if (Length(pl) - idx) < 2 then exit;
  MX.exchange := stringfromlabel(pl, idx);
  MX.preference := NToHs(MX.preference);
  Result := True;
end;

function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoadTCP; out
  ptr: TDNSDomainName): Boolean;
var
  n: Integer;
begin
  Result := False;
  ptr := '';
  if RR.RRMeta.Atype <> DNSQRY_PTR then exit;
  n := RR.RDataSt;
  if (n + RR.RRMeta.RDLength) > Length(pl) then exit;
  ptr := stringfromlabel(pl, n);
  Result := True;
end;

function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoad; out
  ptr: TDNSDomainName): Boolean;
var
  n: Integer;
begin
  Result := False;
  ptr := '';
  if RR.RRMeta.Atype <> DNSQRY_PTR then exit;
  n := RR.RDataSt;
  if (n + RR.RRMeta.RDLength) > Length(pl) then exit;
  ptr := stringfromlabel(pl, n);
  Result := True;
end;

function SkipAnsQueries(var Ans: TQueryData; L: Integer): integer;

Var
  Q,I : Integer;

begin
  Result:=0;
  With Ans do
    begin
    h.qdcount := htons(h.qdcount);
    i:=0;
    q:=0;
    While (Q<h.qdcount) and (i<l) do  
      begin
      If Payload[i]>63 then
        begin
        Inc(I,6);
        Inc(Q);
        end
      else
        begin
        If Payload[i]=0 then
          begin
          inc(q);
          Inc(I,5);
          end
        else
          Inc(I,Payload[i]+1);  
        end;  
      end;
    Result:=I;  
    end;  
end;

function SkipAnsQueriesTCP(var Ans: TQueryDataLengthTCP; L: Integer): integer;
var
  Q,I : Integer;

begin
  Result:=0;
  With Ans do
  begin
    h.qdcount := htons(h.qdcount);
    i:=0;
    q:=0;
    While (Q<h.qdcount) and (i<l) do
    begin
      If Payload[i]>63 then
      begin
        Inc(I,6);
        Inc(Q);
      end
      else
      begin
        If Payload[i]=0 then
        begin
          inc(q);
          Inc(I,5);
        end
        else
          Inc(I,Payload[i]+1);
      end;
    end;
    Result:=I;
  end;
end;

{ ---------------------------------------------------------------------
    DNS Query functions.
  ---------------------------------------------------------------------}
  

function Query(Resolver: Integer; var Qry, Ans: TQueryData; QryLen: Integer;
  var AnsLen: Integer): Boolean;

Var
  SA : TInetSockAddr;
  Sock,L : Longint;
  Al,RTO : Longint;
  ReadFDS : TFDSet;
  
begin
  Result:=False;
  With Qry.h do
    begin
    ID[0]:=Random(256);
    ID[1]:=Random(256);
    Flags1:=QF_RD;
    Flags2:=0;
    qdcount:=htons(1); // was 1 shl 8;
    ancount:=0;
    nscount:=0;
    arcount:=0;
    end;
  Sock:=FpSocket(PF_INET,SOCK_DGRAM,0);
  If Sock=-1 then 
    exit;
  With SA do
    begin
    sin_family:=AF_INET;
    sin_port:=htons(DNSport);
    sin_addr.s_addr:=cardinal(DNSServers[Resolver]); // dnsservers already in net order
    end;
  fpsendto(sock,@qry,qrylen+12,0,@SA,SizeOf(SA));
  // Wait for answer.
  RTO:=TimeOutS*1000+TimeOutMS;
  fpFD_ZERO(ReadFDS);
  fpFD_Set(sock,readfds);
  if fpSelect(Sock+1,@readfds,Nil,Nil,RTO)<=0 then
    begin
    fpclose(Sock);
    exit;
    end;
  AL:=SizeOf(SA);
  L:=fprecvfrom(Sock,@ans,SizeOf(Ans),0,@SA,@AL);
  fpclose(Sock);

  if L < 12 then exit;
  // Return Payload length.
  Anslen:=L-12;
  // even though we return false to indicate no answer, AnsLen is still
  // >= 0 and lets the caller know we got an answer from the dns server/
  If not CheckAnswer(Qry.h,Ans.h) Then
    exit;
  Result:=True;
  //end;
end;

function NextRR(const PayLoad: TPayLoadTCP; var Start: LongInt;
  AnsLen: LongInt; var RR: TRRData): Boolean;
var
  I : Integer;
  HaveName : Boolean;
  PA : PRRData;

begin
  Result:=False;
  I:=Start;
  // Skip labels and pointers. At least 1 label or pointer is present.
  Repeat
    HaveName:=True;
    If (Payload[i]>63) then // Pointer, skip
    begin
      Inc(I,2);
    end
    else If Payload[i]=0 then // Null termination of label, skip.
      Inc(i)
    else
      begin
      Inc(I,Payload[i]+1); // Label, continue scan.
      HaveName:=False;
      end;
  Until HaveName or (I>(AnsLen-SizeOf(TRRData)));
  Result:=(I<=(AnsLen-SizeOf(TRRData)));
  // Check RR record.
  PA:=PRRData(@Payload[i]);
  RR:=PA^;
  Start:=I+SizeOf(TRRData);

end;

function stringfromlabel(pl: TPayLoad; var start: Integer): string;
var
  l,i,n: integer;
  ptrseen: Boolean = False;
begin
  result := '';
  l := 0;
  i := 0;
  n := start;
  repeat
    if (Length(pl) - n) < 2 then exit;
    l := ord(pl[n]);
    { compressed reply }
    while (l >= 192) do
      begin
        if not ptrseen then start := n + 2;
        ptrseen := True;
        { the -12 is because of the reply header length }
        n := (l and not(192)) shl 8 + ord(pl[n+1]) - 12;
        if (Length(pl) - n) < 2 then exit;
        l := ord(pl[n]);
      end;
    if (n+l) >= (Length(pl)-1) then l := 0;
    if l <> 0 then begin
      setlength(result,length(result)+l);
      move(pl[n+1],result[i+1],l);
      result := result + '.';
      inc(n,l); inc(n);
      inc(i,l); inc(i);
      if n > start then start := n;
    end;
  until l = 0;
  // per rfc1035, section 4.1.4, a domain name may be represented by
  // either a sequence of labels followed by 0, or a pointer, or a series
  // of labels followed by a pointer. If there's a pointer there's no 0.
  if not ptrseen then Inc(start); // jump past the 0.
  if result[length(result)] = '.' then setlength(result,length(result)-1);
end;

Function ResolveNameAt(Resolver : Integer; HostName : String; Var Addresses : Array of THostAddr; Recurse: Integer) : Integer;

Var
  Qry, Ans            : TQueryData;
  MaxAnswer,I,QryLen,
  AnsLen,AnsStart     : Longint;
  RR                  : TRRData;
  cname               : string;
begin
  Result:=0;
  QryLen:=BuildPayLoad(Qry,HostName,DNSQRY_A,1);
  If Not Query(Resolver,Qry,Ans,QryLen,AnsLen) then
    Result:=-1
  else  
    begin
    AnsStart:=SkipAnsQueries(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount-1;
    If MaxAnswer>High(Addresses) then
      MaxAnswer:=High(Addresses);
    I:=0;
    While (I<=MaxAnswer) and NextRR(Ans.Payload,AnsStart,AnsLen,RR) do
      begin
      if htons(rr.AClass) = 1 then
        case ntohs(rr.AType) of
          DNSQRY_A: begin
            Move(Ans.PayLoad[AnsStart],Addresses[i],SizeOf(THostAddr));
            inc(Result);
            Inc(AnsStart,htons(RR.RDLength));
          end;
          DNSQRY_CNAME: begin
            if Recurse >= MaxRecursion then begin
              Result := -1;
              exit;
            end;
            rr.rdlength := ntohs(rr.rdlength);
            setlength(cname, rr.rdlength);
            cname := stringfromlabel(ans.payload, ansstart);
            Result := ResolveNameAt(Resolver, cname, Addresses, Recurse+1);
            exit; // FIXME: what about other servers?!
          end;
        end;
        Inc(I);
      end;  
    end;
end;

function ResolveName(HostName: String; var Addresses: array of THostAddr
  ): Integer;

Var
  I : Integer;

begin
  CheckResolveFile;
  I:=0;
  Result:=0;
  While (Result<=0) and (I<=high(DNSServers)) do
    begin
    Result:=ResolveNameAt(I,HostName,Addresses,0);
    Inc(I);
    end;
end;

//const NoAddress6 : array[0..7] of word = (0,0,0,0,0,0,0,0);

Function ResolveNameAt6(Resolver : Integer; HostName : String; Var Addresses : Array of THostAddr6; Recurse: Integer) : Integer;
                                                                                                                                        
Var
  Qry, Ans            : TQueryData;
  MaxAnswer,I,QryLen,
  AnsLen,AnsStart     : Longint;
  RR                  : TRRData;
  cname               : string;
  LIP4mapped: array[0..MaxIP4Mapped-1] of THostAddr;
  LIP4count: Longint;
                                                                                                                                        
begin
  Result:=0;
  QryLen:=BuildPayLoad(Qry,HostName,DNSQRY_AAAA,1);
  If Not Query(Resolver,Qry,Ans,QryLen,AnsLen) then begin
    // no answer? try IPv4 mapped addresses, maybe that will generate one
    LIP4Count := ResolveName(HostName, LIP4Mapped);
    if LIP4Count > 0 then begin
      inc(LIP4Count); // we loop to LIP4Count-1 later
      if LIP4Count > MaxIP4Mapped then LIP4Count := MaxIP4Mapped;
      if LIP4Count > Length(Addresses) then LIP4Count := Length(Addresses);
      for i := 0 to LIP4Count-2 do begin
        Addresses[i] := NoAddress6;
        Addresses[i].u6_addr16[5] := $FFFF;
        Move(LIP4Mapped[i], Addresses[i].u6_addr16[6], 4);
      end;
      Result := LIP4Count;
    end else begin
      Result:=-1
    end;
  end else
    begin
    AnsStart:=SkipAnsQueries(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount-1;
    If MaxAnswer>High(Addresses) then
      MaxAnswer:=High(Addresses);
    I:=0;
    While (I<=MaxAnswer) and NextRR(Ans.Payload,AnsStart,AnsLen,RR) do
      begin
      if (1=NtoHS(RR.AClass)) then
      case ntohs(rr.atype) of
        DNSQRY_AAAA: begin
            Move(Ans.PayLoad[AnsStart],Addresses[i],SizeOf(THostAddr6));
            inc(Result);
            rr.rdlength := ntohs(rr.rdlength);
            Inc(AnsStart,RR.RDLength);
          end;
        DNSQRY_CNAME: begin
          if Recurse >= MaxRecursion then begin
            Result := -1;
            exit;
          end;
          rr.rdlength := ntohs(rr.rdlength);
          setlength(cname, rr.rdlength);
          cname := stringfromlabel(ans.payload, ansstart);
          Result := ResolveNameAt6(Resolver, cname, Addresses, Recurse+1);
          exit; // FIXME: what about other servers?!
        end;
      end;
      Inc(I);
      end;
    end;
end;
                                                                                                                                        


function ResolveName6(HostName: String; var Addresses: array of THostAddr6
  ): Integer;
var
  i: Integer;
begin
  CheckResolveFile;
  i := 0;
  Result := 0;
  while (Result <= 0) and (I<= high(DNSServers)) do begin
    Result := ResolveNameAt6(I, Hostname, Addresses, 0);
    Inc(i);
  end;
end;

Function ResolveAddressAt(Resolver : Integer; Address : String; Var Names : Array of String; Recurse: Integer) : Integer;


Var
  Qry, Ans            : TQueryData;
  MaxAnswer,I,QryLen,
  AnsLen,AnsStart     : Longint;
  RR                  : TRRData;

begin
  Result:=0;
  QryLen:=BuildPayLoad(Qry,Address,DNSQRY_PTR,1);
  If Not Query(Resolver,Qry,Ans,QryLen,AnsLen) then
    Result:=-1
  else  
    begin
    AnsStart:=SkipAnsQueries(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount-1;
    If MaxAnswer>High(Names) then
      MaxAnswer:=High(Names);
    I:=0;
    While (I<=MaxAnswer) and NextRR(Ans.Payload,AnsStart,AnsLen,RR) do
      begin
      Case Ntohs(RR.AType) of
        DNSQRY_PTR:
          if (1=NtoHS(RR.AClass)) then
            begin
            Names[i]:=BuildName(Ans.Payload,AnsStart,AnsLen);
            inc(Result);
            RR.RDLength := ntohs(RR.RDLength);
            Inc(AnsStart,RR.RDLength);
            end;
        DNSQRY_CNAME:
          begin
          if Recurse >= MaxRecursion then
            begin
            Result := -1;
            exit;
            end;
          rr.rdlength := ntohs(rr.rdlength);
          setlength(Address, rr.rdlength);
          address := stringfromlabel(ans.payload, ansstart);
          Result := ResolveAddressAt(Resolver, Address, Names, Recurse+1);
          exit;
          end;
      end;
      Inc(I);
      end;  
    end;
end;

function DnsLookup(dn: String; qtype: Word; out Ans: TQueryData; out
  AnsLen: Longint): Boolean;
var
  Qry: TQueryData;
  QryLen: Longint;
  idx: Word;
begin
  Result := False;
  AnsLen := -1;

  CheckResolveFile;
  if Length(DNSServers) = 0 then
    exit;

  QryLen := BuildPayLoad(Qry, dn, qtype, 1);
  if QryLen <= 0 then exit;

  { Try the query at each configured resolver in turn, until one of them
   returns an answer. We check for AnsLen > -1 because we need to distinguish
   between failure to connect and the server saying it doesn't know or can't
   answer. If AnsLen = -1 then we failed to connect. If AnsLen >= 0 but qr
   = False, then we connected but the server returned an error code.}
  idx := 0;
  repeat
    Result := Query(idx,Qry,Ans,QryLen,AnsLen);
    Inc(idx);
  until (idx > High(DNSServers)) or (Result = True) or (AnsLen >= 0);
end;

function DnsLookup(dn: String; qtype: Word; out Ans: TQueryDataLengthTCP; out
  AnsLen: Longint): Boolean;
var
  Qry: TQueryDataLength;
  QryLen: Longint;
  idx: Word;

begin
  Result := False;
  AnsLen := -1;

  CheckResolveFile;
  if Length(DNSServers) = 0 then
    exit;

  QryLen:=BuildPayLoadTCP(Qry, dn, qtype, 1);
  if QryLen <= 0 then exit;

  { Try the query at each configured resolver in turn, until one of them
   returns an answer. We check for AnsLen > -1 because we need to distinguish
   between failure to connect and the server saying it doesn't know or can't
   answer. If AnsLen = -1 then we failed to connect. If AnsLen >= 0 but qr
   = False, then we connected but the server returned an error code.}
  idx := 0;
  repeat
    Result := QueryTCP(idx,Qry,Ans,QryLen,AnsLen);
    Inc(idx);
  until (idx > High(DNSServers)) or (Result = True) or (AnsLen >= 0);
end;

function ResolveAddress(HostAddr: THostAddr; var Addresses: array of String
  ): Integer;

Var
  I : Integer;
  S : String;
  nt : tnetaddr;
  
begin
  CheckResolveFile;
  I:=0;
  Result:=0;
  nt:=hosttonet(hostaddr);
  S:=Format('%d.%d.%d.%d.in-addr.arpa',[nt.s_bytes[4],nt.s_bytes[3],nt.s_bytes[2],nt.s_bytes[1]]);
  While (Result=0) and (I<=high(DNSServers)) do
    begin
    Result:=ResolveAddressAt(I,S,Addresses,1);
    Inc(I);
    end;
end;

function ResolveAddress6(HostAddr: THostAddr6; var Addresses: array of string
  ): Integer;

const
  hexdig: string[16] = '0123456789abcdef';
                                                                                
Var
  I : Integer;
  S : ShortString;
                                                                                
begin
  CheckResolveFile;
  Result:=0;
  S := '0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.int';
  for i := 7 downto 0 do begin
    S[5+(7-i)*8] := hexdig[1+(HostAddr.u6_addr16[i] and $000F) shr 00];
    S[7+(7-i)*8] := hexdig[1+(HostAddr.u6_addr16[i] and $00F0) shr 04];
    S[1+(7-i)*8] := hexdig[1+(HostAddr.u6_addr16[i] and $0F00) shr 08];
    S[3+(7-i)*8] := hexdig[1+(HostAddr.u6_addr16[i] and $F000) shr 12];
  end;
  I := 0;
  While (Result=0) and (I<=high(DNSServers)) do
    begin
    Result:=ResolveAddressAt(I,S,Addresses,1);
    Inc(I);
    end;
end;

function IN6_IS_ADDR_V4MAPPED(HostAddr: THostAddr6): boolean;
begin
  Result := 
   (HostAddr.u6_addr16[0] = 0) and
   (HostAddr.u6_addr16[1] = 0) and
   (HostAddr.u6_addr16[2] = 0) and
   (HostAddr.u6_addr16[3] = 0) and
   (HostAddr.u6_addr16[4] = 0) and
   (HostAddr.u6_addr16[5] = $FFFF);
end;

Function HandleAsFullyQualifiedName(const HostName: String) : Boolean;
var
  I,J : Integer;
begin
  Result := False;
  J := 0;
  for I := 1 to Length(HostName) do
    if HostName[I] = '.' then
      begin
      Inc(J);
      if J >= NDots then
        begin
        Result := True;
        Break;
        end;
      end;
end;

function ResolveHostByName(HostName: String; var H: THostEntry): Boolean;

Var
  Address : Array[1..MaxResolveAddr] of THostAddr;
  AbsoluteQueryFirst : Boolean;
  L : Integer;
  K : Integer;

begin
  // Use domain or search-list to append to the searched hostname.
  // When the amount of dots in hostname is higher or equal to ndots,
  // do the query without adding any search-domain first.
  // See the resolv.conf manual for more info.
  if (DefaultDomainList<>'') then
    begin
    // Fill the cached DefaultDomainListArr and NDots
    if (Length(DefaultDomainListArr) = 0) then
      begin
      DefaultDomainListArr := DefaultDomainList.Split(' ',Char(9));
      L := Pos('ndots:', DNSOptions);
      if L > 0 then
        NDots := StrToIntDef(Trim(Copy(DNSOptions, L+6, 2)), 1);
      end;

    AbsoluteQueryFirst := HandleAsFullyQualifiedName(HostName);
    if AbsoluteQueryFirst then
      L:=ResolveName(HostName,Address)
    else
      L := -1;

    K := 0;
    while (L < 1) and (K < Length(DefaultDomainListArr)) do
      begin
      L:=ResolveName(HostName + '.' + DefaultDomainListArr[K],Address);
      Inc(K);
      end;
    end
  else
    begin
    AbsoluteQueryFirst := False;
    L := -1;
    end;

  if (L<1) and not AbsoluteQueryFirst then
    L:=ResolveName(HostName,Address);

  Result:=(L>0);
  If Result then
    begin
    // We could add a reverse call here to get the real name and aliases.
    H.Name:=HostName;
    H.Addr:=Address[1];
    H.aliases:='';
    end;
end;

function ResolveHostByName6(Hostname: String; var H: THostEntry6): Boolean;

Var
  Address : Array[1..MaxResolveAddr] of THostAddr6;
  L : Integer;
  
begin
  L:=ResolveName6(HostName,Address);
  Result:=(L>0);
  If Result then
    begin
    // We could add a reverse call here to get the real name and aliases.
    H.Name:=HostName;
    H.Addr:=Address[1];
    H.aliases:='';
    end;
end;


function ResolveHostByAddr(HostAddr: THostAddr; var H: THostEntry): Boolean;

Var
  Names : Array[1..MaxResolveAddr] of String;
  I,L : Integer;
  
begin
  L:=ResolveAddress(HostAddr,Names);
  Result:=(L>0);
  If Result then
    begin
    H.Name:=Names[1];
    H.Addr:=HostAddr;
    H.Aliases:='';
    If (L>1) then
      For I:=2 to L do
        If (I=2) then
          H.Aliases:=Names[i]
        else  
          H.Aliases:=H.Aliases+','+Names[i];
    end;
end;

function ResolveHostByAddr6(HostAddr: THostAddr6; var H: THostEntry6): Boolean;

Var
  Names : Array[1..MaxResolveAddr] of String;
  I,L : Integer;
  
begin
  L:=ResolveAddress6(HostAddr,Names);
  Result:=(L>0);
  If Result then
    begin
    H.Name:=Names[1];
    H.Addr:=HostAddr;
    H.Aliases:='';
    If (L>1) then
      For I:=2 to L do
        If (I=2) then
          H.Aliases:=Names[i]
        else  
          H.Aliases:=H.Aliases+','+Names[i];
    end;
end;




//const NoAddress : in_addr = (s_addr: 0);

function GetHostByName(HostName: String; var H: THostEntry): boolean;

begin
  Result:=FindHostEntryInHostsFile(HostName,NoAddress,H);
end;


function GetHostByAddr(Addr: THostAddr; var H: THostEntry): boolean;

begin
  Result:=FindHostEntryInHostsFile('',Addr,H);
end;


{ ---------------------------------------------------------------------
    /etc/protocols handling.
  ---------------------------------------------------------------------}

Function GetNextProtoEntry(var F : Text; Var H : TProtocolEntry): boolean;

Var
  Line,S : String;
  I      : integer;
  
begin
  Result:=False;
  Repeat
    ReadLn(F,Line);
    StripComment(Line);
    S:=NextWord(Line);
    If (S<>'') then
      begin
        H.Name:=S;	
        S:=NextWord(Line);
	i:=strtointdef(s,-1);
        If (i<>-1) then
          begin
          H.number:=i;
          Result:=True;
          H.Aliases:='';
          Repeat
            S:=NextWord(line);
            If (S<>'') then
              If (H.Aliases='') then
                H.Aliases:=S
              else
                H.Aliases:=H.Aliases+','+S;  
          until (S='');
          end;
      end;
  until Result or EOF(F);
end;  

Function FindProtoEntryInProtoFile(N: String; prot: integer; Var H : TProtocolEntry) : boolean;

Var
  F : Text;
  HE : TProtocolEntry;
  
begin
  Result:=False;
  If FileExists (EtcPath + SProtocolFile) then
    begin
    Assign (F, EtcPath + SProtocolFile);
    {$push}{$i-}
    Reset(F);
    {$pop}
    If (IOResult=0) then
      begin
      While Not Result and GetNextProtoEntry(F,HE) do
        begin
        If (N<>'') then
          Result:=MatchNameOrAlias(N,HE.Name,HE.Aliases)
        else
          Result:=prot=he.number;
        end; 
      Close(f);
      If Result then
        begin
        H.Name:=HE.Name;
        H.number:=he.number;
        H.Aliases:=HE.Aliases;
        end;
      end;  
    end;
end;

function GetProtocolByName(ProtoName: String; var H: TProtocolEntry): boolean;

begin
  Result:=FindProtoEntryInProtoFile(ProtoName,0,H);
end;


function GetProtocolByNumber(proto: Integer; var H: TProtocolEntry): boolean;

begin
  Result:=FindProtoEntryInProtoFile('',Proto,H);
end;

{ ---------------------------------------------------------------------
    /etc/networks handling
  ---------------------------------------------------------------------}

function StrTonetpartial( IP : AnsiString) : in_addr ;

Var
    Dummy : AnsiString;
    I,j,k     : Longint;
//    Temp : in_addr;

begin
  strtonetpartial.s_addr:=0;              //:=NoAddress;
  i:=0; j:=0;
  while (i<4) and (j=0) do
   begin
     J:=Pos('.',IP);
     if j=0 then j:=length(ip)+1;
     Dummy:=Copy(IP,1,J-1);
     Delete (IP,1,J);
     Val (Dummy,k,J);
     if j=0 then
      strtonetpartial.s_bytes[i+1]:=k;
     inc(i);
   end;
   if (i=0) then strtonetpartial.s_addr:=0;
end;

Function GetNextNetworkEntry(var F : Text; Var N : TNetworkEntry): boolean;

Var
  NN,Line,S : String;
  A : TNetAddr;
  
begin
  Result:=False;
  Repeat
    ReadLn(F,Line);
    StripComment(Line);
    S:=NextWord(Line);
    If (S<>'') then
      begin
      NN:=S;
      A:=StrTonetpartial(NextWord(Line));
      Result:=(NN<>'') and (A.s_bytes[1]<>0); // Valid addr.
      If result then
        begin
        N.Addr.s_addr:=A.s_addr; // keep it host.
        N.Name:=NN;
        N.Aliases:='';
        end;      
      end;
  until Result or EOF(F);
end;  

Function FindNetworkEntryInNetworksFile(Net: String; Addr: TNetAddr; Var N : TNetworkEntry) : boolean;

Var
  F : Text;
  NE : TNetworkEntry;
  
begin
  Result:=False;
  If FileExists (EtcPath + SNetworksFile) then
    begin
    Assign (F, EtcPath + SNetworksFile);
    {$push}{$i-}
    Reset(F);
    {$pop}
    If (IOResult=0) then
      begin
      While Not Result and GetNextNetworkEntry(F,NE) do
        begin
        If (Net<>'') then
          Result:=MatchNameOrAlias(Net,NE.Name,NE.Aliases)
        else
          Result:=Cardinal(Addr)=Cardinal(NE.Addr);
        end; 
      Close(f);
      If Result then
        begin
        N.Name:=NE.Name;
        N.Addr:=nettohost(NE.Addr);
        N.Aliases:=NE.Aliases;
        end;
      end;  
    end;
end;

Const NoNet : in_addr = (s_addr:0);
  
function GetNetworkByName(NetName: String; var N: TNetworkEntry): boolean;

begin
  Result:=FindNetworkEntryInNetworksFile(NetName,NoNet,N);
end;

function GetNetworkByAddr(Addr: THostAddr; var N: TNetworkEntry): boolean;

begin
  Result:=FindNetworkEntryInNetworksFile('',Addr,N);
end;

{ ---------------------------------------------------------------------
    /etc/services section
  ---------------------------------------------------------------------}

Function GetNextServiceEntry(Var F : Text; Var E : TServiceEntry) : Boolean;


Var
  Line,S : String;
  P : INteger;
  
begin
  Result:=False;
  Repeat
    ReadLn(F,Line);
    StripComment(Line);
    S:=NextWord(Line);
    If (S<>'') then
      begin
      E.Name:=S;
      S:=NextWord(Line);
      P:=Pos('/',S);
      If (P<>0) then
        begin
        E.Port:=StrToIntDef(Copy(S,1,P-1),0);
        If (E.Port<>0) then
          begin
          E.Protocol:=Copy(S,P+1,Length(S)-P);
          Result:=length(E.Protocol)>0;
          E.Aliases:='';
          Repeat
            S:=NextWord(Line);
            If (S<>'') then
              If (Length(E.Aliases)=0) then
                E.aliases:=S
              else  
                E.Aliases:=E.Aliases+','+S;
          until (S='');
          end;
        end;
      end;
  until Result or EOF(F);
end;


Function FindServiceEntryInFile(Const Name,Proto : String; Port : Integer; Var E : TServiceEntry) : Boolean;

Var
  F : Text;
  TE : TServiceEntry;
  
begin
  Result:=False;
  If FileExists (EtcPath + SServicesFile) then
    begin
    Assign (F, EtcPath + SServicesFile);
    {$push}{$i-}
    Reset(F);
    {$pop}
    If (IOResult=0) then
      begin
      While Not Result and GetNextServiceEntry(F,TE) do
        begin
        If (Port=-1) then
          Result:=MatchNameOrAlias(Name,TE.Name,TE.Aliases)
        else 
          Result:=(Port=TE.Port);
        If Result and (Proto<>'') then
          Result:=(Proto=TE.Protocol);
        end; 
      Close(f);
      If Result then
        begin
        E.Name:=TE.Name;
        E.Port:=TE.Port;
        E.Protocol:=TE.Protocol;
        E.Aliases:=TE.Aliases;
        end;
      end;  
    end;
end;

function GetServiceByName(const Name, Proto: String; var E: TServiceEntry
  ): Boolean;

begin
  Result:=FindServiceEntryInFile(Name,Proto,-1,E);  
end;

function GetServiceByPort(Port: Word; const Proto: String; var E: TServiceEntry
  ): Boolean;

begin
  Result:=FindServiceEntryInFile('',Proto,Port,E);  
end;

{ ---------------------------------------------------------------------
    Initialization section
  ---------------------------------------------------------------------}

Procedure InitResolver;

begin
  TimeOutS :=5;
  TimeOutMS:=0;
  CheckHostsFileAge:=False;
{$IFDEF UNIX_ETC}
  EtcPath := '/etc/';
{$ELSE UNIX_ETC}
 {$IFDEF ETC_BY_ENV}
  EtcPath := GetEnvironmentVariable ('ETC');
  if (EtcPath <> '') and (EtcPath [Length (EtcPath)] <> DirectorySeparator) then
   EtcPath := EtcPath + DirectorySeparator;
 {$ELSE ETC_BY_ENV}
{$WARNING Support for finding /etc/ directory not implemented for this platform!}

 {$ENDIF ETC_BY_ENV}
{$ENDIF UNIX_ETC}
  If FileExists (EtcPath + SHostsFile) then
    HostsList := ProcessHosts (EtcPath + SHostsFile);
{$ifdef android}
  CheckResolveFileAge:=True;
  CheckResolveFile;
{$else}
  CheckResolveFileAge:=False;
  If FileExists(EtcPath + SResolveFile) then
    GetDNsservers(EtcPath + SResolveFile)
{$endif android}
{$IFDEF OS2}
  else if FileExists(EtcPath + SResolveFile2) then
    GetDNsservers(EtcPath + SResolveFile2)
{$ENDIF OS2}
                                         ;
end;

Procedure DoneResolver;

begin
  FreeHostsList(HostsList);
end;


Initialization
  InitResolver;
Finalization
  DoneResolver;  
end.
newnetdb-2.pp (61,826 bytes)   

Noel Duffy

2020-12-13 10:49

reporter   ~0127584

Submitting code as a patch against trunk. This is netdb-dns.patch. The second file is the standalone dnsq program intended for addition to the examples folder for fcl-net. I've not added it to any makefile though.
netdb-dns.patch (30,129 bytes)   
Index: packages/fcl-net/src/netdb.pp
===================================================================
--- packages/fcl-net/src/netdb.pp	(revision 47765)
+++ packages/fcl-net/src/netdb.pp	(working copy)
@@ -84,11 +84,36 @@
   MaxRecursion = 10;
   MaxIP4Mapped = 10;
 
+  { from http://www.iana.org/assignments/dns-parameters }
+  DNSQRY_A     = 1;                     // name to IP address 
+  DNSQRY_AAAA  = 28;                    // name to IP6 address
+  DNSQRY_A6    = 38;                    // name to IP6 (new)
+  DNSQRY_PTR   = 12;                    // IP address to name 
+  DNSQRY_MX    = 15;                    // name to MX 
+  DNSQRY_TXT   = 16;                    // name to TXT
+  DNSQRY_CNAME = 5;
+  DNSQRY_SOA   = 6;
+  DNSQRY_NS    = 2;
+
+  // Flags 1
+  QF_QR     = $80;
+  QF_OPCODE = $78;
+  QF_AA     = $04;
+  QF_TC     = $02;  // Truncated.
+  QF_RD     = $01;
+
+  // Flags 2
+  QF_RA     = $80;
+  QF_Z      = $70;
+  QF_RCODE  = $0F;
+
 var
   EtcPath: string;
 {$endif FPC_USE_LIBC}
 
 Type
+  TDNSRcode = (rcNoError, rcFormatError,rcServFail,rcNXDomain,
+    rcNotImpl,rcRefused,rcReserved,rcInvalid);
   TDNSServerArray = Array of THostAddr;
   TServiceEntry = record
     Name     : String;
@@ -134,6 +159,62 @@
   end;
 
 {$ifndef FPC_USE_LIBC}
+
+Type 
+  TPayLoad  = Array[0..511] of Byte;
+  TPayLoadTCP = Array[0 .. 65535] of Byte;
+
+  TDNSHeader = packed Record
+    id      : Array[0..1] of Byte;
+    flags1  : Byte;
+    flags2  : Byte;
+    qdcount : word;
+    ancount : word;
+    nscount : word;
+    arcount : word;
+  end;
+
+  TQueryData = packed Record
+    h: TDNSHeader;
+    Payload : TPayLoad;
+  end;
+
+  TQueryDataLength = packed record
+    length: Word;
+    hpl: TQueryData;
+  end;
+
+  TQueryDataLengthTCP = packed Record
+    length: Word;
+    h: TDNSHeader;
+    Payload : TPayLoadTCP;
+  end;
+
+  PRRData = ^TRRData;
+  TRRData = Packed record       // RR record
+    Atype    : Word;            // Answer type
+    AClass   : Word;
+    TTL      : Cardinal;
+    RDLength : Word;
+  end;
+
+  TRRNameData = packed record
+    RRName   : ShortString;
+    RRMeta   : TRRData;
+    RDataSt  : Word;
+  end;
+  TRRNameDataArray = array of TRRNameData;
+
+  TDNSDomainName = ShortString;
+  TDNSRR_SOA = packed record
+    mname, rname: TDNSDomainName;
+    serial,refresh,retry,expire,min: Cardinal;
+  end;
+  TDNSRR_MX = packed record
+    preference: Word;
+    exchange: TDNSDomainName;
+  end;
+
 Var
   DNSServers            : TDNSServerArray;
   DNSOptions            : String;
@@ -189,6 +270,77 @@
 Function ProcessHosts(FileName : String) : PHostListEntry;
 Function FreeHostsList(var List : PHostListEntry) : Integer;
 Procedure HostsListToArray(var List : PHostListEntry; Var Hosts : THostEntryArray; FreeList : Boolean);
+
+Procedure CheckResolveFile;
+Function Query(Resolver : Integer; Var Qry,Ans : TQueryData; QryLen : Integer; Var AnsLen : Integer) : Boolean;
+function QueryTCP(Resolver : Integer; Var Qry: TQueryDataLength;
+  var Ans: TQueryDataLengthTCP; QryLen : Integer; Var AnsLen : Integer) : Boolean;
+Function BuildPayLoad(Var Q : TQueryData; Name : String; RR : Word; QClass : Word) : Integer;
+Function BuildPayLoadTCP(Var Q : TQueryDataLength; Name : String; RR : Word; QClass : Word) : Integer;
+
+Function SkipAnsQueries(Var Ans : TQueryData; L : Integer) : integer;
+Function SkipAnsQueriesTCP(Var Ans : TQueryDataLengthTCP; L : Integer) : integer;
+
+function stringfromlabel(pl: TPayLoad; var start: Integer): string;
+function stringfromlabel(pl: TPayLoadTCP; var start: Integer): string;
+Function CheckAnswer(Const Qry : TDNSHeader; Var Ans : TDNSHeader) : Boolean;
+
+function IsTruncated(R: TDNSHeader): Boolean;
+function GetRcode(R: TDNSHeader): TDNSRcode;
+function GetFixlenStr(pl: TPayLoad; startidx: Cardinal; len: Byte;
+  out res: ShortString): Byte;
+function GetFixlenStr(pl: TPayLoadTCP; startidx: Cardinal; len: Byte;
+  out res: ShortString): Byte;
+
+function NextNameRR(const pl: TPayLoadTCP; start: Word;
+  out RRName: TRRNameData): Boolean;
+function NextNameRR(const pl: TPayLoad; start: Word;
+  out RRName: TRRNameData): Boolean;
+
+function GetRRrecords(const pl: TPayloadTCP; var Start: Word; Count: Word):
+  TRRNameDataArray;
+function GetRRrecords(const pl: TPayload; var Start: Word; Count: Word):
+  TRRNameDataArray;
+
+function DnsLookup(dn: String; qtype: Word; out Ans: TQueryData;
+  out AnsLen: Longint): Boolean;
+function DnsLookup(dn: String; qtype: Word; out Ans: TQueryDataLengthTCP;
+  out AnsLen: Longint): Boolean;
+
+function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out IP: THostAddr): Boolean;
+function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoad;
+  out IP: THostAddr): Boolean;
+function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoad;
+  out cn: TDNSDomainName): Boolean;
+function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out cn: TDNSDomainName): Boolean;
+function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out IP: THostAddr6): Boolean;
+function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoad;
+  out IP: THostAddr6): Boolean;
+function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out NSName: TDNSDomainName): Boolean;
+function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoad;
+  out NSName: TDNSDomainName): Boolean;
+function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out dnssoa: TDNSRR_SOA): Boolean;
+function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoad;
+  out dnssoa: TDNSRR_SOA): Boolean;
+function  DNSRRGetText(const RR: TRRNameData; const pl: TPayLoad;
+  out dnstext: AnsiString): Boolean;
+function  DNSRRGetText(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out dnstext: AnsiString): Boolean;
+function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out MX: TDNSRR_MX): Boolean;
+function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoad;
+  out MX: TDNSRR_MX): Boolean;
+function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out ptr: TDNSDomainName): Boolean;
+function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoad;
+  out ptr: TDNSDomainName): Boolean;
+
+
 {$endif FPC_USE_LIBC}
 
 Implementation
@@ -205,50 +357,7 @@
   DefaultDomainListArr : array of string;
   NDots: Integer;
 
-const
-  { from http://www.iana.org/assignments/dns-parameters }
-  DNSQRY_A     = 1;                     // name to IP address 
-  DNSQRY_AAAA  = 28;                    // name to IP6 address
-  DNSQRY_A6    = 38;                    // name to IP6 (new)
-  DNSQRY_PTR   = 12;                    // IP address to name 
-  DNSQRY_MX    = 15;                    // name to MX 
-  DNSQRY_TXT   = 16;                    // name to TXT
-  DNSQRY_CNAME = 5;
-
-  // Flags 1
-  QF_QR     = $80;
-  QF_OPCODE = $78;
-  QF_AA     = $04;
-  QF_TC     = $02;  // Truncated.
-  QF_RD     = $01;
-
-  // Flags 2
-  QF_RA     = $80;
-  QF_Z      = $70;
-  QF_RCODE  = $0F;
-
-
    
-Type 
-  TPayLoad  = Array[0..511] of Byte;
-  TQueryData = packed Record
-    id      : Array[0..1] of Byte;
-    flags1  : Byte;
-    flags2  : Byte; 
-    qdcount : word;
-    ancount : word;
-    nscount : word;
-    arcount : word;
-    Payload : TPayLoad;
-  end;
-  
-  PRRData = ^TRRData;
-  TRRData = Packed record       // RR record
-    Atype    : Word;            // Answer type
-    AClass   : Word;
-    TTL      : Cardinal;
-    RDLength : Word;
-  end;
 
 { ---------------------------------------------------------------------
     Some Parsing routines
@@ -710,8 +819,24 @@
   Inc(Result,2);
 end;
 
+{Construct a TCP query payload from the given name, rr and qclass. The
+ principal difference between the TCP and UDP payloads is the two-octet
+ length field in the TCP payload. The UDP payload has no length field.
 
+ See RFC-1035, section 4.2.2.
 
+ Returns the length of the constructed payload, which doesn't include
+ the header or the length field.}
+function BuildPayLoadTCP(var Q: TQueryDataLength; Name: String; RR: Word;
+  QClass: Word): Integer;
+var
+  l: Word;
+begin
+  l := BuildPayLoad(Q.hpl, Name, RR, QClass);
+  Q.length := htons(l + SizeOf(Q.hpl.h));
+  Result := l;
+end;
+
 Function NextRR(Const PayLoad : TPayLoad;Var Start : LongInt; AnsLen : LongInt; Var RR : TRRData) : Boolean;
 
 Var
@@ -783,9 +908,8 @@
 { ---------------------------------------------------------------------
     QueryData handling functions
   ---------------------------------------------------------------------}
-  
-Function CheckAnswer(Const Qry : TQueryData; Var Ans : TQueryData) : Boolean;
 
+function CheckAnswer(const Qry: TDNSHeader; var Ans: TDNSHeader): Boolean;
 begin
   Result:=False;
   With Ans do
@@ -797,7 +921,7 @@
     If (Flags1 and QF_QR)=0 then
       exit;
     if (Flags1 and QF_OPCODE)<>0 then 
-      exit;  
+      exit;
     if (Flags2 and QF_RCODE)<>0 then
       exit;  
     // Number of answers ?  
@@ -808,6 +932,492 @@
     end;
 end;
 
+function IsTruncated(R: TDNSHeader): Boolean;
+begin
+  Result := ((R.flags1 and QF_TC) > 0);
+end;
+
+function GetRcode(R: TDNSHeader): TDNSRcode;
+var
+  rcode_n: Byte;
+begin
+  rcode_n := (R.flags2 and QF_RCODE);
+  case rcode_n of
+    0: Result := rcNoError;
+    1: Result := rcFormatError;
+    2: Result := rcServFail;
+    3: Result := rcNXDomain;
+    4: Result := rcNotImpl;
+    5: Result := rcRefused;
+    6 .. 15: Result := rcReserved;
+  else
+    Result := rcInvalid;
+  end;
+end;
+
+function GetFixlenStr(pl: TPayLoad; startidx: Cardinal; len: Byte; out
+  res: ShortString): Byte;
+begin
+  Result := 0;
+  res := '';
+  if (startidx + len) > Length(pl) then exit;
+  SetLength(res, len);
+  Move(pl[startidx], res[1], len);
+  Result := len;
+end;
+
+function GetFixlenStr(pl: TPayLoadTCP; startidx: Cardinal; len: Byte;
+  out res: ShortString): Byte;
+begin
+  Result := 0;
+  res := '';
+  if (startidx + len) > Length(pl) then exit;
+  SetLength(res, len);
+  Move(pl[startidx], res[1], len);
+  Result := len;
+end;
+
+function NextNameRR(const pl: TPayLoadTCP; start: Word; out RRName: TRRNameData
+  ): Boolean;
+var
+  I : Integer;
+  PA : PRRData;
+
+begin
+  Result:=False;
+  I:=Start;
+  if (Length(pl) - I) < (SizeOf(TRRData)+2) then exit;
+  RRName.RRName := stringfromlabel(pl, I);
+  if (Length(pl) - I) < (SizeOf(TRRData)) then exit;
+
+  PA:=PRRData(@pl[I]);
+  RRName.RRMeta := PA^;
+  RRName.RRMeta.AClass := NToHs(RRName.RRMeta.AClass);
+  RRName.RRMeta.Atype := NToHs(RRName.RRMeta.Atype);
+  RRName.RRMeta.RDLength := NToHs(RRName.RRMeta.RDLength);
+  RRName.RRMeta.TTL := NToHl(RRName.RRMeta.TTL);
+  RRName.RDataSt := I+SizeOf(TRRData);
+  Result := True;
+end;
+
+function NextNameRR(const pl: TPayLoad; start: Word; out RRName: TRRNameData
+  ): Boolean;
+var
+  I : Integer;
+  PA : PRRData;
+
+begin
+  Result:=False;
+  I:=Start;
+  if (Length(pl) - I) < (SizeOf(TRRData)+2) then exit;
+  RRName.RRName := stringfromlabel(pl, I);
+  if (Length(pl) - I) < (SizeOf(TRRData)) then exit;
+
+  PA:=PRRData(@pl[I]);
+  RRName.RRMeta := PA^;
+  RRName.RRMeta.AClass := NToHs(RRName.RRMeta.AClass);
+  RRName.RRMeta.Atype := NToHs(RRName.RRMeta.Atype);
+  RRName.RRMeta.RDLength := NToHs(RRName.RRMeta.RDLength);
+  RRName.RRMeta.TTL := NToHl(RRName.RRMeta.TTL);
+  RRName.RDataSt := I+SizeOf(TRRData);
+  Result := True;
+end;
+
+function GetRRrecords(const pl: TPayloadTCP; var Start: Word; Count: Word
+  ): TRRNameDataArray;
+var
+  I, Total: Word;
+  B: Boolean;
+  RRN: TRRNameData;
+
+begin
+  I:=0;
+  Total := 0;
+  SetLength(Result,Count);
+  while (I < Count) do
+  begin
+    B := NextNameRR(pl, Start, RRN);
+    if not B then break;
+    Inc(Total);
+    Result[I] := RRN;
+    Inc(I);
+    Start := RRN.RDataSt+RRN.RRMeta.RDLength;
+  end;
+  if Total < Count then SetLength(Result,Total);
+end;
+
+function GetRRrecords(const pl: TPayload; var Start: Word; Count: Word
+  ): TRRNameDataArray;
+var
+  I, Total: Word;
+  B: Boolean;
+  RRN: TRRNameData;
+
+begin
+  I:=0;
+  Total := 0;
+  SetLength(Result,Count);
+  while (I < Count) do
+  begin
+    B := NextNameRR(pl, Start, RRN);
+    if not B then break;
+    Inc(Total);
+    Result[I] := RRN;
+    Inc(I);
+    Start := RRN.RDataSt+RRN.RRMeta.RDLength;
+  end;
+  if Total < Count then SetLength(Result,Total);
+end;
+
+function DnsLookup(dn: String; qtype: Word; out Ans: TQueryData; out
+  AnsLen: Longint): Boolean;
+var
+  Qry: TQueryData;
+  QryLen: Longint;
+  idx: Word;
+begin
+  Result := False;
+  AnsLen := -1;
+
+  CheckResolveFile;
+  if Length(DNSServers) = 0 then
+    exit;
+
+  QryLen := BuildPayLoad(Qry, dn, qtype, 1);
+  if QryLen <= 0 then exit;
+
+  { Try the query at each configured resolver in turn, until one of them
+   returns an answer. We check for AnsLen > -1 because we need to distinguish
+   between failure to connect and the server saying it doesn't know or can't
+   answer. If AnsLen = -1 then we failed to connect. If AnsLen >= 0 but qr
+   = False, then we connected but the server returned an error code.}
+  idx := 0;
+  repeat
+    Result := Query(idx,Qry,Ans,QryLen,AnsLen);
+    Inc(idx);
+  until (idx > High(DNSServers)) or (Result = True) or (AnsLen >= 0);
+end;
+
+function DnsLookup(dn: String; qtype: Word; out Ans: TQueryDataLengthTCP; out
+  AnsLen: Longint): Boolean;
+var
+  Qry: TQueryDataLength;
+  QryLen: Longint;
+  idx: Word;
+
+begin
+  Result := False;
+  AnsLen := -1;
+
+  CheckResolveFile;
+  if Length(DNSServers) = 0 then
+    exit;
+
+  QryLen:=BuildPayLoadTCP(Qry, dn, qtype, 1);
+  if QryLen <= 0 then exit;
+
+  { Try the query at each configured resolver in turn, until one of them
+   returns an answer. We check for AnsLen > -1 because we need to distinguish
+   between failure to connect and the server saying it doesn't know or can't
+   answer. If AnsLen = -1 then we failed to connect. If AnsLen >= 0 but qr
+   = False, then we connected but the server returned an error code.}
+  idx := 0;
+  repeat
+    Result := QueryTCP(idx,Qry,Ans,QryLen,AnsLen);
+    Inc(idx);
+  until (idx > High(DNSServers)) or (Result = True) or (AnsLen >= 0);
+end;
+
+function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  IP: THostAddr): Boolean;
+begin
+  IP.s_addr := 0;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_A then exit;
+  if (Length(pl) - RR.RDataSt) < 4 then exit;
+  Move(pl[RR.RDataSt], IP, SizeOf(THostAddr));
+  IP.s_addr := NToHl(IP.s_addr);
+  Result := True;
+end;
+
+function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoad; out IP: THostAddr
+  ): Boolean;
+begin
+  IP.s_addr := 0;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_A then exit;
+  if (Length(pl) - RR.RDataSt) < 4 then exit;
+  Move(pl[RR.RDataSt], IP, SizeOf(THostAddr));
+  IP.s_addr := NToHl(IP.s_addr);
+  Result := True;
+end;
+
+function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoad; out
+  cn: TDNSDomainName): Boolean;
+var
+  n: Integer;
+begin
+  Result := False;
+  cn := '';
+  if RR.RRMeta.Atype <> DNSQRY_CNAME then exit;
+  n := RR.RDataSt;
+  if (n + RR.RRMeta.RDLength) > Length(pl) then exit;
+  cn := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  cn: TDNSDomainName): Boolean;
+var
+  n: Integer;
+begin
+  Result := False;
+  cn := '';
+  if RR.RRMeta.Atype <> DNSQRY_CNAME then exit;
+  n := RR.RDataSt;
+  if (n + RR.RRMeta.rdlength) > Length(pl) then exit;
+  cn := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  IP: THostAddr6): Boolean;
+begin
+  IP.s6_addr32[0] := 0;
+  IP.s6_addr32[1] := 0;
+  IP.s6_addr32[2] := 0;
+  IP.s6_addr32[3] := 0;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_AAAA then exit;
+  if (RR.RDataSt + SizeOf(THostAddr6)) > Length(pl) then exit;
+  Move(pl[RR.RDataSt],IP,SizeOf(THostAddr6));
+  Result := True;
+end;
+
+function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoad; out
+  IP: THostAddr6): Boolean;
+begin
+  IP.s6_addr32[0] := 0;
+  IP.s6_addr32[1] := 0;
+  IP.s6_addr32[2] := 0;
+  IP.s6_addr32[3] := 0;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_AAAA then exit;
+  if (RR.RDataSt + SizeOf(THostAddr6)) > Length(pl) then exit;
+  Move(pl[RR.RDataSt],IP,SizeOf(THostAddr6));
+  Result := True;
+end;
+
+function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  NSName: TDNSDomainName): Boolean;
+var
+  n: LongInt;
+begin
+  NSName := '';
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_NS then exit;
+  if (RR.RDataSt + RR.RRMeta.RDLength) > Length(pl) then exit;
+  n := RR.RDataSt;
+  NSName := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoad; out
+  NSName: TDNSDomainName): Boolean;
+var
+  n: LongInt;
+begin
+  NSName := '';
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_NS then exit;
+  if (RR.RDataSt + RR.RRMeta.RDLength) > Length(pl) then exit;
+  n := RR.RDataSt;
+  NSName := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  dnssoa: TDNSRR_SOA): Boolean;
+var
+  idx: Integer;
+begin
+  // can't trust the counts we've been given, so check that we never
+  // exceed the end of the payload buffer.
+  idx := RR.RDataSt;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_SOA then exit;
+  dnssoa.mname := stringfromlabel(pl, idx);
+  if idx >= Length(pl) then exit;
+
+  dnssoa.rname := stringfromlabel(pl, idx);
+
+  if (idx + (SizeOf(Cardinal) * 5)) > Length(pl) then exit;
+  Move(pl[idx],dnssoa.serial,SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.refresh, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.retry, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.expire, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.min, SizeOf(Cardinal));
+  Result := True;
+  dnssoa.serial := NToHl(dnssoa.serial);
+  dnssoa.min := NToHl(dnssoa.min);
+  dnssoa.expire := NToHl(dnssoa.expire);
+  dnssoa.refresh := NToHl(dnssoa.refresh);
+  dnssoa.retry := NToHl(dnssoa.retry);
+end;
+
+function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoad; out
+  dnssoa: TDNSRR_SOA): Boolean;
+var
+  idx: Integer;
+begin
+  // can't trust the counts we've been given, so check that we never
+  // exceed the end of the payload buffer.
+  idx := RR.RDataSt;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_SOA then exit;
+  dnssoa.mname := stringfromlabel(pl, idx);
+  if idx >= Length(pl) then exit;
+
+  dnssoa.rname := stringfromlabel(pl, idx);
+
+  if (idx + (SizeOf(Cardinal) * 5)) > Length(pl) then exit;
+  Move(pl[idx],dnssoa.serial,SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.refresh, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.retry, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.expire, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.min, SizeOf(Cardinal));
+  Result := True;
+  dnssoa.serial := NToHl(dnssoa.serial);
+  dnssoa.min := NToHl(dnssoa.min);
+  dnssoa.expire := NToHl(dnssoa.expire);
+  dnssoa.refresh := NToHl(dnssoa.refresh);
+  dnssoa.retry := NToHl(dnssoa.retry);
+end;
+
+function DNSRRGetText(const RR: TRRNameData; const pl: TPayLoad; out
+  dnstext: AnsiString): Boolean;
+var
+  total: Word;
+  wrk: ShortString;
+  idx: LongInt;
+  l: Byte;
+begin
+  Result := False;
+  dnstext := '';
+  if RR.RRMeta.Atype <> DNSQRY_TXT then exit;
+  wrk := '';
+  total := RR.RRMeta.RDLength;
+
+  idx := RR.RDataSt;
+  if (Length(pl) - idx)  < 2 then exit;
+
+  repeat
+    l := GetFixlenStr(pl, idx+1, pl[idx], wrk);
+    dnstext := dnstext + wrk;
+    Inc(idx, l+1);
+  until (idx >= (RR.RDataSt + total)) or ((Length(pl) - idx) < 2);
+  Result := True;
+end;
+
+function DNSRRGetText(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  dnstext: AnsiString): Boolean;
+var
+  total: Word;
+  wrk: ShortString;
+  idx: LongInt;
+  l: Byte;
+begin
+  Result := False;
+  dnstext := '';
+  if RR.RRMeta.Atype <> DNSQRY_TXT then exit;
+  wrk := '';
+  total := RR.RRMeta.RDLength;
+
+  idx := RR.RDataSt;
+  if (Length(pl) - idx)  < 2 then exit;
+
+  repeat
+    l := GetFixlenStr(pl, idx+1, pl[idx], wrk);
+    dnstext := dnstext + wrk;
+    Inc(idx, l+1);
+    until (idx >= (RR.RDataSt + total)) or ((Length(pl) - idx) < 2);
+  Result := True;
+end;
+
+function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  MX: TDNSRR_MX): Boolean;
+var
+  idx: Integer;
+begin
+  Result := False;
+  MX.preference := 0;
+  MX.exchange := '';
+  if RR.RRMeta.Atype <> DNSQRY_MX then exit;
+  idx := RR.RDataSt;
+  if idx + SizeOf(Word) >= Length(pl) then exit;
+  Move(pl[idx],MX.preference, SizeOf(Word));
+  Inc(idx, SizeOf(Word));
+  if (Length(pl) - idx) < 2 then exit;
+  MX.exchange := stringfromlabel(pl, idx);
+  MX.preference := NToHs(MX.preference);
+  Result := True;
+end;
+
+function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoad; out MX: TDNSRR_MX
+  ): Boolean;
+var
+  idx: Integer;
+begin
+  Result := False;
+  MX.preference := 0;
+  MX.exchange := '';
+  if RR.RRMeta.Atype <> DNSQRY_MX then exit;
+  idx := RR.RDataSt;
+  if idx + SizeOf(Word) >= Length(pl) then exit;
+  Move(pl[idx],MX.preference, SizeOf(Word));
+  Inc(idx, SizeOf(Word));
+  if (Length(pl) - idx) < 2 then exit;
+  MX.exchange := stringfromlabel(pl, idx);
+  MX.preference := NToHs(MX.preference);
+  Result := True;
+end;
+
+function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  ptr: TDNSDomainName): Boolean;
+var
+  n: Integer;
+begin
+  Result := False;
+  ptr := '';
+  if RR.RRMeta.Atype <> DNSQRY_PTR then exit;
+  n := RR.RDataSt;
+  if (n + RR.RRMeta.RDLength) > Length(pl) then exit;
+  ptr := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoad; out
+  ptr: TDNSDomainName): Boolean;
+var
+  n: Integer;
+begin
+  Result := False;
+  ptr := '';
+  if RR.RRMeta.Atype <> DNSQRY_PTR then exit;
+  n := RR.RDataSt;
+  if (n + RR.RRMeta.RDLength) > Length(pl) then exit;
+  ptr := stringfromlabel(pl, n);
+  Result := True;
+end;
+
 Function SkipAnsQueries(Var Ans : TQueryData; L : Integer) : integer;
 
 Var
@@ -817,10 +1427,10 @@
   Result:=0;
   With Ans do
     begin
-    qdcount := htons(qdcount);
+    h.qdcount := htons(h.qdcount);
     i:=0;
     q:=0;
-    While (Q<qdcount) and (i<l) do  
+    While (Q<h.qdcount) and (i<l) do  
       begin
       If Payload[i]>63 then
         begin
@@ -842,6 +1452,39 @@
     end;  
 end;
 
+function SkipAnsQueriesTCP(var Ans: TQueryDataLengthTCP; L: Integer): integer;
+var
+  Q,I : Integer;
+
+begin
+  Result:=0;
+  With Ans do
+  begin
+    h.qdcount := htons(h.qdcount);
+    i:=0;
+    q:=0;
+    While (Q<h.qdcount) and (i<l) do
+    begin
+      If Payload[i]>63 then
+      begin
+        Inc(I,6);
+        Inc(Q);
+      end
+      else
+      begin
+        If Payload[i]=0 then
+        begin
+          inc(q);
+          Inc(I,5);
+        end
+        else
+          Inc(I,Payload[i]+1);
+      end;
+    end;
+    Result:=I;
+  end;
+end;
+
 { ---------------------------------------------------------------------
     DNS Query functions.
   ---------------------------------------------------------------------}
@@ -857,7 +1500,7 @@
   
 begin
   Result:=False;
-  With Qry do
+  With Qry.h do
     begin
     ID[0]:=Random(256);
     ID[1]:=Random(256);
@@ -890,41 +1533,190 @@
   AL:=SizeOf(SA);
   L:=fprecvfrom(Sock,@ans,SizeOf(Ans),0,@SA,@AL);
   fpclose(Sock);
-  // Check lenght answer and fields in header data.
-  If (L<12) or not CheckAnswer(Qry,Ans) Then
+
+  if L < 12 then exit;
+  // Return Payload length.
+  Anslen:=L-12;
+  // even though we may still return false to indicate an error, if AnsLen
+  // is >= 0 then the caller knows the dns server responded.
+  If not CheckAnswer(Qry.h,Ans.h) Then
     exit;
-  // Return Payload length.  
-  Anslen:=L-12;  
-  Result:=True;  
+  Result:=True;
+  //end;
 end;
 
-function stringfromlabel(pl: TPayLoad; start: integer): string;
+function QueryTCP(Resolver: Integer; var Qry: TQueryDataLength;
+  var Ans: TQueryDataLengthTCP; QryLen: Integer; var AnsLen: Integer): Boolean;
+Var
+  SA : TInetSockAddr;
+  Sock : cint;
+  L: ssize_t;
+  RTO : Longint;
+  ReadFDS : TFDSet;
+  count: Integer;
+  sendsize: ssize_t;
+  respsize: Word;
+
+begin
+  Result:=False;
+  With Qry.hpl.h do
+  begin
+    ID[0]:=Random(256);
+    ID[1]:=Random(256);
+    Flags1:=QF_RD;
+    Flags2:=0;
+    qdcount:=htons(1); // was 1 shl 8;
+    ancount:=0;
+    nscount:=0;
+    arcount:=0;
+  end;
+  Sock:=FpSocket(AF_INET,SOCK_STREAM,0);
+  If Sock=-1 then
+    exit;
+  With SA do
+  begin
+    sin_family:=AF_INET;
+    sin_port:=htons(DNSport);
+    sin_addr.s_addr:=cardinal(DNSServers[Resolver]); // octets already in net order
+  end;
+  if (fpconnect(Sock, @SA, SizeOf(SA)) <> 0) then
+    exit;
+  sendsize := QryLen + SizeOf(Qry.hpl.h) + SizeOf(Qry.length);
+  count := fpsend(Sock,@Qry,sendsize,0);
+  if count < sendsize then
+  begin
+    fpclose(Sock);
+    exit;
+  end;
+
+  // tell other side we'll write no more.
+  fpshutdown(Sock, SHUT_WR);
+  // Wait for answer.
+  RTO:=TimeOutS*1000+TimeOutMS;
+  fpFD_ZERO(ReadFDS);
+  fpFD_Set(Sock,ReadFDS);
+
+  if fpSelect(Sock+1,@ReadFDS,Nil,Nil,RTO)<=0 then
+  begin
+    fpclose(Sock);
+    exit;
+  end;
+
+  L:=fprecv(Sock,@Ans,SizeOf(Ans),0);
+  if L <= 1 then
+  begin
+    fpclose(Sock);
+    exit;
+  end;
+
+  // get the payload size, which doesn't include the 2 byte length field.
+  // I.e, num bytes read  should be sizeof(word) (2 bytes) greater than the
+  // payload size.
+  respsize := NToHs(Ans.length);
+  if L < (respsize + SizeOf(Ans.length)) then
+  begin
+    // we haven't received the full payload. Unsure in what circumstances
+    // this might happen.
+    fpclose(Sock);
+    exit;
+  end;
+
+  if L < SizeOf(Qry.hpl.h) then exit;
+
+  // if the final check finds problems with the answer, we'll return false
+  // but AnsLen being >=0 will let the caller know that the server did
+  // respond, but either declined to answer or couldn't.
+  Anslen:=respsize-SizeOf(Qry.hpl.h);
+  If not CheckAnswer(Qry.hpl.h,Ans.h) then
+    exit;
+  Result:=True;
+
+end;
+
+{
+Read a string from the payload buffer. Handles compressed as well as
+regular labels. On termination start points to the character after the
+end of the str.
+}
+
+function stringfromlabel(pl: TPayLoad; var start: Integer): string;
 var
-  l,i: integer;
+  l,i,n: integer;
+  ptrseen: Boolean = False;
 begin
   result := '';
   l := 0;
   i := 0;
+  n := start;
   repeat
-    l := ord(pl[start]);
+    if (Length(pl) - n) < 2 then exit;
+    l := ord(pl[n]);
     { compressed reply }
     while (l >= 192) do
       begin
+        if not ptrseen then start := n + 2;
+        ptrseen := True;
         { the -12 is because of the reply header length }
-        start := (l and not(192)) shl 8 + ord(pl[start+1]) - 12;
-        l := ord(pl[start]);
+        n := (l and not(192)) shl 8 + ord(pl[n+1]) - 12;
+        if (Length(pl) - n) < 2 then exit;
+        l := ord(pl[n]);
       end;
+    if (n+l) >= (Length(pl)-1) then l := 0;
     if l <> 0 then begin
       setlength(result,length(result)+l);
-      move(pl[start+1],result[i+1],l);
+      move(pl[n+1],result[i+1],l);
       result := result + '.';
-      inc(start,l); inc(start);
+      inc(n,l); inc(n);
       inc(i,l); inc(i);
+      if n > start then start := n;
     end;
   until l = 0;
+  // per rfc1035, section 4.1.4, a domain name may be represented by
+  // either a sequence of labels followed by 0, or a pointer, or a series
+  // of labels followed by a pointer. If there's a pointer there's no 0.
+  if not ptrseen then Inc(start); // jump past the 0.
   if result[length(result)] = '.' then setlength(result,length(result)-1);
 end;
 
+function stringfromlabel(pl: TPayLoadTCP; var start: Integer): string;
+var
+  l,i,n: integer;
+  ptrseen: Boolean = False;
+begin
+  result := '';
+  l := 0;
+  i := 0;
+  n := start;
+  repeat
+    if (Length(pl) - n) < 2 then exit;
+    l := ord(pl[n]);
+    { compressed reply }
+    while (l >= 192) do
+      begin
+        if not ptrseen then start := n + 2;
+        ptrseen := True;
+        { the -12 is because of the reply header length }
+        n := (l and not(192)) shl 8 + ord(pl[n+1]) - 12;
+        if (Length(pl) - n) < 2 then exit;
+        l := ord(pl[n]);
+      end;
+    if (n+l) >= (Length(pl)-1) then l := 0;
+    if l <> 0 then begin
+      setlength(result,length(result)+l);
+      move(pl[n+1],result[i+1],l);
+      result := result + '.';
+      inc(n,l); inc(n);
+      inc(i,l); inc(i);
+      if n > start then start := n;
+    end;
+  until l = 0;
+  // per rfc1035, section 4.1.4, a domain name may be represented by
+  // either a sequence of labels followed by 0, or a pointer, or a series
+  // of labels followed by a pointer. If there's a pointer there's no 0.
+  if not ptrseen then Inc(start); // jump past the 0.
+  if result[length(result)] = '.' then setlength(result,length(result)-1);
+end;
+
 Function ResolveNameAt(Resolver : Integer; HostName : String; Var Addresses : Array of THostAddr; Recurse: Integer) : Integer;
 
 Var
@@ -941,7 +1733,7 @@
   else  
     begin
     AnsStart:=SkipAnsQueries(Ans,AnsLen);
-    MaxAnswer:=Ans.AnCount-1;
+    MaxAnswer:=Ans.h.AnCount-1;
     If MaxAnswer>High(Addresses) then
       MaxAnswer:=High(Addresses);
     I:=0;
@@ -1022,7 +1814,7 @@
   end else
     begin
     AnsStart:=SkipAnsQueries(Ans,AnsLen);
-    MaxAnswer:=Ans.AnCount-1;
+    MaxAnswer:=Ans.h.AnCount-1;
     If MaxAnswer>High(Addresses) then
       MaxAnswer:=High(Addresses);
     I:=0;
@@ -1085,7 +1877,7 @@
   else  
     begin
     AnsStart:=SkipAnsQueries(Ans,AnsLen);
-    MaxAnswer:=Ans.AnCount-1;
+    MaxAnswer:=Ans.h.AnCount-1;
     If MaxAnswer>High(Names) then
       MaxAnswer:=High(Names);
     I:=0;
netdb-dns.patch (30,129 bytes)   
dnsq.pp (13,150 bytes)   
program dnsq;

{$mode objfpc}
{$h+}

{
This is a simple program that demonstrates how to query DNS using the pure Pascal api. It can
query A, AAAA, MX, NS, SOA, CNAME, PTR, and TXT records. In the event that the returned data is
truncated, this program retries using TCP.

The code should be quite easy to follow as it's written in a plain imperative style with no
fancy tricks.
}

uses
  netdb, sockets, sysutils, math;


{
The dump_payload functions output the DNS response received from the server as
a hex dump. Useful for debugging but not that useful for general use.
}
procedure dump_payload(const pl: TPayLoad; l: Word);
var
  idx,llen: Cardinal;
begin
  idx := 0;
  llen := 0;
  for idx := 0 to l do
  begin
    write('['+inttostr(idx)+'] '+IntToHex(pl[idx],2));
    if (pl[idx] > 48) and (pl[idx] < 123) then
      write(' ' + chr(pl[idx]))
    else
      write(' .');
    write(' ');
    Inc(llen);
    if llen >= 6 then
    begin
      llen := 0;
      writeln();
    end;
  end;
  if llen > 0 then
  begin
    writeln();
  end;
end;

procedure dump_payload(const pl: TPayLoadTCP; l: Word);
var
  idx,llen: Cardinal;
begin
  idx := 0;
  llen := 0;
  for idx := 0 to l do
  begin
    write('['+inttostr(idx)+'] '+IntToHex(pl[idx],2));
    if (pl[idx] > 48) and (pl[idx] < 123) then
      write(' ' + chr(pl[idx]))
    else
      write(' .');
    write(' ');
    Inc(llen);
    if llen >= 6 then
    begin
      llen := 0;
      writeln();
    end;
  end;
  if llen > 0 then
  begin
    writeln();
  end;
end;

{
Write a text representation of a DNS Resource Record to the console.
}
procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayloadTCP);
var
  s: AnsiString;
  ss: ShortString;
  b: Boolean;
  ip: THostAddr;
  ip6: THostAddr6;
  mx: TDNSRR_MX;
  soa: TDNSRR_SOA;
begin
  s := 'Unknown';
  case RRN.RRMeta.Atype of
    DNSQRY_TXT:
        s := 'TXT';
    DNSQRY_A:
        s := 'A';
    DNSQRY_MX:
        s := 'MX';
    DNSQRY_PTR:
        s := 'PTR';
    DNSQRY_CNAME:
      s := 'CNAME';
    DNSQRY_AAAA:
      s := 'AAAA';
    DNSQRY_SOA:
      s := 'SOA';
    DNSQRY_NS:
      s := 'NS';
  end;

  write(RRN.RRName);
  write(' '+inttostr(RRN.RRMeta.TTL));
  write(' '+s+' ');
  case RRN.RRMeta.Atype of
    DNSQRY_A:
      begin
        b := DNSRRGetA(RRN, pl, ip);
        if b then WriteLn(HostAddrToStr(ip)) else WriteLn('Failed');
      end;
    DNSQRY_CNAME:
      begin
        b := DNSRRGetCNAME(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
    DNSQRY_AAAA:
      begin
        b := DNSRRGetAAAA(RRN, pl, ip6);
        if b then WriteLn(HostAddrToStr6(ip6)) else WriteLn('Failed');
      end;
    DNSQRY_NS:
      begin
        b := DNSRRGetNS(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
    DNSQRY_SOA:
      begin
        b := DNSRRGetSOA(RRN, pl, soa);
        if b then
          WriteLn(
            soa.mname+
            ' '+soa.rname+
            ', serial:'+inttostr(soa.serial)+
            ', refresh:'+inttostr(soa.refresh)+
            ', retry:'+inttostr(soa.retry)+
            ', min:'+inttostr(soa.min)+
            ', expire:'+inttostr(soa.expire))
        else WriteLn('Failed');
      end;
    DNSQRY_TXT:
      begin
        b := DNSRRGetText(RRN, pl, s);
        if b then WriteLn(s) else WriteLn('Failed');
      end;
    DNSQRY_MX:
      begin
        b := DNSRRGetMX(RRN, pl, mx);
        if b then WriteLn(inttostr(mx.preference)+' '+mx.exchange)
        else WriteLn('Failed');
      end;
    DNSQRY_PTR:
      begin
        b := DNSRRGetPTR(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
  end;
end;

{
Write a text representation of a DNS Resource Record to the console.
}
procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
var
  s: AnsiString;
  ss: ShortString;
  b: Boolean;
  ip: THostAddr;
  ip6: THostAddr6;
  mx: TDNSRR_MX;
  soa: TDNSRR_SOA;
begin
  s := 'Unknown';
  case RRN.RRMeta.Atype of
    DNSQRY_TXT:
        s := 'TXT';
    DNSQRY_A:
        s := 'A';
    DNSQRY_MX:
        s := 'MX';
    DNSQRY_PTR:
        s := 'PTR';
    DNSQRY_CNAME:
      s := 'CNAME';
    DNSQRY_AAAA:
      s := 'AAAA';
    DNSQRY_SOA:
      s := 'SOA';
    DNSQRY_NS:
      s := 'NS';
  end;

  write(RRN.RRName);
  write(' '+inttostr(RRN.RRMeta.TTL)+' ');
  write(s+' ');
  case RRN.RRMeta.Atype of
    DNSQRY_A:
      begin
        b := DNSRRGetA(RRN, pl, ip);
        if b then WriteLn(HostAddrToStr(ip)) else WriteLn('Failed');
      end;
    DNSQRY_CNAME:
      begin
        b := DNSRRGetCNAME(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
    DNSQRY_AAAA:
      begin
        b := DNSRRGetAAAA(RRN, pl, ip6);
        if b then WriteLn(HostAddrToStr6(ip6)) else WriteLn('Failed');
      end;
    DNSQRY_NS:
      begin
        b := DNSRRGetNS(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
    DNSQRY_SOA:
      begin
        b := DNSRRGetSOA(RRN, pl, soa);
        if b then
          WriteLn(
            soa.mname+
            ' '+soa.rname+
            ', serial:'+inttostr(soa.serial)+
            ', refresh:'+inttostr(soa.refresh)+
            ', retry:'+inttostr(soa.retry)+
            ', min:'+inttostr(soa.min)+
            ', expire:'+inttostr(soa.expire))
        else WriteLn('Failed');
      end;
    DNSQRY_TXT:
      begin
        b := DNSRRGetText(RRN, pl, s);
        if b then WriteLn(s) else WriteLn('Failed');
      end;
    DNSQRY_MX:
      begin
        b := DNSRRGetMX(RRN, pl, mx);
        if b then WriteLn(inttostr(mx.preference)+' '+mx.exchange)
        else WriteLn('Failed');
      end;
    DNSQRY_PTR:
      begin
        b := DNSRRGetPTR(RRN, pl, ss);
        if b then WriteLn(ss) else WriteLn('Failed');
      end;
  end;
end;

{
Query the DNS system resolver using TCP. The RFCs say that clients should always try UDP
first and only resort to TCP if the response header indicates that the response would
not fit in a UDP payload.

The handling of payload is identical no matter whether it's a TCP or UDP payload.
}
procedure QueryTCP(dn: AnsiString; qtype: Word);
var
  Ans: TQueryDataLengthTCP;
  MaxAnswer, AnsLen: Longint;
  AnsStart: Word;
  RRN: TRRNameData;
  r: TDNSRcode;
  RRArr: TRRNameDataArray;
  qr: Boolean;

begin
  qr := DnsLookup(dn, qtype, Ans, AnsLen);
  if not qr then
  begin
    if AnsLen = -1 then
    begin
      writeln('No DNS server could be reached.');
      exit;
    end;
    r := GetRcode(Ans.h);
    case r of
      rcInvalid: writeln('Invalid response');
      rcNoError:
        begin
          writeln('Server returned no records.');
        end;
      rcRefused: writeln('Refused');
      rcNotImpl: writeln('Not implemented');
      rcNXDomain: writeln('NXDOMAIN');
      rcServFail: writeln('SERVFAIL');
      rcFormatError: writeln('Format error');
      rcReserved: writeln('Reserved code');
    end;
    exit;
  end
  else
  begin
    AnsStart:=SkipAnsQueriesTCP(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount;
    //dump_payload(Ans.Payload, AnsLen);
    Write('Answers: '+inttostr(Ans.h.ancount)+' ');
    Write('NS: '+inttostr(NToHS(Ans.h.nscount))+' ');
    Write('Additional: '+inttostr(NToHS(Ans.h.arcount))+' ');
    Write('Length: '+inttostr(AnsLen)+' ');
    WriteLn('AnsStart: '+inttostr(AnsStart));

    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- Answers');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;

    // dump any ns records
    // if there are authority RRs, we're already pointed at the
    // first one.
    MaxAnswer := NToHS(Ans.h.nscount);
    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- NS AUTHORITY Section');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;

    // dump any additional records
    // if there are additional RRs, we're already pointed at the
    // first one.
    MaxAnswer := NToHS(Ans.h.arcount);
    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- Additional section');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;
  end;
end;

{
This is the standard way to query dns using UDP. It will fall back to TCP if the response
contains a flag indicating that truncation occurred.

The handling of the response is the same no matter whether it's UDP or TCP. First,
you skip to the start of the response section using SkipAnsQueries. Then, you check each
of the three defined resource sections. The first section contains the immediate responses
to the query, then the NS section contains any name server authority information, and lastly
the additional section contains any additional resource records.

In all cases, the function GetRRrecords is used to retrieve a dynamic array containing the
resource records in a section. This dynamic array can be iterated using a for loop. E.g,

      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);

A full explanation of the different resource records that can be returned is beyond the scope
of this comment. See RFC 1035 for most of the relevant detail.
}
procedure QueryUDP(dn: AnsiString; qtype: Word);
var
  Ans: TQueryData;
  MaxAnswer, AnsLen: Longint;
  AnsStart: Word;
  r: TDNSRcode;
  RRN: TRRNameData;
  RRArr: TRRNameDataArray;
  qr: Boolean;

begin
  qr := DnsLookup(dn, qtype, Ans, AnsLen);
  if not qr then
  begin
    if AnsLen = -1 then
    begin
      writeln('No DNS server could be reached.');
      exit;
    end;
    r := GetRcode(Ans.h);
    case r of
      rcInvalid: writeln('Invalid response');
      rcNoError:
        begin
          writeln('Server returned no records.');
        end;
      rcRefused: writeln('Refused');
      rcNotImpl: writeln('Not implemented');
      rcNXDomain: writeln('NXDOMAIN');
      rcServFail: writeln('SERVFAIL');
      rcFormatError: writeln('Format error');
      rcReserved: writeln('Reserved code');
    end;
    exit;
  end
  else
  begin
    if IsTruncated(Ans.h) then
    begin
      writeln('Response truncated ... retrying as TCP');
      QueryTCP(dn, qtype);
      exit;
    end;
    //dump_payload(Ans.Payload, AnsLen);
    AnsStart:=SkipAnsQueries(Ans,AnsLen);
    MaxAnswer:=Ans.h.AnCount;

    Write('Answers: '+inttostr(Ans.h.ancount)+' ');
    Write('NS: '+inttostr(NToHS(Ans.h.nscount))+' ');
    Write('Additional: '+inttostr(NToHS(Ans.h.arcount))+' ');
    Write('Length: '+inttostr(AnsLen)+' ');
    WriteLn('AnsStart: '+inttostr(AnsStart));

    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- Answers');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;

    // dump any ns records
    // if there are authority RRs, we're already pointed at the
    // first one.
    MaxAnswer := NToHS(Ans.h.nscount);
    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- NS AUTHORITY Section');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;

    // dump any additional records
    // if there are additional RRs, we're already pointed at the
    // first one.
    MaxAnswer := NToHS(Ans.h.arcount);
    if MaxAnswer > 0 then
    begin
      writeln;
      writeln('-- Additional Section');
      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
      for RRN in RRArr do
        DumpNameRR(RRN, Ans.Payload);
    end;
  end;
end;

function QTypeToStr(qtype: Word): String;
begin
  Result := 'Unknown';
  case qtype of
    DNSQRY_A: Result := 'A';
    DNSQRY_AAAA: Result := 'AAAA';
    DNSQRY_NS: Result := 'NS';
    DNSQRY_SOA: Result := 'SOA';
    DNSQRY_MX: Result := 'MX';
    DNSQRY_CNAME: Result := 'CNAME';
    DNSQRY_PTR: Result := 'PTR';
    DNSQRY_TXT: Result := 'TXT';
  end;
end;

var
  S,S1: String;
  qtype: Word;
begin
  if ParamCount <> 2 then
  begin
    WriteLn(ParamStr(0)+' qtype domain');
    WriteLn('Query DNS records for domain <domain>. Parameter qtype is one of:');
    WriteLn(' - A (A host address)');
    WriteLn(' - MX (Mail Exchanger)');
    WriteLn(' - SOA (Start of Authority)');
    WriteLn(' - NS (Authoritative Name Server)');
    WriteLn(' - AAAA (IPv6 hostname)');
    WriteLn(' - CNAME (Canonical Name)');
    WriteLn(' - PTR (reverse domain lookup for ip using pseudo domain IN-ADDR.ARPA');
    WriteLn(' - TXT (Free-form text strings. May exceed UDP 512 byte limit.)');
    exit;
  end;

  S := ParamStr(2);
  S1 := LowerCase(ParamStr(1));
  case S1 of
    'ns': qtype := DNSQRY_NS;
    'mx': qtype := DNSQRY_MX;
    'soa': qtype := DNSQRY_SOA;
    'a': qtype := DNSQRY_A;
    'aaaa': qtype := DNSQRY_AAAA;
    'cname': qtype := DNSQRY_CNAME;
    'ptr': qtype := DNSQRY_PTR;
    'txt': qtype := DNSQRY_TXT;
  else
    qtype := DNSQRY_A;
  end;

  WriteLn(' - ' + S +' ('+QTypeToStr(qtype)+')');
  QueryUDP(S, qtype);
end.

dnsq.pp (13,150 bytes)   

Noel Duffy

2021-01-30 04:24

reporter   ~0128668

Adding proposed final patches for TCP DNS queries. There are three patches, one for the change to netdb, one for the example program, and one for the tests. This is to simplify the application process should there be any issue with one of the patches.

This version of netdb.pp includes numerous safeguards for stringfromlabel to prevent buffer overruns. It also includes support for SRV records, as specified in RFC 2782.
netdb-tests.patch (165,280 bytes)   
Index: packages/fcl-net/tests/netdbtest.pp
===================================================================
--- packages/fcl-net/tests/netdbtest.pp	(nonexistent)
+++ packages/fcl-net/tests/netdbtest.pp	(working copy)
@@ -0,0 +1,4615 @@
+unit netdbtest;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testregistry, Sockets, math, netdb;
+
+const
+  FAKETLD = 'doesnotexist';
+  FAKEDOMAIN = 'fakedomain';
+
+  FAKEFQDN=FAKEDOMAIN+'.'+FAKETLD;
+
+type
+  TDomainCompressionOffset = packed record
+    nm: String;
+    offset: Word;
+  end;
+  TDomainCompressionTable = Array of TDomainCompressionOffset;
+
+  TTwoByteArr = array[0 .. 1] of Byte;
+  TDNSDomainPointer = packed record
+    case b: boolean of
+      true: (ba: TTwoByteArr);
+      false: (b1,b2: Byte);
+  end;
+
+  TDNSDomainByteStream = packed record
+    ulabels: Array of byte;
+    cptr: Word;
+  end;
+
+  TBuffer = Array of Byte;
+
+  // can't use dynamic arrays in variant records, so fudge things by
+  // having between 1 and 5 subsstrings per text RR. it's good enough
+  // for these tests.
+  TTextArray = array [1 .. 5] of ShortString;
+
+  TFakeQuery = record
+    nm: ShortString;
+    qtype, qclass: Word;
+  end;
+
+  TFakeSOA = record
+    mn,rn: ShortString;
+    serial,refresh,retry,expire,min: Cardinal;
+  end;
+  TFakeMX = record
+    pref: Word;
+    exch: ShortString;
+  end;
+  TFakeSRV = record
+    priority, weight, port: Word;
+    target: ShortString;
+  end;
+
+  TFakeRR = record
+    RRName   : ShortString;
+    AClass   : Word;
+    TTL      : Cardinal;
+    RDLength : Word;
+    case Atype: Word of
+      DNSQRY_A: (ip: THostAddr);
+      DNSQRY_AAAA: (ip6: THostAddr6);
+      DNSQRY_CNAME: (cn: ShortString);
+      DNSQRY_MX: (fmx: TFakeMX);
+      DNSQRY_NS: (nsh: ShortString);
+      DNSQRY_PTR: (ptr: ShortString);
+      DNSQRY_SOA: (fsoa: TFakeSoa);
+      DNSQRY_TXT: (sstrcount: Byte; txtarr: TTextArray);
+      DNSQRY_SRV: (fsrv: TFakeSRV);
+  end;
+
+  TRRSection = Array of TFakeRR;
+
+  TFakeDNSResponse = record
+    strtable: TDomainCompressionTable;
+    compresslabels: Boolean;
+    hdr: TDNSHeader;
+    qry: TFakeQuery;
+    answers, authority, additional: TRRSection;
+  end;
+
+  TRDataWriteRes = packed record
+    bw, etw: Word;
+  end;
+
+  { TNetDbTest }
+
+  TNetDbTest= class(TTestCase)
+  strict private
+    tsl: TStringList;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+
+    procedure BuildFakeRR_A(out RR: TFakeRR; nm: String; ttl: Cardinal;
+      val: String);
+    procedure BuildFakeRR_AAAA(out RR: TFakeRR; nm: String; ttl: Cardinal;
+      val: String);
+    procedure BuildFakeRR_MX(out RR: TFakeRR; nm: String; ttl: Cardinal;
+      pref: Word; exch: ShortString );
+    procedure BuildFakeRR_NS(out RR: TFakeRR; nm: String; ttl: Cardinal;
+      val: String);
+    procedure BuildFakeRR_PTR(out RR: TFakeRR; nm: String; ttl: Cardinal;
+      val: String);
+    procedure BuildFakeRR_CNAME(out RR: TFakeRR; nm: String; ttl: Cardinal;
+      val: String);
+    procedure BuildFakeRR_SOA(out RR: TFakeRR; nm: String; ttl: Cardinal;
+      mn,rn: ShortString; serial,refresh,retry,expire,min: Cardinal);
+    procedure BuildFakeRR_TXT(out RR: TFakeRR; nm: String; ttl: Cardinal;
+      n: Byte; txt: TTextArray);
+    procedure BuildFakeRR_SRV(out RR: TFakeRR; nm: String; ttl: Cardinal;
+       priority, weight, port: Word; target: ShortString);
+
+    procedure CopyBytesTo(var buf: TPayLoad; startidx,destidx,count: Word);
+    procedure CopyBytesTo(var buf: TPayLoadTCP; startidx,destidx,count: Word);
+
+    function WriteNumToBuffer(var buf: TBuffer; var offset: Cardinal;
+       val: Word): Word;
+    function WriteNumToBuffer(var buf: TBuffer; var offset: Cardinal;
+       val: Cardinal): Word;
+
+    function WriteNumToBufferN(var buf: TBuffer; var offset: Cardinal;
+       val: Word): Word;
+    function WriteNumToBufferN(var buf: TBuffer; var offset: Cardinal;
+       val: Cardinal): Word;
+
+    function WriteMXAsRData(var buf: TBuffer; var offset: Cardinal;
+      fmx: TFakeMX): TRDataWriteRes;
+    function WriteSOAasRData(var buf: TBuffer; var offset: Cardinal;
+      fsoa: TFakeSOA): TRDataWriteRes;
+    function WriteSOAasRData(var buf: TBuffer; var offset: Cardinal;
+      fsoa: TFakeSOA; var ctbl: TDomainCompressionTable): TRDataWriteRes;
+    function WriteAAAAasRData(var buf: TBuffer; var offset: Cardinal;
+      ip6: THostAddr6): TRDataWriteRes;
+    function WriteAasRData(var buf: TBuffer; var offset: Cardinal;
+      ip: THostAddr): TRDataWriteRes;
+
+    function WriteSRVasRData(var buf: TBuffer; var offset: Cardinal;
+      fsrv: TFakeSRV): TRDataWriteRes;
+    function WriteSRVasRData(var buf: TBuffer; var offset: Cardinal;
+      fsrv: TFakeSRV; var ctbl: TDomainCompressionTable): TRDataWriteRes;
+
+    function WriteMXAsRData(var buf: TBuffer; var offset: Cardinal;
+      fmx: TFakeMX; var ctbl: TDomainCompressionTable): TRDataWriteRes;
+
+    function CalcRdLength(o: TDNSDomainByteStream): Word;
+    function CalcRdLength(o: TTextArray): Word;
+    function WriteTextRecAsRData(var buf: TBuffer; var offset: Cardinal;
+      tt: TTextArray): TRDataWriteRes;
+
+    function DomainNameToByteStream(nm: ShortString;
+      var ctbl: TDomainCompressionTable): TDNSDomainByteStream;
+    function DomainNameToByteStream(nm: ShortString): TDNSDomainByteStream;
+
+    function WriteDNSDomainByteStreamToBuffer(var buf: TBuffer;
+      var offset: Cardinal; dbs: TDNSDomainByteStream): Word;
+
+    function WriteDomainAsRdata(var buf: TBuffer; var offset: Cardinal;
+      dbs: TDNSDomainByteStream): TRDataWriteRes;
+
+    function WriteRRToBuffer(var buf: TBuffer; var offset: Cardinal;
+      rr: TFakeRR): Word;
+    function WriteRRToBuffer(var buf: TBuffer; var offset: Cardinal;
+      rr: TFakeRR; var ctbl: TDomainCompressionTable): Word;
+    function FakeDNSResponseToByteBuffer(fdr: TFakeDNSResponse;
+      out buf: TBuffer; compress: Boolean = False): Cardinal;
+    function BufferToPayload(const buf: TBuffer; out pl: TPayload): Boolean;
+    function BufferToPayload(const buf: TBuffer; out pl: TPayLoadTCP): Boolean;
+
+    function BuildQueryData(fdr: TFakeDNSResponse; out qd: TQueryData;
+      out qlen: Word; Compress: Boolean = False): Boolean;
+
+    function BuildQueryData(fdr: TFakeDNSResponse;
+      out qd: TQueryDataLengthTCP; out qlen: Word;
+        Compress: Boolean = False): Boolean;
+
+    function BuildTruncatedQueryData(fdr: TFakeDNSResponse; out qd: TQueryData;
+      out qlen: Word; truncoffset: Word): Boolean;
+
+    procedure BuildFakeResponseA(nm: ShortString; out fr: TFakeDNSResponse);
+    procedure BuildFakeResponseAAAA(nm: ShortString; out fr: TFakeDNSResponse);
+    procedure BuildFakeResponseMX(nm: ShortString; out fr: TFakeDNSResponse);
+    procedure BuildFakeResponseSOA(nm: ShortString; out fr: TFakeDNSResponse);
+    procedure BuildFakeResponseCNAME(nm: ShortString; out fr: TFakeDNSResponse);
+    procedure BuildFakeResponseNS(nm: ShortString; out fr: TFakeDNSResponse);
+    procedure BuildFakeResponsePTR(nm: ShortString; out fr: TFakeDNSResponse);
+    procedure BuildFakeResponseTXT(nm: ShortString; out fr: TFakeDNSResponse);
+    procedure BuildFakeResponseSRV(nm: ShortString; out fr: TFakeDNSResponse);
+
+  published
+    procedure TestBuildPayloadSimple;
+    procedure TestBuildPayloadSimpleEmpty;
+    procedure TestBuildPayloadSimpleEndDot;
+    procedure TestBuildPayloadSimpleStartDot;
+    procedure TestBuildPayloadSimpleMultipleDot;
+
+    { * straightforward tests for the api with valid data. Have to test each
+      * known RR type with both TCP and UDP buffer functions, and with and
+      * without compression of domain names.
+      * No network calls will be made. These tests hit all functions for
+      * processing dns requests except network functions.}
+    procedure TestDnsQueryUDP_A;
+    procedure TestDnsQueryTCP_A;
+    procedure TestDnsQueryCompressUDP_A;
+    procedure TestDnsQueryCompressTCP_A;
+
+    procedure TestDnsQueryUDP_AAAA;
+    procedure TestDnsQueryTCP_AAAA;
+    procedure TestDnsQueryCompressUDP_AAAA;
+    procedure TestDnsQueryCompressTCP_AAAA;
+
+    procedure TestDnsQueryUDP_MX;
+    procedure TestDnsQueryTCP_MX;
+    procedure TestDnsQueryCompressUDP_MX;
+    procedure TestDnsQueryCompressTCP_MX;
+
+    procedure TestDnsQueryUDP_SOA;
+    procedure TestDnsQueryTCP_SOA;
+    procedure TestDnsQueryCompressUDP_SOA;
+    procedure TestDnsQueryCompressTCP_SOA;
+
+    procedure TestDnsQueryUDP_CNAME;
+    procedure TestDnsQueryTCP_CNAME;
+    procedure TestDnsQueryCompressUDP_CNAME;
+    procedure TestDnsQueryCompressTCP_CNAME;
+
+    procedure TestDnsQueryUDP_NS;
+    procedure TestDnsQueryTCP_NS;
+    procedure TestDnsQueryCompressUDP_NS;
+    procedure TestDnsQueryCompressTCP_NS;
+
+    procedure TestDnsQueryUDP_PTR;
+    procedure TestDnsQueryTCP_PTR;
+    procedure TestDnsQueryCompressUDP_PTR;
+    procedure TestDnsQueryCompressTCP_PTR;
+
+    procedure TestDnsQueryUDP_TXT;
+    procedure TestDnsQueryTCP_TXT;
+    procedure TestDnsQueryCompressUDP_TXT;
+    procedure TestDnsQueryCompressTCP_TXT;
+
+    procedure TestDnsQueryUDP_SRV;
+    procedure TestDnsQueryTCP_SRV;
+    procedure TestDnsQueryCompressUDP_SRV;
+    procedure TestDnsQueryCompressTCP_SRV;
+
+    {
+      * Tests with invalid input data. These attempt to simulate a hostile
+      * dns server returning deliberately invalid data in an attempt to
+      * cause a buffer overflow, memory corruption, or DDOS.
+    }
+
+    // buffer truncated so RRs have invalid types.
+    procedure TestDnsQueryTruncateRR_UDP_A;
+
+    {
+     * Tests of DNSRRGet* functions where RR is near the end of the buffer,
+     * testing both when the RR just fits, and when it doesn't.
+     }
+    procedure TestDnsRRBufferEdgeA;
+    procedure TestDnsRRBufferPastEdgeA;
+    procedure TestDnsRRBufferEdgeAAAA;
+    procedure TestDNsRRBufferPastEdgeAAAA;
+    procedure TestDnsRRBufferEdgeMX;
+    procedure TestDnsRRBufferPastEdgeMX;
+    procedure TestDnsRRBufferEdgeSOA;
+    procedure TestDnsRRBufferPastEdgeSOA;
+    procedure TestDnsRRBufferEdgeSRV;
+    procedure TestDnsRRBufferPastEdgeSRV;
+    procedure TestDnsRRBufferEdgeCNAME;
+    procedure TestDnsRRBufferPastEdgeCNAME;
+    procedure TestDnsRRBufferEdgeNS;
+    procedure TestDnsRRBufferPastEdgeNS;
+    procedure TestDnsRRBufferEdgePTR;
+    procedure TestDnsRRBufferPastEdgePTR;
+    procedure TestDnsRRBufferEdgeTXT;
+    procedure TestDnsRRBufferPastEdgeTXT;
+
+
+    {
+     * the TCP variants. identical code, but qd variable is a different type
+     * and so different paths get followed in netdb.
+    }
+    procedure TestDnsRRBufferEdgeTCPA;
+    procedure TestDnsRRBufferPastEdgeTCPA;
+    procedure TestDnsRRBufferEdgeTCPAAAA;
+    procedure TestDNsRRBufferPastEdgeTCPAAAA;
+    procedure TestDnsRRBufferEdgeTCPMX;
+    procedure TestDnsRRBufferPastEdgeTCPMX;
+    procedure TestDnsRRBufferEdgeTCPSOA;
+    procedure TestDnsRRBufferPastEdgeTCPSOA;
+    procedure TestDnsRRBufferEdgeTCPSRV;
+    procedure TestDnsRRBufferPastEdgeTCPSRV;
+    procedure TestDnsRRBufferEdgeTCPCNAME;
+    procedure TestDnsRRBufferPastEdgeTCPCNAME;
+    procedure TestDnsRRBufferEdgeTCPNS;
+    procedure TestDnsRRBufferPastEdgeTCPNS;
+    procedure TestDnsRRBufferEdgeTCPPTR;
+    procedure TestDnsRRBufferPastEdgeTCPPTR;
+    procedure TestDnsRRBufferEdgeTCPTXT;
+    procedure TestDnsRRBufferPastEdgeTCPTXT;
+
+    // Testing of NextNameRR at buffer edge and beyond. this differs from
+    // the above tests in that they tests DNSGet* at the edge, but NextNameRR
+    // is never called to read at the edge in those functions.
+    // Because NextNameRR does nothing that is specific to RR types it's
+    // not necessary to test with each type of RR.
+
+    procedure TestNextNameRREdgeA;
+    procedure TestNextNameRRPastEdgeA;
+    procedure TestNextNameRREdgeTCPA;
+    procedure TestNextNameRRPastEdgeTCPA;
+
+    {
+     * Test GetRRrecords at and beyond buffer boundaries.
+    }
+    procedure TestGetRRrecordsInvalidStart;
+    procedure TestGetRRrecordsInvalidStartTCP;
+
+    {
+    Tests for GetFixlenStr
+    }
+    procedure TestGetFixLenStrSimple;
+    procedure TestGetFixLenStrSimpleTCP;
+    procedure TestGetFixLenStrSimpleAtEdge;
+    procedure TestGetFixLenStrSimpleTCPAtEdge;
+    procedure TestGetFixLenStrSimplePastEdge;
+    procedure TestGetFixLenStrSimpleTCPPastEdge;
+
+
+    {
+     * Test stringfromlabel with buffer edges and beyond. Its behaviour
+     * at present is to drop any label that would exceed the buffer boundary
+     * but still return any other labels successfully received.
+
+     * Some of the previous tests already verify what happens with a label
+     * that occurs on the edge. See the tests for TestDnsRRBufferEdgeSRV
+     * and TestDnsRRBufferEdgeTCPSRV, TestDnsRRBufferEdgeCNAME, etc.
+    }
+
+    // read a label starting at the end of the buffer where the count is
+    // greater than 0.
+    procedure TestStringFromLabelCountAsLastByte;
+    procedure TestStringFromLabelCountAsLastByteTCP;
+
+    // compressed label
+    procedure TestStringFromLabelCompress;
+    procedure TestStringFromLabelCompressTCP;
+    // another compressed label test, this time with one uncompressed label
+    procedure TestStringFromLabelCompressWithUncompressedLabel;
+    // as above, but on the tcp payload buffer
+    procedure TestStringFromLabelCompressWithUncompressedLabelTCP;
+    // compressed label at the edge of the buffer
+    procedure TestStringFromLabelCompressEndBuffer;
+    // compressed label at the edge of the tcp buffer
+    procedure TestStringFromLabelCompressEndBufferTCP;
+    // test stringfromlabel when last byte is 192. 192 is the signal
+    // that the next byte is a pointer offset, but of course there's
+    // no next byte.
+    procedure TestStringFromLabelCompressSplit;
+    // repeat using TCP buffer variant
+    procedure TestStringFromLabelCompressSplitTCP;
+    // test that stringfromlabel rejects pointers that go forward. per
+    // rfc 1035, pointers must go backward.
+    procedure TestStringFromLabelCompressPtrFwd;
+    procedure TestStringFromLabelCompressPtrFwdTCP;
+    // fill buffer with 192, pointer marker, then try stringfromlabel on it.
+    procedure TestStringFromLabelCompressAllPtrStart;
+    procedure TestStringFromLabelCompressAllPtrStartTCP;
+
+    // test string from label where second byte is 0.
+    procedure TestStringFromLabelCompressedZero;
+    procedure TestStringFromLabelCompressedZeroTCP;
+
+    // test whether an infinite loop can be triggered.
+    procedure TestStringFromLabelInfiniteLoop;
+    procedure TestStringFromLabelInfiniteLoopTCP;
+
+    // test short domain less than 12 chars. this tests that dns pointer
+    // calculations in stringfromlabel are correct
+    procedure TestCompressShortDomain;
+    procedure TestCompressShortDomainTCP;
+  end;
+
+implementation
+
+procedure dump_payload(const pl: TBuffer);
+var
+  idx,llen: Cardinal;
+begin
+  idx := 0;
+  llen := 0;
+  for idx := 0 to Length(pl) - 1 do
+  begin
+    write('['+inttostr(idx)+'] '+IntToHex(pl[idx],2));
+    if (pl[idx] > 48) and (pl[idx] < 123) then
+      write(' ' + chr(pl[idx]))
+    else
+      write(' .');
+    write(' ');
+    Inc(llen);
+    if llen >= 6 then
+    begin
+      llen := 0;
+      writeln();
+    end;
+  end;
+  if llen > 0 then
+  begin
+    writeln();
+  end;
+end;
+
+procedure dump_payload(const pl: TPayload; count: Word);
+var
+  idx,llen: Cardinal;
+begin
+  idx := 0;
+  llen := 0;
+  for idx := 0 to count - 1 do
+  begin
+    write('['+inttostr(idx)+'] '+IntToHex(pl[idx],2));
+    if (pl[idx] > 48) and (pl[idx] < 123) then
+      write(' ' + chr(pl[idx]))
+    else
+      write(' .');
+    write(' ');
+    Inc(llen);
+    if llen >= 6 then
+    begin
+      llen := 0;
+      writeln();
+    end;
+  end;
+  if llen > 0 then
+  begin
+    writeln();
+  end;
+end;
+
+function LookupStr(ls: String; stt: TDomainCompressionTable; out idx: Word): Boolean;
+var
+  so: TDomainCompressionOffset;
+begin
+  Result := False;
+  for so in stt do
+  begin
+    if ls = so.nm then
+    begin
+      Result := True;
+      idx := so.offset;
+      exit;
+    end;
+  end;
+end;
+
+function AddStr(ls: String; var stt: TDomainCompressionTable; idx: Word): Boolean;
+var
+  so: TDomainCompressionOffset;
+begin
+  so.nm := ls;
+  so.offset := idx;
+  SetLength(stt, Length(stt)+1);
+  stt[Length(stt)-1] := so;
+  Result := True;
+end;
+
+function GetDnsDomainPointer(offset: Word): TDNSDomainPointer;
+begin
+  Result.b1 := 0;
+  Result.b2 := 0;
+  // dns comp. ptr can't be > 2 ** 14 or 16383
+  if offset > 16383 then exit;
+  Result.b1 := (offset SHR 8) OR 192;
+  Result.b2 := (offset AND $00FF);
+end;
+
+procedure DomainNameToLabels(const dmn: String; var labels: TStringList);
+begin
+  labels.Clear;
+  labels.Delimiter := '.';
+  labels.StrictDelimiter := True;
+  labels.DelimitedText := dmn;
+end;
+
+procedure TNetDbTest.BuildFakeRR_A(out RR: TFakeRR; nm: String; ttl: Cardinal;
+  val: String);
+begin
+  RR.RRName := nm;
+  RR.Atype := DNSQRY_A;
+  RR.AClass := 1;
+  RR.TTL := ttl;
+  RR.ip := StrToNetAddr(val);
+  RR.RDLength := 4;
+end;
+
+procedure TNetDbTest.BuildFakeRR_AAAA(out RR: TFakeRR; nm: String;
+  ttl: Cardinal; val: String);
+begin
+  RR.RRName := nm;
+  RR.Atype := DNSQRY_AAAA;
+  RR.AClass := 1;
+  RR.TTL := ttl;
+  RR.ip6 := StrToNetAddr6(val);
+  RR.RDLength := 16;
+end;
+
+procedure TNetDbTest.BuildFakeRR_MX(out RR: TFakeRR; nm: String; ttl: Cardinal;
+  pref: Word; exch: ShortString );
+begin
+  RR.RRName := nm;
+  RR.Atype := DNSQRY_MX;
+  RR.AClass := 1;
+  RR.TTL := ttl;
+  RR.fmx.pref := pref;
+  RR.fmx.exch := exch;
+end;
+
+procedure TNetDbTest.BuildFakeRR_NS(out RR: TFakeRR; nm: String; ttl: Cardinal;
+  val: String);
+begin
+  RR.RRName := nm;
+  RR.Atype := DNSQRY_NS;
+  RR.AClass := 1;
+  RR.TTL := ttl;
+  RR.nsh := val;
+end;
+
+procedure TNetDbTest.BuildFakeRR_PTR(out RR: TFakeRR; nm: String; ttl: Cardinal;
+  val: String);
+begin
+  RR.RRName := nm;
+  RR.Atype := DNSQRY_PTR;
+  RR.AClass := 1;
+  RR.TTL := ttl;
+  RR.ptr := val;
+end;
+
+procedure TNetDbTest.BuildFakeRR_CNAME(out RR: TFakeRR; nm: String;
+  ttl: Cardinal; val: String);
+begin
+  RR.RRName := nm;
+  RR.Atype := DNSQRY_CNAME;
+  RR.AClass := 1;
+  RR.TTL := ttl;
+  RR.cn := val;
+end;
+
+procedure TNetDbTest.BuildFakeRR_SOA(out RR: TFakeRR; nm: String; ttl: Cardinal;
+  mn,rn: ShortString; serial,refresh,retry,expire,min: Cardinal);
+begin
+  RR.RRName := nm;
+  RR.Atype := DNSQRY_SOA;
+  RR.AClass := 1;
+  RR.TTL := ttl;
+  RR.fsoa.mn := mn;
+  RR.fsoa.rn := rn;
+  RR.fsoa.serial := serial;
+  RR.fsoa.refresh := refresh;
+  RR.fsoa.retry := retry;
+  RR.fsoa.expire := expire;
+  RR.fsoa.min := min;
+end;
+
+procedure TNetDbTest.BuildFakeRR_TXT(out RR: TFakeRR; nm: String; ttl: Cardinal;
+  n: Byte; txt: TTextArray);
+var
+  idx: Byte;
+begin
+  RR.RRName := nm;
+  RR.Atype := DNSQRY_TXT;
+  RR.AClass := 1;
+  RR.TTL := ttl;
+  RR.sstrcount := n;
+  RR.txtarr[1] := '';
+  RR.txtarr[2] := '';
+  RR.txtarr[3] := '';
+  RR.txtarr[4] := '';
+  RR.txtarr[5] := '';
+  for idx := Low(txt) to Min(n, High(txt)) do
+    RR.txtarr[idx] := txt[idx];
+end;
+
+procedure TNetDbTest.BuildFakeRR_SRV(out RR: TFakeRR; nm: String; ttl: Cardinal;
+   priority, weight, port: Word; target: ShortString);
+begin
+  RR.RRName := nm;
+  RR.Atype := DNSQRY_SRV;
+  RR.AClass := 1;
+  RR.TTL := ttl;
+  RR.fsrv.priority := priority;
+  RR.fsrv.weight := weight;
+  RR.fsrv.port := port;
+  RR.fsrv.target := target;
+end;
+
+function TNetDbTest.CalcRdLength(o: TTextArray): Word;
+var
+  tmps: ShortString;
+begin
+  Result := 0;
+  for tmps in o do
+  begin
+    if tmps = '' then break;
+    Result := Result + Length(tmps)+1; // don't forget length byte!
+  end;
+end;
+
+function TNetDbTest.WriteAasRData(var buf: TBuffer; var offset: Cardinal;
+  ip: THostAddr): TRDataWriteRes;
+var
+  s,l: Word;
+begin
+  s := offset;
+  l := SizeOf(ip.s_addr);
+  Result.etw := l + 2; //rdlength +2 for length itself
+  // rdlength
+  WriteNumToBuffer(buf, offset, l);
+  // rr data
+  WriteNumToBufferN(buf, offset, ip.s_addr);
+  Result.bw := offset - s;
+end;
+
+function TNetDbTest.WriteSRVasRData(var buf: TBuffer; var offset: Cardinal;
+  fsrv: TFakeSRV): TRDataWriteRes;
+var
+  s, l: Word;
+  dmbs: TDNSDomainByteStream;
+begin
+  s := offset;
+  dmbs := DomainNameToByteStream(fsrv.target);
+  l := CalcRdLength(dmbs) + SizeOf(Word) * 3;
+  Result.etw := l + 2; //rdlength +2 for length byte
+
+  // rdlength
+  WriteNumToBuffer(buf, offset, l);
+
+  // RR data
+  WriteNumToBuffer(buf, offset, fsrv.priority);
+  WriteNumToBuffer(buf, offset, fsrv.weight);
+  WriteNumToBuffer(buf, offset, fsrv.port);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  Result.bw := offset - s;
+end;
+
+function TNetDbTest.WriteSRVasRData(var buf: TBuffer; var offset: Cardinal;
+  fsrv: TFakeSRV; var ctbl: TDomainCompressionTable): TRDataWriteRes;
+var
+  s, l: Word;
+  dmbs: TDNSDomainByteStream;
+begin
+  s := offset;
+  dmbs := DomainNameToByteStream(fsrv.target, ctbl);
+  l := CalcRdLength(dmbs) + SizeOf(Word) * 3;
+  Result.etw := l + 2; //rdlength +2 for length byte
+
+  // rdlength
+  WriteNumToBuffer(buf, offset, l);
+
+  // RR data
+  WriteNumToBuffer(buf, offset, fsrv.priority);
+  WriteNumToBuffer(buf, offset, fsrv.weight);
+  WriteNumToBuffer(buf, offset, fsrv.port);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  Result.bw := offset - s;
+end;
+
+function TNetDbTest.WriteMXAsRData(var buf: TBuffer; var offset: Cardinal;
+  fmx: TFakeMX; var ctbl: TDomainCompressionTable): TRDataWriteRes;
+var
+  s, l: Word;
+  dmbs: TDNSDomainByteStream;
+begin
+  s := offset;
+  dmbs := DomainNameToByteStream(fmx.exch, ctbl);
+  l := SizeOf(fmx.pref) + CalcRdLength(dmbs);
+  Result.etw := l + 2; // we'll write rdlength bytes+2 bytes for length itself.
+
+  // rdlength
+  WriteNumToBuffer(buf, offset, l);
+
+  // RR data
+  // pref
+  WriteNumToBuffer(buf, offset, fmx.pref);
+  // exchange
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  Result.bw := offset - s;
+end;
+
+function TNetDbTest.CalcRdLength(o: TDNSDomainByteStream): Word;
+begin
+  Result := Length(o.ulabels);
+  if o.cptr > 0 then Inc(Result,2);
+end;
+
+function TNetDbTest.WriteAAAAasRData(var buf: TBuffer; var offset: Cardinal;
+  ip6: THostAddr6): TRDataWriteRes;
+var
+  s,l: Word;
+begin
+  s := offset;
+  l := SizeOf(ip6.u6_addr32);
+  Result.etw := l + 2; //rdlength + 2 for length itself
+  // rdlength
+  WriteNumToBuffer(buf, offset, l);
+  // rr data
+  Move(ip6.s6_addr, buf[offset], l);
+  Inc(offset, l);
+  Result.bw := offset - s;
+end;
+
+function TNetDbTest.WriteSOAasRData(var buf: TBuffer; var offset: Cardinal;
+  fsoa: TFakeSOA): TRDataWriteRes;
+var
+  s, l: Word;
+  dmbsmn, dmbsrn: TDNSDomainByteStream;
+begin
+  s := offset;
+  dmbsmn := DomainNameToByteStream(fsoa.mn);
+  dmbsrn := DomainNameToByteStream(fsoa.rn);
+  l := CalcRdLength(dmbsmn) + CalcRdLength(dmbsrn) + (SizeOf(Cardinal) * 5);
+
+  Result.etw := l + 2; // rdlength bytes + 2 for length itself
+
+  // rdlength
+  WriteNumToBuffer(buf, offset, l);
+
+  // rr data
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbsmn);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbsrn);
+
+  WriteNumToBuffer(buf, offset, fsoa.serial);
+  WriteNumToBuffer(buf, offset, fsoa.refresh);
+  WriteNumToBuffer(buf, offset, fsoa.retry);
+  WriteNumToBuffer(buf, offset, fsoa.expire);
+  WriteNumToBuffer(buf, offset, fsoa.min);
+
+  Result.bw := offset - s;
+end;
+
+function TNetDbTest.WriteSOAasRData(var buf: TBuffer; var offset: Cardinal;
+  fsoa: TFakeSOA; var ctbl: TDomainCompressionTable): TRDataWriteRes;
+var
+  s, l: Word;
+  dmbsmn, dmbsrn: TDNSDomainByteStream;
+begin
+  s := offset;
+  dmbsmn := DomainNameToByteStream(fsoa.mn, ctbl);
+  dmbsrn := DomainNameToByteStream(fsoa.rn, ctbl);
+  l := CalcRdLength(dmbsmn) + CalcRdLength(dmbsrn) + (SizeOf(Cardinal) * 5);
+  Result.etw := l + 2; // rdlength bytes + 2 for length itself
+
+  // rdlength
+  WriteNumToBuffer(buf, offset, l);
+
+  // rr data
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbsmn);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbsrn);
+
+  WriteNumToBuffer(buf, offset, fsoa.serial);
+  WriteNumToBuffer(buf, offset, fsoa.refresh);
+  WriteNumToBuffer(buf, offset, fsoa.retry);
+  WriteNumToBuffer(buf, offset, fsoa.expire);
+  WriteNumToBuffer(buf, offset, fsoa.min);
+
+  Result.bw := offset - s;
+end;
+
+function TNetDbTest.WriteMXAsRData(var buf: TBuffer; var offset: Cardinal;
+  fmx: TFakeMX): TRDataWriteRes;
+var
+  s, l: Word;
+  dmbs: TDNSDomainByteStream;
+begin
+  Result.bw := 0;
+  s := offset;
+  dmbs := DomainNameToByteStream(fmx.exch);
+  l := SizeOf(fmx.pref) + CalcRdLength(dmbs);
+  Result.etw := l + 2; // we'll write rdlength + 2 bytes for the length itself.
+
+  // rdlength
+  WriteNumToBuffer(buf, offset, l);
+
+  // RR data
+  // pref
+  WriteNumToBuffer(buf, offset, fmx.pref);
+  // exchange
+  dmbs := DomainNameToByteStream(fmx.exch);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  Result.bw := offset - s;
+end;
+
+function TNetDbTest.WriteTextRecAsRData(var buf: TBuffer; var offset: Cardinal;
+  tt: TTextArray): TRDataWriteRes;
+var
+  s, l: Word;
+  ws: ShortString;
+begin
+  s := offset;
+  l := CalcRdLength(tt);
+  Result.etw := l + 2; // rdlength +2 for length itself
+  // rdlength
+  WriteNumToBuffer(buf, offset, l);
+
+  for ws in tt do
+  begin
+    if  ws = '' then break;
+    Move(ws, buf[offset], Length(ws)+1);
+    Inc(offset,Length(ws)+1);
+  end;
+
+  Result.bw := offset - s;
+end;
+
+{
+Convert a domain name into a byte stream. Compression is supported using the
+supplied compression table.
+}
+function TNetDbTest.DomainNameToByteStream(nm: ShortString;
+  var ctbl: TDomainCompressionTable): TDNSDomainByteStream;
+var
+  dmn: ShortString;
+  offset,cmpoffset: Word;
+  ptrseen: Boolean = False;
+begin
+  SetLength(Result.ulabels, 0);
+  Result.cptr := 0;
+  offset := 0;
+
+  if nm = '' then exit;
+  DomainNameToLabels(nm, tsl);
+  if tsl.Count = 0 then exit;
+
+  dmn := '';
+  cmpoffset := 0;
+
+  {
+  for a domain a.b.c, using the lookup table,
+   -> lookup (a.b.c), if not found, add to table,
+   ->   lookup (b.c), if not found, add to table,
+   ->   lookup   (c), if not found, add to table,
+
+   buf if any label domain is found, add the pointer to the buffer and stop.
+  }
+  repeat
+    dmn := tsl.DelimitedText;
+    ptrseen := LookupStr(dmn, ctbl, cmpoffset);
+    if ptrseen then
+    begin
+      // found the domain name. add a pointer, then we're done. Per RFC1035,
+      // section 4.1.4, a domain name is either a series of labels, a pointer,
+      // or a series of labels ending with a pointer. There's just one pointer
+      // for a domain name.
+      Result.cptr := cmpoffset;
+      break;
+    end
+    else
+    begin
+      // add the last full domain we looked up, not the working label,
+      // to the compression lookup table. E.g, add a.b.c rather than a.
+      // Add 12 for the dns header, which our buffer doesn't include, but
+      // api methods like stringfromlabel adjust offsets to account for it.
+      if Length(dmn) > 0 then AddStr(dmn, ctbl, offset+12);
+      // write the label to the buffer
+      dmn := tsl[0];
+      tsl.Delete(0);
+      SetLength(Result.ulabels, (Length(Result.ulabels) + Length(dmn)+1));
+      Result.ulabels[offset] := Length(dmn);
+      Inc(offset);
+      Move(dmn[1], Result.ulabels[offset], Length(dmn));
+      Inc(offset, Length(dmn));
+    end;
+  until tsl.Count = 0;
+
+  // if we didn't see a pointer then we have to write a 0. see rfc1035, s4.1.4.
+  if not ptrseen then
+  begin
+    SetLength(Result.ulabels, Length(Result.ulabels) + 1);
+    Result.ulabels[offset] := 0;
+    Inc(offset);
+  end;
+end;
+
+{
+This version of DomainNameToByteStream doesn't compress.
+}
+function TNetDbTest.DomainNameToByteStream(nm: ShortString
+  ): TDNSDomainByteStream;
+var
+  dmn: ShortString;
+  offset: Word;
+begin
+  SetLength(Result.ulabels, 0);
+  Result.cptr := 0;
+  offset := 0;
+
+  if nm = '' then exit;
+  DomainNameToLabels(nm, tsl);
+  if tsl.Count = 0 then exit;
+
+  for dmn in tsl do
+  begin
+    SetLength(Result.ulabels, (Length(Result.ulabels) + Length(dmn)+1));
+    Result.ulabels[offset] := Length(dmn);
+    Inc(offset);
+    Move(dmn[1], Result.ulabels[offset], Length(dmn));
+    Inc(offset, Length(dmn));
+  end;
+
+  SetLength(Result.ulabels, Length(Result.ulabels) + 1);
+  Result.ulabels[offset] := 0;
+end;
+
+function TNetDbTest.WriteDNSDomainByteStreamToBuffer(var buf: TBuffer;
+  var offset: Cardinal; dbs: TDNSDomainByteStream): Word;
+var
+  p: TDNSDomainPointer;
+  so: Word;
+begin
+  Result := 0;
+  // no label, no pointer, no write for you.
+  if (Length(dbs.ulabels) = 0) and (dbs.cptr = 0) then exit;
+  if (offset + CalcRdLength(dbs)) > Length(buf) then exit;
+
+  so := offset;
+  // labels can be empty, in which case we're writing just a pointer.
+  if Length(dbs.ulabels) > 0 then
+  begin
+    Move(dbs.ulabels[0], buf[offset], Length(dbs.ulabels));
+    Inc(offset, Length(dbs.ulabels));
+  end;
+  if dbs.cptr > 0 then
+  begin
+    p := GetDnsDomainPointer(dbs.cptr);
+    Move(p.ba, buf[offset], Length(p.ba));
+    Inc(offset, Min(Length(p.ba), (Length(buf) - offset)));
+  end;
+  Result := offset - so;
+end;
+
+{
+Write a domain name as RDATA. This means an RDLength (Word) and the
+domain labels.
+}
+function TNetDbTest.WriteDomainAsRdata(var buf: TBuffer; var offset: Cardinal;
+  dbs: TDNSDomainByteStream): TRDataWriteRes;
+var
+  s,l: Word;
+begin
+  l := CalcRdLength(dbs);
+  Result.etw := l + 2;
+  s := offset;
+  WriteNumToBuffer(buf, offset,l);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dbs);
+  Result.bw := offset - s;
+end;
+
+
+procedure TNetDbTest.BuildFakeResponseA(nm: ShortString; out
+  fr: TFakeDNSResponse);
+begin
+  // metadata
+  SetLength(fr.strtable, 0);
+
+  // start by building a fake header.
+  fr.hdr.ID[0] := 12;
+  fr.hdr.ID[1] := 34;
+  fr.hdr.flags1 := QF_QR or QF_RD;
+  fr.hdr.flags2 := 0;
+  fr.hdr.qdcount := 1;
+  fr.hdr.ancount := 2;
+  fr.hdr.nscount := 0;
+  fr.hdr.arcount := 0;
+
+  // Next is the query part
+  fr.qry.nm := nm;
+  fr.qry.qclass := 1;
+  fr.qry.qtype := DNSQRY_A;
+
+  // now the answer RRs
+  SetLength(fr.answers,2);
+  BuildFakeRR_A(fr.answers[0], nm, 300, '127.0.0.1');
+  BuildFakeRR_A(fr.answers[1], nm, 215, '127.0.5.1');
+end;
+
+procedure TNetDbTest.BuildFakeResponseAAAA(nm: ShortString; out
+  fr: TFakeDNSResponse);
+begin
+  // metadata
+  SetLength(fr.strtable, 0);
+
+  // start by building a fake header.
+  fr.hdr.ID[0] := 12;
+  fr.hdr.ID[1] := 34;
+  fr.hdr.flags1 := QF_QR or QF_RD;
+  fr.hdr.flags2 := 0;
+  fr.hdr.qdcount := 1;
+  fr.hdr.ancount := 2;
+  fr.hdr.nscount := 0;
+  fr.hdr.arcount := 0;
+
+  // Next is the query part
+  fr.qry.nm := nm;
+  fr.qry.qclass := 1;
+  fr.qry.qtype := DNSQRY_AAAA;
+
+  // now the answer RRs
+  SetLength(fr.answers,2);
+  BuildFakeRR_AAAA(fr.answers[0], nm, 300, 'fe80::3b92:3429:ff16:a3e4');
+  BuildFakeRR_AAAA(fr.answers[1], nm, 215, 'fe80::92e6:baff:fe44:ffbb');
+end;
+
+procedure TNetDbTest.BuildFakeResponseMX(nm: ShortString; out
+  fr: TFakeDNSResponse);
+begin
+  // metadata
+  SetLength(fr.strtable, 0);
+
+  // start by building a fake header.
+  fr.hdr.ID[0] := 12;
+  fr.hdr.ID[1] := 34;
+  fr.hdr.flags1 := QF_QR or QF_RD;
+  fr.hdr.flags2 := 0;
+  fr.hdr.qdcount := 1;
+  fr.hdr.ancount := 1;
+  fr.hdr.nscount := 0;
+  fr.hdr.arcount := 2;
+
+  // Next is the query part
+  fr.qry.nm := nm;
+  fr.qry.qclass := 1;
+  fr.qry.qtype := DNSQRY_MX;
+
+  // now the answer RRs
+  SetLength(fr.answers,1);
+  BuildFakeRR_MX(fr.answers[0], nm, 0, 10, 'mailer.'+FAKEFQDN);
+  // now an additional rr with the A record for the above.
+  SetLength(fr.additional, 2);
+  BuildFakeRR_A(fr.additional[0], 'mailer.'+FAKEFQDN, 0,
+    '172.16.27.238');
+  BuildFakeRR_AAAA(fr.additional[1], 'mailer.'+FAKEFQDN, 0,
+    'fe80::3b92:3429:ff16:a3e4');
+end;
+
+procedure TNetDbTest.BuildFakeResponseSOA(nm: ShortString; out
+  fr: TFakeDNSResponse);
+begin
+  // metadata
+  SetLength(fr.strtable, 0);
+
+  // start by building a fake header.
+  fr.hdr.ID[0] := 12;
+  fr.hdr.ID[1] := 34;
+  fr.hdr.flags1 := QF_QR or QF_RD;
+  fr.hdr.flags2 := 0;
+  fr.hdr.qdcount := 1;
+  fr.hdr.ancount := 1;
+  fr.hdr.nscount := 0;
+  fr.hdr.arcount := 0;
+
+  // Next is the query part
+  fr.qry.nm := nm;
+  fr.qry.qclass := 1;
+  fr.qry.qtype := DNSQRY_SOA;
+  // now the answer RRs
+  SetLength(fr.answers,1);
+  BuildFakeRR_SOA(fr.answers[0],FAKEFQDN,33,
+    'mn.'+FAKEFQDN,'rn.'+FAKEFQDN,76543210,
+      123,456,789,60);
+end;
+
+procedure TNetDbTest.BuildFakeResponseCNAME(nm: ShortString; out
+  fr: TFakeDNSResponse);
+begin
+  // metadata
+  SetLength(fr.strtable, 0);
+
+  // start by building a fake header.
+  fr.hdr.ID[0] := 12;
+  fr.hdr.ID[1] := 34;
+  fr.hdr.flags1 := QF_QR or QF_RD;
+  fr.hdr.flags2 := 0;
+  fr.hdr.qdcount := 1;
+  fr.hdr.ancount := 1;
+  fr.hdr.nscount := 0;
+  fr.hdr.arcount := 0;
+
+  // Next is the query part
+  fr.qry.nm := nm;
+  fr.qry.qclass := 1;
+  fr.qry.qtype := DNSQRY_CNAME;
+
+  // now the answer RRs
+  SetLength(fr.answers,1);
+  BuildFakeRR_CNAME(fr.answers[0], nm, 300, 'fakecname.'+FAKEFQDN);
+end;
+
+procedure TNetDbTest.BuildFakeResponseNS(nm: ShortString; out
+  fr: TFakeDNSResponse);
+begin
+  // metadata
+  SetLength(fr.strtable, 0);
+
+  // start by building a fake header.
+  fr.hdr.ID[0] := 12;
+  fr.hdr.ID[1] := 34;
+  fr.hdr.flags1 := QF_QR or QF_RD;
+  fr.hdr.flags2 := 0;
+  fr.hdr.qdcount := 1;
+  fr.hdr.ancount := 1;
+  fr.hdr.nscount := 0;
+  fr.hdr.arcount := 0;
+
+  // Next is the query part
+  fr.qry.nm := nm;
+  fr.qry.qclass := 1;
+  fr.qry.qtype := DNSQRY_NS;
+
+  // now the answer RRs
+  SetLength(fr.answers,1);
+  BuildFakeRR_NS(fr.answers[0], nm, 300, 'fakens.'+FAKEFQDN);
+end;
+
+procedure TNetDbTest.BuildFakeResponsePTR(nm: ShortString; out
+  fr: TFakeDNSResponse);
+begin
+  // metadata
+  SetLength(fr.strtable, 0);
+
+  // start by building a fake header.
+  fr.hdr.ID[0] := 12;
+  fr.hdr.ID[1] := 34;
+  fr.hdr.flags1 := QF_QR or QF_RD;
+  fr.hdr.flags2 := 0;
+  fr.hdr.qdcount := 1;
+  fr.hdr.ancount := 1;
+  fr.hdr.nscount := 0;
+  fr.hdr.arcount := 0;
+
+  // Next is the query part
+  fr.qry.nm := nm;
+  fr.qry.qclass := 1;
+  fr.qry.qtype := DNSQRY_PTR;
+
+  // now the answer RRs
+  SetLength(fr.answers,1);
+  BuildFakeRR_PTR(fr.answers[0], nm, 300, 'fakeptrans.'+FAKEFQDN);
+end;
+
+procedure TNetDbTest.BuildFakeResponseTXT(nm: ShortString; out
+  fr: TFakeDNSResponse);
+var
+  txtarr: TTextArray;
+begin
+  // metadata
+  SetLength(fr.strtable, 0);
+
+  // start by building a fake header.
+  fr.hdr.ID[0] := 12;
+  fr.hdr.ID[1] := 34;
+  fr.hdr.flags1 := QF_QR or QF_RD;
+  fr.hdr.flags2 := 0;
+  fr.hdr.qdcount := 1;
+  fr.hdr.ancount := 1;
+  fr.hdr.nscount := 0;
+  fr.hdr.arcount := 0;
+
+  // Next is the query part
+  fr.qry.nm := nm;
+  fr.qry.qclass := 1;
+  fr.qry.qtype := DNSQRY_TXT;
+
+  txtarr[1] := 'v=spf1 mx a:lists.'+FAKEFQDN;
+  txtarr[2] := 'Always look on the bright side of life!';
+  // now the answer RRs
+  SetLength(fr.answers,1);
+  BuildFakeRR_TXT(fr.answers[0], nm, 300, 2, txtarr);
+end;
+
+procedure TNetDbTest.BuildFakeResponseSRV(nm: ShortString; out
+  fr: TFakeDNSResponse);
+begin
+  // metadata
+  SetLength(fr.strtable, 0);
+
+  // start by building a fake header.
+  fr.hdr.ID[0] := 12;
+  fr.hdr.ID[1] := 34;
+  fr.hdr.flags1 := QF_QR or QF_RD;
+  fr.hdr.flags2 := 0;
+  fr.hdr.qdcount := 1;
+  fr.hdr.ancount := 1;
+  fr.hdr.nscount := 0;
+  fr.hdr.arcount := 0;
+
+  // Next is the query part
+  fr.qry.nm := nm;
+  fr.qry.qclass := 1;
+  fr.qry.qtype := DNSQRY_SRV;
+  // now the answer RRs
+  SetLength(fr.answers,1);
+  BuildFakeRR_SRV(fr.answers[0],FAKEFQDN,3300,22,44,2201,'_this._that._other');
+end;
+
+{
+Test that BuildPayload puts the right values into the payload buffer.
+}
+procedure TNetDbTest.TestBuildPayloadSimple;
+var
+  Q: TQueryData;
+  R, I,J,el: Integer;
+  S: String;
+begin
+  R := BuildPayLoad(Q, FAKEFQDN, DNSQRY_A, 1);
+  // this is the expected length. Essentially, for each label, len(label)+1,
+  // then 4 bytes for the qclass and qtype, and 1 more for a 0 byte.
+  // rather than hardwire the length we calculate it so that no matter
+  // what the fake domain the test passes.
+  el := (Length(FAKEDOMAIN)+1)+(Length(FAKETLD)+1)+5;
+  AssertEquals('Payload byte count wrong:', el, R);
+  I := 0;
+  J := 0;
+  S := stringfromlabel(Q.Payload,I);
+  AssertEquals('Wrong domain name returned:',FAKEFQDN, S);
+  Move(Q.Payload[I],J,SizeOf(Word));
+  AssertEquals('Wrong query type', DNSQRY_A, NToHs(J));
+  Inc(I,2);
+  Move(Q.Payload[I],J,SizeOf(Word));
+  AssertEquals('Wrong class', 1, NToHs(J));
+end;
+
+{
+Test building a payload with an empty str.
+}
+procedure TNetDbTest.TestBuildPayloadSimpleEmpty;
+var
+  Q: TQueryData;
+  R: Integer;
+begin
+  R := BuildPayLoad(Q, '', DNSQRY_A, 1);
+  AssertEquals('Payload byte count wrong:',-1, R);
+end;
+
+{
+Test BuildQuery with a label that ends in a dot. This should be allowed.
+A dot at the end is an empty label but we must not count its 0 byte twice.
+}
+procedure TNetDbTest.TestBuildPayloadSimpleEndDot;
+var
+  Q: TQueryData;
+  R,el: Integer;
+begin
+  // this is the expected length. Essentially, for each label, len(label)+1,
+  // then 4 bytes for the qclass and qtype, and 1 more for a 0 byte.
+  // rather than hardwire the length we calculate it so that no matter
+  // what the fake domain the test passes.
+  el := (Length(FAKEDOMAIN)+1)+(Length(FAKETLD)+1)+5;
+  R := BuildPayLoad(Q, FAKEFQDN+'.', DNSQRY_A, 1);
+  AssertEquals('Payload byte count wrong:',el, R);
+end;
+
+{
+Test BuildPayload with a label that starts with a dot. This should be
+rejected outright.
+}
+procedure TNetDbTest.TestBuildPayloadSimpleStartDot;
+var
+  Q: TQueryData;
+  R: Integer;
+begin
+  R := BuildPayLoad(Q, '.'+FAKEFQDN, DNSQRY_A, 1);
+  AssertEquals('Payload byte count wrong:',-1, R);
+end;
+
+{
+Test BuildPayload with multiple dots (empty labels) in the middle of the domain
+name. This should be rejected outright.
+}
+procedure TNetDbTest.TestBuildPayloadSimpleMultipleDot;
+var
+  Q: TQueryData;
+  R: Integer;
+begin
+  R := BuildPayLoad(Q, FAKEDOMAIN+'.....'+FAKETLD, DNSQRY_A, 1);
+  AssertEquals('Payload byte count wrong:',-1, R);
+end;
+
+procedure TNetDbTest.TestDnsQueryUDP_A;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an A RR.',DNSQRY_A, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an A RR.', DNSQRY_A, RRarr[1].RRMeta.Atype);
+  AssertEquals('Wrong A record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertEquals('Wrong A record name for RR 1', FAKEFQDN,
+    RRArr[1].RRName);
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '127.0.0.1', HostAddrToStr(ip));
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[1], qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '127.0.5.1', HostAddrToStr(ip));
+end;
+
+procedure TNetDbTest.TestDnsQueryTCP_A;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an A RR.',DNSQRY_A, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an A RR.', DNSQRY_A, RRarr[1].RRMeta.Atype);
+  AssertEquals('Wrong A record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertEquals('Wrong A record name for RR 1', FAKEFQDN,
+    RRArr[1].RRName);
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '127.0.0.1', HostAddrToStr(ip));
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[1], qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '127.0.5.1', HostAddrToStr(ip));
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressUDP_A;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an A RR.',DNSQRY_A, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an A RR.', DNSQRY_A, RRarr[1].RRMeta.Atype);
+  AssertEquals('Wrong A record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertEquals('Wrong A record name for RR 1', FAKEFQDN,
+    RRArr[1].RRName);
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '127.0.0.1', HostAddrToStr(ip));
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[1], qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '127.0.5.1', HostAddrToStr(ip));
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressTCP_A;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an A RR.',DNSQRY_A, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an A RR.', DNSQRY_A, RRarr[1].RRMeta.Atype);
+  AssertEquals('Wrong A record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertEquals('Wrong A record name for RR 1', FAKEFQDN,
+    RRArr[1].RRName);
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '127.0.0.1', HostAddrToStr(ip));
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[1], qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '127.0.5.1', HostAddrToStr(ip));
+end;
+
+procedure TNetDbTest.TestDnsQueryUDP_AAAA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr6;
+begin
+  BuildFakeResponseAAAA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of AAAA records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an AAAA RR.',DNSQRY_AAAA, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an AAAA RR.', DNSQRY_AAAA, RRarr[1].RRMeta.Atype);
+  AssertEquals('Wrong AAAA record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertEquals('Wrong AAAA record name for RR 1', FAKEFQDN,
+    RRArr[1].RRName);
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[0], qd.Payload, ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::3B92:3429:FF16:A3E4', HostAddrToStr6(ip));
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[1], qd.Payload, ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::92E6:BAFF:FE44:FFBB', HostAddrToStr6(ip));
+end;
+
+procedure TNetDbTest.TestDnsQueryTCP_AAAA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr6;
+begin
+  BuildFakeResponseAAAA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of AAAA records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an AAAA RR.',DNSQRY_AAAA, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an AAAA RR.', DNSQRY_AAAA, RRarr[1].RRMeta.Atype);
+  AssertEquals('Wrong AAAA record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertEquals('Wrong AAAA record name for RR 1', FAKEFQDN,
+    RRArr[1].RRName);
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[0],
+    qd.Payload, ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::3B92:3429:FF16:A3E4',
+    HostAddrToStr6(ip));
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[1],
+    qd.Payload, ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::92E6:BAFF:FE44:FFBB',
+    HostAddrToStr6(ip));
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressUDP_AAAA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr6;
+begin
+  BuildFakeResponseAAAA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of AAAA records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an AAAA RR.',DNSQRY_AAAA, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an AAAA RR.', DNSQRY_AAAA, RRarr[1].RRMeta.Atype);
+  AssertEquals('Wrong AAAA record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertEquals('Wrong AAAA record name for RR 1', FAKEFQDN,
+    RRArr[1].RRName);
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[0],
+    qd.Payload, ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::3B92:3429:FF16:A3E4',
+    HostAddrToStr6(ip));
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[1],
+    qd.Payload, ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::92E6:BAFF:FE44:FFBB',
+    HostAddrToStr6(ip));
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressTCP_AAAA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr6;
+begin
+  BuildFakeResponseAAAA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of AAAA records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an AAAA RR.',DNSQRY_AAAA, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an AAAA RR.', DNSQRY_AAAA, RRarr[1].RRMeta.Atype);
+  AssertEquals('Wrong AAAA record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertEquals('Wrong AAAA record name for RR 1', FAKEFQDN,
+    RRArr[1].RRName);
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[0],
+    qd.Payload, ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::3B92:3429:FF16:A3E4',
+    HostAddrToStr6(ip));
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[1],
+    qd.Payload, ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::92E6:BAFF:FE44:FFBB',
+    HostAddrToStr6(ip));
+end;
+
+procedure TNetDbTest.TestDnsQueryUDP_MX;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  mxrec: TDNSRR_MX;
+  ip: THostAddr;
+  ip6: THostAddr6;
+begin
+  BuildFakeResponseMX(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of MX records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an MX RR.',DNSQRY_MX, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR MX data.', DNSRRGetMX(RRArr[0], qd.Payload, mxrec));
+  AssertEquals('Wrong MX hostname', 'mailer.'+FAKEFQDN,
+    mxrec.exchange);
+  AssertEquals('Wrong MX preference', 10, mxrec.preference);
+
+  AssertEquals('Should be 2 additional RR records.',2,NToHs(qd.h.arcount));
+  RRArr := GetRRrecords(qd.Payload, ansstart, NToHs(qd.h.arcount));
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an A RR.',DNSQRY_A, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an AAAA RR.', DNSQRY_AAAA, RRarr[1].RRMeta.Atype);
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[1], qd.Payload, ip6));
+
+  AssertEquals('Wrong ip for A.', '172.16.27.238', HostAddrToStr(ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::3B92:3429:FF16:A3E4',
+    HostAddrToStr6(ip6));
+end;
+
+procedure TNetDbTest.TestDnsQueryTCP_MX;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  mxrec: TDNSRR_MX;
+  ip: THostAddr;
+  ip6: THostAddr6;
+begin
+  BuildFakeResponseMX(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of MX records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an MX RR.',DNSQRY_MX, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR MX data.', DNSRRGetMX(RRArr[0], qd.Payload, mxrec));
+  AssertEquals('Wrong MX hostname', 'mailer.'+FAKEFQDN,
+    mxrec.exchange);
+  AssertEquals('Wrong MX preference', 10, mxrec.preference);
+
+  AssertEquals('Should be 2 additional RR records.',2,NToHs(qd.h.arcount));
+  RRArr := GetRRrecords(qd.Payload, ansstart, NToHs(qd.h.arcount));
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an A RR.',DNSQRY_A, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an AAAA RR.', DNSQRY_AAAA, RRarr[1].RRMeta.Atype);
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[1], qd.Payload, ip6));
+
+  AssertEquals('Wrong ip for A.', '172.16.27.238', HostAddrToStr(ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::3B92:3429:FF16:A3E4',
+    HostAddrToStr6(ip6));
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressUDP_MX;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  mxrec: TDNSRR_MX;
+  ip: THostAddr;
+  ip6: THostAddr6;
+begin
+  BuildFakeResponseMX(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of MX records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an MX RR.',DNSQRY_MX, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR MX data.', DNSRRGetMX(RRArr[0], qd.Payload, mxrec));
+  AssertEquals('Wrong MX hostname', 'mailer.'+FAKEFQDN,
+    mxrec.exchange);
+  AssertEquals('Wrong MX preference', 10, mxrec.preference);
+
+  AssertEquals('Should be 2 additional RR records.',2,NToHs(qd.h.arcount));
+  RRArr := GetRRrecords(qd.Payload, ansstart, NToHs(qd.h.arcount));
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an A RR.',DNSQRY_A, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an AAAA RR.', DNSQRY_AAAA, RRarr[1].RRMeta.Atype);
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[1], qd.Payload,
+    ip6));
+
+  AssertEquals('Wrong ip for A.', '172.16.27.238', HostAddrToStr(ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::3B92:3429:FF16:A3E4',
+    HostAddrToStr6(ip6));
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressTCP_MX;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  mxrec: TDNSRR_MX;
+  ip: THostAddr;
+  ip6: THostAddr6;
+begin
+  BuildFakeResponseMX(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of MX records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an MX RR.',DNSQRY_MX, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR MX data.', DNSRRGetMX(RRArr[0], qd.Payload, mxrec));
+  AssertEquals('Wrong MX hostname', 'mailer.'+FAKEFQDN,
+    mxrec.exchange);
+  AssertEquals('Wrong MX preference', 10, mxrec.preference);
+
+  AssertEquals('Should be 2 additional RR records.',2,NToHs(qd.h.arcount));
+  RRArr := GetRRrecords(qd.Payload, ansstart, NToHs(qd.h.arcount));
+  AssertEquals('Wrong number of resource records.', 2, Length(RRArr));
+  AssertEquals('RR 0 is not an A RR.',DNSQRY_A, RRarr[0].RRMeta.Atype);
+  AssertEquals('RR 1 is not an AAAA RR.', DNSQRY_AAAA, RRarr[1].RRMeta.Atype);
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+  AssertTrue('Did not get RR AAAA data.', DNSRRGetAAAA(RRArr[1], qd.Payload,
+    ip6));
+
+  AssertEquals('Wrong ip for A.', '172.16.27.238', HostAddrToStr(ip));
+  AssertEquals('Wrong ip for AAAA.', 'FE80::3B92:3429:FF16:A3E4',
+    HostAddrToStr6(ip6));
+end;
+
+procedure TNetDbTest.TestDnsQueryUDP_SOA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  soarec: TDNSRR_SOA;
+begin
+  BuildFakeResponseSOA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SOA records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SOA RR.',DNSQRY_SOA, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR SOA data.', DNSRRGetSOA(RRArr[0], qd.Payload,
+    soarec));
+  AssertEquals('Wrong mname hostname', 'mn.'+FAKEFQDN,
+    soarec.mname);
+  AssertEquals('Wrong rname hostname', 'rn.'+FAKEFQDN,
+    soarec.rname);
+  AssertEquals('Wrong SOA serial', 76543210, soarec.serial);
+  AssertEquals('Wrong SOA refresh', 123, soarec.refresh);
+  AssertEquals('Wrong SOA retry', 456, soarec.retry);
+  AssertEquals('Wrong SOA expire', 789, soarec.expire);
+  AssertEquals('Wrong SOA min', 60, soarec.min);
+end;
+
+procedure TNetDbTest.TestDnsQueryTCP_SOA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  soarec: TDNSRR_SOA;
+begin
+  BuildFakeResponseSOA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SOA records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SOA RR.',DNSQRY_SOA, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR SOA data.', DNSRRGetSOA(RRArr[0], qd.Payload,
+    soarec));
+  AssertEquals('Wrong mname hostname', 'mn.'+FAKEFQDN,
+    soarec.mname);
+  AssertEquals('Wrong rname hostname', 'rn.'+FAKEFQDN,
+    soarec.rname);
+  AssertEquals('Wrong SOA serial', 76543210, soarec.serial);
+  AssertEquals('Wrong SOA refresh', 123, soarec.refresh);
+  AssertEquals('Wrong SOA retry', 456, soarec.retry);
+  AssertEquals('Wrong SOA expire', 789, soarec.expire);
+  AssertEquals('Wrong SOA min', 60, soarec.min);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressUDP_SOA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  soarec: TDNSRR_SOA;
+begin
+  BuildFakeResponseSOA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SOA records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SOA RR.',DNSQRY_SOA, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR SOA data.', DNSRRGetSOA(RRArr[0], qd.Payload,
+    soarec));
+  AssertEquals('Wrong mname hostname', 'mn.'+FAKEFQDN,
+    soarec.mname);
+  AssertEquals('Wrong rname hostname', 'rn.'+FAKEFQDN,
+    soarec.rname);
+  AssertEquals('Wrong SOA serial', 76543210, soarec.serial);
+  AssertEquals('Wrong SOA refresh', 123, soarec.refresh);
+  AssertEquals('Wrong SOA retry', 456, soarec.retry);
+  AssertEquals('Wrong SOA expire', 789, soarec.expire);
+  AssertEquals('Wrong SOA min', 60, soarec.min);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressTCP_SOA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  soarec: TDNSRR_SOA;
+begin
+  BuildFakeResponseSOA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SOA records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SOA RR.',DNSQRY_SOA, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR SOA data.', DNSRRGetSOA(RRArr[0], qd.Payload,
+    soarec));
+  AssertEquals('Wrong mname hostname', 'mn.'+FAKEFQDN,
+    soarec.mname);
+  AssertEquals('Wrong rname hostname', 'rn.'+FAKEFQDN,
+    soarec.rname);
+  AssertEquals('Wrong SOA serial', 76543210, soarec.serial);
+  AssertEquals('Wrong SOA refresh', 123, soarec.refresh);
+  AssertEquals('Wrong SOA retry', 456, soarec.retry);
+  AssertEquals('Wrong SOA expire', 789, soarec.expire);
+  AssertEquals('Wrong SOA min', 60, soarec.min);
+end;
+
+procedure TNetDbTest.TestDnsQueryUDP_CNAME;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseCNAME(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of CNAME records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an CNAME RR.',DNSQRY_CNAME, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong CNAME record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR CNAME data.', DNSRRGetCNAME(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong CNAME.', 'fakecname.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryTCP_CNAME;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseCNAME(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of CNAME records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an CNAME RR.',DNSQRY_CNAME, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong CNAME record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR CNAME data.', DNSRRGetCNAME(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong CNAME.', 'fakecname.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressUDP_CNAME;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseCNAME(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of CNAME records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an CNAME RR.',DNSQRY_CNAME, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong CNAME record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR CNAME data.', DNSRRGetCNAME(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong CNAME.', 'fakecname.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressTCP_CNAME;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseCNAME(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of CNAME records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an CNAME RR.',DNSQRY_CNAME, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong CNAME record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR CNAME data.', DNSRRGetCNAME(RRArr[0], qd.Payload,
+    s));
+  AssertEquals('Wrong CNAME.', 'fakecname.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryUDP_NS;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseNS(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of NS records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an NS RR.',DNSQRY_NS, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong NS record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR NS data.', DNSRRGetNS(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong NS.', 'fakens.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryTCP_NS;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseNS(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of NS records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an NS RR.',DNSQRY_NS, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong NS record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR NS data.', DNSRRGetNS(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong NS.', 'fakens.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressUDP_NS;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseNS(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of NS records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an NS RR.',DNSQRY_NS, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong NS record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR NS data.', DNSRRGetNS(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong NS.', 'fakens.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressTCP_NS;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseNS(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of NS records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an NS RR.',DNSQRY_NS, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong NS record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR NS data.', DNSRRGetNS(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong NS.', 'fakens.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryUDP_PTR;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  // the str passed in to this function doesn't really matter, but using
+  // a proper in-addr.arpa domain helps keep it clear what we're testing.
+  BuildFakeResponsePTR('0.5.0.127.in-addr.arpa', fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of PTR records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an PTR RR.',DNSQRY_PTR, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong PTR record name for RR 0', '0.5.0.127.in-addr.arpa',
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR PTR data.', DNSRRGetPTR(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong PTR.', 'fakeptrans.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryTCP_PTR;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  // the str passed in to this function doesn't really matter, but using
+  // a proper in-addr.arpa domain helps keep it clear what we're testing.
+  BuildFakeResponsePTR('0.5.0.127.in-addr.arpa', fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of PTR records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an PTR RR.',DNSQRY_PTR, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong PTR record name for RR 0', '0.5.0.127.in-addr.arpa',
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR PTR data.', DNSRRGetPTR(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong PTR.', 'fakeptrans.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressUDP_PTR;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  // the str passed in to this function doesn't really matter, but using
+  // a proper in-addr.arpa domain helps keep it clear what we're testing.
+  BuildFakeResponsePTR('0.5.0.127.in-addr.arpa', fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of PTR records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an PTR RR.',DNSQRY_PTR, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong PTR record name for RR 0', '0.5.0.127.in-addr.arpa',
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR PTR data.', DNSRRGetPTR(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong PTR.', 'fakeptrans.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressTCP_PTR;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  // the str passed in to this function doesn't really matter, but using
+  // a proper in-addr.arpa domain helps keep it clear what we're testing.
+  BuildFakeResponsePTR('0.5.0.127.in-addr.arpa', fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of PTR records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an PTR RR.',DNSQRY_PTR, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong PTR record name for RR 0', '0.5.0.127.in-addr.arpa',
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR PTR data.', DNSRRGetPTR(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong PTR.', 'fakeptrans.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsQueryUDP_TXT;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: AnsiString;
+begin
+  s := '';
+  BuildFakeResponseTXT(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of TXT records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not a TXT RR.',DNSQRY_TXT, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong TXT record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR TXT data.', DNSRRGetText(RRArr[0], qd.Payload, s));
+  AssertEquals(
+    'v=spf1 mx a:lists.'+FAKEFQDN+'Always look on the bright side of life!',
+      s);
+end;
+
+procedure TNetDbTest.TestDnsQueryTCP_TXT;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: AnsiString;
+begin
+  s := '';
+  BuildFakeResponseTXT(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of TXT records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not a TXT RR.',DNSQRY_TXT, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong TXT record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR TXT data.', DNSRRGetText(RRArr[0], qd.Payload, s));
+  AssertEquals(
+    'v=spf1 mx a:lists.'+FAKEFQDN+'Always look on the bright side of life!',
+      s);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressUDP_TXT;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: AnsiString;
+begin
+  s := '';
+  BuildFakeResponseTXT(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of TXT records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not a TXT RR.',DNSQRY_TXT, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong TXT record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR TXT data.', DNSRRGetText(RRArr[0], qd.Payload, s));
+  AssertEquals(
+    'v=spf1 mx a:lists.'+FAKEFQDN+'Always look on the bright side of life!',
+      s);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressTCP_TXT;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  s: AnsiString;
+begin
+  s := '';
+  BuildFakeResponseTXT(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of TXT records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not a TXT RR.',DNSQRY_TXT, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong TXT record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+  AssertTrue('Did not get RR TXT data.', DNSRRGetText(RRArr[0], qd.Payload, s));
+  AssertEquals(
+    'v=spf1 mx a:lists.'+FAKEFQDN+'Always look on the bright side of life!',
+      s);
+end;
+
+procedure TNetDbTest.TestDnsQueryUDP_SRV;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  srvrec: TDNSRR_SRV;
+begin
+  BuildFakeResponseSRV(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SRV records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SRV RR.',DNSQRY_SRV, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR SRV data.', DNSRRGetSRV(RRArr[0], qd.Payload,
+    srvrec));
+  AssertEquals('Wrong SRV priority', 22, srvrec.priority);
+  AssertEquals('Wrong SRV weight', 44, srvrec.weight);
+  AssertEquals('Wrong SRV port', 2201, srvrec.port);
+
+  AssertEquals('Wrong SRV hostname', '_this._that._other', srvrec.target);
+end;
+
+procedure TNetDbTest.TestDnsQueryTCP_SRV;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  srvrec: TDNSRR_SRV;
+begin
+  BuildFakeResponseSRV(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SRV records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SRV RR.',DNSQRY_SRV, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR SOA data.', DNSRRGetSRV(RRArr[0], qd.Payload,
+    srvrec));
+  AssertEquals('Wrong SRV priority', 22, srvrec.priority);
+  AssertEquals('Wrong SRV weight', 44, srvrec.weight);
+  AssertEquals('Wrong SRV port', 2201, srvrec.port);
+
+  AssertEquals('Wrong SRV hostname', '_this._that._other', srvrec.target);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressUDP_SRV;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  srvrec: TDNSRR_SRV;
+begin
+  BuildFakeResponseSRV(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SRV records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SRV RR.',DNSQRY_SRV, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR SRV data.', DNSRRGetSRV(RRArr[0], qd.Payload,
+    srvrec));
+  AssertEquals('Wrong SRV priority', 22, srvrec.priority);
+  AssertEquals('Wrong SRV weight', 44, srvrec.weight);
+  AssertEquals('Wrong SRV port', 2201, srvrec.port);
+
+  AssertEquals('Wrong SRV hostname', '_this._that._other', srvrec.target);
+end;
+
+procedure TNetDbTest.TestDnsQueryCompressTCP_SRV;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  srvrec: TDNSRR_SRV;
+begin
+  BuildFakeResponseSRV(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to compressed querydata',
+    BuildQueryData(fakeresp, qd, anslen, True));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SRV records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SRV RR.',DNSQRY_SRV, RRarr[0].RRMeta.Atype);
+
+  AssertEquals('Wrong record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  AssertTrue('Did not get RR SOA data.', DNSRRGetSRV(RRArr[0], qd.Payload,
+    srvrec));
+  AssertEquals('Wrong SRV priority', 22, srvrec.priority);
+  AssertEquals('Wrong SRV weight', 44, srvrec.weight);
+  AssertEquals('Wrong SRV port', 2201, srvrec.port);
+
+  AssertEquals('Wrong SRV hostname', '_this._that._other', srvrec.target);
+end;
+
+{
+This test is of debatable value, as it only detects truncation if the buffer
+contents are zeroed which gives an invalid RR type.
+}
+procedure TNetDbTest.TestDnsQueryTruncateRR_UDP_A;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildTruncatedQueryData(fakeresp, qd, anslen,40));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  // the header says there are 2 A records, but it's a trap!
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  // truncation of buffer means this call returns 0 RRs.
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of RRs', 0, Length(RRArr));
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+
+  // Change start position for RR[0] to end of buffer - 4
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  qd.Payload[Length(qd.Payload)-1] := $AA; // sentinel marker we can look for
+
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '0.0.0.170', HostAddrToStr(ip));
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+
+  // Change start position for RR[0] to end of buffer - 3
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength - 3);
+  AssertFalse('Got RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+end;
+
+{
+Test that we read the AAAA right at the buffer edge, with the last byte
+being a special value we can test for.
+}
+procedure TNetDbTest.TestDnsRRBufferEdgeAAAA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr6;
+begin
+  BuildFakeResponseAAAA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of AAAA records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  // Change start position for RR[0]
+  RRArr[0].RDataSt := Length(qd.Payload) - SizeOf(THostAddr6);
+  qd.Payload[Length(qd.Payload)-1] := $AA;
+  AssertTrue('Got RR AAAA data.', DNSRRGetAAAA(RRArr[0], qd.Payload, ip));
+  AssertEquals($AA, ip.u6_addr8[15]);
+end;
+
+{
+Attempt to read an AAAA that goes past the end of the buffer.
+}
+procedure TNetDbTest.TestDNsRRBufferPastEdgeAAAA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr6;
+begin
+  BuildFakeResponseAAAA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of AAAA records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  // Change start position for RR[0]. attempting to read 16 bytes
+  // from this position will pass the end of the buffer.
+  RRArr[0].RDataSt := Length(qd.Payload) - (SizeOf(THostAddr6)-1);
+  qd.Payload[Length(qd.Payload)-1] := $AA;
+  AssertFalse('Got RR AAAA data.', DNSRRGetAAAA(RRArr[0], qd.Payload, ip));
+end;
+
+{
+Test reading an MX RR that terminates on the last byte of the buffer.
+}
+procedure TNetDbTest.TestDnsRRBufferEdgeMX;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  fmx: TDNSRR_MX;
+begin
+  BuildFakeResponseMX(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of MX records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+
+  // move the MX RR bytes to the end of the payload buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Got RR MX data.', DNSRRGetMX(RRArr[0], qd.Payload, fmx));
+  AssertEquals('Wrong MX hostname', 'mailer.'+FAKEFQDN,
+    fmx.exchange);
+  AssertEquals('Wrong MX preference', 10, fmx.preference);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeMX;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  fmx: TDNSRR_MX;
+begin
+  BuildFakeResponseMX(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of MX records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+
+  // move the MX RR bytes to the end of the payload buffer. We omit the last
+  // 2 bytes of the MX to attempt to trick the code into reading past the buffer
+  // edge.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength - 2);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength-2));
+
+  AssertTrue('Got RR MX data.', DNSRRGetMX(RRArr[0], qd.Payload, fmx));
+  // stringfromlabel should drop the last label, so the result should be just
+  // missing the tld.
+  AssertEquals('Wrong MX hostname', 'mailer.'+FAKEDOMAIN,
+    fmx.exchange);
+  AssertEquals('Wrong MX preference', 10, fmx.preference);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeSOA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  soarec: TDNSRR_SOA;
+begin
+  BuildFakeResponseSOA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SOA records.', 1, qd.h.ancount);
+
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SOA RR.',DNSQRY_SOA, RRarr[0].RRMeta.Atype);
+
+  // move the SOA RR bytes to the end of the payload buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Did not get RR SOA data.', DNSRRGetSOA(RRArr[0], qd.Payload,
+    soarec));
+  AssertEquals('Wrong mname hostname', 'mn.'+FAKEFQDN,
+    soarec.mname);
+  AssertEquals('Wrong rname hostname', 'rn.'+FAKEFQDN,
+    soarec.rname);
+  AssertEquals('Wrong SOA serial', 76543210, soarec.serial);
+  AssertEquals('Wrong SOA refresh', 123, soarec.refresh);
+  AssertEquals('Wrong SOA retry', 456, soarec.retry);
+  AssertEquals('Wrong SOA expire', 789, soarec.expire);
+  AssertEquals('Wrong SOA min', 60, soarec.min);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeSOA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  soarec: TDNSRR_SOA;
+begin
+  BuildFakeResponseSOA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SOA records.', 1, qd.h.ancount);
+
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SOA RR.',DNSQRY_SOA, RRarr[0].RRMeta.Atype);
+
+  // move the SOA RR bytes to the end of the payload buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength-1);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength-1));
+
+  AssertFalse('Got RR SOA data.', DNSRRGetSOA(RRArr[0], qd.Payload,
+    soarec));
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeSRV;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  srvrec: TDNSRR_SRV;
+begin
+  BuildFakeResponseSRV(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SRV records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SRV RR.',DNSQRY_SRV, RRarr[0].RRMeta.Atype);
+
+  // move the SRV RR bytes to the end of the payload buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Did not get RR SRV data.', DNSRRGetSRV(RRArr[0], qd.Payload,
+    srvrec));
+  AssertEquals('Wrong SRV priority', 22, srvrec.priority);
+  AssertEquals('Wrong SRV weight', 44, srvrec.weight);
+  AssertEquals('Wrong SRV port', 2201, srvrec.port);
+
+  AssertEquals('Wrong SRV hostname', '_this._that._other', srvrec.target);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeSRV;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  srvrec: TDNSRR_SRV;
+begin
+  BuildFakeResponseSRV(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SRV records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SRV RR.',DNSQRY_SRV, RRarr[0].RRMeta.Atype);
+
+  // move the SRV RR bytes to the end of the payload buffer. ensure that
+  // we're one byte short to try and trick the code into reading past the
+  // end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength - 1);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength - 1));
+
+  AssertFalse('Got RR SRV data.', DNSRRGetSRV(RRArr[0], qd.Payload,
+    srvrec));
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeCNAME;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseCNAME(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of CNAME records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an CNAME RR.',DNSQRY_CNAME, RRarr[0].RRMeta.Atype);
+
+  // move the cname to the end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Did not get RR CNAME data.', DNSRRGetCNAME(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong CNAME.', 'fakecname.'+FAKEFQDN, s);
+end;
+
+{
+Test retrieving a cname when the actual string is longer than rdlength says it
+is. The bytes in the payload buffer try to point past the end of the buffer.
+}
+procedure TNetDbTest.TestDnsRRBufferPastEdgeCNAME;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseCNAME(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of CNAME records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an CNAME RR.',DNSQRY_CNAME, RRarr[0].RRMeta.Atype);
+
+  // move the cname to the end of the buffer. we drop two bytes off the end of
+  // the cname, because there's a 0 byte at the end of a label if not a ptr.
+  // now, the last label's size is greater than the number of bytes left in
+  // the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength-2);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength-2));
+
+  // lie about the rdlength too!
+  Dec(RRArr[0].RRMeta.RDLength,2);
+  AssertTrue('Did not get RR CNAME data.', DNSRRGetCNAME(RRArr[0], qd.Payload, s));
+  // last label will get removed, leaving just the domain part.
+  AssertEquals('Wrong CNAME.', 'fakecname.'+FAKEDOMAIN, s);
+end;
+
+{
+Test retrieving an NS RR when it's at the end of the payload buffer.
+}
+procedure TNetDbTest.TestDnsRRBufferEdgeNS;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseNS(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of NS records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an NS RR.',DNSQRY_NS, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong NS record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  // move the ns to the end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Did not get RR NS data.', DNSRRGetNS(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong NS.', 'fakens.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeNS;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseNS(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of NS records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an NS RR.',DNSQRY_NS, RRarr[0].RRMeta.Atype);
+
+  // move the ns to the end of the buffer. we drop two bytes off the end of
+  // the ns, because there's a 0 byte at the end of a label if not a ptr.
+  // now, the last label's size is greater than the number of bytes left in
+  // the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength-2);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength-2));
+
+  // lie about the rdlength too!
+  Dec(RRArr[0].RRMeta.RDLength,2);
+  AssertTrue('Did not get RR NS data.', DNSRRGetNS(RRArr[0], qd.Payload, s));
+  // last label will get removed, leaving just the domain part.
+  AssertEquals('Wrong NS.', 'fakens.'+FAKEDOMAIN, s);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgePTR;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  // the str passed in to this function doesn't really matter, but using
+  // a proper in-addr.arpa domain helps keep it clear what we're testing.
+  BuildFakeResponsePTR('0.5.0.127.in-addr.arpa', fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of PTR records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an PTR RR.',DNSQRY_PTR, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong PTR record name for RR 0', '0.5.0.127.in-addr.arpa',
+    RRArr[0].RRName);
+
+  // move the ptr to the end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Did not get RR PTR data.', DNSRRGetPTR(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong PTR.', 'fakeptrans.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgePTR;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  // the str passed in to this function doesn't really matter, but using
+  // a proper in-addr.arpa domain helps keep it clear what we're testing.
+  BuildFakeResponsePTR('0.5.0.127.in-addr.arpa', fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of PTR records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an PTR RR.',DNSQRY_PTR, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong PTR record name for RR 0', '0.5.0.127.in-addr.arpa',
+    RRArr[0].RRName);
+
+  // move the ns to the end of the buffer. we drop two bytes off the end of
+  // the ns, because there's a 0 byte at the end of a label if not a ptr.
+  // now, the last label's size is greater than the number of bytes left in
+  // the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength-2);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength-2));
+
+  // lie about the rdlength too!
+  Dec(RRArr[0].RRMeta.RDLength,2);
+  AssertTrue('Did not get RR PTR data.', DNSRRGetPTR(RRArr[0], qd.Payload, s));
+  // last label will get removed, leaving just the domain part.
+  AssertEquals('Wrong PTR.', 'fakeptrans.'+FAKEDOMAIN, s);
+end;
+
+{
+Test reading a text record right at the edge of the payload buffer.
+}
+procedure TNetDbTest.TestDnsRRBufferEdgeTXT;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart,oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: AnsiString;
+begin
+  s := '';
+  BuildFakeResponseTXT(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of TXT records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not a TXT RR.',DNSQRY_TXT, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong TXT record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  // Move the text record to the end of the buffer
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+  AssertTrue('Did not get RR TXT data.', DNSRRGetText(RRArr[0], qd.Payload, s));
+  AssertEquals(
+    'v=spf1 mx a:lists.'+FAKEFQDN+'Always look on the bright side of life!',
+      s);
+end;
+
+{
+Try reading a TXT record that points past the end of the payload buffer.
+}
+procedure TNetDbTest.TestDnsRRBufferPastEdgeTXT;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart,oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: AnsiString;
+begin
+  s := '';
+  BuildFakeResponseTXT(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of TXT records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not a TXT RR.',DNSQRY_TXT, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong TXT record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  // Move the text record to the end of the buffer, cutting off the last
+  // 2 bytes. this means the length byte for the second string will point
+  // past the end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength - 2);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength - 2));
+  AssertFalse('Did not get RR TXT data.',
+    DNSRRGetText(RRArr[0], qd.Payload, s));
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeTCPA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+
+  // Change start position for RR[0] to end of buffer - 4
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  qd.Payload[Length(qd.Payload)-1] := $AA; // sentinel marker we can look for
+  AssertTrue('Did not get RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '0.0.0.170', HostAddrToStr(ip));
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeTCPA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+
+  // Change start position for RR[0] to end of buffer - 3
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength - 2);
+  AssertFalse('Got RR A data.', DNSRRGetA(RRArr[0], qd.Payload, ip));
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeTCPAAAA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr6;
+begin
+  BuildFakeResponseAAAA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of AAAA records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  // Change start position for RR[0]
+  RRArr[0].RDataSt := Length(qd.Payload) - SizeOf(THostAddr6);
+  qd.Payload[Length(qd.Payload)-1] := $AA;
+  AssertTrue('Got RR AAAA data.', DNSRRGetAAAA(RRArr[0], qd.Payload, ip));
+  AssertEquals($AA, ip.u6_addr8[15]);
+end;
+
+procedure TNetDbTest.TestDNsRRBufferPastEdgeTCPAAAA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+  ip: THostAddr6;
+begin
+  BuildFakeResponseAAAA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of AAAA records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  // Change start position for RR[0]. attempting to read 16 bytes
+  // from this position will pass the end of the buffer.
+  RRArr[0].RDataSt := Length(qd.Payload) - (SizeOf(THostAddr6)-1);
+  AssertFalse('Got RR AAAA data.', DNSRRGetAAAA(RRArr[0], qd.Payload, ip));
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeTCPMX;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  fmx: TDNSRR_MX;
+begin
+  BuildFakeResponseMX(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of MX records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+
+  // move the MX RR bytes to the end of the payload buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Got RR MX data.', DNSRRGetMX(RRArr[0], qd.Payload, fmx));
+  AssertEquals('Wrong MX hostname', 'mailer.'+FAKEFQDN,
+    fmx.exchange);
+  AssertEquals('Wrong MX preference', 10, fmx.preference);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeTCPMX;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  fmx: TDNSRR_MX;
+begin
+  BuildFakeResponseMX(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of MX records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+
+  // move the MX RR bytes to the end of the payload buffer. We omit the last
+  // 2 bytes of the MX to attempt to trick the code into reading past the buffer
+  // edge.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength - 2);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength-2));
+
+  AssertTrue('Got RR MX data.', DNSRRGetMX(RRArr[0], qd.Payload, fmx));
+  // stringfromlabel should drop the last label, so the result should be just
+  // missing the tld.
+  AssertEquals('Wrong MX hostname', 'mailer.'+FAKEDOMAIN,
+    fmx.exchange);
+  AssertEquals('Wrong MX preference', 10, fmx.preference);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeTCPSOA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  soarec: TDNSRR_SOA;
+begin
+  BuildFakeResponseSOA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SOA records.', 1, qd.h.ancount);
+
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SOA RR.',DNSQRY_SOA, RRarr[0].RRMeta.Atype);
+
+  // move the SOA RR bytes to the end of the payload buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Did not get RR SOA data.', DNSRRGetSOA(RRArr[0], qd.Payload,
+    soarec));
+  AssertEquals('Wrong mname hostname', 'mn.'+FAKEFQDN,
+    soarec.mname);
+  AssertEquals('Wrong rname hostname', 'rn.'+FAKEFQDN,
+    soarec.rname);
+  AssertEquals('Wrong SOA serial', 76543210, soarec.serial);
+  AssertEquals('Wrong SOA refresh', 123, soarec.refresh);
+  AssertEquals('Wrong SOA retry', 456, soarec.retry);
+  AssertEquals('Wrong SOA expire', 789, soarec.expire);
+  AssertEquals('Wrong SOA min', 60, soarec.min);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeTCPSOA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  soarec: TDNSRR_SOA;
+begin
+  BuildFakeResponseSOA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SOA records.', 1, qd.h.ancount);
+
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SOA RR.',DNSQRY_SOA, RRarr[0].RRMeta.Atype);
+
+  // move the SOA RR bytes to the end of the payload buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength-1);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength-1));
+
+  AssertFalse('Got RR SOA data.', DNSRRGetSOA(RRArr[0], qd.Payload,
+    soarec));
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeTCPSRV;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  srvrec: TDNSRR_SRV;
+begin
+  BuildFakeResponseSRV(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SRV records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SRV RR.',DNSQRY_SRV, RRarr[0].RRMeta.Atype);
+
+  // move the SRV RR bytes to the end of the payload buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Did not get RR SRV data.', DNSRRGetSRV(RRArr[0], qd.Payload,
+    srvrec));
+  AssertEquals('Wrong SRV priority', 22, srvrec.priority);
+  AssertEquals('Wrong SRV weight', 44, srvrec.weight);
+  AssertEquals('Wrong SRV port', 2201, srvrec.port);
+
+  AssertEquals('Wrong SRV hostname', '_this._that._other', srvrec.target);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeTCPSRV;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  srvrec: TDNSRR_SRV;
+begin
+  BuildFakeResponseSRV(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of SRV records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an SRV RR.',DNSQRY_SRV, RRarr[0].RRMeta.Atype);
+
+  // move the SRV RR bytes to the end of the payload buffer. ensure that
+  // we're one byte short to try and trick the code into reading past the
+  // end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength - 1);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength - 1));
+
+  AssertFalse('Got RR SRV data.', DNSRRGetSRV(RRArr[0], qd.Payload,
+    srvrec));
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeTCPCNAME;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseCNAME(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of CNAME records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an CNAME RR.',DNSQRY_CNAME, RRarr[0].RRMeta.Atype);
+
+  // move the cname to the end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Did not get RR CNAME data.', DNSRRGetCNAME(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong CNAME.', 'fakecname.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeTCPCNAME;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseCNAME(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of CNAME records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an CNAME RR.',DNSQRY_CNAME, RRarr[0].RRMeta.Atype);
+
+  // move the cname to the end of the buffer. we drop two bytes off the end of
+  // the cname, because there's a 0 byte at the end of a label if not a ptr.
+  // now, the last label's size is greater than the number of bytes left in
+  // the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength-2);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength-2));
+
+  // lie about the rdlength too!
+  Dec(RRArr[0].RRMeta.RDLength,2);
+  AssertTrue('Did not get RR CNAME data.', DNSRRGetCNAME(RRArr[0], qd.Payload, s));
+  // last label will get removed, leaving just the domain part.
+  AssertEquals('Wrong CNAME.', 'fakecname.'+FAKEDOMAIN, s);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeTCPNS;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseNS(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of NS records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an NS RR.',DNSQRY_NS, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong NS record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  // move the ns to the end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Did not get RR NS data.', DNSRRGetNS(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong NS.', 'fakens.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeTCPNS;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  BuildFakeResponseNS(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of NS records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an NS RR.',DNSQRY_NS, RRarr[0].RRMeta.Atype);
+
+  // move the ns to the end of the buffer. we drop two bytes off the end of
+  // the ns, because there's a 0 byte at the end of a label if not a ptr.
+  // now, the last label's size is greater than the number of bytes left in
+  // the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength-2);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength-2));
+
+  // lie about the rdlength too!
+  Dec(RRArr[0].RRMeta.RDLength,2);
+  AssertTrue('Did not get RR NS data.', DNSRRGetNS(RRArr[0], qd.Payload, s));
+  // last label will get removed, leaving just the domain part.
+  AssertEquals('Wrong NS.', 'fakens.'+FAKEDOMAIN, s);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeTCPPTR;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  // the str passed in to this function doesn't really matter, but using
+  // a proper in-addr.arpa domain helps keep it clear what we're testing.
+  BuildFakeResponsePTR('0.5.0.127.in-addr.arpa', fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of PTR records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an PTR RR.',DNSQRY_PTR, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong PTR record name for RR 0', '0.5.0.127.in-addr.arpa',
+    RRArr[0].RRName);
+
+  // move the ptr to the end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  AssertTrue('Did not get RR PTR data.', DNSRRGetPTR(RRArr[0], qd.Payload, s));
+  AssertEquals('Wrong PTR.', 'fakeptrans.'+FAKEFQDN, s);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeTCPPTR;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+begin
+  // the str passed in to this function doesn't really matter, but using
+  // a proper in-addr.arpa domain helps keep it clear what we're testing.
+  BuildFakeResponsePTR('0.5.0.127.in-addr.arpa', fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of PTR records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an PTR RR.',DNSQRY_PTR, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong PTR record name for RR 0', '0.5.0.127.in-addr.arpa',
+    RRArr[0].RRName);
+
+  // move the ns to the end of the buffer. we drop two bytes off the end of
+  // the ns, because there's a 0 byte at the end of a label if not a ptr.
+  // now, the last label's size is greater than the number of bytes left in
+  // the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength-2);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength-2));
+
+  // lie about the rdlength too!
+  Dec(RRArr[0].RRMeta.RDLength,2);
+  AssertTrue('Did not get RR PTR data.', DNSRRGetPTR(RRArr[0], qd.Payload, s));
+  // last label will get removed, leaving just the domain part.
+  AssertEquals('Wrong PTR.', 'fakeptrans.'+FAKEDOMAIN, s);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferEdgeTCPTXT;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart,oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: AnsiString;
+begin
+  s := '';
+  BuildFakeResponseTXT(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of TXT records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not a TXT RR.',DNSQRY_TXT, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong TXT record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  // Move the text record to the end of the buffer
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+  AssertTrue('Did not get RR TXT data.', DNSRRGetText(RRArr[0], qd.Payload, s));
+  AssertEquals(
+    'v=spf1 mx a:lists.'+FAKEFQDN+'Always look on the bright side of life!',
+      s);
+end;
+
+procedure TNetDbTest.TestDnsRRBufferPastEdgeTCPTXT;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart,oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: AnsiString;
+begin
+  s := '';
+  BuildFakeResponseTXT(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of TXT records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not a TXT RR.',DNSQRY_TXT, RRarr[0].RRMeta.Atype);
+  AssertEquals('Wrong TXT record name for RR 0', FAKEFQDN,
+    RRArr[0].RRName);
+
+  // Move the text record to the end of the buffer, cutting off the last
+  // 2 bytes. this means the length byte for the second string will point
+  // past the end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - (RRArr[0].RRMeta.RDLength - 2);
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt,
+    (RRArr[0].RRMeta.RDLength - 2));
+  AssertFalse('Did not get RR TXT data.',
+    DNSRRGetText(RRArr[0], qd.Payload, s));
+end;
+
+{
+Test that NextNameRR correctly reads an RR on the edge of the buffer.
+}
+procedure TNetDbTest.TestNextNameRREdgeA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  rrn: TRRNameData;
+  ip: THostAddr;
+  t: Cardinal;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+
+  // get an RR from its normal position. need this to calculate the length.
+  AssertTrue('NextNameRR should succeed.',
+    NextNameRR(qd.Payload, ansstart,  rrn));
+
+  // calculate the size in bytes of the rr so we can copy it to the end
+  // of the payload buffer
+  t := (rrn.RDataSt + rrn.RRMeta.RDLength) - ansstart;
+  CopyBytesTo(qd.Payload,ansstart,Length(qd.Payload)-t, t);
+  AssertTrue('NextNameRR should succeed.',
+    NextNameRR(qd.Payload, Length(qd.Payload)-t,  rrn));
+  AssertEquals(DNSQRY_A, rrn.RRMeta.Atype);
+  AssertEquals(300, rrn.RRMeta.TTL);
+  AssertTrue(DNSRRGetA(rrn, qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '127.0.0.1', HostAddrToStr(ip));
+end;
+
+{
+Try to trick NextNameRR into reading past the end of the payload buffer.
+}
+procedure TNetDbTest.TestNextNameRRPastEdgeA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  rrn: TRRNameData;
+  t: Cardinal;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+
+  // get an RR from its normal position. need this to calculate the length.
+  AssertTrue('NextNameRR should succeed.',
+    NextNameRR(qd.Payload, ansstart,  rrn));
+
+  // calculate the size in bytes of the rr so we can copy it to the end
+  // of the payload buffer
+  t := (rrn.RDataSt + rrn.RRMeta.RDLength) - ansstart;
+  // copy the bytes, but leave off the last one. leave the rdlength unchanged.
+  CopyBytesTo(qd.Payload,ansstart,Length(qd.Payload)-(t-1), t-1);
+  AssertFalse('NextNameRR should fail.',
+    NextNameRR(qd.Payload, Length(qd.Payload)-(t-1),  rrn));
+end;
+
+procedure TNetDbTest.TestNextNameRREdgeTCPA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  rrn: TRRNameData;
+  ip: THostAddr;
+  t: Cardinal;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+
+  // get an RR from its normal position. need this to calculate the length.
+  AssertTrue('NextNameRR should succeed.',
+    NextNameRR(qd.Payload, ansstart,  rrn));
+
+  // calculate the size in bytes of the rr so we can copy it to the end
+  // of the payload buffer
+  t := (rrn.RDataSt + rrn.RRMeta.RDLength) - ansstart;
+  CopyBytesTo(qd.Payload,ansstart,Length(qd.Payload)-t, t);
+  AssertTrue('NextNameRR should succeed.',
+    NextNameRR(qd.Payload, Length(qd.Payload)-t,  rrn));
+  AssertEquals(DNSQRY_A, rrn.RRMeta.Atype);
+  AssertEquals(300, rrn.RRMeta.TTL);
+  AssertTrue(DNSRRGetA(rrn, qd.Payload, ip));
+  AssertEquals('Wrong ip for A.', '127.0.0.1', HostAddrToStr(ip));
+end;
+
+procedure TNetDbTest.TestNextNameRRPastEdgeTCPA;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  rrn: TRRNameData;
+  t: Cardinal;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+
+  // get an RR from its normal position. need this to calculate the length.
+  AssertTrue('NextNameRR should succeed.',
+    NextNameRR(qd.Payload, ansstart,  rrn));
+
+  // calculate the size in bytes of the rr so we can copy it to the end
+  // of the payload buffer
+  t := (rrn.RDataSt + rrn.RRMeta.RDLength) - ansstart;
+  // copy the bytes, but leave off the last one. leave the rdlength unchanged.
+  CopyBytesTo(qd.Payload,ansstart,Length(qd.Payload)-(t-1), t-1);
+  AssertFalse('NextNameRR should fail.',
+    NextNameRR(qd.Payload, Length(qd.Payload)-(t-1),  rrn));
+end;
+
+{
+Call GetRRrecords with a start position past the end of the buffer.
+}
+
+procedure TNetDbTest.TestGetRRrecordsInvalidStart;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := High(Word);
+  anslen := High(Word);
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, anslen);
+  AssertEquals(0, Length(RRArr));
+end;
+
+procedure TNetDbTest.TestGetRRrecordsInvalidStartTCP;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart: Word;
+  RRArr: TRRNameDataArray;
+begin
+  BuildFakeResponseA(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := High(Word);
+  anslen := High(Word);
+  AssertEquals('Wrong number of A records.', 2, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, anslen);
+  AssertEquals(0, Length(RRArr));
+end;
+
+procedure TNetDbTest.TestGetFixLenStrSimple;
+const
+  s = 'another fine mess';
+var
+  buf: TBuffer;
+  pl: TPayload;
+  tr: TTextArray;
+  offset: Cardinal;
+  res: ShortString;
+begin
+  tr[1] := s;
+  tr[2] := '';
+  tr[3] := '';
+  tr[4] := '';
+  tr[5] := '';
+  SetLength(buf, 1024);
+  offset := 0;
+  WriteTextRecAsRData(buf, offset, tr);
+  SetLength(buf, offset);
+  BufferToPayload(buf, pl);
+  // rdlength is word, so len byte for str is at offset 2 and str starts
+  // at offset 3.
+  GetFixlenStr(pl, 3, pl[2], res);
+  AssertEquals(s, res);
+end;
+
+procedure TNetDbTest.TestGetFixLenStrSimpleTCP;
+const
+  s = 'another fine mess';
+var
+  buf: TBuffer;
+  pl: TPayLoadTCP;
+  tr: TTextArray;
+  offset: Cardinal;
+  res: ShortString;
+begin
+  tr[1] := s;
+  tr[2] := '';
+  tr[3] := '';
+  tr[4] := '';
+  tr[5] := '';
+  SetLength(buf, 1024);
+  offset := 0;
+  WriteTextRecAsRData(buf, offset, tr);
+  SetLength(buf, offset);
+  BufferToPayload(buf, pl);
+  // rdlength is word, so len byte for str is at offset 2 and str starts
+  // at offset 3.
+  GetFixlenStr(pl, 3, pl[2], res);
+  AssertEquals(s, res);
+end;
+
+procedure TNetDbTest.TestGetFixLenStrSimpleAtEdge;
+const
+  s = 'another fine mess';
+var
+  buf: TBuffer;
+  pl: TPayload;
+  tr: TTextArray;
+  offset,n: Cardinal;
+  res: ShortString;
+begin
+  tr[1] := s;
+  tr[2] := '';
+  tr[3] := '';
+  tr[4] := '';
+  tr[5] := '';
+  SetLength(buf, Length(pl));
+  offset := Length(pl) - (Length(s)+3);
+  n := offset+2;
+  WriteTextRecAsRData(buf, offset, tr);
+  SetLength(buf, offset);
+  BufferToPayload(buf, pl);
+  GetFixlenStr(pl, n+1, pl[n], res);
+  AssertEquals(s, res);
+end;
+
+procedure TNetDbTest.TestGetFixLenStrSimpleTCPAtEdge;
+const
+  s = 'another fine mess';
+var
+  buf: TBuffer;
+  pl: TPayLoadTCP;
+  tr: TTextArray;
+  offset,n: Cardinal;
+  res: ShortString;
+begin
+  tr[1] := s;
+  tr[2] := '';
+  tr[3] := '';
+  tr[4] := '';
+  tr[5] := '';
+  SetLength(buf, Length(pl));
+  offset := Length(pl) - (Length(s)+3);
+  n := offset+2;
+  WriteTextRecAsRData(buf, offset, tr);
+  SetLength(buf, offset);
+  BufferToPayload(buf, pl);
+  GetFixlenStr(pl, n+1, pl[n], res);
+  AssertEquals(s, res);
+end;
+
+{
+Test GetFixLenStr where len would take string past edge of buffer.
+}
+procedure TNetDbTest.TestGetFixLenStrSimplePastEdge;
+var
+  pl: TPayLoadTCP;
+  res: ShortString;
+begin
+  pl[Length(pl) - 2] := 30;
+  pl[Length(pl) - 1] := Ord('a');
+  GetFixlenStr(pl, Length(pl)-1, pl[Length(pl)-2], res);
+  AssertEquals('', res);
+end;
+
+procedure TNetDbTest.TestGetFixLenStrSimpleTCPPastEdge;
+var
+  pl: TPayLoadTCP;
+  res: ShortString;
+begin
+  pl[Length(pl) - 2] := 30;
+  pl[Length(pl) - 1] := Ord('a');
+  GetFixlenStr(pl, Length(pl)-1, pl[Length(pl)-2], res);
+  AssertEquals('', res);
+end;
+
+{
+ read a label at the end of the buffer where the last byte is a count
+ greater than 0. this is to try and trick stringfromlabel into reading past
+ the end of the buffer.
+}
+procedure TNetDbTest.TestStringFromLabelCountAsLastByte;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryData;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+  startpos: Longint;
+begin
+  // we can use any of CNAME, NS or PTR because these RRs are just a single
+  // domain name or series of labels.
+  BuildFakeResponseNS(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of NS records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an NS RR.',DNSQRY_NS, RRarr[0].RRMeta.Atype);
+
+  // move the ns to the end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  // Set the last byte in the buffer to a high count
+  qd.Payload[Length(qd.Payload)-1] := 63; // must be less than 64
+
+  // need this var because stringfromlabel expects a longint that's a var type.
+  startpos := RRarr[0].RDataSt;
+  s := stringfromlabel(qd.Payload, startpos);
+  AssertEquals('fakens.'+FAKEFQDN, s);
+  AssertEquals(Length(qd.Payload), startpos);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCountAsLastByteTCP;
+var
+  fakeresp: TFakeDNSResponse;
+  qd: TQueryDataLengthTCP;
+  anslen, ansstart, oldstart: Word;
+  RRArr: TRRNameDataArray;
+  s: TDNSDomainName;
+  startpos: Longint;
+begin
+  // we can use any of CNAME, NS or PTR because these RRs are just a single
+  // domain name or series of labels.
+  BuildFakeResponseNS(FAKEFQDN, fakeresp);
+  AssertTrue('Unable to convert fake dns response to querydata',
+    BuildQueryData(fakeresp, qd, anslen));
+  AssertTrue('CheckAnswer should return true.', CheckAnswer(qd.h,qd.h));
+  ansstart := SkipAnsQueries(qd, anslen);
+  AssertEquals('Wrong number of NS records.', 1, qd.h.ancount);
+  RRArr := GetRRrecords(qd.Payload, ansstart, qd.h.ancount);
+  AssertEquals('Wrong number of resource records.', 1, Length(RRArr));
+  AssertEquals('RR 0 is not an NS RR.',DNSQRY_NS, RRarr[0].RRMeta.Atype);
+
+  // move the ns to the end of the buffer.
+  oldstart := RRArr[0].RDataSt;
+  RRArr[0].RDataSt := Length(qd.Payload) - RRArr[0].RRMeta.RDLength;
+  CopyBytesTo(qd.Payload, oldstart, RRArr[0].RDataSt, RRArr[0].RRMeta.RDLength);
+
+  // Set the last byte in the buffer to a high count
+  qd.Payload[Length(qd.Payload)-1] := 63; // must be less than 64
+
+  // need this var because stringfromlabel expects a longint that's a var type.
+  startpos := RRarr[0].RDataSt;
+  s := stringfromlabel(qd.Payload, startpos);
+  AssertEquals('fakens.'+FAKEFQDN, s);
+  AssertEquals(Length(qd.Payload), startpos);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompress;
+var
+  buf: TBuffer;
+  stt: TDomainCompressionTable;
+  offset: Cardinal;
+  offset2: Longint;
+  pl: TPayload;
+  s: String;
+  dmbs: TDNSDomainByteStream;
+begin
+  SetLength(buf, 1024);
+  SetLength(stt,0);
+  offset := 0;
+  // initial str is uncompressed because compress table empty
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  offset2 := offset;
+  // write same domain, this time we get compression.
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  BufferToPayload(buf,pl);
+  s := stringfromlabel(pl, offset2);
+  AssertEquals(FAKEFQDN,s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompressTCP;
+var
+  buf: TBuffer;
+  stt: TDomainCompressionTable;
+  offset: Cardinal;
+  offset2: Longint;
+  pl: TPayLoadTCP;
+  s: String;
+  dmbs: TDNSDomainByteStream;
+begin
+  SetLength(buf, 1024);
+  SetLength(stt,0);
+  offset := 0;
+  // initial str is uncompressed because compress table empty
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  offset2 := offset;
+  // write same domain, this time we get compression.
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  BufferToPayload(buf,pl);
+  s := stringfromlabel(pl, offset2);
+  AssertEquals(FAKEFQDN,s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompressWithUncompressedLabel;
+var
+  buf: TBuffer;
+  dmbs: TDNSDomainByteStream;
+  offset: Cardinal;
+  so: Longint;
+  stt: TDomainCompressionTable;
+  len: Word;
+  pl: TPayload;
+  s: String;
+begin
+  SetLength(buf, 1024);
+  SetLength(stt,0);
+  // compress table empty so no compression here.
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+  offset := 0;
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+
+  so := offset;
+  // should get compression on FAKEFQDN but label "foo" is written as full label.
+  dmbs := DomainNameToByteStream('foo.' + FAKEFQDN, stt);
+  len := CalcRdLength(dmbs);
+  // len is 4 for 'foo' (including its length byte) and 2 for the pointer.
+  AssertEquals(6, len);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  BufferToPayload(buf,pl);
+  s := stringfromlabel(pl, so);
+  AssertEquals('foo.'+FAKEFQDN,s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompressWithUncompressedLabelTCP;
+var
+  buf: TBuffer;
+  dmbs: TDNSDomainByteStream;
+  offset: Cardinal;
+  so: Longint;
+  stt: TDomainCompressionTable;
+  len: Word;
+  pl: TPayLoadTCP;
+  s: String;
+begin
+  SetLength(buf, 1024);
+  SetLength(stt,0);
+  // compress table empty so no compression here.
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+  offset := 0;
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+
+  so := offset;
+  // should get compression on FAKEFQDN but label "foo" is written as full label.
+  dmbs := DomainNameToByteStream('foo.' + FAKEFQDN, stt);
+  len := CalcRdLength(dmbs);
+  // len is 4 for 'foo' (including its length byte) and 2 for the pointer.
+  AssertEquals(6, len);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  BufferToPayload(buf,pl);
+  s := stringfromlabel(pl, so);
+  AssertEquals('foo.'+FAKEFQDN,s);
+end;
+
+{
+Test stringfromlabel with a compressed label at the end of the buffer.
+}
+procedure TNetDbTest.TestStringFromLabelCompressEndBuffer;
+var
+  buf: TBuffer;
+  stt: TDomainCompressionTable;
+  offset: Cardinal;
+  offset2: Longint;
+  pl: TPayload;
+  s: String;
+  dmbs: TDNSDomainByteStream;
+begin
+  SetLength(buf, 1024);
+  SetLength(stt,0);
+  offset := 0;
+
+  // initial str is uncompressed because compress table empty
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  offset2 := offset;
+  // write same domain, this time we get compression.
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+
+  // write the pointer at the end of the payload buffer
+  offset := Length(pl) - 2;
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  BufferToPayload(buf,pl);
+
+  // read back the label.
+  offset2 := Length(pl) - 2;
+  s := stringfromlabel(pl, offset2);
+  AssertEquals(FAKEFQDN,s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompressEndBufferTCP;
+var
+  buf: TBuffer;
+  stt: TDomainCompressionTable;
+  offset: Cardinal;
+  offset2: Longint;
+  pl: TPayLoadTCP;
+  s: String;
+  dmbs: TDNSDomainByteStream;
+begin
+  SetLength(buf, Length(pl));
+  SetLength(stt,0);
+  offset := 0;
+
+  // initial str is uncompressed because compress table empty
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  offset2 := offset;
+  // write same domain, this time we get compression.
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+
+  // write the pointer at the end of the payload buffer
+  offset := Length(pl) - 2;
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  BufferToPayload(buf,pl);
+
+  // read back the label.
+  offset2 := Length(pl) - 2;
+  s := stringfromlabel(pl, offset2);
+  AssertEquals(FAKEFQDN,s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompressSplit;
+var
+  pl: TPayload;
+  s: String;
+  offset: Longint;
+begin
+  // fill the buffer with 'A' so that we'll know if stringfromlabel read any
+  // of it.
+  FillByte(pl, Length(pl), 65);
+  offset := Length(pl) - 1;
+  pl[offset] := 192;
+  s := stringfromlabel(pl, offset);
+  AssertEquals('', s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompressSplitTCP;
+var
+  pl: TPayLoadTCP;
+  s: String;
+  offset: Longint;
+begin
+  // fill the buffer with 'A' so that we'll know if stringfromlabel read any
+  // of it.
+  FillByte(pl, Length(pl), 65);
+  offset := Length(pl) - 1;
+  pl[offset] := 192;
+  s := stringfromlabel(pl, offset);
+  AssertEquals('', s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompressPtrFwd;
+var
+  pl: TPayload;
+  s: String;
+  offset: Longint;
+  ptr: TDNSDomainPointer;
+begin
+  FillByte(pl, Length(pl), 0);
+  Move('foo', pl[21], 3);
+  pl[20] := 3;
+
+  ptr := GetDnsDomainPointer(32); // offset 20 + 12 for the header
+  offset := 0;
+  pl[offset] := ptr.b1;
+  pl[offset+1] := ptr.b2;
+  s := stringfromlabel(pl, offset);
+  AssertEquals('', s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompressPtrFwdTCP;
+var
+  pl: TPayLoadTCP;
+  s: String;
+  offset: Longint;
+  ptr: TDNSDomainPointer;
+begin
+  FillByte(pl, Length(pl), 0);
+  Move('foo', pl[21], 3);
+  pl[20] := 3;
+
+  ptr := GetDnsDomainPointer(32); // offset 20 + 12 for the header
+  offset := 0;
+  pl[offset] := ptr.b1;
+  pl[offset+1] := ptr.b2;
+  s := stringfromlabel(pl, offset);
+  AssertEquals('', s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompressAllPtrStart;
+var
+  pl: TPayload;
+  s: String;
+  offset: Longint;
+begin
+  FillByte(pl, Length(pl), 192);
+  offset := 0;
+  s := stringfromlabel(pl, offset);
+  AssertEquals('', s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelCompressAllPtrStartTCP;
+var
+  pl: TPayLoadTCP;
+  s: String;
+  offset: Longint;
+begin
+  FillByte(pl, Length(pl), 192);
+  offset := 0;
+  s := stringfromlabel(pl, offset);
+  AssertEquals('', s);
+end;
+
+{
+Test what happens when pointer is 0.
+}
+procedure TNetDbTest.TestStringFromLabelCompressedZero;
+var
+  pl: TPayLoad;
+  s: String;
+  offset: Longint;
+  ptr: TDNSDomainPointer;
+begin
+  FillByte(pl, Length(pl), 0);
+  pl[0] := 1;
+  pl[1] := Ord('a');
+  ptr := GetDnsDomainPointer(0);
+  offset := 5;
+  pl[offset] := ptr.b1;
+  pl[offset+1] := ptr.b2;
+
+  s := stringfromlabel(pl, offset);
+  AssertEquals('', s);
+end;
+
+{
+Test what happens when pointer is 0.
+}
+procedure TNetDbTest.TestStringFromLabelCompressedZeroTCP;
+var
+  pl: TPayLoadTCP;
+  s: String;
+  offset: Longint;
+  ptr: TDNSDomainPointer;
+begin
+  FillByte(pl, Length(pl), 0);
+  pl[0] := 1;
+  pl[1] := Ord('a');
+  ptr := GetDnsDomainPointer(0);
+  offset := 5;
+  pl[offset] := ptr.b1;
+  pl[offset+1] := ptr.b2;
+
+  s := stringfromlabel(pl, offset);
+  AssertEquals('', s);
+end;
+
+procedure TNetDbTest.TestStringFromLabelInfiniteLoop;
+var
+  buf: TBuffer;
+  stt: TDomainCompressionTable;
+  offset: Cardinal;
+  offset2: Longint;
+  pl: TPayload;
+  s: String;
+  dmbs: TDNSDomainByteStream;
+  ptr: TDNSDomainPointer;
+begin
+  SetLength(buf, 1024);
+  SetLength(stt,0);
+  offset := 0;
+  // initial str is uncompressed because compress table empty
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  ptr := GetDnsDomainPointer(12);
+
+  // offset now points to 0 byte at end of label. We're overwriting that
+  // 0 so that stringfromlabel will be tricked into a loop.
+  Dec(offset);
+  Move(ptr.ba, buf[offset], 2);
+
+  BufferToPayload(buf,pl);
+  offset2 := 0;
+  s := stringfromlabel(pl, offset2);
+  // if stringfromlabel returns at all then the test passed.
+end;
+
+procedure TNetDbTest.TestStringFromLabelInfiniteLoopTCP;
+var
+  buf: TBuffer;
+  stt: TDomainCompressionTable;
+  offset: Cardinal;
+  offset2: Longint;
+  pl: TPayLoadTCP;
+  s: String;
+  dmbs: TDNSDomainByteStream;
+  ptr: TDNSDomainPointer;
+begin
+  SetLength(buf, 1024);
+  SetLength(stt,0);
+  offset := 0;
+  // initial str is uncompressed because compress table empty
+  dmbs := DomainNameToByteStream(FAKEFQDN, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  ptr := GetDnsDomainPointer(12);
+
+  // offset now points to 0 byte at end of label. We're overwriting that
+  // 0 so that stringfromlabel will be tricked into a loop.
+  Dec(offset);
+  Move(ptr.ba, buf[offset], 2);
+
+  BufferToPayload(buf,pl);
+  offset2 := 0;
+  s := stringfromlabel(pl, offset2);
+  // if stringfromlabel returns at all then the test passed.
+end;
+
+procedure TNetDbTest.TestCompressShortDomain;
+const
+  shortdomain = 'a.b';
+var
+  buf: TBuffer;
+  stt: TDomainCompressionTable;
+  offset: Cardinal;
+  offset2: Longint;
+  pl: TPayload;
+  s: String;
+  dmbs: TDNSDomainByteStream;
+begin
+  SetLength(buf, 1024);
+  SetLength(stt,0);
+  offset := 0;
+  // initial str is uncompressed because compress table empty
+  dmbs := DomainNameToByteStream(shortdomain, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  offset2 := offset;
+  // second str is compressed
+  dmbs := DomainNameToByteStream(shortdomain, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+
+  BufferToPayload(buf,pl);
+  s := stringfromlabel(pl, offset2);
+  AssertEquals(shortdomain, s);
+end;
+
+procedure TNetDbTest.TestCompressShortDomainTCP;
+const
+  shortdomain = 'a.b';
+var
+  buf: TBuffer;
+  stt: TDomainCompressionTable;
+  offset: Cardinal;
+  offset2: Longint;
+  pl: TPayLoadTCP;
+  s: String;
+  dmbs: TDNSDomainByteStream;
+begin
+  SetLength(buf, 1024);
+  SetLength(stt,0);
+  offset := 0;
+  // initial str is uncompressed because compress table empty
+  dmbs := DomainNameToByteStream(shortdomain, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+  offset2 := offset;
+  // second str is compressed
+  dmbs := DomainNameToByteStream(shortdomain, stt);
+  WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs);
+
+  BufferToPayload(buf,pl);
+  s := stringfromlabel(pl, offset2);
+  AssertEquals(shortdomain, s);
+end;
+
+procedure TNetDbTest.SetUp;
+begin
+  tsl := TStringList.Create;
+end;
+
+procedure TNetDbTest.TearDown;
+begin
+  tsl.Free;
+end;
+
+procedure TNetDbTest.CopyBytesTo(var buf: TPayLoad; startidx, destidx,
+  count: Word);
+begin
+  // no tests for overlapping source and dest.
+  if ((startidx+count) > Length(buf)) or ((destidx+count) > Length(buf)) then
+    exit;
+  Move(buf[startidx], buf[destidx], count);
+end;
+
+procedure TNetDbTest.CopyBytesTo(var buf: TPayLoadTCP; startidx, destidx,
+  count: Word);
+begin
+  // no tests for overlapping source and dest.
+  if ((startidx+count) > Length(buf)) or ((destidx+count) > Length(buf)) then
+    exit;
+  Move(buf[startidx], buf[destidx], count);
+end;
+
+function TNetDbTest.WriteNumToBuffer(var buf: TBuffer; var offset: Cardinal;
+  val: Word): Word;
+begin
+  Result := 0;
+  if (offset + SizeOf(val)) > Length(buf) then exit;
+  Move(HToNs(val), buf[offset], SizeOf(val));
+  Inc(offset, SizeOf(val));
+  Result := SizeOf(val);
+end;
+
+function TNetDbTest.WriteNumToBuffer(var buf: TBuffer; var offset: Cardinal;
+  val: Cardinal): Word;
+begin
+  Result := 0;
+  if (offset + SizeOf(val)) > Length(buf) then exit;
+  Move(HToNl(val), buf[offset], SizeOf(val));
+  Inc(offset, SizeOf(val));
+  Result := SizeOf(val);
+end;
+
+{
+Write a number to the buffer without converting it to network byte order.
+}
+function TNetDbTest.WriteNumToBufferN(var buf: TBuffer; var offset: Cardinal;
+  val: Word): Word;
+begin
+  Result := 0;
+  if (offset + SizeOf(val)) > Length(buf) then exit;
+  Move(val, buf[offset], SizeOf(val));
+  Inc(offset, SizeOf(val));
+  Result := SizeOf(val);
+end;
+
+{
+Write a number to the buffer without converting it to network byte order.
+}
+function TNetDbTest.WriteNumToBufferN(var buf: TBuffer; var offset: Cardinal;
+  val: Cardinal): Word;
+begin
+  Result := 0;
+  if (offset + SizeOf(val)) > Length(buf) then exit;
+  Move(val, buf[offset], SizeOf(val));
+  Inc(offset, SizeOf(val));
+  Result := SizeOf(val);
+end;
+
+{
+Write an RR to the byte buffer. No compression of domain names will occur.
+}
+function TNetDbTest.WriteRRToBuffer(var buf: TBuffer; var offset: Cardinal;
+  rr: TFakeRR): Word;
+var
+  s,etw: Word;
+  dmbs: TDNSDomainByteStream;
+  res: TRDataWriteRes;
+begin
+  etw := 0;
+  s := offset;
+  // write the RR Name
+  dmbs := DomainNameToByteStream(rr.RRName);
+  etw := CalcRdLength(dmbs);
+  if WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs) < etw
+  then
+    Fail('Cannot write RR name to buffer at offset '+ inttostr(offset));
+
+  if (offset + SizeOf(rr.Atype) + SizeOf(rr.AClass) + SizeOf(rr.TTL)) >
+    Length(buf)
+  then
+    Fail('Not enough space to add RR type,class,ttl at offset '+ inttostr(offset));
+
+  // Write the RR type, class and TTL.
+  WriteNumToBuffer(buf, offset,rr.Atype);
+  WriteNumToBuffer(buf, offset, rr.AClass);
+  WriteNumToBuffer(buf, offset, rr.TTL);
+
+  // now the RR data, which is type specific. Each type-specific method
+  // also writes the RDLength word, so we have to account for 2 additional
+  // bytes.
+  case rr.Atype of
+    DNSQRY_A:
+        res := WriteAasRData(buf, offset, rr.ip);
+    DNSQRY_AAAA:
+        res := WriteAAAAasRData(buf, offset, rr.ip6);
+    DNSQRY_SOA:
+        res := WriteSOAasRData(buf, offset, rr.fsoa);
+    DNSQRY_MX:
+        res := WriteMXAsRData(buf, offset, rr.fmx);
+    DNSQRY_NS:
+      begin
+        dmbs := DomainNameToByteStream(rr.nsh);
+        res := WriteDomainAsRdata(buf,offset,dmbs);
+      end;
+    DNSQRY_PTR:
+      begin
+        dmbs := DomainNameToByteStream(rr.nsh);
+        res := WriteDomainAsRdata(buf,offset,dmbs);
+      end;
+    DNSQRY_CNAME:
+      begin
+        dmbs := DomainNameToByteStream(rr.cn);
+        res := WriteDomainAsRdata(buf,offset,dmbs);
+      end;
+    DNSQRY_TXT:
+        res := WriteTextRecAsRData(buf, offset, rr.txtarr);
+    DNSQRY_SRV:
+        res := WriteSRVasRData(buf, offset, rr.fsrv);
+  else
+    Fail('Called to handle RR type '+inttostr(rr.Atype)+
+      ' but no code to handle it.');
+  end;
+
+  if res.bw < res.etw then
+    Fail('Unable to write RR of type ' +inttostr(RR.Atype) +
+      ', name "'  + rr.RRName + '" to buffer at offset '+inttostr(offset)+
+      '. Wrote '+inttostr(res.bw)+' bytes, expected to write '+
+        inttostr(res.etw)+' bytes.');
+
+  Result := offset - s;
+end;
+
+{
+Write an RR to the output buffer, with compression of domain names turned on.
+}
+function TNetDbTest.WriteRRToBuffer(var buf: TBuffer; var offset: Cardinal;
+  rr: TFakeRR; var ctbl: TDomainCompressionTable): Word;
+var
+  s, etw: Word;
+  dmbs: TDNSDomainByteStream;
+  res: TRDataWriteRes;
+begin
+  etw := 0;
+  s := offset;
+  // write the RR Name
+  dmbs := DomainNameToByteStream(rr.RRName, ctbl);
+  etw := CalcRdLength(dmbs);
+  if WriteDNSDomainByteStreamToBuffer(buf, offset, dmbs) < etw
+  then
+    Fail('Cannot write RR name to buffer at offset '+ inttostr(offset));
+
+  if (offset + SizeOf(rr.Atype) + SizeOf(rr.AClass) + SizeOf(rr.TTL)) >
+    Length(buf)
+  then
+    Fail('Not enough space to add RR type,class,ttl at offset '+ inttostr(offset));
+
+  // Write the RR type, class and TTL.
+  WriteNumToBuffer(buf, offset,rr.Atype);
+  WriteNumToBuffer(buf, offset, rr.AClass);
+  WriteNumToBuffer(buf, offset, rr.TTL);
+
+  // now the RR data, which is type specific. Each type-specific method
+  // also writes the RDLength word, so we have to account for 2 additional
+  // bytes.
+  case rr.Atype of
+    DNSQRY_A:
+      begin
+        res := WriteAasRData(buf, offset, rr.ip);
+      end;
+    DNSQRY_AAAA:
+      begin
+        res := WriteAAAAasRData(buf, offset, rr.ip6);
+      end;
+    DNSQRY_SOA:
+      begin
+        res := WriteSOAasRData(buf, offset, rr.fsoa);
+      end;
+    DNSQRY_MX:
+      begin
+        res := WriteMXAsRData(buf, offset, rr.fmx, ctbl);
+      end;
+    DNSQRY_NS:
+      begin
+        dmbs := DomainNameToByteStream(rr.nsh, ctbl);
+        res := WriteDomainAsRdata(buf,offset,dmbs);
+      end;
+    DNSQRY_PTR:
+      begin
+        dmbs := DomainNameToByteStream(rr.nsh, ctbl);
+        res := WriteDomainAsRdata(buf,offset,dmbs);
+      end;
+    DNSQRY_CNAME:
+      begin
+        dmbs := DomainNameToByteStream(rr.cn, ctbl);
+        res := WriteDomainAsRdata(buf,offset,dmbs);
+      end;
+    DNSQRY_TXT:
+      begin
+        res := WriteTextRecAsRData(buf, offset, rr.txtarr);
+      end;
+    DNSQRY_SRV:
+      begin
+        res := WriteSRVasRData(buf, offset, rr.fsrv);
+      end;
+  else
+    Fail('Called to handle RR type '+inttostr(rr.Atype)+
+      ' but no code to handle it.');
+  end;
+
+  if res.bw < res.etw then
+    Fail('Unable to write RR of type ' +inttostr(RR.Atype) +
+      ', name "'  + rr.RRName + '" to buffer at offset '+inttostr(offset)+
+      '. Wrote '+inttostr(res.bw)+' bytes, expected to write '+
+        inttostr(res.etw)+' bytes.');
+
+  Result := offset - s;
+end;
+
+{
+Turn a fake DNS response into a payload buffer. This is a byte buffer minus the
+DNS header. That is, the buffer begins with the question part of the response,
+after which comes the RRs of the answers, authority, and additional sections.
+}
+function TNetDbTest.FakeDNSResponseToByteBuffer(fdr: TFakeDNSResponse; out
+  buf: TBuffer; compress: Boolean): Cardinal;
+var
+  offset: Cardinal;
+  rr: TFakeRR;
+  dbs: TDNSDomainByteStream;
+begin
+  // plenty of room for our test responses. could precalculate this, but there's
+  // no benefit. The return value of this function is the length of the
+  // DNS reply, which we get for free since we have to track our offset into
+  // the buffer as we write it.
+  SetLength(buf, 2048);
+  offset := 0;
+
+  if compress then
+    dbs := DomainNameToByteStream(fdr.qry.nm,fdr.strtable)
+  else
+    dbs := DomainNameToByteStream(fdr.qry.nm);
+
+  // The question section consists of the dns query name, the qtype and
+  // qclass.
+  if WriteDNSDomainByteStreamToBuffer(buf, offset, dbs) < CalcRdLength(dbs)
+  then
+    Fail('Cannot write name to buffer at offset '+ inttostr(offset));
+
+  WriteNumToBuffer(buf, offset, fdr.qry.qtype);
+  WriteNumToBuffer(buf, offset, fdr.qry.qclass);
+
+  // Now the answer sections.
+  for rr in fdr.answers do
+    if compress then
+      WriteRRToBuffer(buf, offset, rr, fdr.strtable)
+    else
+      WriteRRToBuffer(buf, offset, rr);
+  for rr in fdr.authority do
+    if compress then
+      WriteRRToBuffer(buf, offset, rr, fdr.strtable)
+    else
+      WriteRRToBuffer(buf, offset, rr);
+  for rr in fdr.additional do
+    if compress then
+      WriteRRToBuffer(buf, offset, rr, fdr.strtable)
+    else
+      WriteRRToBuffer(buf, offset, rr);
+
+  SetLength(buf, offset);
+  Result := offset;
+end;
+
+{
+Generate a TPayload buffer, a fixed-length array of byte, from the TBuffer
+type, which is a variable-length array of byte.
+}
+function TNetDbTest.BufferToPayload(const buf: TBuffer;
+  out pl: TPayload): Boolean;
+begin
+  Result := False;
+  FillChar(pl,Length(pl),0);
+  Move(buf[0], pl[0], Min(Length(pl),Length(buf)));
+  Result := True;
+end;
+
+function TNetDbTest.BufferToPayload(const buf: TBuffer;
+  out pl: TPayLoadTCP): Boolean;
+begin
+  Result := False;
+  FillChar(pl,Length(pl),0);
+  Move(buf[0], pl[0], Min(Length(pl),Length(buf)));
+  Result := True;
+end;
+
+function TNetDbTest.BuildQueryData(fdr: TFakeDNSResponse; out qd: TQueryData;
+  out qlen: Word; Compress: Boolean = False): Boolean;
+var
+  buf: TBuffer;
+begin
+  qlen := FakeDNSResponseToByteBuffer(fdr, buf, Compress);
+  qd.h.ancount := HToNs(fdr.hdr.ancount);
+  qd.h.arcount := HToNs(fdr.hdr.arcount);
+  qd.h.nscount := HToNs(fdr.hdr.nscount);
+  qd.h.qdcount := HToNs(fdr.hdr.qdcount);
+  qd.h.flags1 := fdr.hdr.flags1;
+  qd.h.flags2 := fdr.hdr.flags2;
+  qd.h.id[0] := fdr.hdr.id[0];
+  qd.h.id[1] := fdr.hdr.id[1];
+  Result := BufferToPayload(buf, qd.Payload);
+end;
+
+function TNetDbTest.BuildQueryData(fdr: TFakeDNSResponse; out
+  qd: TQueryDataLengthTCP; out qlen: Word; Compress: Boolean = False): Boolean;
+var
+  buf: TBuffer;
+begin
+  qlen := FakeDNSResponseToByteBuffer(fdr, buf, Compress);
+  qd.h.ancount := HToNs(fdr.hdr.ancount);
+  qd.h.arcount := HToNs(fdr.hdr.arcount);
+  qd.h.nscount := HToNs(fdr.hdr.nscount);
+  qd.h.qdcount := HToNs(fdr.hdr.qdcount);
+  qd.h.flags1 := fdr.hdr.flags1;
+  qd.h.flags2 := fdr.hdr.flags2;
+  qd.h.id[0] := fdr.hdr.id[0];
+  qd.h.id[1] := fdr.hdr.id[1];
+  Result := BufferToPayload(buf, qd.Payload);
+end;
+
+{
+Create a deliberately invalid DNS response to test our API's ability to cope
+with invalid data without causing memory corruption.
+
+After building a valid DNS response as normal, we truncate it at the given
+offset.}
+function TNetDbTest.BuildTruncatedQueryData(fdr: TFakeDNSResponse; out
+  qd: TQueryData; out qlen: Word; truncoffset: Word): Boolean;
+var
+  buf: TBuffer;
+begin
+  qlen := FakeDNSResponseToByteBuffer(fdr, buf);
+  qd.h.ancount := HToNs(fdr.hdr.ancount);
+  qd.h.arcount := HToNs(fdr.hdr.arcount);
+  qd.h.nscount := HToNs(fdr.hdr.nscount);
+  qd.h.qdcount := HToNs(fdr.hdr.qdcount);
+  qd.h.flags1 := fdr.hdr.flags1;
+  qd.h.flags2 := fdr.hdr.flags2;
+  qd.h.id[0] := fdr.hdr.id[0];
+  qd.h.id[1] := fdr.hdr.id[1];
+  SetLength(buf, truncoffset);
+  Result := BufferToPayload(buf, qd.Payload);
+end;
+
+initialization
+
+  RegisterTest(TNetDbTest);
+end.
+
Index: packages/fcl-net/tests/tresolvertests.pp
===================================================================
--- packages/fcl-net/tests/tresolvertests.pp	(nonexistent)
+++ packages/fcl-net/tests/tresolvertests.pp	(working copy)
@@ -0,0 +1,28 @@
+program tresolvertests;
+
+{$mode objfpc}{$H+}
+
+uses
+  Classes, consoletestrunner, netdbtest;
+
+type
+
+  { TMyTestRunner }
+
+  TMyTestRunner = class(TTestRunner)
+  protected
+  // override the protected methods of TTestRunner to customize its behavior
+  end;
+
+var
+  Application: TMyTestRunner;
+
+begin
+  DefaultFormat:=fPlain;
+  DefaultRunAllTests:=True;
+  Application := TMyTestRunner.Create(nil);
+  Application.Initialize;
+  Application.Title:='resolvertests';
+  Application.Run;
+  Application.Free;
+end.
Index: tests/Makefile.fpc
===================================================================
--- tests/Makefile.fpc	(revision 48358)
+++ tests/Makefile.fpc	(working copy)
@@ -164,7 +164,7 @@
 TESTSUBDIRS=cg cg/variants cg/cdecl cpu16 cpu16/i8086 library opt $(addprefix units/,$(TESTUNITDIRS))
 TESTPACKAGESDIRS=win-base webtbs hash fcl-registry fcl-process zlib fcl-db fcl-xml cocoaint bzip2
 TESTPACKAGESUBDIRS=$(addprefix packages/,$(TESTPACKAGESDIRS))
-TESTPACKAGESDIRECTDIRS=rtl-objpas rtl-generics hash regexpr
+TESTPACKAGESDIRECTDIRS=rtl-objpas rtl-generics hash regexpr fcl-net
 TESTPACKAGESDIRECTSUBDIRS=$(addprefix ../packages/,$(addsuffix /tests,$(TESTPACKAGESDIRECTDIRS)))
 
 ifdef QUICKTEST
netdb-tests.patch (165,280 bytes)   
netdb.patch (36,697 bytes)   
Index: packages/fcl-net/src/netdb.pp
===================================================================
--- packages/fcl-net/src/netdb.pp	(revision 48358)
+++ packages/fcl-net/src/netdb.pp	(working copy)
@@ -84,11 +84,37 @@
   MaxRecursion = 10;
   MaxIP4Mapped = 10;
 
+  { from http://www.iana.org/assignments/dns-parameters }
+  DNSQRY_A     = 1;                     // name to IP address
+  DNSQRY_AAAA  = 28;                    // name to IP6 address
+  DNSQRY_A6    = 38;                    // name to IP6 (new)
+  DNSQRY_PTR   = 12;                    // IP address to name 
+  DNSQRY_MX    = 15;                    // name to MX 
+  DNSQRY_TXT   = 16;                    // name to TXT
+  DNSQRY_CNAME = 5;
+  DNSQRY_SOA   = 6;
+  DNSQRY_NS    = 2;
+  DNSQRY_SRV   = 33;
+
+  // Flags 1
+  QF_QR     = $80;
+  QF_OPCODE = $78;
+  QF_AA     = $04;
+  QF_TC     = $02;  // Truncated.
+  QF_RD     = $01;
+
+  // Flags 2
+  QF_RA     = $80;
+  QF_Z      = $70;
+  QF_RCODE  = $0F;
+
 var
   EtcPath: string;
 {$endif FPC_USE_LIBC}
 
 Type
+  TDNSRcode = (rcNoError, rcFormatError,rcServFail,rcNXDomain,
+    rcNotImpl,rcRefused,rcReserved,rcInvalid);
   TDNSServerArray = Array of THostAddr;
   TServiceEntry = record
     Name     : String;
@@ -134,6 +160,66 @@
   end;
 
 {$ifndef FPC_USE_LIBC}
+
+Type 
+  TPayLoad  = Array[0..511] of Byte;
+  TPayLoadTCP = Array[0 .. 65535] of Byte;
+
+  TDNSHeader = packed Record
+    id      : Array[0..1] of Byte;
+    flags1  : Byte;
+    flags2  : Byte;
+    qdcount : word;
+    ancount : word;
+    nscount : word;
+    arcount : word;
+  end;
+
+  TQueryData = packed Record
+    h: TDNSHeader;
+    Payload : TPayLoad;
+  end;
+
+  TQueryDataLength = packed record
+    length: Word;
+    hpl: TQueryData;
+  end;
+
+  TQueryDataLengthTCP = packed Record
+    length: Word;
+    h: TDNSHeader;
+    Payload : TPayLoadTCP;
+  end;
+
+  PRRData = ^TRRData;
+  TRRData = Packed record       // RR record
+    Atype    : Word;            // Answer type
+    AClass   : Word;
+    TTL      : Cardinal;
+    RDLength : Word;
+  end;
+
+  TRRNameData = packed record
+    RRName   : ShortString;
+    RRMeta   : TRRData;
+    RDataSt  : Word;
+  end;
+  TRRNameDataArray = array of TRRNameData;
+
+  TDNSDomainName = ShortString;
+  TDNSRR_SOA = packed record
+    mname, rname: TDNSDomainName;
+    serial,refresh,retry,expire,min: Cardinal;
+  end;
+  TDNSRR_MX = packed record
+    preference: Word;
+    exchange: TDNSDomainName;
+  end;
+  TDNSRR_SRV = packed record
+    priority, weight, port: Word;
+    target: TDNSDomainName;
+  end;
+
 Var
   DNSServers            : TDNSServerArray;
   DNSOptions            : String;
@@ -189,6 +275,82 @@
 Function ProcessHosts(FileName : String) : PHostListEntry;
 Function FreeHostsList(var List : PHostListEntry) : Integer;
 Procedure HostsListToArray(var List : PHostListEntry; Var Hosts : THostEntryArray; FreeList : Boolean);
+
+Procedure CheckResolveFile;
+Function Query(Resolver : Integer; Var Qry,Ans : TQueryData; QryLen : Integer; Var AnsLen : Integer) : Boolean;
+function QueryTCP(Resolver : Integer; Var Qry: TQueryDataLength;
+  var Ans: TQueryDataLengthTCP; QryLen : Integer; Var AnsLen : Integer) : Boolean;
+Function BuildPayLoad(Var Q : TQueryData; Name : String; RR : Word; QClass : Word) : Integer;
+Function BuildPayLoadTCP(Var Q : TQueryDataLength; Name : String; RR : Word; QClass : Word) : Integer;
+
+Function SkipAnsQueries(Var Ans : TQueryData; L : Integer) : integer;
+Function SkipAnsQueries(Var Ans : TQueryDataLengthTCP; L : Integer) : integer;
+
+function stringfromlabel(pl: TPayLoad; var start: Integer): string;
+function stringfromlabel(pl: TPayLoadTCP; var start: Integer): string;
+Function CheckAnswer(Const Qry : TDNSHeader; Var Ans : TDNSHeader) : Boolean;
+function IsValidAtype(atype: Word): Boolean;
+
+function IsTruncated(R: TDNSHeader): Boolean;
+function GetRcode(R: TDNSHeader): TDNSRcode;
+function GetFixlenStr(pl: TPayLoad; startidx: Cardinal; len: Byte;
+  out res: ShortString): Byte;
+function GetFixlenStr(pl: TPayLoadTCP; startidx: Cardinal; len: Byte;
+  out res: ShortString): Byte;
+
+function NextNameRR(const pl: TPayLoadTCP; start: Word;
+  out RRName: TRRNameData): Boolean;
+function NextNameRR(const pl: TPayLoad; start: Word;
+  out RRName: TRRNameData): Boolean;
+
+function GetRRrecords(const pl: TPayloadTCP; var Start: Word; Count: Word):
+  TRRNameDataArray;
+function GetRRrecords(const pl: TPayload; var Start: Word; Count: Word):
+  TRRNameDataArray;
+
+function DnsLookup(dn: String; qtype: Word; out Ans: TQueryData;
+  out AnsLen: Longint): Boolean;
+function DnsLookup(dn: String; qtype: Word; out Ans: TQueryDataLengthTCP;
+  out AnsLen: Longint): Boolean;
+
+function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out IP: THostAddr): Boolean;
+function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoad;
+  out IP: THostAddr): Boolean;
+function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoad;
+  out cn: TDNSDomainName): Boolean;
+function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out cn: TDNSDomainName): Boolean;
+function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out IP: THostAddr6): Boolean;
+function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoad;
+  out IP: THostAddr6): Boolean;
+function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out NSName: TDNSDomainName): Boolean;
+function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoad;
+  out NSName: TDNSDomainName): Boolean;
+function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out dnssoa: TDNSRR_SOA): Boolean;
+function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoad;
+  out dnssoa: TDNSRR_SOA): Boolean;
+function  DNSRRGetText(const RR: TRRNameData; const pl: TPayLoad;
+  out dnstext: AnsiString): Boolean;
+function  DNSRRGetText(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out dnstext: AnsiString): Boolean;
+function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out MX: TDNSRR_MX): Boolean;
+function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoad;
+  out MX: TDNSRR_MX): Boolean;
+function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoadTCP;
+  out ptr: TDNSDomainName): Boolean;
+function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoad;
+  out ptr: TDNSDomainName): Boolean;
+function DNSRRGetSRV(const RR: TRRNameData; const pl: TPayload;
+  out srv: TDNSRR_SRV): Boolean;
+function DNSRRGetSRV(const RR: TRRNameData; const pl: TPayloadTCP;
+  out srv: TDNSRR_SRV): Boolean;
+
+
 {$endif FPC_USE_LIBC}
 
 Implementation
@@ -201,54 +363,14 @@
    sysutils;
 
 {$ifndef FPC_USE_LIBC}
+type
+  TTCPSocketResult = (srTimeout,srPartial,srSocketClose,srOK);
+
 var
   DefaultDomainListArr : array of string;
   NDots: Integer;
 
-const
-  { from http://www.iana.org/assignments/dns-parameters }
-  DNSQRY_A     = 1;                     // name to IP address 
-  DNSQRY_AAAA  = 28;                    // name to IP6 address
-  DNSQRY_A6    = 38;                    // name to IP6 (new)
-  DNSQRY_PTR   = 12;                    // IP address to name 
-  DNSQRY_MX    = 15;                    // name to MX 
-  DNSQRY_TXT   = 16;                    // name to TXT
-  DNSQRY_CNAME = 5;
-
-  // Flags 1
-  QF_QR     = $80;
-  QF_OPCODE = $78;
-  QF_AA     = $04;
-  QF_TC     = $02;  // Truncated.
-  QF_RD     = $01;
-
-  // Flags 2
-  QF_RA     = $80;
-  QF_Z      = $70;
-  QF_RCODE  = $0F;
-
-
    
-Type 
-  TPayLoad  = Array[0..511] of Byte;
-  TQueryData = packed Record
-    id      : Array[0..1] of Byte;
-    flags1  : Byte;
-    flags2  : Byte; 
-    qdcount : word;
-    ancount : word;
-    nscount : word;
-    arcount : word;
-    Payload : TPayLoad;
-  end;
-  
-  PRRData = ^TRRData;
-  TRRData = Packed record       // RR record
-    Atype    : Word;            // Answer type
-    AClass   : Word;
-    TTL      : Cardinal;
-    RDLength : Word;
-  end;
 
 { ---------------------------------------------------------------------
     Some Parsing routines
@@ -685,9 +807,10 @@
   
 begin
   Result:=-1;
-  If length(Name)>506 then
+  If (Length(Name) = 0) or (length(Name)>506) then
     Exit;
-  Result:=0;  
+
+  Result:=0;
   P:=@Q.Payload[0];
   Repeat
     L:=Pos('.',Name);
@@ -695,6 +818,17 @@
       S:=Length(Name)
     else
       S:=L-1;
+    // empty label is invalid, unless it's a dot at the end.
+    if (S = 0) then
+    begin
+      if (Length(Name) > 0) then
+      begin
+        Result := -1;
+        exit;
+      end
+      else
+        break; // empty label at end, break out for final 0 length byte.
+    end;
     P[Result]:=S;
     Move(Name[1],P[Result+1],S);
     Inc(Result,S+1);
@@ -710,8 +844,24 @@
   Inc(Result,2);
 end;
 
+{Construct a TCP query payload from the given name, rr and qclass. The
+ principal difference between the TCP and UDP payloads is the two-octet
+ length field in the TCP payload. The UDP payload has no length field.
 
+ See RFC-1035, section 4.2.2.
 
+ Returns the length of the constructed payload, which doesn't include
+ the header or the length field.}
+function BuildPayLoadTCP(var Q: TQueryDataLength; Name: String; RR: Word;
+  QClass: Word): Integer;
+var
+  l: Word;
+begin
+  l := BuildPayLoad(Q.hpl, Name, RR, QClass);
+  Q.length := htons(l + SizeOf(Q.hpl.h));
+  Result := l;
+end;
+
 Function NextRR(Const PayLoad : TPayLoad;Var Start : LongInt; AnsLen : LongInt; Var RR : TRRData) : Boolean;
 
 Var
@@ -783,9 +933,8 @@
 { ---------------------------------------------------------------------
     QueryData handling functions
   ---------------------------------------------------------------------}
-  
-Function CheckAnswer(Const Qry : TQueryData; Var Ans : TQueryData) : Boolean;
 
+function CheckAnswer(const Qry: TDNSHeader; var Ans: TDNSHeader): Boolean;
 begin
   Result:=False;
   With Ans do
@@ -797,7 +946,7 @@
     If (Flags1 and QF_QR)=0 then
       exit;
     if (Flags1 and QF_OPCODE)<>0 then 
-      exit;  
+      exit;
     if (Flags2 and QF_RCODE)<>0 then
       exit;  
     // Number of answers ?  
@@ -808,6 +957,586 @@
     end;
 end;
 
+{
+ Check that Atype is valid. These are the DNSQRY_? params we support. See the
+ definitions at the top of this unit for the names.
+ Deliberately excluding axfr (252), mailb (253), maila (254), and * (255).
+}
+function IsValidAtype(atype: Word): Boolean;
+begin
+  Result := False;
+  case atype of
+    1 .. 16, 28, 33: Result := True;
+  end;
+end;
+
+function IsTruncated(R: TDNSHeader): Boolean;
+begin
+  Result := ((R.flags1 and QF_TC) > 0);
+end;
+
+function GetRcode(R: TDNSHeader): TDNSRcode;
+var
+  rcode_n: Byte;
+begin
+  rcode_n := (R.flags2 and QF_RCODE);
+  case rcode_n of
+    0: Result := rcNoError;
+    1: Result := rcFormatError;
+    2: Result := rcServFail;
+    3: Result := rcNXDomain;
+    4: Result := rcNotImpl;
+    5: Result := rcRefused;
+    6 .. 15: Result := rcReserved;
+  else
+    Result := rcInvalid;
+  end;
+end;
+
+function GetFixlenStr(pl: TPayLoad; startidx: Cardinal; len: Byte; out
+  res: ShortString): Byte;
+begin
+  Result := 0;
+  res := '';
+  if (startidx + len) > Length(pl) then exit;
+  SetLength(res, len);
+  Move(pl[startidx], res[1], len);
+  Result := len;
+end;
+
+function GetFixlenStr(pl: TPayLoadTCP; startidx: Cardinal; len: Byte;
+  out res: ShortString): Byte;
+begin
+  Result := 0;
+  res := '';
+  if (startidx + len) > Length(pl) then exit;
+  SetLength(res, len);
+  Move(pl[startidx], res[1], len);
+  Result := len;
+end;
+
+function NextNameRR(const pl: TPayLoadTCP; start: Word; out RRName: TRRNameData
+  ): Boolean;
+var
+  I : Integer;
+  PA : PRRData;
+
+begin
+  Result:=False;
+  I:=Start;
+  if (Length(pl) - I) < (SizeOf(TRRData)+2) then exit;
+  RRName.RRName := stringfromlabel(pl, I);
+  if (Length(pl) - I) < (SizeOf(TRRData)) then exit;
+
+  PA:=PRRData(@pl[I]);
+  RRName.RRMeta := PA^;
+  RRName.RRMeta.AClass := NToHs(RRName.RRMeta.AClass);
+  RRName.RRMeta.Atype := NToHs(RRName.RRMeta.Atype);
+  if not IsValidAtype(RRName.RRMeta.Atype) then
+    exit;
+  RRName.RRMeta.RDLength := NToHs(RRName.RRMeta.RDLength);
+  RRName.RRMeta.TTL := NToHl(RRName.RRMeta.TTL);
+  RRName.RDataSt := I+SizeOf(TRRData);
+  // verify that start + rdlength is within the buffer boundary.
+  if RRName.RDataSt + RRName.RRMeta.RDLength > Length(pl) then exit;
+  Result := True;
+end;
+
+function NextNameRR(const pl: TPayLoad; start: Word; out RRName: TRRNameData
+  ): Boolean;
+var
+  I : Integer;
+  PA : PRRData;
+
+begin
+  Result:=False;
+  I:=Start;
+  if (Length(pl) - I) < (SizeOf(TRRData)+2) then exit;
+  RRName.RRName := stringfromlabel(pl, I);
+  if (Length(pl) - I) < (SizeOf(TRRData)) then exit;
+
+  PA:=PRRData(@pl[I]);
+  RRName.RRMeta := PA^;
+  RRName.RRMeta.AClass := NToHs(RRName.RRMeta.AClass);
+  RRName.RRMeta.Atype := NToHs(RRName.RRMeta.Atype);
+  if not IsValidAtype(RRName.RRMeta.Atype) then
+    exit;
+
+  RRName.RRMeta.RDLength := NToHs(RRName.RRMeta.RDLength);
+  RRName.RRMeta.TTL := NToHl(RRName.RRMeta.TTL);
+  RRName.RDataSt := I+SizeOf(TRRData);
+  // verify that start + rdlength is within the buffer boundary.
+  if RRName.RDataSt + RRName.RRMeta.RDLength > Length(pl) then exit;
+  Result := True;
+end;
+
+function GetRRrecords(const pl: TPayloadTCP; var Start: Word; Count: Word
+  ): TRRNameDataArray;
+var
+  I, Total: Word;
+  B: Boolean;
+  RRN: TRRNameData;
+
+begin
+  I:=0;
+  Total := 0;
+  SetLength(Result,Count);
+  while (I < Count) do
+  begin
+    B := NextNameRR(pl, Start, RRN);
+    if not B then break;
+    Inc(Total);
+    Result[I] := RRN;
+    Inc(I);
+    Start := RRN.RDataSt+RRN.RRMeta.RDLength;
+  end;
+  if Total < Count then SetLength(Result,Total);
+end;
+
+function GetRRrecords(const pl: TPayload; var Start: Word; Count: Word
+  ): TRRNameDataArray;
+var
+  I, Total: Word;
+  B: Boolean;
+  RRN: TRRNameData;
+
+begin
+  I:=0;
+  Total := 0;
+  SetLength(Result,Count);
+  while (I < Count) do
+  begin
+    B := NextNameRR(pl, Start, RRN);
+    if not B then break;
+    Inc(Total);
+    Result[I] := RRN;
+    Inc(I);
+    Start := RRN.RDataSt+RRN.RRMeta.RDLength;
+  end;
+  if Total < Count then SetLength(Result,Total);
+end;
+
+function DnsLookup(dn: String; qtype: Word; out Ans: TQueryData; out
+  AnsLen: Longint): Boolean;
+var
+  Qry: TQueryData;
+  QryLen: Longint;
+  idx: Word;
+begin
+  Result := False;
+  AnsLen := -2;
+
+  CheckResolveFile;
+  if Length(DNSServers) = 0 then
+    exit;
+
+  QryLen := BuildPayLoad(Qry, dn, qtype, 1);
+  if QryLen <= 0 then exit;
+
+  AnsLen := -1;
+  { Try the query at each configured resolver in turn, until one of them
+   returns an answer. We check for AnsLen > -1 because we need to distinguish
+   between failure to connect and the server saying it doesn't know or can't
+   answer. If AnsLen = -1 then we failed to connect. If AnsLen >= 0 but qr
+   = False, then we connected but the server returned an error code.}
+  idx := 0;
+  repeat
+    Result := Query(idx,Qry,Ans,QryLen,AnsLen);
+    Inc(idx);
+  until (idx > High(DNSServers)) or (Result = True) or (AnsLen >= 0);
+end;
+
+function DnsLookup(dn: String; qtype: Word; out Ans: TQueryDataLengthTCP; out
+  AnsLen: Longint): Boolean;
+var
+  Qry: TQueryDataLength;
+  QryLen: Longint;
+  idx: Word;
+
+begin
+  Result := False;
+  AnsLen := -2;
+
+  CheckResolveFile;
+  if Length(DNSServers) = 0 then
+    exit;
+
+  QryLen:=BuildPayLoadTCP(Qry, dn, qtype, 1);
+  if QryLen <= 0 then exit;
+  AnsLen := -1;
+
+  { Try the query at each configured resolver in turn, until one of them
+   returns an answer. We check for AnsLen > -1 because we need to distinguish
+   between failure to connect and the server saying it doesn't know or can't
+   answer. If AnsLen = -1 then we failed to connect. If AnsLen >= 0 but qr
+   = False, then we connected but the server returned an error code.}
+  idx := 0;
+  repeat
+    Result := QueryTCP(idx,Qry,Ans,QryLen,AnsLen);
+    Inc(idx);
+  until (idx > High(DNSServers)) or (Result = True) or (AnsLen >= 0);
+end;
+
+function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  IP: THostAddr): Boolean;
+begin
+  IP.s_addr := 0;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_A then exit;
+  if (Length(pl) - RR.RDataSt) < 4 then exit;
+  Move(pl[RR.RDataSt], IP, SizeOf(THostAddr));
+  IP.s_addr := NToHl(IP.s_addr);
+  Result := True;
+end;
+
+function DNSRRGetA(const RR: TRRNameData; const pl: TPayLoad; out IP: THostAddr
+  ): Boolean;
+begin
+  IP.s_addr := 0;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_A then exit;
+  if (Length(pl) - RR.RDataSt) < 4 then exit;
+  Move(pl[RR.RDataSt], IP, SizeOf(THostAddr));
+  IP.s_addr := NToHl(IP.s_addr);
+  Result := True;
+end;
+
+function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoad; out
+  cn: TDNSDomainName): Boolean;
+var
+  n: Integer;
+begin
+  Result := False;
+  cn := '';
+  if RR.RRMeta.Atype <> DNSQRY_CNAME then exit;
+  n := RR.RDataSt;
+  if (RR.RDataSt + RR.RRMeta.RDLength) > Length(pl) then exit;
+  cn := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetCNAME(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  cn: TDNSDomainName): Boolean;
+var
+  n: Integer;
+begin
+  Result := False;
+  cn := '';
+  if RR.RRMeta.Atype <> DNSQRY_CNAME then exit;
+  n := RR.RDataSt;
+  if (n + RR.RRMeta.rdlength) > Length(pl) then exit;
+  cn := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  IP: THostAddr6): Boolean;
+begin
+  IP.s6_addr32[0] := 0;
+  IP.s6_addr32[1] := 0;
+  IP.s6_addr32[2] := 0;
+  IP.s6_addr32[3] := 0;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_AAAA then exit;
+  if (RR.RDataSt + SizeOf(THostAddr6)) > Length(pl) then exit;
+  Move(pl[RR.RDataSt],IP,SizeOf(THostAddr6));
+  Result := True;
+end;
+
+function DNSRRGetAAAA(const RR: TRRNameData; const pl: TPayLoad; out
+  IP: THostAddr6): Boolean;
+begin
+  IP.s6_addr32[0] := 0;
+  IP.s6_addr32[1] := 0;
+  IP.s6_addr32[2] := 0;
+  IP.s6_addr32[3] := 0;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_AAAA then exit;
+  if (RR.RDataSt + SizeOf(THostAddr6)) > Length(pl) then exit;
+  Move(pl[RR.RDataSt],IP,SizeOf(THostAddr6));
+  Result := True;
+end;
+
+function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  NSName: TDNSDomainName): Boolean;
+var
+  n: LongInt;
+begin
+  NSName := '';
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_NS then exit;
+  if (RR.RDataSt + RR.RRMeta.RDLength) > Length(pl) then exit;
+  n := RR.RDataSt;
+  NSName := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetNS(const RR: TRRNameData; const pl: TPayLoad; out
+  NSName: TDNSDomainName): Boolean;
+var
+  n: LongInt;
+begin
+  NSName := '';
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_NS then exit;
+  if (RR.RDataSt + RR.RRMeta.RDLength) > Length(pl) then exit;
+  n := RR.RDataSt;
+  NSName := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  dnssoa: TDNSRR_SOA): Boolean;
+var
+  idx: Integer;
+begin
+  // can't trust the counts we've been given, so check that we never
+  // exceed the end of the payload buffer.
+  idx := RR.RDataSt;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_SOA then exit;
+  dnssoa.mname := stringfromlabel(pl, idx);
+  if idx >= Length(pl) then exit;
+
+  dnssoa.rname := stringfromlabel(pl, idx);
+
+  if (idx + (SizeOf(Cardinal) * 5)) > Length(pl) then exit;
+  Move(pl[idx],dnssoa.serial,SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.refresh, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.retry, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.expire, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.min, SizeOf(Cardinal));
+  Result := True;
+  dnssoa.serial := NToHl(dnssoa.serial);
+  dnssoa.min := NToHl(dnssoa.min);
+  dnssoa.expire := NToHl(dnssoa.expire);
+  dnssoa.refresh := NToHl(dnssoa.refresh);
+  dnssoa.retry := NToHl(dnssoa.retry);
+end;
+
+function DNSRRGetSOA(const RR: TRRNameData; const pl: TPayLoad; out
+  dnssoa: TDNSRR_SOA): Boolean;
+var
+  idx: Integer;
+begin
+  // can't trust the counts we've been given, so check that we never
+  // exceed the end of the payload buffer.
+  idx := RR.RDataSt;
+  Result := False;
+  if RR.RRMeta.Atype <> DNSQRY_SOA then exit;
+  dnssoa.mname := stringfromlabel(pl, idx);
+  if idx >= Length(pl) then exit;
+
+  dnssoa.rname := stringfromlabel(pl, idx);
+
+  if (idx + (SizeOf(Cardinal) * 5)) > Length(pl) then exit;
+  Move(pl[idx],dnssoa.serial,SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.refresh, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.retry, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.expire, SizeOf(Cardinal));
+  Inc(idx, SizeOf(Cardinal));
+  Move(pl[idx], dnssoa.min, SizeOf(Cardinal));
+  Result := True;
+  dnssoa.serial := NToHl(dnssoa.serial);
+  dnssoa.min := NToHl(dnssoa.min);
+  dnssoa.expire := NToHl(dnssoa.expire);
+  dnssoa.refresh := NToHl(dnssoa.refresh);
+  dnssoa.retry := NToHl(dnssoa.retry);
+end;
+
+function DNSRRGetText(const RR: TRRNameData; const pl: TPayLoad; out
+  dnstext: AnsiString): Boolean;
+var
+  wrk: ShortString;
+  idx: LongInt;
+  l: Byte;
+begin
+  Result := False;
+  dnstext := '';
+  if RR.RRMeta.Atype <> DNSQRY_TXT then exit;
+  wrk := '';
+
+  idx := RR.RDataSt;
+  if (Length(pl) - idx)  < 2 then exit;
+
+  repeat
+    l := GetFixlenStr(pl, idx+1, pl[idx], wrk);
+    if l = 0 then exit; // count would send us past end of buffer
+    dnstext := dnstext + wrk;
+    Inc(idx, l+1);
+  until (idx >= (RR.RDataSt + RR.RRMeta.RDLength)) or ((Length(pl) - idx) < 2);
+  Result := True;
+end;
+
+function DNSRRGetText(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  dnstext: AnsiString): Boolean;
+var
+  wrk: ShortString;
+  idx: LongInt;
+  l: Byte;
+begin
+  Result := False;
+  dnstext := '';
+  if RR.RRMeta.Atype <> DNSQRY_TXT then exit;
+  wrk := '';
+
+  idx := RR.RDataSt;
+  if (Length(pl) - idx)  < 2 then exit;
+
+  repeat
+    l := GetFixlenStr(pl, idx+1, pl[idx], wrk);
+    if l = 0 then exit; // count would send us past end of buffer
+    dnstext := dnstext + wrk;
+    Inc(idx, l+1);
+  until (idx >= (RR.RDataSt + RR.RRMeta.RDLength)) or ((Length(pl) - idx) < 2);
+  Result := True;
+end;
+
+function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  MX: TDNSRR_MX): Boolean;
+var
+  idx: Integer;
+begin
+  Result := False;
+  MX.preference := 0;
+  MX.exchange := '';
+  if RR.RRMeta.Atype <> DNSQRY_MX then exit;
+  idx := RR.RDataSt;
+  if idx + SizeOf(Word) >= Length(pl) then exit;
+  Move(pl[idx],MX.preference, SizeOf(Word));
+  Inc(idx, SizeOf(Word));
+  if (Length(pl) - idx) < 2 then exit;
+  MX.exchange := stringfromlabel(pl, idx);
+  MX.preference := NToHs(MX.preference);
+  Result := True;
+end;
+
+function DNSRRGetMX(const RR: TRRNameData; const pl: TPayLoad; out MX: TDNSRR_MX
+  ): Boolean;
+var
+  idx: Integer;
+begin
+  Result := False;
+  MX.preference := 0;
+  MX.exchange := '';
+  if RR.RRMeta.Atype <> DNSQRY_MX then exit;
+  idx := RR.RDataSt;
+  if idx + SizeOf(Word) >= Length(pl) then exit;
+  Move(pl[idx],MX.preference, SizeOf(Word));
+  Inc(idx, SizeOf(Word));
+  if (Length(pl) - idx) < 2 then exit;
+  MX.exchange := stringfromlabel(pl, idx);
+  MX.preference := NToHs(MX.preference);
+  Result := True;
+end;
+
+function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoadTCP; out
+  ptr: TDNSDomainName): Boolean;
+var
+  n: Integer;
+begin
+  Result := False;
+  ptr := '';
+  if RR.RRMeta.Atype <> DNSQRY_PTR then exit;
+  n := RR.RDataSt;
+  if (n + RR.RRMeta.RDLength) > Length(pl) then exit;
+  ptr := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetPTR(const RR: TRRNameData; const pl: TPayLoad; out
+  ptr: TDNSDomainName): Boolean;
+var
+  n: Integer;
+begin
+  Result := False;
+  ptr := '';
+  if RR.RRMeta.Atype <> DNSQRY_PTR then exit;
+  n := RR.RDataSt;
+  if (n + RR.RRMeta.RDLength) > Length(pl) then exit;
+  ptr := stringfromlabel(pl, n);
+  Result := True;
+end;
+
+function DNSRRGetSRV(const RR: TRRNameData; const pl: TPayload; out
+  srv: TDNSRR_SRV): Boolean;
+var
+  idx: Integer;
+begin
+  Result := False;
+  srv.priority := 0;
+  srv.weight := 0;
+  srv.port := 0;
+  srv.target := '';
+  if RR.RRMeta.Atype <> DNSQRY_SRV then exit;
+
+  idx := RR.RDataSt;
+  if idx +  RR.RRMeta.RDLength > Length(pl) then exit;
+
+  Move(pl[idx], srv.priority, SizeOf(Word));
+  Inc(idx, SizeOf(Word));
+  if (Length(pl) - idx) < 2 then exit;
+
+  Move(pl[idx], srv.weight, SizeOf(Word));
+  Inc(idx, SizeOf(Word));
+  if (Length(pl) - idx) < 2 then exit;
+
+  Move(pl[idx], srv.port, SizeOf(Word));
+  Inc(idx, SizeOf(Word));
+  if (Length(pl) - idx) < 2 then exit;
+
+  srv.target := stringfromlabel(pl, idx);
+
+  srv.priority := NToHs(srv.priority);
+  srv.weight := NToHs(srv.weight);
+  srv.port := NToHs(srv.port);
+
+  Result := True;
+end;
+
+function DNSRRGetSRV(const RR: TRRNameData; const pl: TPayloadTCP; out
+  srv: TDNSRR_SRV): Boolean;
+var
+  idx: Integer;
+begin
+  Result := False;
+  srv.priority := 0;
+  srv.weight := 0;
+  srv.port := 0;
+  srv.target := '';
+  if RR.RRMeta.Atype <> DNSQRY_SRV then exit;
+
+  idx := RR.RDataSt;
+  if idx +  RR.RRMeta.RDLength > Length(pl) then exit;
+
+  Move(pl[idx], srv.priority, SizeOf(Word));
+  Inc(idx, SizeOf(Word));
+  if (Length(pl) - idx) < 2 then exit;
+
+  Move(pl[idx], srv.weight, SizeOf(Word));
+  Inc(idx, SizeOf(Word));
+  if (Length(pl) - idx) < 2 then exit;
+
+  Move(pl[idx], srv.port, SizeOf(Word));
+  Inc(idx, SizeOf(Word));
+  if (Length(pl) - idx) < 2 then exit;
+
+  srv.target := stringfromlabel(pl, idx);
+
+  srv.priority := NToHs(srv.priority);
+  srv.weight := NToHs(srv.weight);
+  srv.port := NToHs(srv.port);
+
+  Result := True;
+end;
+
 Function SkipAnsQueries(Var Ans : TQueryData; L : Integer) : integer;
 
 Var
@@ -817,10 +1546,10 @@
   Result:=0;
   With Ans do
     begin
-    qdcount := htons(qdcount);
+    h.qdcount := htons(h.qdcount);
     i:=0;
     q:=0;
-    While (Q<qdcount) and (i<l) do  
+    While (Q<h.qdcount) and (i<l) do  
       begin
       If Payload[i]>63 then
         begin
@@ -842,6 +1571,39 @@
     end;  
 end;
 
+function SkipAnsQueries(var Ans: TQueryDataLengthTCP; L: Integer): integer;
+var
+  Q,I : Integer;
+
+begin
+  Result:=0;
+  With Ans do
+  begin
+    h.qdcount := htons(h.qdcount);
+    i:=0;
+    q:=0;
+    While (Q<h.qdcount) and (i<l) do
+    begin
+      If Payload[i]>63 then
+      begin
+        Inc(I,6);
+        Inc(Q);
+      end
+      else
+      begin
+        If Payload[i]=0 then
+        begin
+          inc(q);
+          Inc(I,5);
+        end
+        else
+          Inc(I,Payload[i]+1);
+      end;
+    end;
+    Result:=I;
+  end;
+end;
+
 { ---------------------------------------------------------------------
     DNS Query functions.
   ---------------------------------------------------------------------}
@@ -857,7 +1619,7 @@
   
 begin
   Result:=False;
-  With Qry do
+  With Qry.h do
     begin
     ID[0]:=Random(256);
     ID[1]:=Random(256);
@@ -890,41 +1652,258 @@
   AL:=SizeOf(SA);
   L:=fprecvfrom(Sock,@ans,SizeOf(Ans),0,@SA,@AL);
   fpclose(Sock);
-  // Check lenght answer and fields in header data.
-  If (L<12) or not CheckAnswer(Qry,Ans) Then
+
+  if L < 12 then exit;
+  // Return Payload length.
+  Anslen:=L-12;
+  // even though we may still return false to indicate an error, if AnsLen
+  // is >= 0 then the caller knows the dns server responded.
+  If not CheckAnswer(Qry.h,Ans.h) Then
     exit;
-  // Return Payload length.  
-  Anslen:=L-12;  
-  Result:=True;  
+  Result:=True;
+  //end;
 end;
 
-function stringfromlabel(pl: TPayLoad; start: integer): string;
+function FetchDNSResponse(sock: Cint; out len: ssize_t;
+  out Ans: TQueryDataLengthTCP): TTCPSocketResult;
 var
-  l,i: integer;
+  respsize: Word;
+  L: ssize_t;
+
 begin
+  Result := srOK;
+  len := 0;
+
+  // peek into the socket buffer and see if a full message is waiting.
+  L := fprecv(sock, @Ans, SizeOf(Ans), MSG_PEEK);
+  if L = 0 then
+  begin
+    Result := srSocketClose;
+    exit;
+  end;
+  // The first two bytes of a DNS TCP payload is the number of octets in the
+  // response, excluding the two bytes of length. This lets us see if we've
+  // received the full response.
+  respsize := NToHs(Ans.length);
+  if (L < 2) or (L < (respsize + SizeOf(Ans.length))) then
+  begin
+    Result := srPartial;
+    exit;
+  end;
+
+  // The full DNS response is waiting in the buffer. Get it now.
+  len := fprecv(sock, @Ans, SizeOf(Ans), 0);
+end;
+
+function QueryTCP(Resolver: Integer; var Qry: TQueryDataLength;
+  var Ans: TQueryDataLengthTCP; QryLen: Integer; var AnsLen: Integer): Boolean;
+Var
+  SA : TInetSockAddr;
+  Sock : cint;
+  L: ssize_t;
+  RTO : Longint;
+  ReadFDS : TFDSet;
+  count: Integer;
+  sendsize: ssize_t;
+  respsize: Word;
+  resp: TTCPSocketResult;
+  tstart: QWord;
+
+begin
+  tstart := GetTickCount64;
+  Result:=False;
+  With Qry.hpl.h do
+  begin
+    ID[0]:=Random(256);
+    ID[1]:=Random(256);
+    Flags1:=QF_RD;
+    Flags2:=0;
+    qdcount:=htons(1); // was 1 shl 8;
+    ancount:=0;
+    nscount:=0;
+    arcount:=0;
+  end;
+  Sock:=FpSocket(AF_INET,SOCK_STREAM,0);
+  If Sock=-1 then
+    exit;
+  With SA do
+  begin
+    sin_family:=AF_INET;
+    sin_port:=htons(DNSport);
+    sin_addr.s_addr:=cardinal(DNSServers[Resolver]); // octets already in net order
+  end;
+
+  // connect to the resolver
+  if (fpconnect(Sock, @SA, SizeOf(SA)) <> 0) then
+    exit;
+
+  // send the query to the resolver
+  sendsize := QryLen + SizeOf(Qry.hpl.h) + SizeOf(Qry.length);
+  count := fpsend(Sock,@Qry,sendsize,0);
+  if count < sendsize then
+  begin
+    fpclose(Sock);
+    exit;
+  end;
+
+  // tell other side we're done writing.
+  fpshutdown(Sock, SHUT_WR);
+
+  RTO := 5000;
+  fpFD_ZERO(ReadFDS);
+  fpFD_Set(sock,ReadFDS);
+
+  // select to wait for data
+  if fpSelect(sock+1, @ReadFDS, Nil, Nil,  RTO)<=0 then
+  begin
+    // timed out, nothing received.
+    fpclose(sock);
+    exit;
+  end;
+
+  // for partial responses, keep trying until all data received or the
+  // timeout period has elapsed. the timeout period includes the time
+  // spent waiting on select.
+  resp := FetchDNSResponse(Sock, L, Ans);
+  while (resp = srPartial) and ((GetTickCount64 - tstart) < RTO) do
+  begin
+    // need to sleep to avoid high cpu. 50ms means a 5 second timeout will
+    // make up to 100 calls to FetchDNSResponse.
+    Sleep(50);
+    resp := FetchDNSResponse(Sock, L, Ans);
+  end;
+
+  fpclose(sock);
+  if resp <> srOK then exit;
+
+  // Set AnsLen to be the size of the payload minus the header.
+  Anslen := L-SizeOf(Qry.hpl.h);
+  // if the final check finds problems with the answer, we'll return false
+  // but AnsLen being >=0 will let the caller know that the server did
+  // respond, but either declined to answer or couldn't.
+  If not CheckAnswer(Qry.hpl.h,Ans.h) then
+    exit;
+  Result:=True;
+end;
+
+{
+Read a string from the payload buffer. Handles compressed as well as
+regular labels. On termination start points to the character after the
+end of the str.
+}
+
+function stringfromlabel(pl: TPayLoad; var start: Integer): string;
+var
+  l,i,n,lc: integer;
+  ptr: Word;
+  ptrseen: Boolean = False;
+begin
   result := '';
   l := 0;
   i := 0;
+  n := start;
+  // Label counter. Per rfc1035, s. 3.1, each label is at least 2 bytes and the
+  // max length for a domain is 255, so there can't be more than 127 labels.
+  // This helps to short-circuit loops in label pointers.
+  lc := 0;
   repeat
-    l := ord(pl[start]);
+    // each iteration of this loop is for one label. whether a pointer or a
+    // regular label, we need 2 bytes headroom minimum.
+    if n > (Length(pl) - 2) then break;
+    l := ord(pl[n]);
     { compressed reply }
     while (l >= 192) do
       begin
-        { the -12 is because of the reply header length }
-        start := (l and not(192)) shl 8 + ord(pl[start+1]) - 12;
-        l := ord(pl[start]);
+        if not ptrseen then start := n + 2;
+        ptrseen := True;
+        ptr := (l and not(192)) shl 8 + ord(pl[n+1]);
+        {ptr must point backward and be >= 12 (for the dns header.}
+        if (ptr >= (n+12)) or (ptr < 12) then l := 0 // l=0 causes loop to exit
+        else
+        begin
+          { the -12 is because of the reply header length. we do the decrement
+          here to avoid overflowing if ptr < 12.}
+          n := ptr - 12;
+          l := ord(pl[n]);
+        end;
       end;
+    // check we point inside the buffer
+    if (n+l+1) > Length(pl) then l := 0;
     if l <> 0 then begin
       setlength(result,length(result)+l);
-      move(pl[start+1],result[i+1],l);
+      move(pl[n+1],result[i+1],l);
       result := result + '.';
-      inc(start,l); inc(start);
+      inc(n,l); inc(n);
       inc(i,l); inc(i);
+      if n > start then start := n;
     end;
-  until l = 0;
-  if result[length(result)] = '.' then setlength(result,length(result)-1);
+    Inc(lc); // label count
+  until (l = 0) or (lc > 127);
+  // per rfc1035, section 4.1.4, a domain name may be represented by
+  // either a sequence of labels followed by 0, or a pointer, or a series
+  // of labels followed by a pointer. If there's a pointer there's no 0 to
+  // skip over when calculating the final index.
+  if not ptrseen then Inc(start); // jump past the 0.
+  if (Length(result) > 0) and (result[length(result)] = '.') then
+    setlength(result,length(result)-1);
 end;
 
+function stringfromlabel(pl: TPayLoadTCP; var start: Integer): string;
+var
+  l,i,n,lc: integer;
+  ptr: Word;
+  ptrseen: Boolean = False;
+begin
+  result := '';
+  l := 0;
+  i := 0;
+  n := start;
+  // Label counter. Per rfc1035, s. 3.1, each label is at least 2 bytes and the
+  // max length for a domain is 255, so there can't be more than 127 labels.
+  // This helps to short-circuit loops in label pointers.
+  lc := 0;
+  repeat
+    // each iteration of this loop is for one label. whether a pointer or a
+    // regular label, we need 2 bytes headroom minimum.
+    if n > (Length(pl) - 2) then break;
+    l := ord(pl[n]);
+    { compressed reply }
+    while (l >= 192) do
+      begin
+        if not ptrseen then start := n + 2;
+        ptrseen := True;
+        ptr := (l and not(192)) shl 8 + ord(pl[n+1]);
+        {ptr must point backward and be >= 12 (for the dns header.}
+        if (ptr >= (n+12)) or (ptr < 12) then l := 0 // l=0 causes loop to exit
+        else
+        begin
+          { the -12 is because of the reply header length. we do the decrement
+          here to avoid overflowing if ptr < 12.}
+          n := ptr - 12;
+          l := ord(pl[n]);
+        end;
+      end;
+    // check we point inside the buffer
+    if (n+l+1) > Length(pl) then l := 0;
+    if l <> 0 then begin
+      setlength(result,length(result)+l);
+      move(pl[n+1],result[i+1],l);
+      result := result + '.';
+      inc(n,l); inc(n);
+      inc(i,l); inc(i);
+      if n > start then start := n;
+    end;
+    Inc(lc); // label count
+  until (l = 0) or (lc > 127);
+  // per rfc1035, section 4.1.4, a domain name may be represented by
+  // either a sequence of labels followed by 0, or a pointer, or a series
+  // of labels followed by a pointer. If there's a pointer there's no 0 to
+  // skip over when calculating the final index.
+  if not ptrseen then Inc(start); // jump past the 0.
+  if (Length(result) > 0) and (result[length(result)] = '.') then
+    setlength(result,length(result)-1);
+end;
+
 Function ResolveNameAt(Resolver : Integer; HostName : String; Var Addresses : Array of THostAddr; Recurse: Integer) : Integer;
 
 Var
@@ -941,7 +1920,7 @@
   else  
     begin
     AnsStart:=SkipAnsQueries(Ans,AnsLen);
-    MaxAnswer:=Ans.AnCount-1;
+    MaxAnswer:=Ans.h.AnCount-1;
     If MaxAnswer>High(Addresses) then
       MaxAnswer:=High(Addresses);
     I:=0;
@@ -1022,7 +2001,7 @@
   end else
     begin
     AnsStart:=SkipAnsQueries(Ans,AnsLen);
-    MaxAnswer:=Ans.AnCount-1;
+    MaxAnswer:=Ans.h.AnCount-1;
     If MaxAnswer>High(Addresses) then
       MaxAnswer:=High(Addresses);
     I:=0;
@@ -1085,7 +2064,7 @@
   else  
     begin
     AnsStart:=SkipAnsQueries(Ans,AnsLen);
-    MaxAnswer:=Ans.AnCount-1;
+    MaxAnswer:=Ans.h.AnCount-1;
     If MaxAnswer>High(Names) then
       MaxAnswer:=High(Names);
     I:=0;
netdb.patch (36,697 bytes)   
netdb-example.patch (14,499 bytes)   
Index: packages/fcl-net/examples/dnsq.pp
===================================================================
--- packages/fcl-net/examples/dnsq.pp	(nonexistent)
+++ packages/fcl-net/examples/dnsq.pp	(working copy)
@@ -0,0 +1,506 @@
+program dnsq;
+
+{$mode objfpc}
+{$h+}
+
+{
+This is a simple program that demonstrates how to query DNS using the pure
+Pascal api. It can query A, AAAA, MX, NS, SOA, CNAME, PTR, TXT, and SRV
+records. In the event that the returned data is truncated, this program retries
+using TCP.
+
+The code should be quite easy to follow as it's written in a plain imperative
+style with no fancy tricks.
+}
+
+uses
+  netdb, sockets, sysutils, math;
+
+
+{
+The dump_payload functions output the DNS response received from the server as
+a hex dump. Useful for debugging but not that useful for general use.
+}
+procedure dump_payload(const pl: TPayLoad; l: Word);
+var
+  idx,llen: Cardinal;
+begin
+  idx := 0;
+  llen := 0;
+  for idx := 0 to l do
+  begin
+    write('['+inttostr(idx)+'] '+IntToHex(pl[idx],2));
+    if (pl[idx] > 48) and (pl[idx] < 123) then
+      write(' ' + chr(pl[idx]))
+    else
+      write(' .');
+    write(' ');
+    Inc(llen);
+    if llen >= 6 then
+    begin
+      llen := 0;
+      writeln();
+    end;
+  end;
+  if llen > 0 then
+  begin
+    writeln();
+  end;
+end;
+
+procedure dump_payload(const pl: TPayLoadTCP; l: Word);
+var
+  idx,llen: Cardinal;
+begin
+  idx := 0;
+  llen := 0;
+  for idx := 0 to l do
+  begin
+    write('['+inttostr(idx)+'] '+IntToHex(pl[idx],2));
+    if (pl[idx] > 48) and (pl[idx] < 123) then
+      write(' ' + chr(pl[idx]))
+    else
+      write(' .');
+    write(' ');
+    Inc(llen);
+    if llen >= 6 then
+    begin
+      llen := 0;
+      writeln();
+    end;
+  end;
+  if llen > 0 then
+  begin
+    writeln();
+  end;
+end;
+
+function QTypeToStr(qtype: Word): String;
+begin
+  Result := 'Unknown';
+  case qtype of
+    DNSQRY_A: Result := 'A';
+    DNSQRY_AAAA: Result := 'AAAA';
+    DNSQRY_NS: Result := 'NS';
+    DNSQRY_SOA: Result := 'SOA';
+    DNSQRY_MX: Result := 'MX';
+    DNSQRY_CNAME: Result := 'CNAME';
+    DNSQRY_PTR: Result := 'PTR';
+    DNSQRY_TXT: Result := 'TXT';
+    DNSQRY_SRV: Result := 'SRV';
+  end;
+end;
+
+{
+Write a text representation of a DNS Resource Record to the console.
+}
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayloadTCP);
+var
+  s: AnsiString;
+  ss: ShortString;
+  b: Boolean;
+  ip: THostAddr;
+  ip6: THostAddr6;
+  mx: TDNSRR_MX;
+  soa: TDNSRR_SOA;
+  srv: TDNSRR_SRV;
+begin
+  s := QTypeToStr(RRN.RRMeta.Atype);
+
+  write(RRN.RRName);
+  write(' '+inttostr(RRN.RRMeta.TTL));
+  write(' '+s+' ');
+  case RRN.RRMeta.Atype of
+    DNSQRY_A:
+      begin
+        b := DNSRRGetA(RRN, pl, ip);
+        if b then WriteLn(HostAddrToStr(ip)) else WriteLn('Failed');
+      end;
+    DNSQRY_CNAME:
+      begin
+        b := DNSRRGetCNAME(RRN, pl, ss);
+        if b then WriteLn(ss) else WriteLn('Failed');
+      end;
+    DNSQRY_AAAA:
+      begin
+        b := DNSRRGetAAAA(RRN, pl, ip6);
+        if b then WriteLn(HostAddrToStr6(ip6)) else WriteLn('Failed');
+      end;
+    DNSQRY_NS:
+      begin
+        b := DNSRRGetNS(RRN, pl, ss);
+        if b then WriteLn(ss) else WriteLn('Failed');
+      end;
+    DNSQRY_SOA:
+      begin
+        b := DNSRRGetSOA(RRN, pl, soa);
+        if b then
+          WriteLn(
+            soa.mname+
+            ' '+soa.rname+
+            ', serial:'+inttostr(soa.serial)+
+            ', refresh:'+inttostr(soa.refresh)+
+            ', retry:'+inttostr(soa.retry)+
+            ', min:'+inttostr(soa.min)+
+            ', expire:'+inttostr(soa.expire))
+        else WriteLn('Failed');
+      end;
+    DNSQRY_TXT:
+      begin
+        b := DNSRRGetText(RRN, pl, s);
+        if b then WriteLn(s) else WriteLn('Failed');
+      end;
+    DNSQRY_MX:
+      begin
+        b := DNSRRGetMX(RRN, pl, mx);
+        if b then WriteLn(inttostr(mx.preference)+' '+mx.exchange)
+        else WriteLn('Failed');
+      end;
+    DNSQRY_PTR:
+      begin
+        b := DNSRRGetPTR(RRN, pl, ss);
+        if b then WriteLn(ss) else WriteLn('Failed');
+      end;
+    DNSQRY_SRV:
+      begin
+        b := DNSRRGetSRV(RRN, pl, srv);
+        if b then WriteLn(inttostr(srv.priority)+','+inttostr(srv.weight)+
+          ','+inttostr(srv.port)+','+srv.target);
+
+      end;
+  end;
+end;
+
+{
+Write a text representation of a DNS Resource Record to the console.
+}
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
+var
+  s: AnsiString;
+  ss: ShortString;
+  b: Boolean;
+  ip: THostAddr;
+  ip6: THostAddr6;
+  mx: TDNSRR_MX;
+  soa: TDNSRR_SOA;
+  srv: TDNSRR_SRV;
+begin
+  s := QTypeToStr(RRN.RRMeta.Atype);
+
+  write(RRN.RRName);
+  write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+  write(s+' ');
+  case RRN.RRMeta.Atype of
+    DNSQRY_A:
+      begin
+        b := DNSRRGetA(RRN, pl, ip);
+        if b then WriteLn(HostAddrToStr(ip)) else WriteLn('Failed');
+      end;
+    DNSQRY_CNAME:
+      begin
+        b := DNSRRGetCNAME(RRN, pl, ss);
+        if b then WriteLn(ss) else WriteLn('Failed');
+      end;
+    DNSQRY_AAAA:
+      begin
+        b := DNSRRGetAAAA(RRN, pl, ip6);
+        if b then WriteLn(HostAddrToStr6(ip6)) else WriteLn('Failed');
+      end;
+    DNSQRY_NS:
+      begin
+        b := DNSRRGetNS(RRN, pl, ss);
+        if b then WriteLn(ss) else WriteLn('Failed');
+      end;
+    DNSQRY_SOA:
+      begin
+        b := DNSRRGetSOA(RRN, pl, soa);
+        if b then
+          WriteLn(
+            soa.mname+
+            ' '+soa.rname+
+            ', serial:'+inttostr(soa.serial)+
+            ', refresh:'+inttostr(soa.refresh)+
+            ', retry:'+inttostr(soa.retry)+
+            ', min:'+inttostr(soa.min)+
+            ', expire:'+inttostr(soa.expire))
+        else WriteLn('Failed');
+      end;
+    DNSQRY_TXT:
+      begin
+        b := DNSRRGetText(RRN, pl, s);
+        if b then WriteLn(s) else WriteLn('Failed');
+      end;
+    DNSQRY_MX:
+      begin
+        b := DNSRRGetMX(RRN, pl, mx);
+        if b then WriteLn(inttostr(mx.preference)+' '+mx.exchange)
+        else WriteLn('Failed');
+      end;
+    DNSQRY_PTR:
+      begin
+        b := DNSRRGetPTR(RRN, pl, ss);
+        if b then WriteLn(ss) else WriteLn('Failed');
+      end;
+    DNSQRY_SRV:
+      begin
+        b := DNSRRGetSRV(RRN, pl, srv);
+        if b then WriteLn(inttostr(srv.priority)+','+inttostr(srv.weight)+
+          ','+inttostr(srv.port)+','+srv.target);
+
+      end;
+  end;
+end;
+
+{
+Query the DNS system resolver using TCP. The RFCs say that clients should
+always try UDP first and only resort to TCP if the response header indicates
+that the response would not fit in a UDP payload.
+
+The handling of payload is identical no matter whether it's a TCP or UDP
+payload.
+}
+procedure QueryTCP(dn: AnsiString; qtype: Word);
+var
+  Ans: TQueryDataLengthTCP;
+  MaxAnswer, AnsLen: Longint;
+  AnsStart: Word;
+  RRN: TRRNameData;
+  r: TDNSRcode;
+  RRArr: TRRNameDataArray;
+  qr: Boolean;
+
+begin
+  qr := DnsLookup(dn, qtype, Ans, AnsLen);
+  if not qr then
+  begin
+    if AnsLen = -2 then
+    begin
+      writeln('Invalid DNS domain name.');
+      exit;
+    end
+    else if AnsLen = -1 then
+    begin
+      writeln('No DNS server could be reached.');
+      exit;
+    end;
+    r := GetRcode(Ans.h);
+    case r of
+      rcInvalid: writeln('Invalid response');
+      rcNoError:
+        begin
+          writeln('Server returned no records.');
+        end;
+      rcRefused: writeln('Refused');
+      rcNotImpl: writeln('Not implemented');
+      rcNXDomain: writeln('NXDOMAIN');
+      rcServFail: writeln('SERVFAIL');
+      rcFormatError: writeln('Format error');
+      rcReserved: writeln('Reserved code');
+    end;
+    exit;
+  end
+  else
+  begin
+    AnsStart:=SkipAnsQueries(Ans, AnsLen);
+    MaxAnswer:=Ans.h.AnCount;
+    //dump_payload(Ans.Payload, AnsLen);
+    Write('Answers: '+inttostr(Ans.h.ancount)+' ');
+    Write('NS: '+inttostr(NToHS(Ans.h.nscount))+' ');
+    Write('Additional: '+inttostr(NToHS(Ans.h.arcount))+' ');
+    Write('Length: '+inttostr(AnsLen)+' ');
+    WriteLn('AnsStart: '+inttostr(AnsStart));
+
+    if MaxAnswer > 0 then
+    begin
+      writeln;
+      writeln('-- Answers');
+      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+      for RRN in RRArr do
+        DumpNameRR(RRN, Ans.Payload);
+    end;
+
+    // dump any ns records
+    // if there are authority RRs, we're already pointed at the
+    // first one.
+    MaxAnswer := NToHS(Ans.h.nscount);
+    if MaxAnswer > 0 then
+    begin
+      writeln;
+      writeln('-- NS AUTHORITY Section');
+      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+      for RRN in RRArr do
+        DumpNameRR(RRN, Ans.Payload);
+    end;
+
+    // dump any additional records
+    // if there are additional RRs, we're already pointed at the
+    // first one.
+    MaxAnswer := NToHS(Ans.h.arcount);
+    if MaxAnswer > 0 then
+    begin
+      writeln;
+      writeln('-- Additional section');
+      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+      for RRN in RRArr do
+        DumpNameRR(RRN, Ans.Payload);
+    end;
+  end;
+end;
+
+{
+This is the standard way to query dns using UDP. It will fall back to TCP if
+the response contains a flag indicating that truncation occurred.
+
+The handling of the response is the same no matter whether it's UDP or TCP.
+First, you skip to the start of the response section using SkipAnsQueries.
+Then, you check each of the three defined resource sections. The first
+section contains the immediate responses to the query, then the NS section
+contains any name server authority information, and lastly
+the additional section contains any additional resource records.
+
+In all cases, the function GetRRrecords is used to retrieve a dynamic array
+containing the resource records in a section. This dynamic array can be
+iterated using a for loop. E.g,
+
+      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+      for RRN in RRArr do
+        DumpNameRR(RRN, Ans.Payload);
+
+A full explanation of the different resource records that can be returned is
+beyond the scope of this comment. See RFC 1035 for most of the relevant detail.
+}
+procedure QueryUDP(dn: AnsiString; qtype: Word);
+var
+  Ans: TQueryData;
+  MaxAnswer, AnsLen: Longint;
+  AnsStart: Word;
+  r: TDNSRcode;
+  RRN: TRRNameData;
+  RRArr: TRRNameDataArray;
+  qr: Boolean;
+
+begin
+  qr := DnsLookup(dn, qtype, Ans, AnsLen);
+  if not qr then
+  begin
+    if AnsLen = -2 then
+    begin
+      writeln('Invalid DNS domain name.');
+      exit;
+    end
+    else if AnsLen = -1 then
+    begin
+      writeln('No DNS server could be reached.');
+      exit;
+    end;
+    r := GetRcode(Ans.h);
+    case r of
+      rcInvalid: writeln('Invalid response');
+      rcNoError:
+        begin
+          writeln('Server returned no records.');
+        end;
+      rcRefused: writeln('Refused');
+      rcNotImpl: writeln('Not implemented');
+      rcNXDomain: writeln('NXDOMAIN');
+      rcServFail: writeln('SERVFAIL');
+      rcFormatError: writeln('Format error');
+      rcReserved: writeln('Reserved code');
+    end;
+    exit;
+  end
+  else
+  begin
+    if IsTruncated(Ans.h) then
+    begin
+      writeln('Response truncated ... retrying as TCP');
+      QueryTCP(dn, qtype);
+      exit;
+    end;
+    //dump_payload(Ans.Payload, AnsLen);
+    AnsStart:=SkipAnsQueries(Ans,AnsLen);
+    MaxAnswer:=Ans.h.AnCount;
+
+    Write('Answers: '+inttostr(Ans.h.ancount)+' ');
+    Write('NS: '+inttostr(NToHS(Ans.h.nscount))+' ');
+    Write('Additional: '+inttostr(NToHS(Ans.h.arcount))+' ');
+    Write('Length: '+inttostr(AnsLen)+' ');
+    WriteLn('AnsStart: '+inttostr(AnsStart));
+
+    if MaxAnswer > 0 then
+    begin
+      writeln;
+      writeln('-- Answers');
+      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+      for RRN in RRArr do
+        DumpNameRR(RRN, Ans.Payload);
+    end;
+
+    // dump any ns records
+    // if there are authority RRs, we're already pointed at the
+    // first one.
+    MaxAnswer := NToHS(Ans.h.nscount);
+    if MaxAnswer > 0 then
+    begin
+      writeln;
+      writeln('-- NS AUTHORITY Section');
+      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+      for RRN in RRArr do
+        DumpNameRR(RRN, Ans.Payload);
+    end;
+
+    // dump any additional records
+    // if there are additional RRs, we're already pointed at the
+    // first one.
+    MaxAnswer := NToHS(Ans.h.arcount);
+    if MaxAnswer > 0 then
+    begin
+      writeln;
+      writeln('-- Additional Section');
+      RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+      for RRN in RRArr do
+        DumpNameRR(RRN, Ans.Payload);
+    end;
+  end;
+end;
+
+var
+  S,S1: String;
+  qtype: Word;
+begin
+  if ParamCount <> 2 then
+  begin
+    WriteLn(ParamStr(0)+' qtype domain');
+    WriteLn('Query DNS records for domain <domain>. Parameter qtype is one of:');
+    WriteLn(' - A (A host address)');
+    WriteLn(' - MX (Mail Exchanger)');
+    WriteLn(' - SOA (Start of Authority)');
+    WriteLn(' - NS (Authoritative Name Server)');
+    WriteLn(' - AAAA (IPv6 hostname)');
+    WriteLn(' - CNAME (Canonical Name)');
+    WriteLn(' - PTR (reverse domain lookup for ip using pseudo domain IN-ADDR.ARPA');
+    WriteLn(' - TXT (Free-form text strings. May exceed UDP 512 byte limit.)');
+    WriteLn(' - SRV (Service (RFC 2782).)');
+    exit;
+  end;
+
+  S := ParamStr(2);
+  S1 := LowerCase(ParamStr(1));
+  case S1 of
+    'ns': qtype := DNSQRY_NS;
+    'mx': qtype := DNSQRY_MX;
+    'soa': qtype := DNSQRY_SOA;
+    'a': qtype := DNSQRY_A;
+    'aaaa': qtype := DNSQRY_AAAA;
+    'cname': qtype := DNSQRY_CNAME;
+    'ptr': qtype := DNSQRY_PTR;
+    'txt': qtype := DNSQRY_TXT;
+    'srv': qtype := DNSQRY_SRV;
+  else
+    qtype := DNSQRY_A;
+  end;
+
+  WriteLn(' - ' + S +' ('+QTypeToStr(qtype)+')');
+  QueryUDP(S, qtype);
+end.
+
Index: packages/fcl-net/examples/Makefile.fpc
===================================================================
--- packages/fcl-net/examples/Makefile.fpc	(revision 48358)
+++ packages/fcl-net/examples/Makefile.fpc	(working copy)
@@ -6,7 +6,7 @@
 packages=fcl-base fcl-xml fcl-net
 
 [target]
-programs=rpcserv rpccli isockcli isocksvr
+programs=rpcserv rpccli isockcli isocksvr dnsq
 units=svrclass svrclass_xmlrpc
 
 [compiler]
netdb-example.patch (14,499 bytes)   

Michael Van Canneyt

2021-01-30 09:33

administrator   ~0128670

Impressive :-)

I checked your code and applied it.

Thank you for your efforts !

Noel Duffy

2021-04-17 05:54

reporter   ~0130418

Adding a patch for documentation. This is against fpcdocs trunk. In addition to netdb.xml, there are 11 example programs in netdbex. I've built and tested these on Fedora.
netdb-2.patch (141,790 bytes)   
Index: Makefile.fpc
===================================================================
--- Makefile.fpc	(revision 1852)
+++ Makefile.fpc	(working copy)
@@ -1118,6 +1118,7 @@
         $(MAKE) -C linuxex clean
         $(MAKE) -C sockex clean
         $(MAKE) -C ipcex clean
+        $(MAKE) -C netdbex clean
 
 dosexamples: examples
         $(MAKE) -C go32ex
@@ -1127,6 +1128,7 @@
         $(MAKE) -C linuxex
         $(MAKE) -C sockex
         $(MAKE) -C ipcex
+        $(MAKE) -C netdbex
 
 execute:
         $(MAKE) -C dosex all
Index: fcl-project.xml
===================================================================
--- fcl-project.xml	(revision 1852)
+++ fcl-project.xml	(working copy)
@@ -56,6 +56,7 @@
         <unit file="{{FPCDIR}}/packages/fcl-db/src/sqldb/mssql/mssqlconn.pp" options=""/>
         <unit file="{{FPCDIR}}/packages/fcl-json/src/fpjson.pp" options=""/>
         <unit file="{{FPCDIR}}/packages/fcl-web/src/base/fpmimetypes.pp" options=""/>
+        <unit file="{{FPCDIR}}/packages/fcl-net/src/netdb.pp" options=""/>
       </units>
       <descriptions>
         <description file="fcl.xml"/>
@@ -101,6 +102,7 @@
         <description file="mssqlconn.xml"/>
         <description file="fpjson.xml"/>
         <description file="fpmimetypes.xml"/>
+        <description file="netdb.xml"/>
       </descriptions>
       <imports>
         <import file="rtl.xct" prefix="../rtl/"/>
Index: netdb.xml
===================================================================
--- netdb.xml	(nonexistent)
+++ netdb.xml	(working copy)
@@ -0,0 +1,3087 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<fpdoc-descriptions>
+<package name="fcl">
+
+<!--
+  ====================================================================
+    netdb
+  ====================================================================
+-->
+
+<module name="netdb">
+<short>Functions and procedures for making DNS queries.</short>
+<descr>
+  <file>netdb</file> contains routines for making DNS queries and
+  parsing the results. It uses the <file>Sockets</file> unit for
+  definitions of IPv4 and IPv6 addresses.
+</descr>
+
+<!-- alias type Visibility: default -->
+<element name="THostAddr">
+<short>An alias for for Sockets.in_addr</short>
+<descr>
+  An alias for the in_addr type defined in
+  <file>Sockets</file>. DNS routines that parse resource records containing IPv4 addresses, such
+  as A, return a THostAddr.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- alias type Visibility: default -->
+<element name="THostAddr6">
+<short>An alias for Sockets.in_addr6</short>
+<descr>
+  An alias for the in_addr6 type, as defined in
+  <file>Sockets.</file>. DNS routines that parse resource records
+  containing IPv6 addresses, such as AAAA, return a THostAddr6.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- alias type Visibility: default -->
+<element name="TNetAddr">
+<short>Same as THostAddr, but in network-byte order.</short>
+<descr>
+  A TNetAddr is identical to a THostAddr except that it's in
+  network-byte order.
+</descr>
+<seealso>
+<link id="THostAddr"/>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="MaxResolveAddr">
+<short>A constant that sets the maximum number of results that some
+functions will return for a given query.</short>
+<descr>
+  The functions ResolveName, ResolveName6, ResolveAddress,
+  ResolveAddress6 accept a Var array into which the resolved IPv4,
+  IPv6, or name results are written. The functions ResolveHostByName,
+  ResolveHostByName6, ResolveHostByAddr, and ResolveHostByAddr6 use
+  this constant to limit the maximum number of elements in this array.
+</descr>
+<seealso>
+<link id="ResolveName"/>
+<link id="ResolveName6"/>
+<link id="ResolveAddress"/>
+<link id="ResolveAddress6"/>
+<link id="ResolveHostByName"/>
+<link id="ResolveHostByName6"/>
+<link id="ResolveHostByAddr"/>
+<link id="ResolveHostByAddr6"/>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="DNSPort">
+<short>Defines the UDP and TCP port.</short>
+<descr>
+Defines the UDP and DNS transport port. Per RFC 1035, section 4.2,
+port 53 is used for both TCP and UDP.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="SServicesFile">
+<short>The unix services file, /etc/services, which lists defined ports.</short>
+<descr>
+  The services file defines a mapping between human-friendly service
+  names and assigned port numbers and protocol types. Port numbers are
+  assigned by the IANA, the Internet Assigned Numbers Authority.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="SHostsFile">
+<short>The hosts file.</short>
+<descr>
+  Name of the hosts file. The hosts file is a text file containing name to IP address mappings.
+</descr>
+<seealso>
+  <link id="FindHostEntryInHostsFile"/>
+  <link id="GetHostByName"/>
+  <link id="GetHostByAddr"/>
+  <link id="GetDNSServers"/>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="SNetworksFile">
+<short>The name of the network information file.</short>
+<descr>
+  The networks file is a text file describing known
+  DARPA networks and defining names for them. The functions
+  GetNetworkByName and GetNetworkByAddr return information from this file.
+</descr>
+<seealso>
+  <link id="GetNetworkByName"/>
+  <link id="GetNetworkByAddr"/>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="SProtocolFile">
+<short>Name of the protocols definition file. </short>
+<descr>
+  The protocols file is a text file listing DARPA protocols available
+  on the TCP/IP subsystem. The protocol numbers and names are assigned
+  by IANA, the Internet Assigned Numbers Authority.
+</descr>
+<seealso>
+  <link id="FindProtoEntryInProtoFile"/>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="SResolveFile">
+<short>The name of the resolver configuration file..</short>
+<descr>
+  The resolver configuration file contains information about the DNS
+  resolvers available to the host. It defines the IP addresses of
+  one or more DNS servers. It can also define default domains to be
+  queried if a hostname is queried without a domain, and it can
+  contain options that affect the system resolver.
+</descr>
+<seealso>
+  <link id="CheckResolveFile"/>
+  <link id="InitResolver"/>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="MaxRecursion">
+<short>A constant specify the maximum number of recursive calls that
+can be made to convert a host name to an IPv4 address.</short>
+<descr>
+  A constant specifying the maximum number of recursive DNS queries
+  that can be made to resolve a host name to an IP address. A query
+  for an A record may return a CNAME, in which case the functions,
+  ResolveHostByName and ResolveHostByName6, look up that CNAME in a
+  bid to get its address. If subsequent queries also return CNAMES
+  then the functions will continue to recursively resolve them until
+  either an IP address is reached or MaxRecursion is reached. Its
+  value is 10, indicating that no more than 10 recursive calls will be
+  made for one name.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="MaxIP4Mapped">
+<short></short>
+<descr>
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="DNSQRY_A">
+<short>IANA assigned number for an A record.</short>
+<descr>
+  All resource records have a TYPE field. This constant is used for an
+  A record or host address. TYPE values are defined in RFC 1035, section 3.2.2.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="DNSQRY_AAAA">
+<short>IANA assigned number for an AAAA record.</short>
+<descr>
+  All resource records have a TYPE field. This constant is used for an
+  IPv6 AAAA record. TYPE values are defined in RFC 1035, section 3.2.2.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="DNSQRY_PTR">
+<short>IANA assigned number for a PTR resource record type.</short>
+<descr>
+  All resource records have a TYPE field. This constant is used for an
+  PTR record, a pointer to name data. TYPE values are defined in RFC 1035, section 3.2.2.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="DNSQRY_MX">
+<short>IANA assigned number for a MX resource record type.</short>
+<descr>
+  All resource records have a TYPE field. This constant is used for an
+  MX record, a mail exchanger. TYPE values are defined in RFC 1035, section 3.2.2.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="DNSQRY_TXT">
+<short>IANA assigned number for a text resource record type.</short>
+<descr>
+  All resource records have a TYPE field. This constant is used for an
+  TXT record, a text record. Text records may exceed the 512 UDP byte
+  limit. TYPE values are defined in RFC 1035, section 3.2.2.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="DNSQRY_CNAME">
+<short>IANA assigned number for a text resource record type.</short>
+<descr>
+  All resource records have a TYPE field. This constant is used for an
+  CNAME record, a canonical name. TYPE values are defined in RFC 1035, section 3.2.2.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="DNSQRY_SOA">
+<short>IANA assigned number for an SOA resource record type.</short>
+<descr>
+  All resource records have a TYPE field. This constant is used for an
+  SOA record, a start of authority. TYPE values are defined in RFC 1035, section 3.2.2.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="DNSQRY_NS">
+<short>IANA assigned number for an NS resource record type.</short>
+<descr>
+  All resource records have a TYPE field. This constant is used for an
+  NS record, a name server. TYPE values are defined in RFC 1035, section 3.2.2.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="DNSQRY_SRV">
+<short>IANA assigned number for an SRV resource record type.</short>
+<descr>
+  All resource records have a TYPE field. This constant is used for an
+  SRV record, a location of service. TYPE values are defined in RFC
+  1035, section 3.2.2. SRV records are defined in RFC 2782.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="QF_QR">
+<short>An octet that can be ANDed with the TDNSHeader.flags1 field to
+retrieve the single-bit value of the QR field.</short>
+<descr>
+  DNS message headers contain a 16 bit flags field broken down into 8
+  sub-fields of varying bit lengths. QR is a single-bit sub-field that
+  indicates whether this DNS message is a query or a reply. The value
+  in QF_QR can be ANDed with the byte field Flags1 in a <var>TDNSHeader</var> to
+  determine if this flag is 0 (query) or 1 (reply). See RFC1035
+  section 4.1.1.
+</descr>
+<seealso>
+  <link id="CheckAnswer">CheckAnswer</link>
+  <link id="TDNSHeader">TDNSHeader</link>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="QF_OPCODE">
+<short>An octet that can be ANDed with the TDNSHeader.flags1 field to
+retrieve the value of the OPCODE sub-field..</short>
+<descr>
+  DNS message headers contain a 16 bit flags field broken down into 8
+  sub-fields of varying bit lengths. OPCODE is a four-bit sub-field that
+  specifies the type of query. This can be 0, standard query, 1,
+  IQUERY (inverse query), or 2, STATUS, a server status
+  request. Values 3-15 are reserved for future use. Only 0, standard
+  query, is supported. Queries specifying values other than 0 will
+  fail in <link id="CheckAnswer">CheckAnswer</link>. See RFC1035
+  section 4.1.1.
+</descr>
+<seealso>
+  <link id="TDNSHeader">TDNSHeader</link>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="QF_AA">
+<short>An octet that can be ANDed with the TDNSHeader.flags1 field to
+retrieve the single-bit AA sub-field.</short>
+<descr>
+  DNS message headers contain a 16 bit flags field broken down into 8
+  sub-fields of varying bit lengths. AA is a single-bit sub-field that
+  specifies whether the responding server is an authority for the
+  domain name in the query. The value in QF_AA can be ANDed with the
+  byte field Flags1 in a <var>TDNSHeader</var> to determine if the
+  server is authoritative. See RFC1035 section 4.1.1.
+</descr>
+<seealso>
+  <link id="TDNSHeader">TDNSHeader</link>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="QF_TC">
+<short>An octet that can be anded with the TDNSHeader.flags1 field to
+retrieve the single-bit sub-field TC.</short>
+<descr>
+  DNS message headers contain a 16 bit flags field broken down into 8
+  sub-fields of varying bit lengths. TC is a single-bit sub-field that
+  indicates whether this DNS reply was truncated. The value in QF_TC
+  can be ANDed with the byte field Flags1 in a <var>TDNSHeader</var>
+  to determine if truncation occurred. See RFC1035 section 4.1.1.
+</descr>
+<seealso>
+  <link id="IsTruncated">IsTruncated</link>
+  <link id="TDNSHeader">TDNSHeader</link>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="QF_RD">
+<short>An octet that can be ANDed with the TDNSHeader.flags1 field to
+retrieve the single-bit sub-field RD.</short>
+<descr>
+  DNS message headers contain a 16 bit flags field broken down into 8
+  sub-fields of varying bit lengths. RD is a single-bit sub-field that
+  specifies whether the DNS server should pursue the query recursively. The value in QF_RD
+  can be ANDed with the byte field Flags1 in a <var>TDNSHeader</var>
+  to determine if recursion is enabled. Recursion is always enabled by
+  the Query function. See RFC1035 section 4.1.1.
+</descr>
+<seealso>
+  <link id="Query">Query</link>
+  <link id="TDNSHeader">TDNSHeader</link>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="QF_RA">
+<short>An octet that can be ANDed with the TDNSHeader.flags2 field to
+retrieve the single bit sub-field RA.</short>
+<descr>
+  DNS message headers contain a 16 bit flags field broken down into 8
+  sub-fields of varying bit lengths. RA is a single-bit sub-field that
+  specifies whether the DNS server supports recursive queries. The
+  value in QF_RA can be ANDed with the byte field Flags2 in a
+  <var>TDNSHeader</var> to determine if recursion is available. This
+  is only set in responses. See RFC1035 section 4.1.1.
+</descr>
+<seealso>
+  <link id="TDNSHeader">TDNSHeader</link>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="QF_Z">
+<short>An octet that can be ANDed with the TDNSheader.flags2 field to
+retrieve the value of the Z sub-field..</short>
+<descr>
+  DNS message headers contain a 16 bit flags field broken down into 8
+  sub-fields of varying bit lengths. Z is a three-bit sub-field
+  reserved for future use and must be 0 in all queries and
+  replies. The value in QF_Z can be ANDed with the byte field Flags2
+  in a <var>TDNSHeader</var> to retrieve the value. See RFC1035
+  section 4.1.1.
+</descr>
+<seealso>
+  <link id="TDNSHeader">TDNSHeader</link>
+</seealso>
+</element>
+
+<!-- constant Visibility: default -->
+<element name="QF_RCODE">
+<short>An octet that can be ANDed with the TDNSHeader.flags2 field to
+retrieve the RCODE sub-field.</short>
+<descr>
+  DNS message headers contain a 16 bit flags field broken down into 8
+  sub-fields of varying bit lengths. RCODE is a four-bit sub-field
+  containing a response code returned by the DNS server. The values
+  and their meanings are: 0 (No error), 1 (Format error), 2 (Server
+  failure), 3 (Name error), 4 (Not implemented), and 5
+  (Refused). Values 6 to 15 are reserved. The value in QF_RCODE can be
+  ANDed with the byte field Flags2 in a <var>TDNSHeader</var> to
+  retrieve the status code. See RFC1035 section 4.1.1.
+</descr>
+<seealso>
+  <link id="CheckAnswer">CheckAnswer</link>
+  <link id="GetRCode">GetRCode</link>
+  <link id="TDNSHeader">TDNSHeader</link>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="EtcPath">
+<short>The system path for finding the hosts and resolver files.</short>
+<descr>
+  A global variable holding the system path for finding the hosts and
+  resolver files. The variable is set in <var>InitResolver</var> prior
+  to the loading of the hosts file and the resolver file. On Unix
+  platforms <var>EtcPath</var> is set to '/etc' while on OS/2 and some
+  others the value is read from the environment variable "ETC".
+</descr>
+<seealso>
+  <link id="InitResolver"/>
+</seealso>
+</element>
+
+<!-- enumeration type Visibility: default -->
+<element name="TDNSRcode">
+<short>An enumeration for resolver return codes.</short>
+<descr>
+  An enumeration of resolver return codes. Resolver return codes are
+  values between 0 and 15 with values 6 to 15 reserved. The
+  enumeration contains the values rcNoError (0), rcFormatError (1),
+  rcServFail (2), rcNXDomain (3), rcNotImpl (4), rcRefused (5),
+  rcServed (6-15), and rcInvalid to indicate an invalid return
+  code. The function <var>GetRCode</var> extracts the return code from
+  a message header record (<var>TDNSHeader</var>) and converts the
+  code into an enumeration value.
+</descr>
+<seealso>
+  <link id="GetRCode">GetRCode</link>
+  <link id="QF_RCODE">QF_RCODE</link>
+</seealso>
+</element>
+
+<!-- enumeration value Visibility: default -->
+<element name="TDNSRcode.rcNoError">
+<short>Indicates that no error occured. The query succeeded.</short>
+</element>
+
+<!-- enumeration value Visibility: default -->
+<element name="TDNSRcode.rcFormatError">
+<short>The name server couldn't parse the query.</short>
+</element>
+
+<!-- enumeration value Visibility: default -->
+<element name="TDNSRcode.rcServFail">
+<short>The name server couldn't process the query due to a problem
+with the server.</short>
+</element>
+
+<!-- enumeration value Visibility: default -->
+<element name="TDNSRcode.rcNXDomain">
+<short>The domain name in the query does not exist.</short>
+</element>
+
+<!-- enumeration value Visibility: default -->
+<element name="TDNSRcode.rcNotImpl">
+<short>The name server doesn't support the query type.</short>
+</element>
+
+<!-- enumeration value Visibility: default -->
+<element name="TDNSRcode.rcRefused">
+<short>The name server refused to perform the query for policy reasons.</short>
+</element>
+
+<!-- enumeration value Visibility: default -->
+<element name="TDNSRcode.rcReserved">
+<short>Reserved but unused return code, corresponding to return codes
+betwen 6 and 15 in the DNS header.</short>
+</element>
+
+<!-- enumeration value Visibility: default -->
+<element name="TDNSRcode.rcInvalid">
+<short>Indicates the return code was not valid.</short>
+</element>
+
+<!-- array type Visibility: default -->
+<element name="TDNSServerArray">
+<short>A dynamic array type for holding an array of IPv4 addresses of
+DNS name servers.</short>
+<descr>
+  A dynamic array type for holding one or more IPv4 addresses of name
+  servers. At unit initialisation time the global variable
+  <var>DNSServers</var>, which is of type <var>TDNSServerArray</var>,
+  is populated with one or more IPv4 addresses of configured DNS name
+  servers. On unix platforms the resolvers are read from
+  /etc/resolv.conf. On Android Google's default name servers are
+  inserted into this array.
+</descr>
+<seealso>
+  <link id="GetDNSServers"/>
+</seealso>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TServiceEntry">
+<short>A record type for holding entries read from the system services file.</short>
+<descr>
+  A record type for holding entries read from the system services
+  file. The services file provides human-friendly names for internet
+  services and a mapping between this name and the port and protocol type.
+</descr>
+<seealso>
+  <link id="GetServiceByName"/>
+  <link id="GetServiceByPort"/>
+  <link id="GetProtocolByName"/>
+  <link id="GetProtocolByNumber"/>
+  <link id="GetNextServiceEntry"/>
+  <link id="FindServiceEntryInFile"/>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TServiceEntry.Name">
+<short>The human-friendly name for the service.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TServiceEntry.Protocol">
+<short>The protocol of the service. Typically either TCP or UDP, but
+other protocols like DCCP, DDP, and SCTP also occur.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TServiceEntry.Port">
+<short>The port number used by the service.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TServiceEntry.Aliases">
+<short>Comma-separated list of service aliases.</short>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="THostEntry">
+<short>A record type for holding host name to IP address mappings, such as
+hosts file entries.</short>
+<descr>
+  A record type for holding host to IP address mappings, such as those
+  used in hosts files. It is also returned by <var>ResolveHostByName</var>.
+</descr>
+<seealso>
+  <link id="ResolveHostByName">ResolveHostByName</link>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="THostEntry.Name">
+<short>The host name.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="THostEntry.Addr">
+<short>The host's IPv4 address.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="THostEntry.Aliases">
+<short>Zero or more comma-separated aliases for the host.</short>
+</element>
+
+<!-- pointer type Visibility: default -->
+<element name="PHostEntry">
+<short>A pointer to a THostEntry.</short>
+<descr>
+  A pointer to a THostEntry record.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- array type Visibility: default -->
+<element name="THostEntryArray">
+<short>A dynamic array type for THostEntry records.</short>
+<descr>
+  A dynamic array type for THostEntry records.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="THostEntry6">
+<short>A record type for holding host name to IPv6 address mappings.</short>
+<descr>
+  A record type for holding a mapping between a host name and an IPv6 address.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="THostEntry6.Name">
+<short>The host name.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="THostEntry6.Addr">
+<short>The IPv6 address.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="THostEntry6.Aliases">
+<short>Zero or more comma-separated aliases for the hostname.</short>
+</element>
+
+<!-- pointer type Visibility: default -->
+<element name="PHostEntry6">
+<short>A pointer to a THostEntry6 record.</short>
+<descr>
+  A pointer to a THostEntry6 record.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- array type Visibility: default -->
+<element name="THostEntry6Array">
+<short>A dynamic array type for THostEntry6.</short>
+<descr>
+  A dynamic array type for THostEntry6 records.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TNetworkEntry">
+<short>Like THostAddr but the bytes of the IP address are in network
+byte order.</short>
+<descr>
+  Like THostAddr, this record type maps a host name to an IPv4
+  address. The bytes of the IPv4 address are in network byte order.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TNetworkEntry.Name">
+<short>The host name.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TNetworkEntry.Addr">
+<short>The IPv4 address associated with the host name.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TNetworkEntry.Aliases">
+<short>Zero or more aliases, comma-separated, for the host.</short>
+</element>
+
+<!-- pointer type Visibility: default -->
+<element name="PNetworkEntry">
+<short>A pointer to a THostEntry record.</short>
+<descr>
+  A pointer to a THostEntry record.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TProtocolEntry">
+<short>A record holding a mapping between a human-friendly protocol
+name and its IANA assigned number.</short>
+<descr>
+  The protocols file maps user-friendly names to IANA assigned
+  numbers. The TProtocolEntry record type is used for holding these mappings.
+</descr>
+<seealso>
+  <link id="GetProtocolByName"/>
+  <link id="GetProtocolByNumber"/>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TProtocolEntry.Name">
+<short>The protocol name.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TProtocolEntry.Number">
+<short>The protocol's assigned IANA number.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TProtocolEntry.Aliases">
+<short>Zero or more aliases, comma-separated.</short>
+</element>
+
+<!-- pointer type Visibility: default -->
+<element name="PProtocolEntry">
+<short>A pointer to a TProtocolEntry.</short>
+<descr>
+  A pointer to a TProtocolEntry.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- pointer type Visibility: default -->
+<element name="PHostListEntry">
+<short></short>
+<descr>
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="THostListEntry">
+<short></short>
+<descr>
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="THostListEntry.Entry">
+<short></short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="THostListEntry.Next">
+<short></short>
+</element>
+
+<!-- array type Visibility: default -->
+<element name="TPayLoad">
+<short>A fixed-length array of 512 bytes for holding a DNS message.</short>
+<descr>
+  A fixed-length array of 512 bytes for holding a DNS message. The
+  length is defined by the maximum size of a UDP packet. Messages
+  passed over TCP do not use TPayLoad.
+</descr>
+<seealso>
+  <link id="TPayloadTCP"/>
+  <link id="BuildPayLoad"/>
+  <link id="Query"/>
+</seealso>
+</element>
+
+<!-- array type Visibility: default -->
+<element name="TPayLoadTCP">
+<short>A fixed-length array of 65535 bytes for holding a DNS message
+to be transmitted over TCP.</short>
+<descr>
+  A fixed-length array of 65535 bytes for holding DNS messages that
+  are to be transmitted over TCP.
+</descr>
+<seealso>
+  <link id="TPayload"/>
+  <link id="BuildPayLoadTCP"/>
+  <link id="QueryTCP"/>
+</seealso>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TDNSHeader">
+<short>A record type for the header portion of a DNS message.</short>
+<descr>
+  A record type for the header portion of a DNS message. The header
+  contains meta information about the query or response.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSHeader.id">
+<short>A 16 bit identifier generated by the program making a DNS
+query. The DNS server will include this identifier in its response, so
+the caller can match up the query and the response.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSHeader.flags1">
+<short>A flag field. This byte contains the header flags QR (1 bit),
+OPCODE (4 bits), AA (1 bit), TC (1 bit), and RD (1 bit). These are, in
+order, Query or Response, Query Kind, Authoritative Answer, Truncation
+Occurred, and Recursion Desired.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSHeader.flags2">
+<short>A flag field. This byte contains the header flags RA (1 bit), Z (3
+bits), and RCODE (4 bits). In order these are Recursion Available,
+Reserved, and Response Code.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSHeader.qdcount">
+<short>An unsigned 16 bit integer specifying the number of entries in
+the question section of the message.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSHeader.ancount">
+<short>An unsigned 16 bit integer specifying the number of resource
+records in the answer section of the message. </short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSHeader.nscount">
+<short>An unsigned 16 bit integer specifying the number of resource
+records in the authority records section of the message. </short></element>
+<!-- variable Visibility: default -->
+<element name="TDNSHeader.arcount">
+<short>An unsigned 16 bit integer specifying the number of resource
+records in the additional records section of the message</short>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TQueryData">
+<short>A record representing a complete DNS message.</short>
+<descr>
+  A record type that represents a complete DNS message. It consists of
+  a header (TDNSHeader) and a payload (TPayload). This message is
+  only capable of holding up to 512 bytes.
+</descr>
+<seealso>
+  <link id="TQueryDataTCP"/>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TQueryData.h">
+<short>The header part of the message.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TQueryData.Payload">
+<short>The 512 byte payload buffer.</short>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TQueryDataLength">
+<short>A DNS message incorporating a length field.</short>
+<descr>
+  A DNS message incorporating a length field but otherwise identical
+  to TQueryData. When querying a DNS server using TCP, an unsigned 16
+  bit length field must be included at the start of the query and the
+  reply. This length is not present when querying over UDP. This
+  record is used in TCP queries to avoid the overhead of a full 64k
+  query buffer (TQueryDataTCP). See also RFC-1035, section 4.2.2.
+</descr>
+<seealso>
+  <link id="TQueryDataTCP"/>
+  <link id="BuildPayLoadTCP"/>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TQueryDataLength.length">
+<short>An unsigned 16 bit integer specifying the length of the
+payload. This length excludes the length field itself.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TQueryDataLength.hpl">
+<short>A TQueryData record.</short>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TQueryDataLengthTCP">
+<short>A DNS message incorporating a length field and a 64k buffer.</short>
+<descr>
+  A DNS message incorporating a 16 bit length field and a 64k buffer
+  large enough for TCP responses. DNS server TCP responses include the
+  length field so that clients can tell when the full message has been
+  received.
+</descr>
+<seealso>
+  <link id="TQueryData"/>
+  <link id="TQueryDataLength"/>
+  <link id="QueryTCP"/>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TQueryDataLengthTCP.length">
+<short>A 16 unsigned integer specifying the length of the DNS message.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TQueryDataLengthTCP.h">
+<short>The header of the message. This is a TDNSHeader.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TQueryDataLengthTCP.Payload">
+<short>The 65,535 byte payload buffer.</short>
+</element>
+
+<!-- pointer type Visibility: default -->
+<element name="PRRData">
+<short>A pointer to a TRRData record.</short>
+<descr>
+  A pointer to a TRRData record.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TRRData">
+<short>A record for holding Resource Record metadata.</short>
+<descr>
+  A record for holding metadata about a resource record.
+</descr>
+<seealso>
+  <link id="DNSQRY_A">DNSQRY_A</link>
+  <link id="DNSQRY_AAAA">DNSQRY_AAAA</link>
+  <link id="DNSQRY_CNAME">DNSQRY_CNAME</link>
+  <link id="DNSQRY_PTR">DNSQRY_PTR</link>
+  <link id="DNSQRY_SRV">DNSQRY_SRV</link>
+  <link id="DNSQRY_NS">DNSQRY_NS</link>
+  <link id="DNSQRY_MX">DNSQRY_MX</link>
+  <link id="DNSQRY_TXT">DNSQRY_TXT</link>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TRRData.Atype">
+  <short>The type of resource record. This will be one of the
+  constants DNSQRY_A, DNSQRY_AAAA, DNSQRY_PTR, DNSQRY_MX, DNSQRY_TXT,
+  DNSQRY_CNAME, DNSQRY_SOA, DNSQRY_NS, or DNSQRY_SRV.
+</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TRRData.AClass">
+<short>Always 1, for IN or Internet.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TRRData.TTL">
+<short>The Time to Live is a 32 bit value specifying a time interval
+that the resource record may be cached. A value of zero indicates that
+the resource record should not be cached. </short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TRRData.RDLength">
+<short>The RDLength is a 16 bit field specifying the number of octets
+in the RDATA section of a resource record.</short>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TRRNameData">
+<short>A record type for representing metadata about a Resource Record.</short>
+<descr>
+  The answer, authority, and additional sections of a DNS response
+  each consist of zero or more resource records. This record type is
+  used to represent the data common to all resource records. It
+  includes the DNS name and the offset into the payload buffer from
+  which this record was retrieved.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TRRNameData.RRName">
+<short>A domain name to which this record pertains.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TRRNameData.RRMeta">
+<short>The resource record's metadata. This consists of Type, Class,
+TTL, and data length. This is a <link id="TRRData">TRRData</link> record.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TRRNameData.RDataSt">
+<short>An offset into the payload buffer where this resource record's
+unique data begins.</short>
+</element>
+
+<!-- array type Visibility: default -->
+<element name="TRRNameDataArray">
+<short>A dynamic array of TRRNameData records.</short>
+<descr>
+  A dynamic array type for arrays of TRRNameData. The function
+  <var>GetRRrecords</var> returns one of these arrays populated with
+  resource records.
+</descr>
+<seealso>
+  <link id="GetRRrecords"/>
+</seealso>
+</element>
+
+<!-- alias type Visibility: default -->
+<element name="TDNSDomainName">
+<short>A type for representing a DNS domain name.</short>
+<descr>
+  A type for DNS domain names. It's an alias for ShortString.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TDNSRR_SOA">
+<short>A record type for representing a DNS SOA record.</short>
+<descr>
+  SOA stands for start of authority. SOA record fields contain
+  information about the authoritative name server for a zone. See RFC
+  1035, section 3.3.13.
+</descr>
+<seealso>
+  <link id="DNSRRGetSOA"/>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SOA.mname">
+<short>Domain name of the server that is authoritative for this zone.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SOA.rname">
+<short>A domain name specifying the mailbox of the person responsible
+for the zone.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SOA.serial">
+<short>An unsigned 32 bit integer representing the serial number of
+this zone.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SOA.refresh">
+<short>A 32 time interval before this zone should be refreshed.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SOA.retry">
+<short>A 32 bit time interval that should elapse before a failed
+refresh should be retried.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SOA.expire">
+<short>A 32 bit time value specifying the upper limit on the interval
+that can elapse before the zone is considered no longer authoritative.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SOA.min">
+<short>Minimum TTL that should be exported with any RR from this zone.</short>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TDNSRR_MX">
+<short>A record type representing an MX record.</short>
+<descr>
+  MX records are mail exchanger records. This record type contains the
+  two fields present in all MX records, the preference and the dns
+  domain name of a mail host. See RFC 1035, section 3.3.9.
+</descr>
+<seealso>
+  <link id="DNSRRGetMX"/>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_MX.preference">
+<short>A 16 bit integer indicating the preference value to be assigned
+this host. Lower values are more highly preferred.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_MX.exchange">
+<short>The DNS domain name of a host that can process mail for this domain.</short>
+</element>
+
+<!-- record type Visibility: default -->
+<element name="TDNSRR_SRV">
+<short>A record type representing an SRV record.</short>
+<descr>
+  SRV records are location of service records. SRV records are defined
+  in RFC 2782.
+</descr>
+<seealso>
+  <link id="DNSRRGetSRV"/>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SRV.priority">
+<short>The priority associated with this host. Clients must query the
+host with the lowest priority. If more than one host has the same
+priority, the weight field is used to define the order in which these
+hosts are queried.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SRV.weight">
+<short>A server selection mechanism, specifying the relative weight of
+hosts with the same priority.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SRV.port">
+<short>The port number on this target host. This is a 16 bit unsigned
+number. Although DNS servers return the port in network byte order,
+the function DNSGetSRV returns the port in host byte order for convenience.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TDNSRR_SRV.target">
+<short>The domain name of the target host. This name must not be a
+CNAME. It is common for the A record for this domain name to be
+returned in the additional section but this is not required.</short>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="DNSServers">
+<short>A dynamic array of THostAddr records. Each THostAddr record is
+the IP address of a system DNS resolver. It is populated using
+<var>GetDNSServers</var>, called from
+<var>CheckResolveFile</var>.</short> There is normally no need to call
+these functions directly.
+<descr>
+</descr>
+<seealso>
+  <link id="CheckResolveFile">CheckResolveFile</link>
+  <link id="GetDNSServers">CheckResolveFile</link>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="DNSOptions">
+<short>A global variable that holds the value of any "options" string
+read from a system resolver file. The options string can be used on
+some platforms to set query debugging, timeouts, and other platform
+specific options. One common option is "ndots" which specifies a
+threshold for the number of dots that must appear in a name before an
+absolute query will be made. This defaults to 1. Currently the only
+function that uses DNSOptions is <var>ResolveHostByName</var>.</short>
+<descr>
+</descr>
+<seealso>
+  <link id="ResolveHostByName">ResolveHostByName</link>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="DefaultDomainList">
+<short></short>
+<descr>
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="CheckResolveFileAge">
+<short></short>
+<descr>
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="CheckHostsFileAge">
+<short>A global variable holding the age of the system hosts file when
+it was last read.</short>
+<descr>
+  A global variable holding the age of the system hosts file when it
+  was last read. If the file has been modified then functions that use
+  the hosts file will reload it.
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TimeOutS">
+<short></short>
+<descr>
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- variable Visibility: default -->
+<element name="TimeOutMS">
+<short></short>
+<descr>
+</descr>
+<seealso>
+</seealso>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetDNSServers">
+<short>Get the names of local DNS resolvers to use for DNS queries.</short>
+<descr>
+  Get the names of local DNS resolvers. On most platforms this
+  involves reading the resolv.conf file, but on Android the list is
+  hard-wired to Google's servers. The global variable DNSServers is
+  populated with the resolvers' IPv4 addresses. GetDNSServers returns
+  the number of resolvers found. Normally user programs call
+  <var>CheckResolveFile</var> to load resolvers. This function is very efficient
+  and won't re-parse the resolver file unless it has changed since the
+  previous run.
+</descr>
+<errors>
+  0 is returned if no resolvers are loaded.
+</errors>
+<seealso>
+  <link id="CheckResolveFile">CheckResolveFile</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetDNSServers.Result">
+<short>The number of resolvers loaded.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetDNSServers.FN">
+<short>The full path to the file containing the addresses of local resolvers.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="ResolveName">
+<short>Resolves a hostname to one or more IP addresses.</short>
+<descr>
+  Recursively resolve a hostname to one or more IP addresses and store the IP
+  addresses in the var parameter <var>Addresses</var>. ResolveName
+  performs a DNS query for an A record at each configured system
+  resolver until one of them returns an answer. If a CNAME record is
+  returned, ResolveName recursively performs a query for an A record
+  for that name until an IP address is found or the number of
+  recursive calls exceeds <link id="MaxRecursion">MaxRecursion</link>.
+</descr>
+<errors>
+  -1 is returned if MaxRecursion is exceeded or all resolvers fail to
+  return a result.
+</errors>
+<seealso>
+  <link id="ResolveName6">ResolveName6</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="ResolveName.Result">
+<short>The number of IP addresses found.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveName.HostName">
+<short>The hostname to be resolved.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveName.Addresses">
+<short>A dynamic array of THostAddr records. THostAddr is an alias for
+in_addr.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="ResolveName6">
+<short>Resolve a hostname to one or more IPv6 addresses.</short>
+<descr>
+  Recursively resolve a hostname to one or more IPv6 addresses and
+  store the addresses in the var parameter <var>Addresses</var>. Each
+  address is a THostAddr6, an alias for in_addr6. ResolveName6 perfors
+  a DNS query for an AAAA record at each configured system resolver
+  until one of them returns an answer. If a CNAME is returned,
+  ResolveName recursively performs a query for the AAAA record for
+  that name until an IP address is found or the number of recursive
+  calls exceeds <link id="MaxRecursion">MaxRecursion</link>.
+</descr>
+<errors>
+  -1 is returned if the query failed at all resolvers or MaxRecursion
+  was exceeded.
+</errors>
+<seealso>
+  <link id="ResolveName6">ResolveName</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="ResolveName6.Result">
+<short>The number of addresses found. -1 if an error occurred.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveName6.HostName">
+<short>The hostname to be resolved.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveName6.Addresses">
+<short>A dynamic array of THostAddr6 records.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="ResolveAddress">
+<short>Convert an IP address to a hostname.</short>
+<descr>
+  Convert an IP address to one or more hostnames by performing a PTR
+  DNS query. The hostnames are written to the var parameter HostAddr,
+  a dynamic array of Strings.
+</descr>
+<errors>
+  -1 is returned if the query failed.
+</errors>
+<seealso>
+  <link id="ResolveAddr6">ResolveAddr6</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="ResolveAddress.Result">
+<short>The number of matched addresses, or -1 if the query failed.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveAddress.HostAddr">
+<short>The IP address to be converted to a hostname.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveAddress.Addresses">
+<short>A dynamic array of strings into which the hostnames are written.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="ResolveAddress6">
+<short>Convert an IPv6 address into a hostname.</short>
+<descr>
+  Convert an IPv6 address into one or more hostnames by performing a PTR DNS
+  query. The returned hostnames are written into the var parameter
+  Addresses, a dynamic array of String.
+</descr>
+<errors>
+  If the query fails -1 is returned.
+</errors>
+<seealso>
+  <link id="ResolveAddr">ResolveAddr</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="ResolveAddress6.Result">
+<short>The number of hostnames found, or -1 if the query failed.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveAddress6.HostAddr">
+<short>The IPv6 address to be converted to one or more hostnames.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveAddress6.Addresses">
+<short>A dynamic array of string. The hostnames will be written to
+this var parameter.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="IN6_IS_ADDR_V4MAPPED">
+<short></short>
+<descr>
+</descr>
+<errors>
+</errors>
+<seealso>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="IN6_IS_ADDR_V4MAPPED.Result">
+<short></short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="IN6_IS_ADDR_V4MAPPED.HostAddr">
+<short></short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="ResolveHostByName">
+<short>Resolve a hostname to an IPv4 address.</short>
+<descr>
+  Resolve a hostname to an IPv4 address. A query of type DNSQRY_A is
+  made to each system resolver in turn until one of them returns an
+  answer. If the system resolver returns a CNAME rather than an A
+  record then ResolveHostByName makes recursive calls to find the A
+  record that matches the CNAME. It stops when it finds the A record
+  or the number of recursive calls exceeds <var>MaxRecursion</var>. If
+  multiple IP addresses are matched only the first is written to the
+  var parameter <var>H</var>.
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+  <link id="ResolveName">ResolveName</link>
+  <link id="THostEntry">THostEntry</link>
+  <link id="MaxRecursion">MaxRecursion</link>
+  <link id="DNSQRY_A">DNSQRY_A</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="ResolveHostByName.Result">
+<short>True if a successful response was received from at least one
+resolver. False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveHostByName.HostName">
+<short>The DNS domain name to query.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveHostByName.H">
+<short>A var THostEntry record into which the IPv4 address, hostname
+and aliases returned by the DNS server are written.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="ResolveHostByAddr">
+<short>Get the hostname associated with an IPv4 address.</short>
+<descr>
+  Given an IPv4 address, query the system resolver to perform a PTR
+  lookup to find the DNS name. The hostname and any aliases are
+  written to a THostEntry record parameter, <var>H</var>.
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+  <link id="THostAddr">THostAddr</link>
+  <link id="THostEntry">THostEntry</link>
+  <link id="">ResolveHostByAddr6</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="ResolveHostByAddr.Result">
+<short>True if the hostname associated with the IPv4 address was
+retrieved. False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveHostByAddr.HostAddr">
+<short>The IPv4 address, in host byte order, to be queried.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveHostByAddr.H">
+<short>A var parameter into which the hostname and any aliases are
+written. </short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="ResolveHostByName6">
+<short>Resolve a hostname to an IPv6 address.</short>
+<descr>
+  Resolve a hostname to an IPv6 address. A query of type DNSQRY_AAAA
+  is made to each system resolver in turn until one of them returns an
+  answer. If the system resolver returns a CNAME rather than an AAAA
+  record then ResolveHostByName6 makes recursive calls to find the AAAA
+  record that matches the CNAME. It stops when it finds the AAAA record
+  or the number of recursive calls exceeds <var>MaxRecursion</var>. If
+  more than one address is returned only the first is written to the
+  var parameter <var>H</var>.
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+  <link id="ResolveHostByName">ResolveHostByName</link>
+  <link id="MaxRecursion">MaxRecursion</link>
+  <link id="THostEntry6">THostEntry6</link>
+  <link id="DNSQRY_AAAA">DNSQRY_AAAA</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="ResolveHostByName6.Result">
+<short>True if the HostName was successfully resolved. False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveHostByName6.Hostname">
+<short>The host name to be resolved to an IPv6 address.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveHostByName6.H">
+<short>A var parameter where the resolved IPv6 address and any aliases
+will be written.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="ResolveHostByAddr6">
+<short>Get the hostname associated with an IPv6 address.</short>
+<descr>
+  Get the hostname associated with an IPv6 address by performing a DNS
+  PTR query. On success the hostname and any aliases are written to
+  the var parameter <var>H</var>, a THostEntry6 record. The IPv6
+  address to be queried is passed using a THostAddr6 record.
+</descr>
+<errors>
+  On error False is returned, otherwise True.
+</errors>
+<seealso>
+  <link id="THostEntry6"></link>
+  <link id="THostAddr6"></link>
+  <link id="ResolveHostByAddr"></link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="ResolveHostByAddr6.Result">
+<short>True if the DNS host name was successfully retrieved. False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveHostByAddr6.HostAddr">
+<short>The IPv6 address to be queried.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ResolveHostByAddr6.H">
+<short>A var parameter into which the host name and any aliases are written.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetHostByName">
+<short>Find a host's IPv4 address in the system hosts file.</short>
+<descr>
+  <p>Find a host's IPv4 address in the system hosts file. Typically this is
+  /etc/hosts on Unix platforms. On success the IPv4 address is written
+  to the var parameter <var>H</var>, of type THostEntry, and True is
+  returned.</p>
+  <p>Functions that work with entries from the system hosts file will
+  automatically load it. There is no need for user programs to do this
+  manually.</p>
+</descr>
+<errors>
+  False is returned if an error occurred. True otherwise.
+</errors>
+<seealso>
+  <link id="GetHostByAddr">GetHostByAddr</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetHostByName.Result">
+<short>True if an entry for the host name was found. False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetHostByName.HostName">
+<short>The hostname to be found in the hosts file.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetHostByName.H">
+<short>A var parameter, of type THostEntry, into which the host's
+address is written on success. Host aliases are also written into this
+record.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetHostByAddr">
+<short>Find the hostname associated with an IPv4 address in the system
+hosts file.</short>
+<descr>
+  Find the hostname associated with an IPv4 address in the system
+  hosts file. Typically this is /etc/hosts on Unix platforms. On
+  success the hostname and aliases are written to the var parameter
+  <var>H</var>, of type THostEntry, and True is returned.
+</descr>
+<errors>
+  On error False is returned, otherwise True.
+</errors>
+<seealso>
+  <link id="GetHostByName">GetHostByName</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetHostByAddr.Result">
+<short>True if an entry in the hosts file exists for the given address. False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetHostByAddr.Addr">
+<short>The input IPv4 address, in host byte order.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetHostByAddr.H">
+<short>A var parameter of type THostEntry into which the hostname and
+any aliases are written.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetNetworkByName">
+<short>Search for a network by name in the system networks file.</short>
+<descr>
+  <p>Search for a network by name in the system networks file. On Unix
+  platforms this is /etc/networks. If the name is found then True is
+  returned and the network name and address are written a to a <link
+  id="TNetworkEntry">TNetworkEntry</link> record, the var <var>N</var>
+  parameter.</p>
+  <p>This function reads the entire networks file each time it is called.</p>
+</descr>
+<errors>
+  False is returned if an error occurs.
+</errors>
+<seealso>
+  <link id="GetNetworkByAddr">GetNetworkByAddr</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetNetworkByName.Result">
+<short>True if the network name was found.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetNetworkByName.NetName">
+<short>The network name to search for.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetNetworkByName.N">
+<short>The var TNetworkEntry where this function will write the
+network name, address, and alias if found.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetNetworkByAddr">
+<short>Search for a network by address in the system networks file.</short>
+<descr>
+  <p>Search for a network using its address in the system networks
+  file. On Unix platforms the networks file is /etc/networks. If the
+  address is found True is returned and the network name is written to
+  the var parameter <var>N</var>, a <link
+  id="TNetworkEntry">TNetworkEntry</link> record..</p>
+  <p>This function reads the entire network file each time it is called.</p>
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+  <link id="GetNetworkByName">GetNetworkByName</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetNetworkByAddr.Result">
+<short>True if the network address was found.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetNetworkByAddr.Addr">
+<short>The IPv4 network address. This is an alias for in_addr.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetNetworkByAddr.N">
+<short>A record containing a network name and its network address,
+plus any aliases defined for the network.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetServiceByName">
+<short>Search for a service by name in the system services file.</short>
+<descr>
+  <p>Search for a service by name in the system services file. On Unix
+  platforms the services file is /etc/services. A service is defined
+  as a protocol port and protocol name, such as 22/tcp for secure
+  shell. If the service is found then True is returned and the service
+  details are written to the var record parameter E, which is of type
+  <link id="TServiceEntry">TServiceEntry</link>.</p>
+  <p>The services file is scanned linearly line by line until a name
+  is matched or end of file is reached. No caching is performed.</p>
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+  <link id="GetServiceByPort">GetServiceByPort</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetServiceByName.Result">
+<short>True if the protocol name is found, otherwise False.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetServiceByName.Name">
+<short>The service name to search for.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetServiceByName.Proto">
+<short>The protocol, such as tcp, udp, sctp etc, to match. If empty, any
+protocol will match.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetServiceByName.E">
+<short>The TServiceEntry record into which the service, if found, will
+be written.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetServiceByPort">
+<short>Search for a system service by port number in the system
+services file.</short>
+<descr>
+  <p>Search for a service by port number in the system services
+  file. On Unix platforms this file is /etc/services. A service is
+  defined as a protocol port and protocol name, such as 22/tcp for
+  secure shell. If the service is found then True is returned and the
+  service details are written to the var record parameter E which is
+  of type <link id="TServiceEntry">TServiceEntry</link>.</p>
+  <p>The services file is scanned linearly line by line until a name
+  is matched or end of file is reached. No caching is performed.</p>
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+  <link id="GetServiceByName">GetServiceByName</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetServiceByPort.Result">
+<short>True if the service port was found, False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetServiceByPort.Port">
+<short>The protocol port to search for.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetServiceByPort.Proto">
+<short>The protocol, such as udp, tcp, sctp, etc. If empty then the
+first entry matching the port number is returned.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetServiceByPort.E">
+<short>If the service is found then the details are written to this
+<link id="TServiceEntry">TServiceEntry</link> record.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetProtocolByName">
+<short>Search for a protocol entry in the system protocols file.</short>
+<descr>
+  <p>Search for a protocol in the system protocols files. This is
+  /etc/protocols on Unix platforms. On success True is returned and a
+  var parameter, H, a <link id="TProtocolEntry">TProtocolEntry</link>, has the details of the protocol
+  written to it.</p>
+  <p>This function reads the protocols file and searches linearly from
+  the beginning each time it is called.</p>
+  <p>A <link id="TProtocolEntry">TProtocolEntry</link> defines a
+  mapping between a protocol name and its port, plus any aliases for
+  the protocol. The assigned network names and ports are made by IANA.</p>
+</descr>
+<errors>
+  False is returned if an error occurs.
+</errors>
+<seealso>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetProtocolByName.Result">
+<short>True if the protocol name was found, otherwise False.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetProtocolByName.ProtoName">
+<short>The protocol name to search for.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetProtocolByName.H">
+<short>A var parameter of type <link id="">TProtocolEntry</link> where the result will be
+written, if found.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetProtocolByNumber">
+<short>Search for a protocol by its IANA-assigned protocol number in
+the system protocols file.</short>
+<descr>
+  <p>Search for a protocol by its IANA assigned protocol number in the
+  system protocols file. On Unix platforms this is /etc/protocols. On
+  success True is returned and the protocol details are written to the
+  var parameter H, of type <link
+  id="TProtocolEntry">TProtocolEntry</link>.</p>
+  <p>This function reads the protocols file and searches linearly from
+  the beginning each time it is called.</p>
+  <p>A <link id="TProtocolEntry">TProtocolEntry></link> defines a
+  mapping between a protocol name and its port, plus any aliases for
+  the protocol. The assigned network names and ports are made by IANA.</p>
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetProtocolByNumber.Result">
+<short>True if the network number was found.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetProtocolByNumber.proto">
+<short>The protocol number to search for.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetProtocolByNumber.H">
+<short>A var parameter into which the protocol's details will be
+written, if found.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="ProcessHosts">
+<short>Read entries from the system hosts file.</short>
+<descr>
+  <p>Read entries from the system hosts file. Memory is allocated on the
+  heap for a THostListEntry record for each line. A pointer to the
+  first THostListEntry is returned on completion. Nil is returned if
+  an error occurs. It is not expected that user programs will ever
+  call this function directly. It is called from the internal
+  CheckHostsFile function to load the system hosts file.</p>
+  <p>There is never any need for user programs to load the hosts file
+  manually. All functions that deal with entries from it will
+  automatically load it as necessary.</p>
+</descr>
+<errors>
+  On error Nil is returned.
+</errors>
+<seealso>
+  <link id="CheckHostsFile">CheckHostsFile</link>
+  <link id="SHostsFile">SHostsFile</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="ProcessHosts.Result">
+<short>A pointer to the first THostsFileEntry on success, otherwise nil.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="ProcessHosts.FileName">
+<short>The full path of the hosts filename to read.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="FreeHostsList">
+<short>Free the memory associated with the global linked list variable <var>HostsList</var>.</short>
+<descr>
+  <p>Free the memory associated with the global linked list variable
+  HostsList. User programs do not normally need to call this function
+  directly. It is used by the internal function CheckHostsFile when
+  that function needs to reload the hosts file.</p>
+  <p>User programs never need to load or free the hosts file
+  manually. Functions that deal with the hosts file will automatically
+  load it as necessary.</p>
+</descr>
+<errors>
+  The number of records freed is returned in all cases. If this is
+  zero, no records were freed.
+</errors>
+<seealso>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="FreeHostsList.Result">
+<short>The number of records that were freed.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="FreeHostsList.List">
+<short>The first list entry, from which point list entries will be freed.</short>
+</element>
+
+<!-- procedure Visibility: default -->
+<element name="HostsListToArray">
+<short>Copy THostEntry records from a linked list of THostListEntry
+records into a dynamic array.</short>
+<descr>
+  Copy THostEntry records from a linked list of THostListEntry
+records into a dynamic array of THostEntry records.
+</descr>
+<errors>
+  The var parameter Hosts will be empty if List is nil.
+</errors>
+<seealso>
+</seealso>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="HostsListToArray.List">
+<short>A pointer to a THostListEntry record. This is the head of the
+linked list.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="HostsListToArray.Hosts">
+<short>A dynamic array of THostEntry which will contain the THostEntry
+records read from the linked list.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="HostsListToArray.FreeList">
+<short>Whether the linked list should be freed at the end of the copy.</short>
+</element>
+
+<!-- procedure Visibility: default -->
+<element name="CheckResolveFile">
+<short>Load system DNS resolvers from the Operating System's defined
+location and make them available for queries.</short>
+<descr>
+  <p>Load system DNS resolvers from the system-defined location and make
+  them available for queries. The global array <var>DNSServers</var>
+  is populated with the results.</p>
+  <p>This function can be called multiple times. It will only reload
+  DNS servers if the modification date of the resolver file is newer
+  than the age recorded when the file was first loaded.</p>
+</descr>
+<errors>
+  On error 0 is returned.
+</errors>
+<seealso>
+  <link id="DNSServers">DNSServers</link>
+  <link id="GetDNSServers">GetDNSServers</link>
+</seealso>
+<example file="netdbex/ex4"/>
+</element>
+
+<!-- function Visibility: default -->
+<element name="Query">
+<short>Perform a DNS query using UDP.</short>
+<descr>
+  Query performs a DNS query over UDP. It queries a single DNS
+  resolver. The query question is specified in Qry, and the response
+  from the DNS server is written to the Ans parameter. Most user
+  programs will not call this function directly. Instead, they should
+  use functions like <var>DNSLookup</var>, <var>ResolveName</var>,
+  <var>ResolveName6</var>, <var>ResolveHostByName</var>,
+  <var>ResolveHostByName6</var>,<var>ResolveAddress</var>, or <var>ResolveAddress6</var>.
+</descr>
+<errors>
+  On error Result is False. This may indicate that the resolver could
+  not be reached, or it could indicate that the resolver didn't find
+  the answer. The former case can be detected by checking if AnsLen =
+  -1. If AnsLen = -1 then the resolver didn't return a response. If
+  AnsLen = -2 then the Query was malformed.  If AnsLen >= 0 then the
+  query was successfully made but the answer was not found. More
+  information may also be found by examining the RCODE field in the
+  heading using <var>GetRCode</var>. No error is returned if the
+  response is truncated. User programs must check for this using <var>IsTruncated</var>.
+</errors>
+<seealso>
+  <link id="DNSLookup">DNSLookup</link>
+  <link id="ResolveName">ResolveName</link>
+  <link id="ResolveName6">ResolveName6</link>
+  <link id="ResolveHostByName">ResolveHostByName</link>
+  <link id="ResolveHostByName6">ResolveHostByName6</link>
+  <link id="ResolveAddress">ResolveAddress</link>
+  <link id="ResolveAddress6">ResolveAddress6</link>
+  <link id="GetRCode">GetRCode</link>
+  <link id="IsTruncated">IsTruncated</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="Query.Result">
+<short>A boolean indicating whether the query was performed
+successfully and an answer returned by the DNS server.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="Query.Resolver">
+<short>An index into the global DNSServers array specifying the
+resolver IP to use.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="Query.Qry">
+<short>This <var>TQueryData</var> record contains the query to be
+performed. The query record can be constructed using
+<var>BuildPayload</var>.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="Query.Ans">
+<short>This <var>TQueryData</var> record contains the result returned
+by the resolver.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="Query.QryLen">
+<short>The length in octets of the Qry payload. That is, how many
+octets of the 512 byte buffer are used.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="Query.AnsLen">
+<short>The length in octets of the Ans payload returned by the DNS
+resolver. That is, how many bytes of the 512 byte payload are used.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="QueryTCP">
+<short>Perform a DNS query using TCP.</short>
+<descr>
+  QueryTCP performs a DNS query over TCP. It queries a single DNS
+  resolver. The query question is specified in Qry, and the response
+  from the DNS server is written to the Ans parameter. Most user
+  programs will not call this function directly. Instead, they should
+  use functions like <var>DNSLookup</var>, <var>ResolveName</var>,
+  <var>ResolveName6</var>, <var>ResolveHostByName</var>,
+  <var>ResolveHostByName6</var>,<var>ResolveAddress</var>, or
+  <var>ResolveAddress6</var>.
+</descr>
+<errors>
+  On error Result is False. This may indicate that the resolver could
+  not be reached, or it could indicate that the resolver didn't find
+  the answer. The former case can be detected by checking if AnsLen =
+  -1. If AnsLen = -1 then the resolver didn't return a response. If
+  AnsLen = -2 then the Query was malformed.  If AnsLen >= 0 then the
+  query was successfully made but the answer was not found. More
+  information may also be found by examining the RCODE field in the
+  heading using <var>GetRCode</var>.
+</errors>
+<seealso>
+  <link id="DNSLookup">DNSLookup</link>
+  <link id="ResolveName">ResolveName</link>
+  <link id="ResolveName6">ResolveName6</link>
+  <link id="ResolveHostByName">ResolveHostByName</link>
+  <link id="ResolveHostByName6">ResolveHostByName6</link>
+  <link id="ResolveAddress">ResolveAddress</link>
+  <link id="ResolveAddress6">ResolveAddress6</link>
+  <link id="GetRCode">GetRCode</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="QueryTCP.Result">
+<short>A boolean indicating whether the query was performed
+successfully and an answer returned by the DNS server.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="QueryTCP.Resolver">
+<short>An index into the global DNSServers array specifying the
+resolver IP to use.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="QueryTCP.Qry">
+<short>This <var>TQueryDataLength</var> record contains the query to be
+performed. The query record can be built using <var>BuildPayloadTCP</var>.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="QueryTCP.Ans">
+<short>This <var>TQueryDataLengthTCP</var> record contains the result returned
+by the resolver.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="QueryTCP.QryLen">
+<short>The length in octets of the Qry payload. That is, how many
+octets of the 64k byte buffer are used.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="QueryTCP.AnsLen">
+<short>The length in octets of the Ans payload returned by the DNS
+resolver. That is, how many bytes of the 512 byte payload are used.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="BuildPayLoad">
+<short>Construct a DNS query message.</short>
+<descr>
+  <p>Construct a TQueryData record suitable for passing to <var>Query</var> and
+  <var>QueryTCP</var>. Returns the length in octets of the payload
+  buffer. The constructed TQueryData record is written to the var
+  parameter <var>Q</var>.</p>
+  <p>User programs will not normally need to call
+  BuildPayload. Instead, user programs will usually call <link
+  id="DnsLookup">DnsLookup</link> which constructs the specified query
+  using BuildPayload. However, BuildPayload is accessible in case very
+  fine-grained control of the query is required.</p>
+</descr>
+<errors>
+  On error the length in bytes is -1.
+</errors>
+<seealso>
+  <link id="TQueryData">TQueryData</link>
+  <link id="DnsLookup">DnsLookup</link>
+  <link id="BuildPayloadTCP">BuildPayloadTCP</link>
+  <link id="Query">Query</link>
+  <link id="DNSQRY_A">DNSQRY_A</link>
+  <link id="DNSQRY_AAAA">DNSQRY_AAAA</link>
+  <link id="DNSQRY_CNAME">DNSQRY_CNAME</link>
+  <link id="DNSQRY_PTR">DNSQRY_PTR</link>
+  <link id="DNSQRY_SRV">DNSQRY_SRV</link>
+  <link id="DNSQRY_NS">DNSQRY_NS</link>
+  <link id="DNSQRY_MX">DNSQRY_MX</link>
+  <link id="DNSQRY_TXT">DNSQRY_TXT</link>
+</seealso>
+<example file="netdbex/ex4"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="BuildPayLoad.Result">
+<short>Length in octets of the payload buffer. This is -1 if an error occurred.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="BuildPayLoad.Q">
+<short>A var TQueryData record into which BuildPayload will write the
+constructed DNS Query.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="BuildPayLoad.Name">
+<short>The DNS name to be queried.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="BuildPayLoad.RR">
+<short>The QTYPE value as specified by RFC 1035. This is the type of
+record being queried, such as A, MX, SOA, etc. Constants such as
+DNSQRY_A, DNSQRY_MX, DNSQRY_SOA etc are provided for specifying this value.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="BuildPayLoad.QClass">
+<short>The QCLASS value as specified by RFC 1035. This is almost
+always 1 for IN. RFC 1035 includes additional values of 2 for CSNET
+(obsolete), 3 for CHAOS, and 4 for Hesiod, but none of these is in
+widespread use.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="BuildPayLoadTCP">
+<short>Construct a DNS Query message suitable for passing to QueryTCP. </short>
+<descr>
+  <p>Construct a DNS Query message suitable for a passing to <link
+  id="QueryTCP">QueryTCP</link>. The constructed message is written to
+  a var parameter, Q, of type TQueryDataLength. The resulting record
+  can be passed to QueryTCP to initiate the DNS Query.</p>
+  <p>User programs will not normally need to call
+  BuildPayloadTCP. Instead, user programs will usually call <link
+  id="DnsLookup">DnsLookup</link> which constructs the specified query
+  using BuildPayloadTCP. However, BuildPayloadTCP is accessible in
+  case very fine-grained control of the query is required.</p>
+</descr>
+<errors>
+  On error the length in bytes is -1.
+</errors>
+<seealso>
+  <link id="TQueryDataLength">TQueryDataLength</link>
+  <link id="BuildPayloadTCP">BuildPayloadTCP</link>
+  <link id="QueryTCP">QueryTCP</link>
+  <link id="DNSQRY_A">DNSQRY_A</link>
+  <link id="DNSQRY_AAAA">DNSQRY_AAAA</link>
+  <link id="DNSQRY_CNAME">DNSQRY_CNAME</link>
+  <link id="DNSQRY_PTR">DNSQRY_PTR</link>
+  <link id="DNSQRY_SRV">DNSQRY_SRV</link>
+  <link id="DNSQRY_NS">DNSQRY_NS</link>
+  <link id="DNSQRY_MX">DNSQRY_MX</link>
+  <link id="DNSQRY_MX">DNSQRY_TXT</link>
+</seealso>
+<example file="netdbex/ex5"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="BuildPayLoadTCP.Result">
+<short>Length in octets of the payload buffer. This is -1 if an error occurred.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="BuildPayLoadTCP.Q">
+<short>The TQueryDataLength record which BuildPayloadTCP will
+write. This is identical to a TQueryData record except for a 16 bit
+length field at the start.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="BuildPayLoadTCP.Name">
+<short>The DNS name to be queried.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="BuildPayLoadTCP.RR">
+<short>The QTYPE value as specified by RFC 1035. This is the type of
+record being queried, such as A, MX, SOA, etc. Constants such as
+DNSQRY_A, DNSQRY_MX, DNSQRY_SOA etc are provided for specifying this value.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="BuildPayLoadTCP.QClass">
+<short>The QCLASS value as specified by RFC 1035. This is almost
+always 1 for IN. RFC 1035 includes additional values of 2 for CSNET
+(obsolete), 3 for CHAOS, and 4 for Hesiod, but none of these is in
+widespread use.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="SkipAnsQueries">
+<short>Find the offset in a payload buffer where resource records can
+be found.</short>
+<descr>
+  Finds the offset in a payload buffer where resource records
+  start. This means skipping over the header and query sections. This
+  function expects to be given the index of the last byte in the
+  payload buffer. It will not go past this offset.
+</descr>
+<errors>
+  On error 0 is returned.
+</errors>
+<seealso>
+  <link id="DNSLookup">DNSLookup</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="SkipAnsQueries.Result">
+<short>The offset into the payload buffer where the resource records begin.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="SkipAnsQueries.Ans">
+<short>The <var>TQueryData</var> or <var>TQueryDataLengthTCP</var> record containing the DNS
+response whose resource records we want to retrieve.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="SkipAnsQueries.L">
+<short>The length in octets of the payload buffer.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="stringfromlabel">
+<short>Extract a string from a DNS label in the DNS message.</short>
+<descr>
+  Extract a string from a DNS label in the DNS
+  message. stringfromlabel correctly handles compressed labels as well
+  as uncompressed labels. Normally user code will not call this
+  function directly. Instead, call GetRRrecords and then one of the
+  GetDNSRR_* functions to retrieve data from resource records. This
+  function can be used for parsing custom resource records.
+</descr>
+<errors>
+  On error an empty string is returned.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+  <link id="DNSRRGetA">DNSRRGetA</link>
+  <link id="DNSRRGetMX">DNSRRGetMX</link>
+  <link id="DNSRRGetSOA">DNSRRGetSOA</link>
+  <link id="DNSRRGetSRV">DNSRRGetSRV</link>
+  <link id="DNSRRGetCNAME">DNSRRGetCNAME</link>
+  <link id="DNSRRGetPTR">DNSRRGetPTR</link>
+  <link id="DNSRRGetAAAA">DNSRRGetAAAA</link>
+  <link id="DNSRRGetText">DNSRRGetText</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="stringfromlabel.Result">
+<short>A string containing the DNS label.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="stringfromlabel.pl">
+<short>The DNS payload buffer containing the label.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="stringfromlabel.start">
+<short>The offset into the payload buffer where the label begins.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="CheckAnswer">
+<short>Check a DNS response for validity.</short>
+<descr>
+  <p>Checks a DNS response to ensure that it's valid. This function
+  is given both the query and response headers. It validates that the
+  IDs match, that certain fields in the header are 0 (QR, OPCODE and
+  RCODE), and that at least one answer has been supplied.</p>
+  <p>The query functions <link id="Query">Query</link> and <link
+  id="QueryTCP">QueryTCP</link> call CheckAnswer immediately after
+  receiving a response from a resolver. Only if CheckAnswer returns
+  <var>True</var> will Query and QueryTCP return True.</p>
+</descr>
+<errors>
+  False is returned if any errors occur.
+</errors>
+<seealso>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="CheckAnswer.Result">
+<short>True if the DNS response is valid. False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="CheckAnswer.Qry">
+<short>The TDNSHeader of the query.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="CheckAnswer.Ans">
+<short>The TDNSHeader of the DNS response.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="IsValidAtype">
+<short>Checks that a 16 bit value is one of the supported AType values.</short>
+<descr>
+  Checks that a 16 bit value is one of the supported AType
+  values. Currently suported values are 1 - 16, 28, and 33. This
+  excludes AXFR, MAILB, MAILA, and *. All other TYPE fields are
+  allowed.
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="IsValidAtype.Result">
+<short>True if the 16 bit value is a supported ATYPE value.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="IsValidAtype.atype">
+<short>The 16 bit ATYPE field to check.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="IsTruncated">
+<short>Is the DNS response truncated.</short>
+<descr>
+  <p>Checks to see if a DNS response has been truncated. Truncation
+  occurs only in UDP payloads when the payload size exceeds the 512
+  byte size of a UDP packet. On detecting truncation resolver programs
+  should retry the query using TCP.</p>
+  <p>Truncation is indicated by a single-bit field, named TC, in the
+  DNS <link id="TDNSHeader">message header</link>. The header field
+  <var>field1</var> can be ANDed with the constant <link
+  id="QF_TC">QF_TC</link> to see if the message was truncated, or
+  programs can call IsTruncated which is simply a convenience function
+  that performs the AND for you.</p>
+  <p>Truncation most often occurs for TEXT queries. It's very rare for
+  most other query types.</p>
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+  <link id="TDNSHeader">TDNSHeader</link>
+  <link id="QF_TC">QF_TC</link>
+</seealso>
+<example file="netdbex/ex6"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="IsTruncated.Result">
+<short>True if the DNS response is truncated. False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="IsTruncated.R">
+<short>The TDNSHeader of the response where we want to test for truncation.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetRcode">
+<short>Get the value of the RCODE field from a DNS header.</short>
+<descr>
+  Get the value of the RCode field from a DNS header. The RCODE field
+  contains the status returned by the DNS server. The returned value
+  is from the enumeration type <var>TDNSRCode</var>.
+</descr>
+<errors>
+  On error the return value is rcInvalid, one of the values of the
+  enumeration type <var>TDNSRCode</var>.
+</errors>
+<seealso>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetRcode.Result">
+<short>A value from the enumeration type TDNSRcode.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetRcode.R">
+<short>The TDNSHeader value whose RCODE field is to be retrieved.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetFixlenStr">
+<short>Get a fixed length string from the RDATA value of a TEXT
+resource record.</short>
+<descr>
+  Get a fixed length string from the RDATA value of a TEXT resource
+  record. TEXT resource records contain one or more string values,
+  each of which is preceded by a single-byte length field. Normally
+  user programs do not need to call this function directly. The
+  function DNSRRGetText is normally used to retrieve all the strings
+  in a TEXT resource record.
+</descr>
+<errors>
+  If the start index plus the length field would exceed the length of
+  the payload buffer the out string result is empty and the return
+  value is 0.
+</errors>
+<seealso>
+  <link id="DNSRRGetText">DNSRRGetText</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetFixlenStr.Result">
+<short>The length in bytes of the out parameter res.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetFixlenStr.pl">
+<short>The payload buffer from which the string is to be
+retrieved. Overloaded variants of the function handle 512 byte
+TPayload and 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetFixlenStr.startidx">
+<short>The start offset within the buffer.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetFixlenStr.len">
+<short>The length in bytes of the fixed length string.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetFixlenStr.res">
+<short>The retrieved string is copied into this out variable.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="NextNameRR">
+<short>Retrieve the next resource record in the payload buffer.</short>
+<descr>
+  <p>Retrieve the next resource record from the payload buffer. A
+  <var>TRRNameData</var> record is populated with the resource
+  record's metadata.</p>
+  <p>User programs do not normally call this function
+  directly. Instead, use <link id="GetRRrecords">GetRRrecords</link>
+  to retrieve all the resource records in a given section.</p>
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="NextNameRR.Result">
+<short>True if a resource record was successfully read, False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="NextNameRR.pl">
+<short>The payload buffer from which the resource record is to be
+read. Overloaded variants of the function handle 512 byte TPayload and
+ 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="NextNameRR.start">
+<short>A 16 bit unsigned value containing the offset with the buffer
+where the resource record begins.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="NextNameRR.RRName">
+<short>If successful, the resource record is written to this out parameter.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="GetRRrecords">
+<short>Retrieve metadata for one or more resource records from a payload buffer.</short>
+<descr>
+  <p>Retrieve metadata for one or more resource records from a payload buffer. The
+  number of records to retrieve is specified in the <var>count</var>
+  parameter. The returned records are contained in a
+  <link id="TRRNameDataArray">TRRNameDataArray</link> dynamic array. Each metadata record is a
+  <link id="TRRNameData">TRRNameData</link> record.</p>
+  <p>The metadata records can be passed to one of the DNSRRGet*
+  functions to retrieve the resource record's value, sometimes
+  referred to as the RDATA. Each resource record type may hold one or
+  more fields. For example, an A resource record has the
+  octets of an IP address as its value, while an MX record has a
+  hostname and a priority field as its value.</p>
+  <p>There is a DNSRRGet* function for retrieving RDATA values for each supported resource
+  record. Use the resource record field <link
+  id="TRRData">RRMeta.Atype</link> to determine what kind of resource record
+  you have. See the example for this function and for the DNSRRGet* functions. </p>
+</descr>
+<errors>
+  If no resource records are found, the returned dynamic array is
+  empty. If fewer records are found than were specified, no error is
+  raised and the returned array contains all records that were found.
+</errors>
+<seealso>
+  <link id="TDNSHeader">TDNSHeader</link>
+  <link id="DNSLookup">DNSLookup</link>
+  <link id="DNSRRGetA">DNSRRGetA</link>
+  <link id="DNSRRGetAAAA">DNSRRGetAAAA</link>
+  <link id="DNSRRGetPTR">DNSRRGetPTR</link>
+  <link id="DNSRRGetSOA">DNSRRGetSOA</link>
+  <link id="DNSRRGetNS">DNSRRGetNS</link>
+  <link id="DNSRRGetSRV">DNSRRGetSRV</link>
+  <link id="DNSRRGetCNAME">DNSRRGetCNAME</link>
+  <link id="DNSRRGetTEXT">DNSRRGetTEXT</link>
+</seealso>
+<example file="netdbex/ex2"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="GetRRrecords.Result">
+<short>A TRRNameDataArray dynamic array containing the resource
+records retrieved.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetRRrecords.pl">
+<short>The payload buffer from which the resource records are to be
+retrieved. Overloaded variants of the function exist to handle 512 byte
+TPayload and 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetRRrecords.Start">
+<short>An unsigned 16 bit integer offset into the payload buffer where
+resource record retrieval should start.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="GetRRrecords.Count">
+<short>The number of resource records to retrieve. DNS server
+responses include the number of resource records in the header of the
+response message.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="DnsLookup">
+<short>Perform a DNS query.</short>
+<descr>
+  <p>This high-level function is the main entry point for user programs
+  to query DNS servers. It queries each defined system resolver in
+  turn until a valid response has been received or all resolvers have
+  been tried.</p>
+  <p>On completion of the query, the response payload is written to
+  <var>Ans</var>. The response consists of three sections of resource
+  records. These are named the Answer, Authority, and Additional
+  sections. Direct answers to the query will be in the Answer section,
+  the authority section will contain records pointing to authorities
+  for the domain, and the additional section may be used to add
+  additional records. For some query types, for example MX and NS,
+  the additional section will hold A and AAAA records for the names in
+  the answer section. These allow client programs to get all relevant
+  information in a single query and lifts the burden of having to make
+  multiple queries.</p>
+  <p>The response header, the <var>h</var> field in <var>Ans</var>,
+  contains fields that declare how many resource records are to be
+  found in each of the three sections. The response header is a record
+  of type <link id="TDNSHeader">TDNSHeader</link>. The field
+  <var>ancount</var> specifies the number of records in the answer
+  section; <var>nscount</var> specifies the number in the authority
+  section; <var>arcount</var> specifies how many records are in the
+  additional section.</p>
+  <p>Two variants of the function exist. One will make DNS queries
+  over UDP, and the other TCP. If the <var>Ans</var> parameter is of
+  type <link id="TQueryData">TQueryData</link> then UDP is used, and
+  if <var>Ans</var> is of type <link
+  id="TQueryDataLengthTCP">TQueryDataLengthTCP</link>, the query is
+  performed over TCP. </p>
+  <p>Note that RFC 5966, section 4, relaxes the original requirement
+  from RFC 1123 that DNS queries MUST first be made over UDP. Instead,
+  resolvers SHOULD use UDP first, but may choose not to if they have
+  good reason to expect that the response will be too large for a UDP
+  payload. After querying over UDP, truncation can be detected using
+  <link id="IsTruncated">IsTruncated</link> and the query retried over
+  TCP if truncation is detected.</p>
+</descr>
+<errors>
+  On error, False is returned. More information about the reason for
+  the failure can be found by examining the RCODE header field.
+</errors>
+<seealso>
+  <link id="TQueryData">TQueryData</link>
+  <link id="TQueryDataLengthTCP">TQueryDataLengthTCP</link>
+  <link id="GetRcode">GetRcode</link>
+  <link id="GetRRrecords">GetRRrecords</link>
+  <link id="IsTruncated">IsTruncated</link>
+  <link id="DNSQRY_A">DNSQRY_A</link>
+  <link id="DNSQRY_AAAA">DNSQRY_AAAA</link>
+  <link id="DNSQRY_CNAME">DNSQRY_CNAME</link>
+  <link id="DNSQRY_PTR">DNSQRY_PTR</link>
+  <link id="DNSQRY_SRV">DNSQRY_SRV</link>
+  <link id="DNSQRY_NS">DNSQRY_NS</link>
+  <link id="DNSQRY_MX">DNSQRY_MX</link>
+  <link id="DNSQRY_TXT">DNSQRY_TXT</link>
+</seealso>
+<example file="netdbex/ex1"/>
+<example file="netdbex/ex2"/>
+<example file="netdbex/ex3"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="DnsLookup.Result">
+<short>True if a query was made and a response retrieved from one of
+the system's DNS resolvers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DnsLookup.dn">
+<short>The domain name to be queried.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DnsLookup.qtype">
+<short>The RFC 1035 query type. This unsigned 16-bit value defines the
+type of query such as A, MX, SOA, NS etc. Constants are defined for
+each supported query type.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DnsLookup.Ans">
+<short>An out parameter to hold the result returned by the DNS
+server. For UDP queries this is a TQueryData record, while for TCP,
+it's TQueryDataTCP.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DnsLookup.AnsLen">
+<short>The length in bytes of the payload returned by the DNS server.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="DNSRRGetA">
+<short>Retrieve an A record's IP address.</short>
+<descr>
+  Given an A resource record, get the IPv4 address from its RDATA. The
+  IPv4 address is returned in a THostAddr record.
+</descr>
+<errors>
+  On error False is returned.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+</seealso>
+<example file="netdbex/ex1"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="DNSRRGetA.Result">
+<short>True if an IPv4 address was returned from the resource
+record. False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetA.RR">
+<short>The resource record that defines the location in the payload
+buffer where the resource record data is found.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetA.pl">
+<short>The payload buffer. Variants of this function exist for 512
+byte TPayload and 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetA.IP">
+<short>An out THostAddr record, into which the IPv4 address is copied.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="DNSRRGetCNAME">
+<short>Given a CNAME resource record, retrieve the CNAME value as a TDNSDomainName.</short>
+<descr>
+  Given a CNAME resource record, retrieve the CNAME value as a
+  TDNSDomainName from the RDATA section of the resource record. A
+  TDNSDomainName is an alias for a shortstring.
+</descr>
+<errors>
+  On error False is returned, otherwise True.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+</seealso>
+<example file="netdbex/ex2"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="DNSRRGetCNAME.Result">
+<short>True if a CNAME was copied into the out parameter cn.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetCNAME.RR">
+<short>The resource record from which the CNAME is retrieved.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetCNAME.pl">
+<short>The payload buffer. Variants of this function exist for 512
+byte UDP and 64k buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetCNAME.cn">
+<short>An out TDNSDomainName variable. The CNAME is copied into this parameter.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="DNSRRGetAAAA">
+<short>Given an AAAA resource record, retrieve the AAAA value as a THostAddr6.</short>
+<descr>
+  Given an AAAA resource record, retrieve the AAAA value from the
+  RDATA section of the record. This value is an IPv6 address and is
+  stored in a THostAddr6 record.
+</descr>
+<errors>
+  On error False is returned, otherwise True.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+</seealso>
+<example file="netdbex/ex7"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="DNSRRGetAAAA.Result">
+<short>True if an IPv6 address was retrieved, False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetAAAA.RR">
+<short>The resource record's metadata that indicates where the
+resource record's RDATA section begins in the payload buffer. </short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetAAAA.pl">
+<short>The payload buffer. Variants of this function exist for 512
+byte TPayload and 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetAAAA.IP">
+<short>An out parameter to hold the IPv6 record. This is a THostAddr6.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="DNSRRGetNS">
+<short>Given an NS resource record, retrieve the name server name as a
+TDNSDomainName.</short>
+<descr>
+  Given an NS resource record, retrieve the name server name as a
+  TDNSDomainName. TDNSDomainName is an alias for a shortstring.
+</descr>
+<errors>
+  On error False is returned, otherwise True.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+</seealso>
+<example file="netdbex/ex2"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="DNSRRGetNS.Result">
+<short>True if a name server record was retrieved, False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetNS.RR">
+<short>The resource record's metadata that indicates where the
+resource record's RDATA section begins in the payload buffer.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetNS.pl">
+<short>The payload buffer. Variants of this function exist for 512
+byte TPayload and 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetNS.NSName">
+<short>An out TDNSDomainName parameter into which the retrieved name server
+name is written.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="DNSRRGetSOA">
+<short>Given an SOA resource record, retrieve and write its fields into a TDNSRR_SOA record.</short>
+<descr>
+  Given an SOA resource record, retrieve all the SOA fields from the
+  RDATA section of the resource record and write them into a
+  <link id="TDNSRR_SOA">TDNSRR_SOA</link> record.
+</descr>
+<errors>
+  On error False is returned, otherwise True.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+</seealso>
+<example file="netdbex/ex10"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="DNSRRGetSOA.Result">
+<short>True if a TDNSRR_SOA record was written, False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetSOA.RR">
+<short>The resource record's metadata that indicates where the
+resource record may be found in the payload buffer.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetSOA.pl">
+<short>The payload buffer. Variants of this function exist for 512
+byte TPayload and 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetSOA.dnssoa">
+<short>The <link id="TDNSRR_SOA">TDNSRR_SOA</link> record into which the SOA record's fields will
+be written. All fields are written in host byte order.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="DNSRRGetText">
+<short>Given a TEXT resource record, retrieve the text value as an AnsiString.</short>
+<descr>
+  Given a TEXT resource record, retrieve the text value as an
+  AnsiString. If multiple strings are stored in the RDATA section then
+  they are concatenated and returned as a single string.
+</descr>
+<errors>
+  On error False is returned, otherwise True.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+</seealso>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="DNSRRGetText.Result">
+<short>True if a text string was retrieved, False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetText.RR">
+<short>The resource record's metadata that specifies where the TXT
+record may be found in the payload buffer.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetText.pl">
+<short>The payload buffer. Variants of this function exist for 512
+byte TPayload and 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetText.dnstext">
+<short>The contents of the text record are written into this out
+parameter, an AnsiString.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="DNSRRGetMX">
+<short>Given an MX resource record, retrieve a TDNSRR_MX record
+containing the MX hostname and priority.</short>
+<descr>
+  Given an MX resource record, retrieve a TDNSRR_MX record containing
+  the MX hostname and the MX priority. The MX priority indicates the
+  weight that should be given to this record in the case where
+  multiple MX records are returned. Lower values have higher priorities.
+</descr>
+<errors>
+  On error False is returned, otherwise True.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+</seealso>
+<example file="netdbex/ex8"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="DNSRRGetMX.Result">
+<short>True if an MX record was written to the out parameter MX,
+otherwise False.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetMX.RR">
+<short>The resource record metadata indicating where the MX record
+begins in the payload buffer.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetMX.pl">
+<short>The payload buffer. Variants of this function exist for 512
+byte TPayload and 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetMX.MX">
+<short>An out TDNSRR_MX record. The retrieved MX record is written to
+this record.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="DNSRRGetPTR">
+<short>Given a PTR resource record, retrieve the DNS domain name from
+the RDATA section.</short>
+<descr>
+  Given a PTR resource record, retrieve the DNS domain name from the
+  RDATA section. The DNS domain name is written to the out parameter
+  ptr, which is of type TDNSDomainName. This type is an alias for ShortString.
+</descr>
+<errors>
+  On error False is returned, otherwise True.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+</seealso>
+<example file="netdbex/ex9"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="DNSRRGetPTR.Result">
+<short>True if a domain name was retrieved and written to the
+parameter ptr, False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetPTR.RR">
+<short>The resource record metadata indicating where the PTR record
+begins in the payload buffer.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetPTR.pl">
+<short>The payload buffer. Variants of this function exist for 512
+byte TPayload and 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetPTR.ptr">
+<short>An out parameter to receive the retrieved DNS domain name.</short>
+</element>
+
+<!-- function Visibility: default -->
+<element name="DNSRRGetSRV">
+<short>Given an SRV resource record, retrieve its field values from
+the RDATA section of the resource record and write them to a
+TDNSRR_SRV record.</short>
+<descr>
+  <p>Given an SRV resource record, retrieve its values from the RDATA
+  section of the resource record and write them to a TDNSRR_SRV
+  record. SRV records are service records containing three unsigned 16
+  bit fields, priority, weight, and port, and a DNS domain name. All
+  16 bit values are written in host byte order.</p>
+  <p>SRV records are intended for service discovery on a network. A
+  program performs an SRV lookup for a particular service and the
+  resource record indicates the hostname and port to which the program
+  can connect.</p>
+</descr>
+<errors>
+  On error False is returned, otherwise True.
+</errors>
+<seealso>
+  <link id="GetRRrecords">GetRRrecords</link>
+</seealso>
+<example file="netdbex/ex11"/>
+</element>
+
+<!-- function result Visibility: default -->
+<element name="DNSRRGetSRV.Result">
+<short>True if an SRV record was successfully retrieved, False otherwise.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetSRV.RR">
+<short>The resource record metadata indicating where the SRV record
+begins in the payload buffer.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetSRV.pl">
+<short>The payload buffer. Variants of this function exist for 512
+byte TPayload and 64k TPayloadTCP buffers.</short>
+</element>
+
+<!-- argument Visibility: default -->
+<element name="DNSRRGetSRV.srv">
+<short>The out TDNSRR_SRV record into which the result is written.</short>
+</element>
+
+</module> <!-- netdb -->
+
+</package>
+</fpdoc-descriptions>
Index: netdbex/Makefile
===================================================================
--- netdbex/Makefile	(nonexistent)
+++ netdbex/Makefile	(working copy)
@@ -0,0 +1,73 @@
+#######################################################################
+#
+# Makefile to compile all examples and convert them to LaTeX
+# 
+#######################################################################
+
+# Compiler
+
+ifndef FPC
+ifdef PP
+FPC=$(PP)
+endif
+endif
+ifndef FPC
+FPCPROG:=$(strip $(wildcard $(addsuffix /fpc$(SRCEXEEXT),$(SEARCHPATH))))
+ifneq ($(FPCPROG),)
+FPCPROG:=$(firstword $(FPCPROG))
+FPC:=$(shell $(FPCPROG) -PB)
+ifneq ($(findstring Error,$(FPC)),)
+override FPC=ppc386
+endif
+else
+override FPC=ppc386
+endif
+endif
+override FPC:=$(subst $(SRCEXEEXT),,$(FPC))
+override FPC:=$(subst \,/,$(FPC))$(SRCEXEEXT)
+
+# Unit directory
+# UNITDIR=/usr/lib/ppc/0.99.0/linuxunits
+
+
+# Any options you wish to pass.
+PPOPTS=-XX
+
+# Script to convert the programs to LaTeX examples which can be included.
+PP2TEX=../pp2tex
+
+# Script to collect all examples in 1 file.
+MAKETEX=make1tex
+
+#######################################################################
+# No need to edit after this line.
+#######################################################################
+
+ifdef UNITDIR
+PPOPTS:=$(PPOPTS) -Up$(UNITDIR);
+endif
+
+.SUFFIXES: .pp .tex
+
+.PHONY: all tex clean
+
+OBJECTS=ex1 ex2 ex3 ex4 ex5 ex6 ex7 ex8 ex9 ex10 ex11
+
+TEXOBJECTS=$(addsuffix .tex, $(OBJECTS))
+
+all : $(OBJECTS)
+
+tex : $(TEXOBJECTS)
+
+onetex : tex
+	$(MAKETEX) $(TEXOBJECTS)
+
+clean : 
+	-rm -f *.o *.s $(OBJECTS) $(TEXOBJECTS)
+	-rm -f *.ow *.sw *.exe *.dll
+ 
+$(OBJECTS): %: %.pp
+	$(FPC) $(PPOPTS) $*
+
+$(TEXOBJECTS): %.tex: %.pp head.tex foot.tex
+	$(PP2TEX) $*
Index: netdbex/README
===================================================================
--- netdbex/README	(nonexistent)
+++ netdbex/README	(working copy)
@@ -0,0 +1,15 @@
+These are the example programs that appear in the FPC documentation.
+ 
+Units guide, netdb unit :
+
+ex1.pp shows simple A and CNAME queries with DnsLookup
+ex2.pp shows a simple NS query
+ex3.pp shows TCP queries for TEXT records
+ex4.pp shows low-level queries using BuildPayload and Query
+ex5.pp shows low-level TCP queries using BuildPayloadTCP and QueryTCP
+ex6.pp shows how to detect truncation
+ex7.pp shows how to get the data from an AAAA resource record
+ex8.pp shows how to get the data from an MX resource record
+ex9.pp shows how to do reverse (PTR) lookups
+ex10.pp shows how to get the data from an SOA resource record
+ex11.pp shows how to get the data from an SRV resource record
Index: netdbex/ex1.pp
===================================================================
--- netdbex/ex1.pp	(nonexistent)
+++ netdbex/ex1.pp	(working copy)
@@ -0,0 +1,83 @@
+Program Example1;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate the DnsLookup function. We'll query DNS for
+  an A record and print the result. The query will be UDP.}
+
+Uses netdb, sockets, sysutils;
+
+function QTypeToStr(qtype: Word): String;
+begin
+   Result := 'Unknown';
+   case qtype of
+      DNSQRY_A: Result := 'A';
+      DNSQRY_AAAA: Result := 'AAAA';
+      DNSQRY_NS: Result := 'NS';
+      DNSQRY_SOA: Result := 'SOA';
+      DNSQRY_MX: Result := 'MX';
+      DNSQRY_CNAME: Result := 'CNAME';
+      DNSQRY_PTR: Result := 'PTR';
+      DNSQRY_TXT: Result := 'TXT';
+      DNSQRY_SRV: Result := 'SRV';
+   end;
+end;
+
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
+var
+   s: AnsiString;
+   ss: ShortString;
+   ip: THostAddr;
+begin
+   s := QTypeToStr(RRN.RRMeta.Atype);
+
+   write('  ' + RRN.RRName);
+   write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+   write(s+' ');
+   case RRN.RRMeta.Atype of
+      DNSQRY_A:
+         begin
+            if DNSRRGetA(RRN, pl, ip) then
+               WriteLn(HostAddrToStr(ip))
+         end;
+      DNSQRY_CNAME:
+         begin
+            if DNSRRGetCNAME(RRN, pl, ss) then
+               WriteLn(ss);
+         end;
+   end;
+end;
+
+const
+   DMN = 'freepascal.org';
+
+var
+   Ans: TQueryData;
+   MaxAnswer, AnsLen: Longint;
+   AnsStart: Word;
+   RRN: TRRNameData;
+   RRArr: TRRNameDataArray;
+
+begin
+   WriteLn('Query '+DMN);
+   // Make a DNS query over UDP (Ans is a TQueryData). We'll query the A
+   // record for our test domain.
+   if DnsLookup(DMN, DNSQRY_A, Ans, AnsLen) then
+   begin
+      AnsStart:=SkipAnsQueries(Ans,AnsLen);
+      MaxAnswer:=Ans.h.AnCount;
+
+      // Get the answers in the Answers section. A record queries
+      // do not result in entries in the other sections.
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Answer');
+         // A record queries may generate multiple answers.
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+   end
+   else
+      WriteLn('DNS query failed.');
+end.
Index: netdbex/ex10.pp
===================================================================
--- netdbex/ex10.pp	(nonexistent)
+++ netdbex/ex10.pp	(working copy)
@@ -0,0 +1,83 @@
+Program Example10;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate reading SOA records.}
+
+Uses netdb, sockets, sysutils;
+
+function QTypeToStr(qtype: Word): String;
+begin
+   Result := 'Unknown';
+   case qtype of
+      DNSQRY_A: Result := 'A';
+      DNSQRY_AAAA: Result := 'AAAA';
+      DNSQRY_NS: Result := 'NS';
+      DNSQRY_SOA: Result := 'SOA';
+      DNSQRY_MX: Result := 'MX';
+      DNSQRY_CNAME: Result := 'CNAME';
+      DNSQRY_PTR: Result := 'PTR';
+      DNSQRY_TXT: Result := 'TXT';
+      DNSQRY_SRV: Result := 'SRV';
+   end;
+end;
+
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
+var
+   s: AnsiString;
+   soa: TDNSRR_SOA;
+begin
+   s := QTypeToStr(RRN.RRMeta.Atype);
+
+   write('  ' + RRN.RRName);
+   write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+   write(s+' ');
+   case RRN.RRMeta.Atype of
+      DNSQRY_SOA:
+         begin
+            if DNSRRGetSOA(RRN, pl, soa) then
+            begin
+               WriteLn(
+                  soa.mname+
+                     ' '+soa.rname+
+                     ', serial:'+inttostr(soa.serial)+
+                     ', refresh:'+inttostr(soa.refresh)+
+                     ', retry:'+inttostr(soa.retry)+
+                     ', min:'+inttostr(soa.min)+
+                     ', expire:'+inttostr(soa.expire))
+            end;
+         end;
+   end;
+end;
+
+const
+   DMN = 'freepascal.org';
+
+var
+   Ans: TQueryData;
+   MaxAnswer, AnsLen: Longint;
+   AnsStart: Word;
+   RRN: TRRNameData;
+   RRArr: TRRNameDataArray;
+
+begin
+   WriteLn('Query '+DMN);
+   // Make a DNS query over UDP (Ans is a TQueryData).
+   if DnsLookup(DMN, DNSQRY_SOA, Ans, AnsLen) then
+   begin
+      AnsStart:=SkipAnsQueries(Ans,AnsLen);
+      MaxAnswer:=Ans.h.AnCount;
+
+      // Get the answers in the Answers section.
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Answer');
+         // A record queries may generate multiple answers.
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+   end
+   else
+      WriteLn('DNS query failed.');
+end.
Index: netdbex/ex11.pp
===================================================================
--- netdbex/ex11.pp	(nonexistent)
+++ netdbex/ex11.pp	(working copy)
@@ -0,0 +1,117 @@
+Program Example11;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate reading SRV records.}
+
+Uses netdb, sockets, sysutils;
+
+function QTypeToStr(qtype: Word): String;
+begin
+   Result := 'Unknown';
+   case qtype of
+      DNSQRY_A: Result := 'A';
+      DNSQRY_AAAA: Result := 'AAAA';
+      DNSQRY_NS: Result := 'NS';
+      DNSQRY_SOA: Result := 'SOA';
+      DNSQRY_MX: Result := 'MX';
+      DNSQRY_CNAME: Result := 'CNAME';
+      DNSQRY_PTR: Result := 'PTR';
+      DNSQRY_TXT: Result := 'TXT';
+      DNSQRY_SRV: Result := 'SRV';
+   end;
+end;
+
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
+var
+   s: AnsiString;
+   ip: THostAddr;
+   srv: TDNSRR_SRV;
+   ip6: THostAddr6;
+begin
+   s := QTypeToStr(RRN.RRMeta.Atype);
+
+   write('  ' + RRN.RRName);
+   write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+   write(s+' ');
+   case RRN.RRMeta.Atype of
+      DNSQRY_A:
+         begin
+            if DNSRRGetA(RRN, pl, ip) then
+               WriteLn(HostAddrToStr(ip))
+         end;
+      DNSQRY_AAAA:
+         begin
+            if DNSRRGetAAAA(RRN, pl, ip6) then
+               WriteLn(HostAddrToStr6(ip6))
+         end;
+      DNSQRY_SRV:
+         begin
+            if DNSRRGetSRV(RRN, pl, srv) then
+               WriteLn('priority: '+inttostr(srv.priority)+
+                          ', weight: '+inttostr(srv.weight)+
+                          ', port: '+inttostr(srv.port)+
+                          ', target host: '+srv.target);
+         end;
+   end;
+end;
+
+const
+   DMN = '_sip._udp.sip.voice.google.com';
+
+var
+   Ans: TQueryData;
+   MaxAnswer, AnsLen: Longint;
+   AnsStart: Word;
+   RRN: TRRNameData;
+   RRArr: TRRNameDataArray;
+
+begin
+   WriteLn('Query '+DMN);
+   // Make a DNS query over UDP (Ans is a TQueryData). We'll find the
+   // host and port for voip calls on Google.
+   if DnsLookup(DMN, DNSQRY_SRV, Ans, AnsLen) then
+   begin
+      AnsStart:=SkipAnsQueries(Ans,AnsLen);
+      MaxAnswer:=Ans.h.AnCount;
+
+      // Get the answers in the Answers section.
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Answer');
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+
+      // Get the answers in the Authority section. The previous call to
+      // GetRRrecords left AnsStart pointing to the start of this section.
+
+      // Note too how we must call NToHS on nscount. It is still in network
+      // byte order.
+      MaxAnswer := NToHS(Ans.h.nscount);
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Authority');
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+
+      // Get the answers in the Additional section. The previous call to
+      // GetRRrecords left AnsStart pointing to the start of this section.
+
+      // Note too how we must call NToHS on arcount. It is still in network
+      // byte order.
+      MaxAnswer := NToHS(Ans.h.arcount);
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Additional');
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+   end
+   else
+      WriteLn('DNS query failed.');
+end.
Index: netdbex/ex2.pp
===================================================================
--- netdbex/ex2.pp	(nonexistent)
+++ netdbex/ex2.pp	(working copy)
@@ -0,0 +1,117 @@
+Program Example2;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate the DnsLookup function. We'll query DNS for
+  NS records and output the results for all sections of the
+  response. The query will be transmitted via UDP. }
+
+Uses netdb, sockets, sysutils;
+
+function QTypeToStr(qtype: Word): String;
+begin
+   Result := 'Unknown';
+   case qtype of
+      DNSQRY_A: Result := 'A';
+      DNSQRY_AAAA: Result := 'AAAA';
+      DNSQRY_NS: Result := 'NS';
+      DNSQRY_SOA: Result := 'SOA';
+      DNSQRY_MX: Result := 'MX';
+      DNSQRY_CNAME: Result := 'CNAME';
+      DNSQRY_PTR: Result := 'PTR';
+      DNSQRY_TXT: Result := 'TXT';
+      DNSQRY_SRV: Result := 'SRV';
+   end;
+end;
+
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
+var
+   s: AnsiString;
+   ss: ShortString;
+   ip: THostAddr;
+   ip6: THostAddr6;
+begin
+   s := QTypeToStr(RRN.RRMeta.Atype);
+
+   write('  ' + RRN.RRName);
+   write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+   write(s+' ');
+   case RRN.RRMeta.Atype of
+      DNSQRY_A:
+         begin
+            if DNSRRGetA(RRN, pl, ip) then
+               WriteLn(HostAddrToStr(ip))
+         end;
+      DNSQRY_AAAA:
+         begin
+            if DNSRRGetAAAA(RRN, pl, ip6) then
+               WriteLn(HostAddrToStr6(ip6));
+         end;
+      DNSQRY_NS:
+         begin
+            if DNSRRGetNS(RRN, pl, ss) then
+               WriteLn(ss);
+         end;
+   end;
+end;
+
+const
+   DMN = 'google.com';
+
+var
+   Ans: TQueryData;
+   MaxAnswer, AnsLen: Longint;
+   AnsStart: Word;
+   RRN: TRRNameData;
+   RRArr: TRRNameDataArray;
+
+begin
+   WriteLn('Query '+DMN);
+   // Make a DNS query over UDP (Ans is a TQueryData). We'll query NS (name server)
+   // records for our test domain. This should return one or more NS records and
+   // one or more A and AAAA records in the additional section.
+   if DnsLookup(DMN, DNSQRY_NS, Ans, AnsLen) then
+   begin
+      AnsStart:=SkipAnsQueries(Ans,AnsLen);
+      MaxAnswer:=Ans.h.AnCount;
+
+      // Get the answers in the Answers section.
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Answer');
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+
+      // Get the answers in the Authority section. The previous call to
+      // GetRRrecords left AnsStart pointing to the start of this section.
+
+      // Note too how we must call NToHS on nscount. It is still in network
+      // byte order.
+      MaxAnswer := NToHS(Ans.h.nscount);
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Authority');
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+
+      // Get the answers in the Additional section. The previous call to
+      // GetRRrecords left AnsStart pointing to the start of this section.
+
+      // Note too how we must call NToHS on arcount. It is still in network
+      // byte order.
+      MaxAnswer := NToHS(Ans.h.arcount);
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Additional');
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+   end
+   else
+      WriteLn('DNS query failed.');
+end.
Index: netdbex/ex3.pp
===================================================================
--- netdbex/ex3.pp	(nonexistent)
+++ netdbex/ex3.pp	(working copy)
@@ -0,0 +1,78 @@
+Program Example3;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate the DnsLookup function. We'll query DNS for a
+  TXT record and print the result. The query will be transmitted over
+  TCP.}
+
+Uses netdb,sysutils;
+
+function QTypeToStr(qtype: Word): String;
+begin
+   Result := 'Unknown';
+   case qtype of
+      DNSQRY_A: Result := 'A';
+      DNSQRY_AAAA: Result := 'AAAA';
+      DNSQRY_NS: Result := 'NS';
+      DNSQRY_SOA: Result := 'SOA';
+      DNSQRY_MX: Result := 'MX';
+      DNSQRY_CNAME: Result := 'CNAME';
+      DNSQRY_PTR: Result := 'PTR';
+      DNSQRY_TXT: Result := 'TXT';
+      DNSQRY_SRV: Result := 'SRV';
+   end;
+end;
+
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayloadTCP);
+var
+   s: AnsiString;
+   ss: AnsiString;
+begin
+   s := QTypeToStr(RRN.RRMeta.Atype);
+
+   write('  ' + RRN.RRName);
+   write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+   write(s+' ');
+   case RRN.RRMeta.Atype of
+      DNSQRY_TXT:
+         begin
+            if DNSRRGetText(RRN, pl, ss) then
+               WriteLn(ss);
+         end;
+   end;
+end;
+
+const
+   DMN = 'sony.com';
+
+var
+   Ans: TQueryDataLengthTCP;
+   MaxAnswer, AnsLen: Longint;
+   AnsStart: Word;
+   RRN: TRRNameData;
+   RRArr: TRRNameDataArray;
+
+begin
+   WriteLn('Query '+DMN);
+   // Make a DNS query over TCP (Ans is a TQueryDataLengthTCP). We'll query the TXT
+   // record for our test domain.
+   if DnsLookup(DMN, DNSQRY_TXT, Ans, AnsLen) then
+   begin
+      AnsStart:=SkipAnsQueries(Ans,AnsLen);
+      MaxAnswer:=Ans.h.AnCount;
+
+      // Get the answers in the Answers section. TXT record queries
+      // do not result in entries in the other sections.
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Answer');
+         // TXT record queries may generate multiple answers.
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+   end
+   else
+      WriteLn('DNS query failed.');
+end.
Index: netdbex/ex4.pp
===================================================================
--- netdbex/ex4.pp	(nonexistent)
+++ netdbex/ex4.pp	(working copy)
@@ -0,0 +1,118 @@
+Program Example4;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate how to build a low-level DNS query message
+  using BuildPayload.}
+
+Uses netdb,sysutils,sockets;
+
+function QTypeToStr(qtype: Word): String;
+begin
+   Result := 'Unknown';
+   case qtype of
+      DNSQRY_A: Result := 'A';
+      DNSQRY_AAAA: Result := 'AAAA';
+      DNSQRY_NS: Result := 'NS';
+      DNSQRY_SOA: Result := 'SOA';
+      DNSQRY_MX: Result := 'MX';
+      DNSQRY_CNAME: Result := 'CNAME';
+      DNSQRY_PTR: Result := 'PTR';
+      DNSQRY_TXT: Result := 'TXT';
+      DNSQRY_SRV: Result := 'SRV';
+   end;
+end;
+
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
+var
+   s: AnsiString;
+   ss: ShortString;
+   ip: THostAddr;
+begin
+   s := QTypeToStr(RRN.RRMeta.Atype);
+
+   write('  ' + RRN.RRName);
+   write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+   write(s+' ');
+   case RRN.RRMeta.Atype of
+      DNSQRY_A:
+         begin
+            if DNSRRGetA(RRN, pl, ip) then
+               WriteLn(HostAddrToStr(ip))
+         end;
+      DNSQRY_CNAME:
+         begin
+            if DNSRRGetCNAME(RRN, pl, ss) then
+               WriteLn(ss);
+         end;
+   end;
+end;
+
+{
+LowLevelQuery demonstrates how to use BuildPayload to construct a query.
+}
+function LowLevelQuery(dn: String; qtype: Word; out Ans: TQueryData; out
+  AnsLen: Longint): Boolean;
+var
+  Qry: TQueryData;
+  QryLen: Longint;
+  idx: Word;
+begin
+  Result := False;
+  AnsLen := -2;
+
+  // load the system DNS resolvers, if not already loaded.
+  CheckResolveFile;
+  if Length(DNSServers) = 0 then
+    exit;
+
+  // Construct a query message.
+  QryLen := BuildPayLoad(Qry, dn, qtype, 1);
+  if QryLen <= 0 then exit;
+
+  AnsLen := -1;
+  { Try the query at each configured resolver in turn, until one of them
+   returns an answer. We check for AnsLen > -1 because we need to distinguish
+   between failure to connect and the server saying it doesn't know or can't
+   answer. If AnsLen = -1 then we failed to connect. If AnsLen >= 0 but qr
+   = False, then we connected but the server returned an error code.}
+  idx := 0;
+  repeat
+    Result := Query(idx,Qry,Ans,QryLen,AnsLen);
+    Inc(idx);
+  until (idx > High(DNSServers)) or (Result = True) or (AnsLen >= 0);
+end;
+
+const
+   DMN = 'freepascal.org';
+
+var
+   Ans: TQueryData;
+   MaxAnswer, AnsLen: Longint;
+   AnsStart: Word;
+   RRN: TRRNameData;
+   RRArr: TRRNameDataArray;
+
+begin
+   WriteLn('Query '+DMN);
+   // Make a DNS query over UDP (Ans is a TQueryData). We'll query the A
+   // record for our test domain.
+   if LowLevelQuery(DMN, DNSQRY_A, Ans, AnsLen) then
+   begin
+      AnsStart:=SkipAnsQueries(Ans,AnsLen);
+      MaxAnswer:=Ans.h.AnCount;
+
+      // Get the answers in the Answers section. A record queries
+      // do not result in entries in the other sections.
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Answer');
+         // A record queries may generate multiple answers.
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+   end
+   else
+      WriteLn('DNS query failed.');
+end.
Index: netdbex/ex5.pp
===================================================================
--- netdbex/ex5.pp	(nonexistent)
+++ netdbex/ex5.pp	(working copy)
@@ -0,0 +1,113 @@
+Program Example5;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate how to build a low-level DNS query message
+  using BuildPayloadTCP.}
+
+Uses netdb,sysutils,sockets;
+
+function QTypeToStr(qtype: Word): String;
+begin
+   Result := 'Unknown';
+   case qtype of
+      DNSQRY_A: Result := 'A';
+      DNSQRY_AAAA: Result := 'AAAA';
+      DNSQRY_NS: Result := 'NS';
+      DNSQRY_SOA: Result := 'SOA';
+      DNSQRY_MX: Result := 'MX';
+      DNSQRY_CNAME: Result := 'CNAME';
+      DNSQRY_PTR: Result := 'PTR';
+      DNSQRY_TXT: Result := 'TXT';
+      DNSQRY_SRV: Result := 'SRV';
+   end;
+end;
+
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayloadTCP);
+var
+   s: AnsiString;
+   ss: AnsiString;
+begin
+   s := QTypeToStr(RRN.RRMeta.Atype);
+
+   write('  ' + RRN.RRName);
+   write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+   write(s+' ');
+   case RRN.RRMeta.Atype of
+      DNSQRY_TXT:
+         begin
+            if DNSRRGetText(RRN, pl, ss) then
+               WriteLn(ss);
+         end;
+   end;
+end;
+
+{
+LowLevelQuery demonstrates how to use BuildPayloadTCP to construct a query.
+}
+function LowLevelQuery(dn: String; qtype: Word; out Ans: TQueryDataLengthTCP; out
+  AnsLen: Longint): Boolean;
+var
+  Qry: TQueryDataLength;
+  QryLen: Longint;
+  idx: Word;
+
+begin
+  Result := False;
+  AnsLen := -2;
+
+  // load system resolvers, if not already loaded.
+  CheckResolveFile;
+  if Length(DNSServers) = 0 then
+    exit;
+
+  // Construct a TCP payload.
+  QryLen:=BuildPayLoadTCP(Qry, dn, qtype, 1);
+  if QryLen <= 0 then exit;
+  AnsLen := -1;
+
+  { Try the query at each configured resolver in turn, until one of them
+   returns an answer. We check for AnsLen > -1 because we need to distinguish
+   between failure to connect and the server saying it doesn't know or can't
+   answer. If AnsLen = -1 then we failed to connect. If AnsLen >= 0 but qr
+   = False, then we connected but the server returned an error code.}
+  idx := 0;
+  repeat
+    Result := QueryTCP(idx,Qry,Ans,QryLen,AnsLen);
+    Inc(idx);
+  until (idx > High(DNSServers)) or (Result = True) or (AnsLen >= 0);
+end;
+
+const
+   DMN = 'sony.com';
+
+var
+   Ans: TQueryDataLengthTCP;
+   MaxAnswer, AnsLen: Longint;
+   AnsStart: Word;
+   RRN: TRRNameData;
+   RRArr: TRRNameDataArray;
+
+begin
+   WriteLn('Query '+DMN);
+   // Make a DNS query over TCP (Ans is a TQueryDataLengthTCP). We'll query the TXT
+   // record for our test domain.
+   if LowLevelQuery(DMN, DNSQRY_TXT, Ans, AnsLen) then
+   begin
+      AnsStart:=SkipAnsQueries(Ans,AnsLen);
+      MaxAnswer:=Ans.h.AnCount;
+
+      // Get the answers in the Answers section. TXT record queries
+      // do not result in entries in the other sections.
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Answer');
+         // TXT record queries may generate multiple answers.
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+   end
+   else
+      WriteLn('DNS query failed.');
+end.
Index: netdbex/ex6.pp
===================================================================
--- netdbex/ex6.pp	(nonexistent)
+++ netdbex/ex6.pp	(working copy)
@@ -0,0 +1,40 @@
+Program Example3;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate the IsTruncated function. We'll query DNS for a
+  TXT record using UDP and if the response is Truncated, retry as TCP.}
+
+Uses netdb,sysutils;
+
+const
+   DMN = 'sony.com';
+
+var
+   AnsU: TQueryData;
+   AnsT: TQueryDataLengthTCP;
+   AnsLen: Longint;
+
+begin
+   WriteLn('Query '+DMN);
+   // Make the initial DNS query over UDP
+   if DnsLookup(DMN, DNSQRY_TXT, AnsU, AnsLen) then
+   begin
+      // Got a response. Now see if it's truncated.
+      if IsTruncated(AnsU.h) then
+      begin
+         writeln('Response truncated ... retrying as TCP');
+
+         if DnsLookup(DMN, DNSQRY_TXT, AnsT, AnsLen) then
+         begin
+            // process the resource records
+         end
+         else WriteLn('DNS query failed.');
+      end
+      else
+      begin
+         WriteLn('Response was not truncated.');
+         // process the resource records
+      end;
+   end;
+end.
Index: netdbex/ex7.pp
===================================================================
--- netdbex/ex7.pp	(nonexistent)
+++ netdbex/ex7.pp	(working copy)
@@ -0,0 +1,82 @@
+Program Example7;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate the DNSRRGetAAAA function. We'll query DNS for
+  an AAAA record and print the result. The query will be UDP.}
+
+Uses netdb, sockets, sysutils;
+
+function QTypeToStr(qtype: Word): String;
+begin
+   Result := 'Unknown';
+   case qtype of
+      DNSQRY_A: Result := 'A';
+      DNSQRY_AAAA: Result := 'AAAA';
+      DNSQRY_NS: Result := 'NS';
+      DNSQRY_SOA: Result := 'SOA';
+      DNSQRY_MX: Result := 'MX';
+      DNSQRY_CNAME: Result := 'CNAME';
+      DNSQRY_PTR: Result := 'PTR';
+      DNSQRY_TXT: Result := 'TXT';
+      DNSQRY_SRV: Result := 'SRV';
+   end;
+end;
+
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
+var
+   s: AnsiString;
+   ss: ShortString;
+   ip6: THostAddr6;
+begin
+   s := QTypeToStr(RRN.RRMeta.Atype);
+
+   write('  ' + RRN.RRName);
+   write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+   write(s+' ');
+   case RRN.RRMeta.Atype of
+      DNSQRY_AAAA:
+         begin
+            if DNSRRGetAAAA(RRN, pl, ip6) then
+               WriteLn(HostAddrToStr6(ip6))
+         end;
+      DNSQRY_CNAME:
+         begin
+            if DNSRRGetCNAME(RRN, pl, ss) then
+               WriteLn(ss);
+         end;
+   end;
+end;
+
+const
+   DMN = 'google.com';
+
+var
+   Ans: TQueryData;
+   MaxAnswer, AnsLen: Longint;
+   AnsStart: Word;
+   RRN: TRRNameData;
+   RRArr: TRRNameDataArray;
+
+begin
+   WriteLn('Query '+DMN);
+   // Make a DNS query over UDP (Ans is a TQueryData). We'll query the AAAA
+   // record for our test domain.
+   if DnsLookup(DMN, DNSQRY_AAAA, Ans, AnsLen) then
+   begin
+      AnsStart:=SkipAnsQueries(Ans,AnsLen);
+      MaxAnswer:=Ans.h.AnCount;
+
+      // Get the answers in the Answers section. 
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Answer');
+         // Get all resource records
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+   end
+   else
+      WriteLn('DNS query failed.');
+end.
Index: netdbex/ex8.pp
===================================================================
--- netdbex/ex8.pp	(nonexistent)
+++ netdbex/ex8.pp	(working copy)
@@ -0,0 +1,114 @@
+Program Example8;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate reading MX records.}
+
+Uses netdb, sockets, sysutils;
+
+function QTypeToStr(qtype: Word): String;
+begin
+   Result := 'Unknown';
+   case qtype of
+      DNSQRY_A: Result := 'A';
+      DNSQRY_AAAA: Result := 'AAAA';
+      DNSQRY_NS: Result := 'NS';
+      DNSQRY_SOA: Result := 'SOA';
+      DNSQRY_MX: Result := 'MX';
+      DNSQRY_CNAME: Result := 'CNAME';
+      DNSQRY_PTR: Result := 'PTR';
+      DNSQRY_TXT: Result := 'TXT';
+      DNSQRY_SRV: Result := 'SRV';
+   end;
+end;
+
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
+var
+   s: AnsiString;
+   ip: THostAddr;
+   mx: TDNSRR_MX;
+   ip6: THostAddr6;
+begin
+   s := QTypeToStr(RRN.RRMeta.Atype);
+
+   write('  ' + RRN.RRName);
+   write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+   write(s+' ');
+   case RRN.RRMeta.Atype of
+      DNSQRY_A:
+         begin
+            if DNSRRGetA(RRN, pl, ip) then
+               WriteLn(HostAddrToStr(ip))
+         end;
+      DNSQRY_AAAA:
+         begin
+            if DNSRRGetAAAA(RRN, pl, ip6) then
+               WriteLn(HostAddrToStr6(ip6))
+         end;
+      DNSQRY_MX:
+         begin
+            if DNSRRGetMX(RRN, pl, mx) then
+               WriteLn(inttostr(mx.preference)+' '+mx.exchange);
+         end;
+   end;
+end;
+
+const
+   DMN = 'freepascal.org';
+
+var
+   Ans: TQueryData;
+   MaxAnswer, AnsLen: Longint;
+   AnsStart: Word;
+   RRN: TRRNameData;
+   RRArr: TRRNameDataArray;
+
+begin
+   WriteLn('Query '+DMN);
+   // Make a DNS query over UDP (Ans is a TQueryData).
+   if DnsLookup(DMN, DNSQRY_MX, Ans, AnsLen) then
+   begin
+      AnsStart:=SkipAnsQueries(Ans,AnsLen);
+      MaxAnswer:=Ans.h.AnCount;
+
+      // Get the answers in the Answers section.
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Answer');
+         // A record queries may generate multiple answers.
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+
+      // Get the answers in the Authority section. The previous call to
+      // GetRRrecords left AnsStart pointing to the start of this section.
+
+      // Note too how we must call NToHS on nscount. It is still in network
+      // byte order.
+      MaxAnswer := NToHS(Ans.h.nscount);
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Authority');
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+
+      // Get the answers in the Additional section. The previous call to
+      // GetRRrecords left AnsStart pointing to the start of this section.
+
+      // Note too how we must call NToHS on arcount. It is still in network
+      // byte order.
+      MaxAnswer := NToHS(Ans.h.arcount);
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Additional');
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+   end
+   else
+      WriteLn('DNS query failed.');
+end.
Index: netdbex/ex9.pp
===================================================================
--- netdbex/ex9.pp	(nonexistent)
+++ netdbex/ex9.pp	(working copy)
@@ -0,0 +1,84 @@
+Program Example9;
+
+{$mode objfpc}{$H+}
+
+{ Program to demonstrate PTR lookups. A PTR lookup converts an IP
+  address to a hostname.  To make a PTR query, the IP address octets
+  must be reversed and the special domain in-addr.arpa appended.}
+
+Uses netdb, sockets, sysutils;
+
+function QTypeToStr(qtype: Word): String;
+begin
+   Result := 'Unknown';
+   case qtype of
+      DNSQRY_A: Result := 'A';
+      DNSQRY_AAAA: Result := 'AAAA';
+      DNSQRY_NS: Result := 'NS';
+      DNSQRY_SOA: Result := 'SOA';
+      DNSQRY_MX: Result := 'MX';
+      DNSQRY_CNAME: Result := 'CNAME';
+      DNSQRY_PTR: Result := 'PTR';
+      DNSQRY_TXT: Result := 'TXT';
+      DNSQRY_SRV: Result := 'SRV';
+   end;
+end;
+
+procedure DumpNameRR(const RRN: TRRNameData; const pl: TPayload);
+var
+   s: AnsiString;
+   ss: ShortString;
+begin
+   s := QTypeToStr(RRN.RRMeta.Atype);
+
+   write('  ' + RRN.RRName);
+   write(' '+inttostr(RRN.RRMeta.TTL)+' ');
+   write(s+' ');
+   case RRN.RRMeta.Atype of
+      DNSQRY_PTR:
+         begin
+            if DNSRRGetPTR(RRN, pl, ss) then
+               WriteLn(ss);
+         end;
+   end;
+end;
+
+const
+   IP= '85.222.228.11';
+
+var
+   Ans: TQueryData;
+   MaxAnswer, AnsLen: Longint;
+   AnsStart: Word;
+   RRN: TRRNameData;
+   RRArr: TRRNameDataArray;
+   S1: AnsiString;
+   H: THostAddr;
+
+begin
+   // convert IP address in string format to THostAddr in network byte order.
+   H := StrToNetAddr(IP);
+   // Reverse ip and append in-addr.arpa
+   S1:=Format('%d.%d.%d.%d.in-addr.arpa',[
+                 H.s_bytes[4],H.s_bytes[3],H.s_bytes[2],H.s_bytes[1]]);
+
+   WriteLn('PTR Query for IP '+IP+', '+S1);
+   // Make a DNS query over UDP (Ans is a TQueryData). We'll query the PTR
+   // record for our test IP.
+   if DnsLookup(S1, DNSQRY_PTR, Ans, AnsLen) then
+   begin
+      AnsStart:=SkipAnsQueries(Ans,AnsLen);
+      MaxAnswer:=Ans.h.AnCount;
+
+      // Get the answers in the Answers section.
+      if MaxAnswer > 0 then
+      begin
+         WriteLn('Answer');
+         RRArr := GetRRrecords(Ans.Payload, AnsStart, MaxAnswer);
+         for RRN in RRArr do
+            DumpNameRR(RRN, Ans.Payload);
+      end;
+   end
+   else
+      WriteLn('DNS query failed.');
+end.
Index: netdbex/foot.tex
===================================================================
--- netdbex/foot.tex	(nonexistent)
+++ netdbex/foot.tex	(working copy)
@@ -0,0 +1,2 @@
+\end{verbatim}
+\end{FPCList}
\ No newline at end of file
Index: netdbex/head.tex
===================================================================
--- netdbex/head.tex	(nonexistent)
+++ netdbex/head.tex	(working copy)
@@ -0,0 +1,3 @@
+\begin{FPCList}
+\item[Example]
+\begin{verbatim}
Index: netdbex/newex
===================================================================
--- netdbex/newex	(nonexistent)
+++ netdbex/newex	(working copy)
@@ -0,0 +1,8 @@
+#!/bin/sh
+if [ -e ex${1}.pp ]; then
+  mv ex${1}.pp ex${1}.pp.orig
+fi
+sed -e s/Example/Example$1/ -e s/\\\*\\\*\\\*/$2/ <template.pp >ex${1}.pp
+echo "ex${1}.pp contains an example of the $2 function." >>README
+joe ex${1}.pp
+ppc386 ex${1}.pp && ex${1}
Index: netdbex/template.pp
===================================================================
--- netdbex/template.pp	(nonexistent)
+++ netdbex/template.pp	(working copy)
@@ -0,0 +1,8 @@
+Program Example;
+
+{ Program to demonstrate the *** function. }
+
+Uses BaseUnix,Unix;
+
+begin
+end.
netdb-2.patch (141,790 bytes)   

Issue History

Date Modified Username Field Change
2020-10-11 00:30 Noel Duffy New Issue
2020-10-11 00:33 Noel Duffy Note Added: 0126231
2020-10-11 00:51 Noel Duffy Note Added: 0126232
2020-10-11 00:51 Noel Duffy File Added: dnstest.lpr
2020-10-11 00:51 Noel Duffy File Added: newnetdb.pp
2020-12-06 03:02 Noel Duffy Note Added: 0127368
2020-12-06 03:02 Noel Duffy File Added: dnsq.lpr
2020-12-06 03:02 Noel Duffy File Added: newnetdb-2.pp
2020-12-13 10:49 Noel Duffy Note Added: 0127584
2020-12-13 10:49 Noel Duffy File Added: netdb-dns.patch
2020-12-13 10:49 Noel Duffy File Added: dnsq.pp
2021-01-30 04:24 Noel Duffy Note Added: 0128668
2021-01-30 04:24 Noel Duffy File Added: netdb.patch
2021-01-30 04:24 Noel Duffy File Added: netdb-example.patch
2021-01-30 04:24 Noel Duffy File Added: netdb-tests.patch
2021-01-30 09:33 Michael Van Canneyt Assigned To => Michael Van Canneyt
2021-01-30 09:33 Michael Van Canneyt Status new => resolved
2021-01-30 09:33 Michael Van Canneyt Resolution open => fixed
2021-01-30 09:33 Michael Van Canneyt Fixed in Version => 3.3.1
2021-01-30 09:33 Michael Van Canneyt Fixed in Revision => 48455
2021-01-30 09:33 Michael Van Canneyt FPCTarget => 3.2.2
2021-01-30 09:33 Michael Van Canneyt Note Added: 0128670
2021-04-17 05:54 Noel Duffy Note Added: 0130418
2021-04-17 05:54 Noel Duffy File Added: netdb-2.patch