[ zurück ]
"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 | Active | zeigt an, ob der Client momentan Verbindung zum DNS-Server unterhält |
| Answer | enthält das Ergebnis der Anfrage an den Server, sobald das Ereignis OnAnswer eingetreten ist |
| Server | bezeichnet den für Anfragen zu kontaktierenden DNS-Server über seinen Domain-Namen oder die IP-Adresse |
| TimeOut | enthält die maximale Wartezeit auf die Antwort zu einer Anfrage in Millisekunden |
Methoden | Connect | Stellt die Verbindung zum in "Server" angegebenen Server her und geht dazu bei Notwendigkeit online |
| Close | beendet die Verbindung mit dem DNS-Dienst |
| Query | sendet eine Anfrage an den DNS-Dienst mit dem als Parameter angegebenen Inhalt. Es können sowohl IP-Adressen als auch Domains erfragt werden |
Ereignisse | OnAnswer | tritt auf, wenn die Antwort auf eine Anfrage komplett empfangen wurde |
| OnConnect | tritt ein, nachdem die Verbindung zum Server aufgenommen wurde |
| OnError | signalisiert, 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 |
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.
meine Komponente DNS-Resolver nebst Demoprojekt:
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:
http://velociraptor.mni.fh-giessen.de/rfc/rfc1034.txt (129 KByte)
http://velociraptor.mni.fh-giessen.de/rfc/rfc1035.txt (125 KByte)
J. Hummel, Januar 2001
[ Programmieren ]
[ TCP/IP-Seite ]