Cet article est l'article du 24ème jour du Calendrier de l'Avent Python 2015.
Jusqu'à présent, c'est un gars qui peut faire Name <-> IP Address sans comprendre le DNS ou quoi que ce soit. Il n'y avait qu'une question de reconnaissance. Eh bien, les spécifications sont claires, et je l'ai essayé avec une colle légère que je pourrais l'écrire bientôt.
C'est plutôt bien pour tout le monde, mais je pense que c'est une bonne idée de mettre ce que vous voulez écrire maintenant.
PyPI dispose d'une bibliothèque liée au protocole DNS. (Et pas mal) J'en prends généralement quelques-uns et je les essaie, mais cette fois, j'étais motivé à les écrire moi-même, alors j'ai décidé de ne pas les utiliser. (Parce que les spécifications DNS ne me viennent pas à l'esprit lorsque je l'utilise ...)
Je l'ai téléchargé sur Gist pour le moment. https://gist.github.com/TakesxiSximada/802d36068f09a3393541
Cela ne fonctionne pas comme un serveur DNS approprié. Je peux à peine dessiner un enregistrement A. (Mais c'est aussi une décision définitive ...)
La seule bibliothèque tierce que j'ai utilisée était binarize. https://pypi.python.org/pypi/binarize C'est une bibliothèque pour effectuer des opérations de pack (encodage) et de décompression (décodage) en donnant des données binaires à une classe comme une structure. Vous pouvez faire presque la même chose que le module struct de la bibliothèque standard, mais il semble que vous pouvez donner à la variable de classe des informations sur les données.
Par exemple, supposons qu'il existe un en-tête qui exprime le code dans le premier octet 1 et l'état dans les 2 octets suivants. Dans ce cas, la binarisation peut être exprimée comme suit.
>>> from binarize import Structure, UINT8, UINT16
>>> class Header(Structure):
... code = UINT8()
... status = UINT16()
...
>>>
L'ordre des données est l'ordre de définition des variables. L'ordre des octets est par défaut big endian. Cette classe a une méthode de classe appelée decode. Vous pouvez traiter les données comme un objet Header en lui passant une chaîne d'octets.
>>> header = Header.decode(b'\x01\x00\x02')
>>> header.code
1
>>> header.status
2
L'en-tête a encode () et peut être sérialisé dans une chaîne d'octets.
>>> header.encode()
b'\x01\x00\x02'
>>>
Cette binarisation semble être rarement utilisée. Il n'y avait pas d'étoiles Github et le nombre de téléchargements PyPI n'était pas terrible (il semble que ce ne soit presque pas DL, et c'est un peu négligé). Cependant, il a été écrit relativement bien, et il était relativement bon en termes de convivialité.
L'en-tête de la requête DNS a ce 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 |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Cela ressemble aux 16 bits suivants depuis le début.
Nom | sens |
---|---|
ID | identifiant |
QR, Opcode, AA, TC, RD, RA, Z, RCODE | drapeau |
QDCOUNT | Nombre de questions RR |
ANCOUNT | Nombre de RR de réponse |
NSCOUNT | Nombre de RR faisant autorité |
ARCOUNT | Nombre de RR supplémentaires |
RR est un enregistrement de ressource.
Les significations des drapeaux sont les suivantes.
Nom | sens |
---|---|
QR | enquête=0,réponse=1 |
Opcode | Vers l'avant=1,Résolution inverse=2,Obtenir l'état du serveur=3 |
AA | Informations gérées par le serveur DNS=1 |
TC | La réponse était trop longue et divisée=1 |
RD | Requête récursive=1,Requête itérative=0 |
RA | Récursif=1 |
Z | réserve |
RCODE | État de la réponse(Réussite=0,Format illégal=1,Erreur du serveur=2,Erreur de nom=3,Non mis en œuvre=4,Rejeter=5) |
Pour plus d'informations, consultez RFC. Il est décrit dans "4.1.1. Format de la section d'en-tête" sur http://www.ietf.org/rfc/rfc1035.txt.
La représentation de ces en-têtes à l'aide de binarize est la suivante.
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 Cela ressemble à une structure. La structure est binarize.Structure. J'ai réservé 16 bits pour les drapeaux dans Header.flags et j'ai eu envie de l'utiliser. https://gist.github.com/TakesxiSximada/802d36068f09a3393541#file-dns-py-L141-L186
L'enregistrement de requête a le format suivant:
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 |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Nom | sens |
---|---|
QNAME | Données à interroger("nombre de mots,Nom de domaine,nombre de motsNom de domaine,0x00" <-Comme ça) |
QTYPE | Type de demande(Cette fois, je ne demande que l'enregistrement A, donc 1 volera) |
QCLASS | Communication que vous utilisez(l'Internet=1) |
Les détails peuvent être trouvés dans «4.1.2. Format de la section des questions» à http://www.ietf.org/rfc/rfc1035.txt.
Exprimé en utilisant binarize, il ressemble à ceci:
class QuestionRecord(Structure):
qname = BYTES(size=32, fill=b'')
qtype = UINT16()
qclass = UINT16()
https://gist.github.com/TakesxiSximada/802d36068f09a3393541#file-dns-py-L188-L191
Une chose à garder à l'esprit ici est que qname est de longueur variable. Le qname (non compressé) est représenté par une combinaison du nombre de caractères et de la chaîne du nom de domaine et se termine à 0 x 00. Ce format ne peut pas être analysé par binarize, j'ai donc dû le décoder moi-même.
@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
En outre, binarize.BYTES () était une spécification pour insérer le nombre d'octets suivants dans le premier octet s'il est laissé seul.
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
Cette fois, j'ai coupé les coins ronds et j'ai obtenu le qname entier sous forme de chaîne d'octets, mais si vous y réfléchissez, c'est une spécification assez astucieuse, donc si vous pouvez ajouter des champs de manière dynamique, vous pouvez également conserver le qname dans la liste BYTES (). J'ai pensé que ce serait bien de l'avoir tel quel.
Bien sûr, vous devriez utiliser les données enregistrées dans la base de données ici, mais c'est un peu gênant, je vais donc l'écrire directement.
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
Le test de nom renvoie l'adresse de bouclage.
Démarrez le serveur. DNS s'exécute avec sudo car le numéro de port est 53.
$ sudo python dns.py
Password:
Essayez d'extraire le nom d'un autre terminal avec la commande dig. L'adresse après @ est l'adresse DNS pour accéder à l'enquête. Sans cela, il interrogera le DNS sérieux. Cette fois, nous ne fonctionnons qu'en local, alors spécifiez 127.0.0.1.
$ 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, on dirait que c'est fermé. 127.0.0.1 a été correctement fermé dans l'enregistrement A.
En travaillant, je n'ai pas aimé le message suivant.
$ dig A test @127.0.0.1
;; Warning: Message parser reports malformed message packet.
Étant donné que les données renvoyées étaient dans un format étrange, la commande dig vient d'émettre un avertissement, mais j'ai eu du mal à ne pas en connaître la cause. La plupart du temps, c'était assez rudimentaire, comme le fait que la longueur des données était incorrecte ou le nombre de données était incorrect, et cette information était jointe.
Peu importe combien vous lisez le RFC ou collectez les informations sur le Web, si vous ne le remarquez pas par erreur, vous ne le remarquez pas vraiment ... Vous ne pouvez pas vraiment le remarquer en regardant les informations sur le RFC et le Web tout en analysant les données avec Wireshark. C'était souvent étrange. C'était la meilleure façon de voir les données qui circulaient réellement.
Il y a quelques jours, j'ai eu un rhume et je me suis endormi, alors j'ai posté l'article un jour plus tard. Je suis encore un peu terne, alors je dors avec des antibiotiques. Au revoir.
Recommended Posts