[ zurück ] Icon

"Wer ist Wer im Internet" - DNS-Resolver mit Delphi 3


Im Magazin "Der Entwickler" habe ich unter "Wer ist Wer im Internet" eine Komponente DNS-Resolver beschrieben. Sie baut ebenfalls auf den TCP/IP-Komponenten aus der Unit ScktComp auf. IhreFunktion besteht darin, zu einer vorgegebenen IP-Adresse den Domain-Namen oder umgekehrt zu einem bekannten Domain-Namen die IP-Adresse zu ermitteln. Beides geschieht durch eine Anfrage beim DNS-Dienst im Internet.

Meine Komponente TResolver realisiert nur eine minimale Untermenge der Funktionalität des DNS-Dienstes. Sie ist ein reines Zweckprodukt und auch nicht gemäß allen Konventionen der Delphi-Komponentenentwicklung programmiert. Das bedeutet aber nicht, daß sie nachlässig programmiert oder in ihrer Funktion beeinträchtigt wäre.

Im Manuskript sind Leistungsmerkmale, Eigenschaften, Methoden und Ereignisse der Komponente beschrieben. Hier noch einmal eine Schnellübersicht:
Die Komponente TResolver
Eigenschaften Activezeigt an, ob der Client momentan Verbindung zum DNS-Server unterhält
Answerenthält das Ergebnis der Anfrage an den Server, sobald das Ereignis OnAnswer eingetreten ist
Serverbezeichnet den für Anfragen zu kontaktierenden DNS-Server über seinen Domain-Namen oder die IP-Adresse
TimeOutenthält die maximale Wartezeit auf die Antwort zu einer Anfrage in Millisekunden
MethodenConnectStellt die Verbindung zum in "Server" angegebenen Server her und geht dazu bei Notwendigkeit online
Closebeendet die Verbindung mit dem DNS-Dienst
Querysendet eine Anfrage an den DNS-Dienst mit dem als Parameter angegebenen Inhalt. Es können sowohl IP-Adressen als auch Domains erfragt werden
EreignisseOnAnswertritt auf, wenn die Antwort auf eine Anfrage komplett empfangen wurde
OnConnecttritt ein, nachdem die Verbindung zum Server aufgenommen wurde
OnErrorsignalisiert, daß in der Verbindung ein Fehler aufgetreten ist und die Verbindung getrennt wurde. Der Fehler wird als Textmeldung übergeben
OnTimeOut zeigt eine Zeitüberschreitung beim Warten auf die Antwort an

Programmierung der Komponente

Die Komponente TResolver basiert zwar auf den TCP/IP-Komponenten aus der Unit ScktComp, ist aber tatsächlich vom Typ TComponent abgeleitet:
type TResolver = class(TComponent)
 private
  FActive: Boolean;                                   // Verbindungszustand
  FAnswer: String;                                          // Rückgabewert
  FID: Word;                                             // Query-ID-Nummer
  FInLength: Integer;                            // empfangene Antwortlänge
  FInString: String;                                     // Empfangsantwort
  FQueryLength: Integer;                                    // Abfragelänge
  FQueryTyp: Integer;                                         // Abfragetyp
  FServer: String;                                      //remote DNS-Server
  FSock: TClientSocket;                                     // ClientSocket
  FTimer: TTimer;                                          // TimeOut-Timer
  FOnConnect: TNotifyEvent;
  FOnAnswer: TNotifyEvent;
  FOnError: TResolverErrorEvent;
  FOnTimeOut: TNotifyEvent;
   :
 published
  property Server: String read FServer write FServer;
  property TimeOut: Cardinal read GetTimeOut write SetTimeOut default 15000;
  property OnConnect: TNotifyEvent read FOnConnect write FOnConnect;
  property OnError: TResolverErrorEvent read FOnError write FOnError;
  property OnAnswer: TNotifyEvent read FOnAnswer write FOnAnswer;
  property OnTimeOut: TNotifyEvent read FOnTimeOut write FOnTimeOut;
end;
Während der Konstruktion der Komponente TResolver werden auch die untergeordneten Objekte FSock und FTimer konstruiert. Die wichtigsten Eigenschaften beider Child-Objekte werden dabei gleich gesetzt:
 FSock:= TClientSocket.Create(Self);                // ClientSocket erzeugen
 FSock.ClientType:= ctNonBlocking;
 FSock.Port:= 53;                                              // DNS-Dienst
 FSock.OnConnect:= SockConnect;
 FSock.OnRead:= SockRead;
 FSock.OnError:= SockError;
 FSock.Socket.OnErrorEvent:= SockError;
 FTimer:= TTimer.Create(Self);                     // TimeOut-Timer erzeugen
 FTimer.Enabled:= false;
 FTimer.Interval:= 15000;
 FTimer.OnTimer:= TimerIsOut;
Einige Ereignisse der TClientSocket-Komponente werden an Ereignisse des TResolver einfach nur durchgereicht. Das ist beispielsweise beim OnConnect-Event so. Löst der Socket ein entsprechendes Signal aus, so wird das gleichnamige Ereignis von TResolver aufgerufen. Natürlich nur, wenn der Nutzer der Komponente in das OnConnect-Ereignis etwas eingeklinkt hat:
 procedure TResolver.SockConnect(Sender: TObject; Socket: TCustomWinSocket);
 begin
  if Assigned(FOnConnect) then FOnConnect(Self);      // bei Connect-Erfolg
 end;
Andere Ereignisse müssen aber auch eigene Arbeit leisten. Das wichtigste Beispiel dafür ist das OnAnswer-Signal des Resolvers. Es kann entstehen, wenn der Socket anzeigt, daß er etwas aus dem Netz gelesen hat. Außerdem müssen etliche andere Bedingungen erfüllt sein (z.B. darf der TimeOut-Timer noch nicht abgelaufen sein, das OnAnswer-Event der Komoponente Resolver muß benutzt werden, die Antwort komplett gelesen sein u.s.w.). Folglich ist die Entstehung von OnAnswer in die Prozedur TResolver.SockRead eingebettet:
 procedure TResolver.SockRead(Sender: TObject; Socket: TCustomWinSocket);
 var i, j, nRDLen: Integer; sBuf: String;                  // bei Socketempfang
 begin
  sBuf:= Socket.ReceiveText;
  if FTimer.Enabled then FInString:=FInString + sBuf; //nach TimeOut nicht mehr
  if Assigned(FOnAnswer) and (Length(FInString)>=FQueryLength) then begin
   if FInLength=0 then FInLength:=256*Ord(FInString[1])+Ord(FInString[2])+2;
   if Length(FInString)=FInLength then begin        // Antwort komplett gelesen
     :
    FAnswer:=sBuf;                      // komplette Antwort in FAnswer bringen
    FOnAnswer(Self);               // auslösen des Resolverereignisses OnAnswer
   end; // if Length=FInLength
  end; // if Assigned
 end;
Der Algorithmus für das Decodieren der Antwort innerhalb von TResolver.SockRead folgt den Gesetzmäßigkeiten des Messageaufbaus, wie ich Sie in "über den DNS-Dienst" kurz beschrieben habe:

Zuerst wird die Länge der empfangenen Antwort getestet. Sie muß dem in den ersten beiden Bytes enthaltenen Wert entsprechen:
  if FInLength=0 then FInLength:= 256*Ord(FInString[1])+Ord(FInString[2])+2;
  if Length(FInString)= FInLength then begin        // Antwort komplett gelesen
Danach wird geprüft, ob die dann folgende Message-ID mit der ID der Anfrage übereinstimmt. Anderenfalls wird die ganze bisherige Antwort verworfen:
   if FID<>(256*Ord(FInString[3])+Ord(FInString[4])) then begin   // falsche ID
    FInString:='';
    FInLength:=0;
    Exit;
   end;
Danach wird der Response-Code aus dem Header auf den Wert 0 verglichen:
   if (Ord(FInString[6])mod 16)=0 then begin                  // ResponseCode=0
Bei positivem Ergebnis ist die Antwort informationstragend und wird weiter decodiert. Dazu wird zuerst die ganze in der Antwort enthaltene Query abgeschnitten:
    Delete(FInString,1,FQueryLength);                      // Query abschneiden
Nun muß unterscheiden werden, ob es eine Anfrage nach einer IP-Adresse (Typ 1) oder nach einem Domain-Namen (Typ 12) war:
    if FQueryTyp=1 then                                 // es war Address-Query
     repeat
      nRDLen:=0;                                // Dummy, wegen Compilerwarnung
      i:=Pos(Chr(00)+Chr(01)+Chr(00)+Chr(01),FInString); // Type1+Class1 suchen
       :
     until (nRDLen=4)or(i=0)
    else begin                                          // es war Pointer-Query
     i:=Pos(Chr(00)+Chr(12)+Chr(00)+Chr(01),FInString); // Type12+Class1 suchen
     if i>0 then begin
      :
     end;
    end; // if QueryType 1 or 12
Im Falle einer IP-Anfrage muß im Datenrecord die vierstellige IP-Adresse stehen. Ist der Datenrecord tatsächlich vier Byte lang, wird aus diesen vier Byte die Textentsprechung der IP-Adresse inklusive der drei Punkte generiert.
       nRDLen:=256*Ord(FInString[i+8])+Ord(FInString[i+9]);         // RDLength
       if nRDLen=4 then begin
        for j:=i+10 to i+13 do sBuf:=sBuf+IntToStr(Ord(FInString[j]))+'.';
        Delete(sBuf,Length(sBuf),1);                // letzten Punkt wieder weg
       end else Delete(FInString,1,i+1)       // falsche Fundstelle, neu suchen
Im Falle der Frage nach einem Domain-Namen wird zunächst auch erst einmal die Länge des Antwortrecords ermittelt und der Datensatz herausgeschnitten. Dabei wird nur der erste Record ausgewertet. Eventuell folgende Anwortrecords werden ignoriert:
      nRDLen:=256*Ord(FInString[i+8])+Ord(FInString[i+9]);          // RDLength
      FInString:=Copy(FInString,i+10,nRDLen);       // RDLength herausschneiden
Der selektierte Datenabschnitt wird nun Label für Label interpretiert und die Punkte dazwischen gesetzt:
      while Length(FInString)>1 do begin
       i:=Ord(FInString[1]);                       // Länge des nächsten Labels
       sBuf:=sBuf+Copy(FInString,2,i);                        // Label anhängen
       Delete(FInString,1,i+1);               // Label in InString wegschneiden
       if Length(FInString)>1 then sBuf:=sBuf+'.';            // Punkt anhängen
      end;
Treten währen dieses gesamten Empfangs-Prozesses Fehler auf, wird als Antwort ein Leerstring geliefert. Dennoch wird das Resolver-Event OnAnswer ausgelöst:

Ähnliches läuft in der Prozedur TResolver.Query ab, die die Anfrage erzeugt. Da dieser Prozeß aber wesentlich einfacher ist, wird er hier nicht erläutert. Sehen Sie bei Verständnisschwierigkeiten immer wieder in das Aufbauschema von DNS-Anfragen auf der Seite "über den DNS-Dienst". Wichtige Fragen zur Komponente Resolver beantworte ich im Rahmen meines Zeitfonds. Für Kritik und Fehleranzeigen bin ich jederzeit dankbar. Die Verbesserungswürdigkeit der Komponente ist mir bewußt. Für den beabsichtigten Zweck hat sie sich aber bisher als tauglich erwiesen.

Listinganhang

meine Komponente DNS-Resolver nebst Demoprojekt:
Disk Gesamtlisting mit Resolver-Komponente und Demoprogramm als Zip-Archiv (6 KByte)

Wer mehr über den DNS-Dienst wissen will, wird dann doch mal einen Blick in die Spezifikationen riskieren müssen:
Disk http://velociraptor.mni.fh-giessen.de/rfc/rfc1034.txt (129 KByte)
Disk http://velociraptor.mni.fh-giessen.de/rfc/rfc1035.txt (125 KByte)

J. Hummel,   Januar 2001

[ Programmieren ] [ TCP/IP-Seite ]