![]() |
DNS-Server mit Indys TIdDNSServer = Verwirrung
Hi zusammen!
Ich wollte für das lokale Netzwerk einen "kleinen" DNS-Server programmieren, damit ich den LAN-IPs (z.B. 192.168.x.x) Namen zuordnen kann. Also der FileServer z.B. nicht mehr mit seiner IP angesprochen werden muss sondern mit meinetwegen FileServer.lan oder wie auch immer. Also habe ich mit der Indy IdDNSServer-Komponente einen Server gebastelt und eine dazugehörige "Zonefile" in der steht welcher Name welcher IP zugeordnet wird. Teste ich es allerdings, gibt der Server keine antwort. Eine Verbindung kommt aber zustande, da dass UDP-OnRead Event ausgelöst wird. Da ich nicht wirklich viel Ahnung von DNS habe kann ich den Fehler nicht eingrenzen. Habe im Internet nirgends Tutorials, etwas ausführlichere Beschreibungen der Komponente oder Beispiele gefunden. Danke schonmal vorweg! |
Re: DNS-Server mit Indys TIdDNSServer = Verwirrung
Liste der Anhänge anzeigen (Anzahl: 1)
Du must den Rechnern auch sagen welchen DNS-Server sie verwenden sollen (falls du es nicht getan hast...)
Ob die Rechner den richtigen verwenden kannst du ja auch nachschauen...
Code:
Schon siehst du welchen sie nehmen...
CMD
ipconfig /all ev. musst du dann noch die DNS-Anfragen weiterschleifen, wenn dein Server die nicht kennen sollte, nicht das du gar keinen Zugriff mehr auf irgendwas bekommst... ;) Aber so tief steck ich da nicht drinn.. Bye Christian |
Re: DNS-Server mit Indys TIdDNSServer = Verwirrung
Habe mit der entsprechenden Indy-komponente einen Resolver für den DNS-Server geschrieben, um ihn zu testen.
Wie gesagt, die Anfrage kommt an, aber er reagiert nicht drauf. Habe das Testprogramm online getestet. Da klappts. Und das Programm brauch unbekannte Namen nicht weiterleiten. Es soll nur die im LAN bekannten Namen auflösen, den Rest ignorieren. |
Re: DNS-Server mit Indys TIdDNSServer = Verwirrung
Hat sich erledigt. Hab nen eigenen DNS-Server programmiert. Der von Indy is totaler Mist.
Danke trotzdem! |
Re: DNS-Server mit Indys TIdDNSServer = Verwirrung
Auf Nachfrage hier grob wie man sich mit Hilfe der IdUDPServer Komponente einen eigenen DNS-Server programmiert, der nicht so überladen ist wie der IdDNSServer aus der Indy-Sammlung.
Schwierigkeit: Fortgeschritten(+) Ich habe den Code auf das grundlegendste gekürzt !! Vorweg einen Link, auf dessen Basis ich den folgenden Code aufbaue: ![]() Das Prinzip: Man erzeugt eine neue Klasse, welche Nachfahre von TIdUDPServer ist, da DNS auf UDP-Basis arbeitet, und auf Port 53 (DNS-Standard) lauscht. Jedes eintreffende Packet wird in die Form wie im RFC beschrieben zerlegt und abgearbeitet. Die daraus entstehende Antwort (falls es eine gibt) wird dann in der selben Session zurückgesendet. Die Klasse könnte so aussehen:
Delphi-Quellcode:
Die Klasse TDNSPackage, ist lediglich ein record, der die eintreffenden Byte-Daten ordentlich getrennt in passende Variablentypen aufteilt.
type
TSimpleDNSServer = class(TIdUDPServer) protected procedure DoUDPRead(AData: TIdBytes; ABinding: TIdSocketHandle); override; // die Prozedur, wird bei eintreffenden Daten aufgerufen private // Packetverwaltung function ReadPackage(Data: TBytes): TDNSPackage; // Packet "zerlegen" procedure HandlePackage(var DNSPackage: TDNSPackage; Binding: TIdSocketHandle); // Packet behandeln procedure SendDNSPackage(DNSPackage: TDNSPackage; Binding: TIdSocketHandle); // Antwort zurücksenden end; Könnte so aussehen: (Die Konnentare sind teilweise aus dem RFC kopiert)
Delphi-Quellcode:
Wie es im RFC steht, beinhaltet das Packet ausser dem Header natürlich noch die Anfrage:
type
TDNSHeader = record ID: Word; // 16bitu Identifier QR: Boolean; // 1bit False=Query True=Response OPCODE: Array[0..3] of Boolean; // 4bit 0=Query 1=IQuery 2=Status 3-15 Reserved AA: Boolean; // 1bit True in Response - Authoritative Answer TC: Boolean; // 1bit TrunCation RD: Boolean; // 1bit Recursion Desired RA: Boolean; // 1bit Recursion Available Z: Array[0..2] of Boolean; // 3bit Reserved --> 0 RCODE: Array[0..3] of Boolean; // 4bit - Response Code QDCOUNT: Word; // 16bitu Number of Entries in Question Section ANCOUNT: Word; // 16bitu Number of RRs in Answer Section NSCOUNT: Word; // 16bitu Number of Nameserver RRs in the Authority Records Section ARCOUNT: Word; // 16bitu Number of RRs in Additional Records Section end;
Delphi-Quellcode:
Alle anderen Teile des Packets bestehen aus sogenannten ResourceRecords:
type
TDNSQuestion = record QNAME: String; // Query-Name NamePointer: Integer; // Compression (etwas komplizierter, erkläre ich später) QTYPE: Word; // 16bitu Type of Query QCLASS: Word; // 16bitu Class of Query end;
Delphi-Quellcode:
Und das ganze vereinen wir dann in einer Klasse DNSPackage:
type
TResorceRecord = record NAME: TBytes; // a domain name to which this resource record pertains. RTYPE: Word; // 16bitu Meaning of Data in RDATA RCLASS: Word; // 16bitu Class of Data in RDATA TTL: LongWord; // 32bitu Cache-Time RDLENGTH: Word; // 16bitu Number of Octets in RDATA RDATA: TBytes; // Datenspeicher end;
Delphi-Quellcode:
Wie man hier schon sieht: Ein Packet kann mehrere Fragen und Antworten enthalten!
type
TDNSPackage = record Header : TDNSHeader; Question : Array of TDNSQuestion; Answer : Array of TResorceRecord; //Authority : Array of TResorceRecord; // für den grundlegenden Server uninteressant, gehe ich hier also nicht weiter drauf ein //Additional: Array of TResorceRecord; // s.o. end; So nun da eine solide Datenstruktur besteht kann man die eingehenden Daten so zerhackstückeln, dass sie auch darein passen. (Hier sollte man evtl noch einen Try-Finally Block einbauen und prüfen ob das Packet überhaupt beantwortet werden konnte, denn andernfalls brauch man es nicht zurückzusenden)
Delphi-Quellcode:
Dann geben wir unserer Funktion ReadPackage die "rohen" Daten und lassen uns ein schönes formatiertes DNSPackage zurückgeben:
procedure TSimpleDNSServer.DoUDPRead(AData: TIdBytes; ABinding: TIdSocketHandle);
var DNSPackage: TDNSPackage; begin DNSPackage := ReadPackage(AData); HandlePackage(DNSPackage, ABinding); SendDNSPackage(DNSPackage, Binding); FreePackage(DNSPackage); end;
Delphi-Quellcode:
Das war der schwerste Teil.. Wers nicht verstanden hat: Im RFC nachgucken was die einzelnen Werte zu sagen haben und dann nochmal lesen :P
function TSimpleDNSServer.ReadPackage(Data: TBytes): TDNSPackage;
var Buffer16bit: TBytes; Position: Integer; Flags, Flags2: Array[0..7] of Boolean; // 1Byte in Bits geteilt i: Integer; begin SetLength(Buffer16bit, 2); // jaa, 16 Bit sind 2 Byte ;) Result.Header.ID := BytesToWord(ReadFromBytes(Data, 0, 2)); Flags := ByteToBits(Data[2]); // die Funktion muss man sich noch selbst basteln und das oben genannte "Array[0..7] of Boolean;" einem Type zuweisen. Flags2 := ByteToBits(Data[3]); with Result.Header do begin QR := Flags[0]; OPCODE[0] := Flags[1]; OPCODE[1] := Flags[2]; OPCODE[2] := Flags[3]; OPCODE[3] := Flags[4]; AA := Flags[5]; TC := Flags[6]; RD := Flags[7]; RA := Flags2[0]; Z[0] := Flags2[1]; Z[1] := Flags2[2]; Z[2] := Flags2[3]; RCODE[0] := Flags2[4]; RCODE[1] := Flags2[5]; RCODE[2] := Flags2[6]; RCODE[3] := Flags2[7]; end; Result.Header.QDCOUNT := BytesToWord(ReadFromBytes(Data, 4, 2)); // Diese Funktionen muss man dann auch noch programmieren, sie Lesen z.B. aus TBytes ab einer gewissen Position eine bestimmte Anzahl an Bytes aus und geben sie als TBytes zurück Result.Header.ANCOUNT := BytesToWord(ReadFromBytes(Data, 6, 2)); Result.Header.NSCOUNT := BytesToWord(ReadFromBytes(Data, 8, 2)); Result.Header.ARCOUNT := BytesToWord(ReadFromBytes(Data, 10, 2)); SetLength(Result.Question, Result.Header.QDCOUNT); // Platz für alle eingehenden Anfragen reservieren Position := 12; // nach dem Header sind wir am 13. Byte // Questions for i := 0 to Result.Header.QDCOUNT-1 do begin Result.Question[i].QNAME := GetStringInBytes(Data, Position); // Diese Funktion kopiert einen String (bis zum Zeichen #0 - daher Nullterminiert), aus einem TByte ab einer gewissen Position Result.Question[i].NamePointer := Position; // Kompression - etwas komplizierter: Damit der Name nich in Frage und Antwort doppelt steht wird in der Antwort später einfach ein "Zeiger" auf den Namen in der Frage anstatt des kompletten Strings geschrieben. Der "Zeiger" ist eine Zahl, welche angibt mit dem wievielten Byte des Packets der String beginnt Position := Position + Length(Result.Question[i].QNAME); Result.Question[i].QTYPE := BytesToWord(ReadFromBytes(Data, Position, 2)); Inc(Position, 2); Result.Question[i].QCLASS := BytesToWord(ReadFromBytes(Data, Position, 2)); Inc(Position, 2); end; // Alles andere gehört zu den Teilen die hier nicht behandelt werden, für einen grundlegenden DNS-Server aber auch nicht benötigt werden. SetLength(Buffer16bit, 0); // sonst gibts Leaks! end; Jetz wo man die eigentliche Anfrage hat bearbeitet man sie. Der String der Anfrage ist in Question[x].QNAME wie im vorherigen Code zu erkennen ist. Man sollte sich vorher eine Tabelle anlegen, welche alle Namen und dazugehörigen IP-Addressen kennt (TStrings, TStringGrid und was es da alles gibt.. PointerArrays sind hier am schnellsten). Das wäre dann die Procedure HandlePackage(..) Sieht dann ca so aus:
Delphi-Quellcode:
Das Beispiel kann und wird also NUR IP-Anfragen beantworten.. Es gibt noch viele weitere Funktionen des DNS. Steht alles im RFC!
procedure TSimpleDNSServer.HandlePackage(var DNSPackage: TDNSPackage; Binding: TIdSocketHandle);
var i: Integer; CompressionBytes: TBytes; Compression1: Array[0..7] of Boolean; IPAddress: Array[0..3] of Byte; begin if DNSPackage.Header.QR then // ist keine Anfrage sondern bereits eine Antwort Exit; with DNSPackage do begin with Header do begin QR := True; // jetzt ist es eine Antwort! AA := False; RA := True; RCODE := // 0 zurückgeben falls kein Fehler bisher ANCOUNT := DNSPackage.Header.QDCOUNT; // wir veruschen jede Frage zu beantworten! end; end; SetLength(DNSPackage.Answer, DNSPackage.Header.ANCOUNT); // Speicher für die Antworten reservieren for i := 0 to DNSPackage.Header.QDCOUNT-1 do begin if (DNSPackage.Question[i].QTYPE = 1) and (DNSPackage.Question[i].QCLASS = 1) then // IP-Request - Internet(auch LAN) begin with DNSPackage.Answer[i] do begin IPAddress := // Irgendwie in der oben erwähnten Tabelle nachschlagen anhand des Namens: DNSPackage.Question[i].QNAME // Kompression CompressionBytes := WordToBytes(DNSPackage.Question[i].NamePointer); // Hier wird der Zeiger den wir vorher vermerkt haben dann eingefügt Compression1 := ByteToBits(CompressionBytes[0]); Compression1[0] := True; Compression1[1] := True; CompressionBytes[0] := BitsToByte(Compression1); SetLength(NAME, 2); NAME[0] := CompressionBytes[0]; NAME[1] := CompressionBytes[1]; RTYPE := 1; // IP RCLASS := 1; // Internet(auch LAN) TTL := 300; // s. RFC RDLENGTH := 4; SetLength(RDATA, 4); RDATA[0] := IPAddress[0]; RDATA[1] := IPAddress[1]; RDATA[2] := IPAddress[2]; RDATA[3] := IPAddress[3]; end; end; end; end; Aber wir brauchen ja erstmal nur die IP-Anfrage. Das fertig beantwortete Packet kann dann (falls sinnvolle Antworten enthalten sind) zurückgesendet werden:
Delphi-Quellcode:
So das wars dann auch "schon" ! Hoffe es ist halbwegs verständlich!
procedure TSimpleDNSServer.SendDNSPackage(DNSPackage: TDNSPackage; Binding: TIdSocketHandle);
var Bytes: TBytes; Flags, Flags2: Array[0..7] of Boolean; i: Integer; Position: Integer; begin SetLength(Bytes, SizeOfPackage(DNSPackage)); // SizeOfPackage gibt die vorhersehbare größe des Packets zurück (lässt sich leicht berechnen) // Header WriteToBytes(Bytes, WordToBytes(DNSPackage.Header.ID), 0); // Ab hier ist alles wie im ReadPackage nur wird nicht gelesen sondern geschrieben! BITTE BITTE nie mit SetLength(.. , Length(..) + 1); .. Das ist totaler Code-Müll und stresst den PC enorm! Darum vorher reservieren! with DNSPackage.Header do begin Flags[0] := QR; Flags[1] := OPCODE[0]; Flags[2] := OPCODE[1]; Flags[3] := OPCODE[2]; Flags[4] := OPCODE[3]; Flags[5] := AA; Flags[6] := TC; Flags[7] := RD; Flags2[0] := RA; Flags2[1] := Z[0]; Flags2[2] := Z[1]; Flags2[3] := Z[2]; Flags2[4] := RCODE[0]; Flags2[5] := RCODE[1]; Flags2[6] := RCODE[2]; Flags2[7] := RCODE[3]; end; Bytes[2] := BitsToByte(Flags); Bytes[3] := BitsToByte(Flags2); WriteToBytes(Bytes, WordToBytes(DNSPackage.Header.QDCOUNT), 4); WriteToBytes(Bytes, WordToBytes(DNSPackage.Header.ANCOUNT), 6); WriteToBytes(Bytes, WordToBytes(DNSPackage.Header.NSCOUNT), 8); WriteToBytes(Bytes, WordToBytes(DNSPackage.Header.ARCOUNT), 10); Position := 12; // Questions for i := 0 to DNSPackage.Header.QDCOUNT-1 do begin WriteToBytes(Bytes, ToBytes(DNSPackage.Question[i].QNAME), Position); Inc(Position, Length(DNSPackage.Question[i].QNAME)); WriteToBytes(Bytes, WordToBytes(DNSPackage.Question[i].QTYPE), Position); Inc(Position, 2); WriteToBytes(Bytes, WordToBytes(DNSPackage.Question[i].QCLASS), Position); Inc(Position, 2); end; // Answer for i := 0 to DNSPackage.Header.ANCOUNT-1 do begin Bytes[Position] := DNSPackage.Answer[i].NAME[0]; inc(Position); Bytes[Position] := DNSPackage.Answer[i].NAME[1]; inc(Position); WriteToBytes(Bytes, WordToBytes(DNSPackage.Answer[i].RTYPE), Position); Inc(Position, 2); WriteToBytes(Bytes, WordToBytes(DNSPackage.Answer[i].RCLASS), Position); Inc(Position, 2); WriteToBytes(Bytes, LongWordToBytes(DNSPackage.Answer[i].TTL), Position); Inc(Position, 4); WriteToBytes(Bytes, WordToBytes(DNSPackage.Answer[i].RDLENGTH), Position); Inc(Position, 2); WriteToBytes(Bytes, DNSPackage.Answer[i].RDATA, Position); Inc(Position, DNSPackage.Answer[i].RDLENGTH); end; Binding.SendTo(Binding.PeerIP, Binding.PeerPort, Bytes); end; Wie gesagt das ist höchstwarscheinlich kein lauffähiger Code. Zeigt aber wie man Daten auf Byte- und Bit-Ebene behandelt um etwas Übersicht zu bekommen. Der Vorteil an dieser Struktur: Man kann es sehr leicht ausbauen auf sämtliche anderen Funktionen! Der Code ist hier wie gesagt aufs grundlegendste reduziert. Damit das Programm sicher läuft sollte hier und dort Try-Finally Blöcke eingefügt werden und IMMER aller speicher freigegeben werden, der (manuell) reserviert wurde (evtl habe ich hier etwas vergessen)!!! Ich weis auch, dass der Code nicht sonderlich schön ist, an alle Pingel, die was bemängeln wollen ^^ Aber es geht nur ums Prinzip! Den Rest müsst ihr euch selbst erdenken! --> Wer nen Rechtschreibfehler findet darf ihn behalten :P Grz, NiGhTmArE |
Re: DNS-Server mit Indys TIdDNSServer = Verwirrung
Perfekt!! :hello:
Vielen Dank :-D |
Alle Zeitangaben in WEZ +1. Es ist jetzt 17:23 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz