DNS-Server in Python ....

Dieser Artikel ist der Artikel zum 24. Tag des Python-Adventskalenders 2015.

Bis jetzt ist es ein Typ, der Name <-> IP-Adresse ausführen kann, ohne DNS oder irgendetwas zu verstehen. Es ging nur um Anerkennung. Nun, die Spezifikationen sind klar und ich habe es mit einem leichten Kleber versucht, damit ich es bald schreiben kann.

Es ist ziemlich gut für alle, aber ich denke, es ist eine gute Idee, das zu schreiben, was Sie jetzt schreiben möchten.

Mystery Rule-> Verwenden Sie keine DNS-bezogenen Bibliotheken

PyPI verfügt über eine Bibliothek, die sich auf das DNS-Protokoll bezieht. (Und ziemlich viel) Normalerweise nehme ich einige davon und probiere sie aus, aber diesmal war ich motiviert, sie selbst zu schreiben, also habe ich beschlossen, sie nicht zu verwenden. (Weil mir die DNS-Spezifikationen bei der Verwendung nicht in den Sinn kommen ...)

Was ich schrieb

Ich habe es vorerst auf Gist hochgeladen. https://gist.github.com/TakesxiSximada/802d36068f09a3393541

Es funktioniert nicht als richtiger DNS-Server. Ich kann kaum einen A-Rekord zeichnen. (Aber das ist auch eine definitive Entscheidung ...)

Bibliothek verwendet-> binärisieren

Die einzige Bibliothek von Drittanbietern, die ich verwendet habe, war binarize. https://pypi.python.org/pypi/binarize Dies ist eine Bibliothek zum Ausführen von Pack- (Codieren) und Entpacken (Decodieren), indem einer Klasse wie einer Struktur Binärdaten zugewiesen werden. Sie können fast dasselbe tun wie das struct-Modul der Standardbibliothek, aber es scheint, als könnten Sie der Klassenvariablen Informationen zu den Daten geben.

Angenommen, es gibt einen Header, der Code in den ersten 1 Bytes und Status in den nächsten 2 Bytes ausdrückt. In diesem Fall kann die Binärisierung wie folgt ausgedrückt werden.

>>> from binarize import Structure, UINT8, UINT16
>>> class Header(Structure):
...     code = UINT8()
...     status = UINT16()
...
>>>

Die Reihenfolge der Daten ist die Reihenfolge der Variablendefinition. Die Bytereihenfolge ist standardmäßig Big Endian. Diese Klasse hat eine Klassenmethode namens decode. Sie können die Daten als Header-Objekt behandeln, indem Sie eine Zeichenfolge von Bytes an sie übergeben.

>>> header = Header.decode(b'\x01\x00\x02')
>>> header.code
1
>>> header.status
2

Der Header hat encode () und kann in eine Byte-Zeichenfolge serialisiert werden.

>>> header.encode()
b'\x01\x00\x02'
>>>

Diese Binarisierung scheint selten verwendet zu werden. Es hatte keine Github-Sterne und die Anzahl der PyPI-Downloads war nicht so groß (es sieht so aus, als wäre es fast kein DL und es wird ein wenig vernachlässigt). Es wurde jedoch relativ gut geschrieben und war in Bezug auf die Benutzerfreundlichkeit relativ gut.

Wenn ich einen DNS-Anforderungsheader mit binarize schreibe, sieht es so aus

Der DNS-Anforderungsheader hat dieses Format.

      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Es sieht aus wie die folgenden 16 Bits von Anfang an.

Name Bedeutung
ID Kennung
QR, Opcode, AA, TC, RD, RA, Z, RCODE Flagge
QDCOUNT Anzahl der Frage-RRs
ANCOUNT Anzahl der Antwort-RRs
NSCOUNT Anzahl der maßgeblichen RRs
ARCOUNT Anzahl zusätzlicher RRs

RR ist ein Ressourceneintrag.

Die Bedeutungen der Flags sind wie folgt.

Name Bedeutung
QR Anfrage=0,Antwort=1
Opcode Nach vorne=1,Umgekehrte Auflösung=2,Serverstatus abrufen=3
AA Vom DNS-Server verwaltete Informationen=1
TC Die Antwort war zu lang und gespalten=1
RD Rekursive Abfrage=1,Iterative Abfrage=0
RA Rekursiv=1
Z Reservieren
RCODE Antwortstatus(Erfolgreiche Fertigstellung=0,Unzulässiges Format=1,Serverfehler=2,Namensfehler=3,Nicht implementiert=4,Ablehnen=5)

Weitere Informationen finden Sie unter RFC. Es wird in "4.1.1. Header-Abschnittsformat" unter http://www.ietf.org/rfc/rfc1035.txt beschrieben.

Die Darstellung dieser Header mit binarize ist wie folgt.

class Header(Structure):
    identifier = UINT16()
    flags = UINT16()
    query_count = UINT16()
    answer_count = UINT16()
    authority_rr_count = UINT16()
    addon_rr_count = UINT16()

https://gist.github.com/TakesxiSximada/802d36068f09a3393541#file-dns-py-L133-L139 Es fühlt sich an wie eine Struktur. Struktur ist binärisieren. Struktur. Ich habe 16 Bit für die Flags in Header.flags reserviert und es fühlte sich an, als würde ich es bedienen. https://gist.github.com/TakesxiSximada/802d36068f09a3393541#file-dns-py-L141-L186

Wenn ich einen Abfragedatensatz mit binarize schreibe, sieht es so aus

Der Abfragedatensatz hat das folgende Format:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                     QNAME                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QTYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QCLASS                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Name Bedeutung
QNAME Abzufragende Daten("Wortzahl,Domainname,WortzahlDomainname,0x00" <-So was)
QTYPE Art der Anfrage(Dieses Mal erkundige ich mich nur nach einem Rekord, also werde ich fliegen)
QCLASS Kommunikation, die Sie verwenden(das Internet=1)

Details finden Sie im Format 4.1.2. Fragenbereich unter http://www.ietf.org/rfc/rfc1035.txt.

Mit binarize ausgedrückt sieht es folgendermaßen aus:

class QuestionRecord(Structure):
    qname = BYTES(size=32, fill=b'')
    qtype = UINT16()
    qclass = UINT16()

https://gist.github.com/TakesxiSximada/802d36068f09a3393541#file-dns-py-L188-L191

Eine Sache, die Sie hier beachten sollten, ist, dass qname eine variable Länge hat. Der (unkomprimierte) qname wird durch eine Kombination aus der Anzahl der Zeichen und der Zeichenfolge des Domänennamens dargestellt und endet bei 0x00. Dieses Format kann nicht durch Binärisierung analysiert werden, daher musste ich es selbst dekodieren.

    @classmethod
    def decode(cls, data):
        class _AnalyzeRecord(Structure):
            qtype = UINT16()
            qclass = UINT16()

        record = cls()
        qname_end = data.index(b'\x00') + 1
        record.qname = data[:qname_end]

        data = data[qname_end:]
        analyze_record = _AnalyzeRecord.decode(data)
        record.qtype = analyze_record.qtype
        record.qclass = analyze_record.qclass
        return record

https://gist.github.com/TakesxiSximada/802d36068f09a3393541#file-dns-py-L193-L207

Außerdem war binarize.BYTES () eine Spezifikation, um die Anzahl der nachfolgenden Bytes in das erste Byte einzufügen, wenn es in Ruhe gelassen wird.

def pack_bytes(bytes_, size=-1, fill=b'\x00'):
    """Pack Bytes."""
    if size < 0:
        yield from pack_size(len(bytes_))
        yield bytes_
    else:
        missing = size - len(bytes_)
        if missing < 0 or (missing > 0 and fill is None):
            raise ValueError()
        yield bytes_
        yield fill * missing

https://github.com/socialcube/binarize/blob/4f0c71c985e22d1975dd8f5dfefdb7e8b5c01c57/binarize/primitives.py#L502

Dieses Mal habe ich Ecken abgeschnitten und den gesamten q-Namen als Byte-Zeichenfolge erhalten, aber wenn Sie darüber nachdenken, ist dies eine ziemlich raffinierte Spezifikation. Wenn Sie also Felder dynamisch hinzufügen können, können Sie den q-Namen auch in der BYTES () -Liste speichern. Ich dachte, es wäre schön, es so zu haben.

Wenn Sie Test ziehen, wird eine Loopback-Adresse zurückgegeben

Natürlich sollten Sie die in der Datenbank gespeicherten Daten hier verwenden, aber es ist ein wenig mühsam, also schreibe ich sie direkt.

NAME_IPADDR = {
    (b'test', b''): b'\x7f\x00\x00\x01',  # 127.0.0.1
}

https://gist.github.com/TakesxiSximada/802d36068f09a3393541#file-dns-py-L29-L31

Der Namenstest gibt die Loopback-Adresse zurück.

Versuchen Sie, die IP-Adresse mit dig abzurufen

Starten Sie den Server. DNS wird mit sudo ausgeführt, da die Portnummer 53 lautet.

$ sudo python dns.py
Password:

Versuchen Sie, den Namen mit dem Befehl dig von einem anderen Terminal abzurufen. Die Adresse nach @ ist die DNS-Adresse, um zur Anfrage zu gelangen. Ohne dies wird das seriöse DNS abgefragt. Dieses Mal wird nur lokal ausgeführt. Geben Sie daher 127.0.0.1 an.

$ dig A test @127.0.0.1

; <<>> DiG 9.8.3-P1 <<>> A test @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3096
;; flags: qr; QUERY: 0, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; ANSWER SECTION:
test.                   9       IN      A       127.0.0.1

;; Query time: 2 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Fri Dec 25 21:43:04 2015
;; MSG SIZE  rcvd: 32

$

Oh, es sieht so aus, als wäre es geschlossen. 127.0.0.1 wurde in A-Datensatz ordnungsgemäß geschlossen.

Ich hatte viel Ärger ...

Während der Arbeit mochte ich die folgende Nachricht nicht.

$ dig A test @127.0.0.1
;; Warning: Message parser reports malformed message packet.

Da die zurückgegebenen Daten in einem seltsamen Format vorliegen, gab der Befehl dig nur eine Warnung aus, aber es fiel mir schwer, die Ursache nicht zu kennen. Meistens war es ziemlich rudimentär, wie zum Beispiel die Tatsache, dass die Länge der Daten falsch war oder die Anzahl der Daten falsch war und dass Informationen angehängt wurden.

Irgendwann Wireshark ...

Egal wie oft Sie den RFC lesen oder die Informationen im Web sammeln, wenn Sie sie nicht versehentlich bemerken, bemerken Sie sie nicht wirklich ... Sie können dies nicht tun, während Sie die Daten mit Wireshark analysieren, während Sie die Informationen im RFC und im Web betrachten. Es war oft seltsam. Dies war der beste Weg, um die tatsächlich fließenden Daten zu sehen.

Nachwort

Vor ein paar Tagen hatte ich eine Erkältung und bin eingeschlafen, also habe ich einen Tag später einen Artikel gepostet. Ich bin immer noch ein bisschen langweilig, also schlafe ich mit Antibiotika. Auf Wiedersehen.

Recommended Posts

DNS-Server in Python ....
Schreiben Sie einen HTTP / 2-Server in Python
Quadtree in Python --2
CURL in Python
Metaprogrammierung mit Python
Python 3.3 mit Anaconda
Geokodierung in Python
SendKeys in Python
Metaanalyse in Python
Unittest in Python
Zwietracht in Python
DCI in Python
Quicksort in Python
nCr in Python
N-Gramm in Python
Programmieren mit Python
Plink in Python
Konstante in Python
SQLite in Python
Schritt AIC in Python
LINE-Bot [0] in Python
CSV in Python
Reverse Assembler mit Python
Reflexion in Python
Konstante in Python
nCr in Python.
Format in Python
Scons in Python 3
Puyopuyo in Python
Python in Virtualenv
PPAP in Python
Quad-Tree in Python
Reflexion in Python
Chemie mit Python
Hashbar in Python
DirectLiNGAM in Python
LiNGAM in Python
In Python reduzieren
In Python flach drücken
CGI Server (1) Python Edition in einer Zeile
Sortierte Liste in Python
Täglicher AtCoder # 36 mit Python
Clustertext in Python
AtCoder # 2 jeden Tag mit Python
Täglicher AtCoder # 32 in Python
Richten Sie mit Python 3 einen einfachen HTTPS-Server ein
Täglicher AtCoder # 6 in Python
Bearbeiten Sie Schriftarten in Python
Singleton-Muster in Python
Dateioperationen in Python
Lesen Sie DXF mit Python
Täglicher AtCoder # 53 in Python
Tastenanschlag in Python
Verwenden Sie config.ini mit Python
Täglicher AtCoder # 33 in Python
Löse ABC168D in Python
Logistische Verteilung in Python
python> udp> echo server