Serveur DNS en Python ....

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.

Mystery Rule-> Ne pas utiliser les bibliothèques liées au DNS

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

Ce que j'ai écrit

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

Bibliothèque utilisée-> binarize

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

Lorsque j'écris un en-tête de requête DNS avec binarize, cela ressemble à ceci

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

Lorsque j'écris un enregistrement de requête avec binarize, cela ressemble à ceci

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.

Si vous tirez test, il renvoie une adresse de bouclage

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.

Essayez de tirer l'adresse IP avec dig

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.

J'ai eu beaucoup de mal ...

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.

Finalement, WireShark ...

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.

Épilogue

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

Serveur DNS en Python ....
Ecrire un serveur HTTP / 2 en Python
Quadtree en Python --2
CURL en Python
Métaprogrammation avec Python
Python 3.3 avec Anaconda
Géocodage en python
SendKeys en Python
Méta-analyse en Python
Unittest en Python
Discord en Python
DCI en Python
tri rapide en python
nCr en python
N-Gram en Python
Programmation avec Python
Plink en Python
Constante en Python
Sqlite en Python
Étape AIC en Python
LINE-Bot [0] en Python
CSV en Python
Assemblage inversé avec Python
Réflexion en Python
Constante en Python
nCr en Python.
format en python
Scons en Python 3
Puyopuyo en python
python dans virtualenv
PPAP en Python
Quad-tree en Python
Réflexion en Python
Chimie avec Python
Hashable en Python
DirectLiNGAM en Python
LiNGAM en Python
Aplatir en Python
Aplatir en python
CGI Server (1) édition python en une ligne
Liste triée en Python
AtCoder # 36 quotidien avec Python
Texte de cluster en Python
AtCoder # 2 tous les jours avec Python
Daily AtCoder # 32 en Python
Configurer un serveur HTTPS simple avec Python 3
Daily AtCoder # 6 en Python
Modifier les polices en Python
Motif singleton en Python
Opérations sur les fichiers en Python
Lire DXF avec python
Daily AtCoder # 53 en Python
Séquence de touches en Python
Utilisez config.ini avec Python
Daily AtCoder # 33 en Python
Résoudre ABC168D en Python
Distribution logistique en Python
python> udp> serveur d'écho