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.
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 ...)
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 ...)
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.
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
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.
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.
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.
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.
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.
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