AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Netzwerke Delphi DNS-Server mit Indys TIdDNSServer = Verwirrung
Thema durchsuchen
Ansicht
Themen-Optionen

DNS-Server mit Indys TIdDNSServer = Verwirrung

Ein Thema von nightmare · begonnen am 28. Feb 2007 · letzter Beitrag vom 12. Mär 2007
Antwort Antwort
Benutzerbild von nightmare
nightmare

Registriert seit: 13. Nov 2003
Ort: Koblenz a. Rhein
5 Beiträge
 
Delphi 2005 Personal
 
#1

DNS-Server mit Indys TIdDNSServer = Verwirrung

  Alt 28. Feb 2007, 17:12
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!
Florian
  Mit Zitat antworten Zitat
Benutzerbild von Kedariodakon
Kedariodakon

Registriert seit: 10. Sep 2004
Ort: Mönchengladbach
833 Beiträge
 
Delphi 7 Enterprise
 
#2

Re: DNS-Server mit Indys TIdDNSServer = Verwirrung

  Alt 28. Feb 2007, 17:19
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:
CMD
ipconfig /all
Schon siehst du welchen sie nehmen...

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
Miniaturansicht angehängter Grafiken
dns_895.jpg  
Christian
  Mit Zitat antworten Zitat
Benutzerbild von nightmare
nightmare

Registriert seit: 13. Nov 2003
Ort: Koblenz a. Rhein
5 Beiträge
 
Delphi 2005 Personal
 
#3

Re: DNS-Server mit Indys TIdDNSServer = Verwirrung

  Alt 28. Feb 2007, 17:29
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.
Florian
  Mit Zitat antworten Zitat
Benutzerbild von nightmare
nightmare

Registriert seit: 13. Nov 2003
Ort: Koblenz a. Rhein
5 Beiträge
 
Delphi 2005 Personal
 
#4

Re: DNS-Server mit Indys TIdDNSServer = Verwirrung

  Alt 1. Mär 2007, 21:12
Hat sich erledigt. Hab nen eigenen DNS-Server programmiert. Der von Indy is totaler Mist.
Danke trotzdem!
Florian
  Mit Zitat antworten Zitat
Benutzerbild von nightmare
nightmare

Registriert seit: 13. Nov 2003
Ort: Koblenz a. Rhein
5 Beiträge
 
Delphi 2005 Personal
 
#5

Re: DNS-Server mit Indys TIdDNSServer = Verwirrung

  Alt 12. Mär 2007, 14:05
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: RFC-1035

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:
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;
Die Klasse TDNSPackage, ist lediglich ein record, der die eintreffenden Byte-Daten ordentlich getrennt in passende Variablentypen aufteilt.
Könnte so aussehen: (Die Konnentare sind teilweise aus dem RFC kopiert)
Delphi-Quellcode:
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;
Wie es im RFC steht, beinhaltet das Packet ausser dem Header natürlich noch die Anfrage:
Delphi-Quellcode:
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;
Alle anderen Teile des Packets bestehen aus sogenannten ResourceRecords:
Delphi-Quellcode:
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;
Und das ganze vereinen wir dann in einer Klasse DNSPackage:
Delphi-Quellcode:
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;
Wie man hier schon sieht: Ein Packet kann mehrere Fragen und Antworten enthalten!
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:
procedure TSimpleDNSServer.DoUDPRead(AData: TIdBytes; ABinding: TIdSocketHandle);
var
  DNSPackage: TDNSPackage;
begin
  DNSPackage := ReadPackage(AData);
  HandlePackage(DNSPackage, ABinding);
  SendDNSPackage(DNSPackage, Binding);

  FreePackage(DNSPackage);
end;
Dann geben wir unserer Funktion ReadPackage die "rohen" Daten und lassen uns ein schönes formatiertes DNSPackage zurückgeben:
Delphi-Quellcode:
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;
Das war der schwerste Teil.. Wers nicht verstanden hat: Im RFC nachgucken was die einzelnen Werte zu sagen haben und dann nochmal lesen
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:
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;
Das Beispiel kann und wird also NUR IP-Anfragen beantworten.. Es gibt noch viele weitere Funktionen des DNS. Steht alles im RFC!
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:
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;
So das wars dann auch "schon" ! Hoffe es ist halbwegs verständlich!
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
Grz, NiGhTmArE
Florian
  Mit Zitat antworten Zitat
T0mee

Registriert seit: 31. Jul 2006
1 Beiträge
 
#6

Re: DNS-Server mit Indys TIdDNSServer = Verwirrung

  Alt 12. Mär 2007, 23:00
Perfekt!!

Vielen Dank
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:16 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz