Python3でUDPのDNSクエリーを送信し、応答をダンプ表示する

## pyDNSdump

This program sends a DNS UDP request and outputs a response.
https://github.com/NobuyukiInoue/pyDNSdump/


```
python pyDNSdump.py [dnsserver] [target] [recordtype]
```

## pyDNSdump from lambda

http://www.asahi-net.or.jp/~gx3n-inue/lambda/pyDNSdump/index.html


## UDPのDNSクエリーを送信するプログラムの例（サンプル）

Pythonのsocketモジュールを使用して、
UDPのDNSクエリーを送信し、応答をdump表示する例を紹介します。

https://github.com/NobuyukiInoue/pyDNSdump/sample_udp_dnsquery.py


```python:メイン処理
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket

def main():
    # "or.jp" "any"
    # data_send = b'\x00\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x02or\x02jp\x00\x00\xff\x00\x01'
    # "." "ns"
    # data_send = b'\x00\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01'
    data_send = set_Header_and_Question(1, "jp", "ns")
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # send a DNS udp request.
    s.sendto(data_send, ("8.8.8.8", 53))

    # recv a DNS udp response.
    data_recv, address = s.recvfrom(8192)

    # print(data_recv)
    print("DNS Response from {0}".format(address))
    for i in range(len(data_recv)):
        if i % 16 == 0:
            print("\n{0:04x}: {1:02x}".format(i, data_recv[i]), end = "")
        else:
            print(" {0:02x}".format(data_recv[i]), end = "")
```

DNSクエリーのHeader Section およびQuestion Section は
[RFC 1035](https://www.ietf.org/rfc/rfc1035.txt) に準拠するように**bytes型**で用意しておきます。


Transaction ID は、繰り返し連続して送信しないのであれば **0x0001** で良いでしょう。

Qestion Section については、FDQNを ***.*** で分割し、

[文字列の長さ, １バイト文字, ..., 0x00]

といったフォーマットで連結します。

Classについては、[RFC 1035](https://tools.ietf.org/html/rfc1035) や [RFC 2930](https://tools.ietf.org/html/rfc2929#section-3.2) にて何種類か定義されていますが、IN以外はほとんど利用されておらず、IN(0x0001)を２オクテットの値としてセットしておきます。

```python:
def set_Header_and_Question(Transaction_ID, resolvstring, type):
    data = Transaction_ID.to_bytes(2, 'big')    # Transaction ID
    data += 0x0100.to_bytes(2, 'big')           # Flags
    data += 0x0001.to_bytes(2, 'big')           # Questions
    data += 0x0000.to_bytes(2, 'big')           # Answer RRS
    data += 0x0000.to_bytes(2, 'big')           # Answer RRS
    data += 0x0000.to_bytes(2, 'big')           # Additional RRS

    # Queries
    if resolvstring == ".":
        data += 0x00.to_bytes(1, 'big')
    else:
        flds = resolvstring.split(".")
        for name in flds:
            data += len(name).to_bytes(1, 'big')
            data += name.encode(encoding = 'ascii')
        data += 0x00.to_bytes(1, 'big')
    data += set_RecordType(type.upper())        # Type
    data += 0x0001.to_bytes(2, 'big')           # Class ... IN(0x0001)

    return data
```

レコードタイプは、"A", "NS", "CNAME", ... といった文字列で渡せるようにしていますが、

Headerセクションには整数でセットしなければならないので、
下記のような処理で２バイトの値に変換するようにします。


```python:

def set_RecordType(type):
    if type.isnumeric() == True:
        if int(type) > 0:
            return int(type).to_bytes(2, 'big')
    # Type
    if type is None:
        return 0x00ff.to_bytes(2, 'big')
    elif type == 'A':
        return 0x0001.to_bytes(2, 'big')
    elif type == 'NS':
        return 0x0002.to_bytes(2, 'big')
    elif type == 'CNAME':
        return 0x0005.to_bytes(2, 'big')
    elif type == 'SOA':
        return 0x0006.to_bytes(2, 'big')
    elif type == 'PTR':
        return 0x000c.to_bytes(2, 'big')
    elif type == 'HINFO':
        return 0x000d.to_bytes(2, 'big')
    elif type == 'MX':
        return 0x000f.to_bytes(2, 'big')
    elif type == 'TXT':
        return 0x0010.to_bytes(2, 'big')
    elif type == 'AAAA':
        return 0x001c.to_bytes(2, 'big')
    elif type == 'SRV':
        return 0x0021.to_bytes(2, 'big')
    elif type == 'DS':
        return 0x002b.to_bytes(2, 'big')
    elif type == 'RRSIG':
        return 0x002e.to_bytes(2, 'big')
    elif type == 'NSEC':
        return 0x002f.to_bytes(2, 'big')
    elif type == 'DNSKEY':
        return 0x0030.to_bytes(2, 'big')
    elif type == 'NSEC3':
        return 0x0032.to_bytes(2, 'big')
    elif type == 'NSEC3PARAM':
        return 0x0033.to_bytes(2, 'big')
    elif type == 'CAA':
        return 0x0101.to_bytes(2, 'big')
    elif type == 'ANY':
        return 0x00ff.to_bytes(2, 'big')
    else:
        return 0x00ff.to_bytes(2, 'big')
```

* **sample_udp_dnsquery.py** の実行結果例

```
$ python sample_udp_dnsquery.py
DNS Response from ('8.8.8.8', 53)

0000: 00 01 81 80 00 01 00 08 00 00 00 00 02 6a 70 00
0010: 00 02 00 01 c0 0c 00 02 00 01 00 00 31 fb 00 08
0020: 01 66 03 64 6e 73 c0 0c c0 0c 00 02 00 01 00 00
0030: 31 fb 00 04 01 65 c0 22 c0 0c 00 02 00 01 00 00
0040: 31 fb 00 04 01 62 c0 22 c0 0c 00 02 00 01 00 00
0050: 31 fb 00 04 01 67 c0 22 c0 0c 00 02 00 01 00 00
0060: 31 fb 00 04 01 68 c0 22 c0 0c 00 02 00 01 00 00
0070: 31 fb 00 04 01 63 c0 22 c0 0c 00 02 00 01 00 00
0080: 31 fb 00 04 01 64 c0 22 c0 0c 00 02 00 01 00 00
0090: 31 fb 00 04 01 61 c0 22
```


* **pyDNSdump.py** の実行結果例

```
$ python pyDNSdump.py 8.8.8.8 jp ns
============================================================================================
DNS Server    = 8.8.8.8:53
target        = jp
record type   = NS
============================================================================================
Reply from    : 8.8.8.8:53
length        : 0x0098(152) bytes.
Response time : 51.352024[ms]
============================================================================================
0000:               Header:
0000: 0001          Transaction ID:          1
0002: 8180          Flags:                   0b1000000110000000
/*
                      [bit 0]     QR      (1) ... Response
                      [bit 1-4]   OPCODE  (0) ... standard query
                      [bit 5]     AA      (0) ... Not Authoritative
                      [bit 6]     TC      (0) ... Did not Flagment
                      [bit 7]     RD      (1) ... Repeat Query
                      [bit 8]     RA      (1) ... Recursion Available is False
                      [bit 9]     Reserve (0)
                      [bit 10]    Authentic Data(0)
                      [bit 11]    Checking Disable(0)
                      [bit 12-15] RCODE   (0) ... No Error
*/
0004: 0001          Questions:               1
0006: 0008          Answer RRS:              8
0008: 0000          Authority RRS:           0
000a: 0000          Additional RRS:          0

000c:               Querys:
000c: 026a7000      Name:                    jp
0010: 0002          Type:                    NS(2)
0012: 0001          Class:                   IN(1)

0014:               Answer[0]:
0014: c00c          Name:                    jp
0016: 0002          Type:                    NS(2)
0018: 0001          Class:                   IN(1)
001a: 0000545f      Time to live:            0 day 05:59:59(21599)
001e: 0008          data_length:             8
0020: 016103646e73c00c  Name:                    a.dns[.jp]

0028:               Answer[1]:
0028: c00c          Name:                    jp
002a: 0002          Type:                    NS(2)
002c: 0001          Class:                   IN(1)
002e: 0000545f      Time to live:            0 day 05:59:59(21599)
0032: 0004          data_length:             4
0034: 0162c022      Name:                    b[.dns[.jp]]

0038:               Answer[2]:
0038: c00c          Name:                    jp
003a: 0002          Type:                    NS(2)
003c: 0001          Class:                   IN(1)
003e: 0000545f      Time to live:            0 day 05:59:59(21599)
0042: 0004          data_length:             4
0044: 0165c022      Name:                    e[.dns[.jp]]

0048:               Answer[3]:
0048: c00c          Name:                    jp
004a: 0002          Type:                    NS(2)
004c: 0001          Class:                   IN(1)
004e: 0000545f      Time to live:            0 day 05:59:59(21599)
0052: 0004          data_length:             4
0054: 0164c022      Name:                    d[.dns[.jp]]

0058:               Answer[4]:
0058: c00c          Name:                    jp
005a: 0002          Type:                    NS(2)
005c: 0001          Class:                   IN(1)
005e: 0000545f      Time to live:            0 day 05:59:59(21599)
0062: 0004          data_length:             4
0064: 0167c022      Name:                    g[.dns[.jp]]

0068:               Answer[5]:
0068: c00c          Name:                    jp
006a: 0002          Type:                    NS(2)
006c: 0001          Class:                   IN(1)
006e: 0000545f      Time to live:            0 day 05:59:59(21599)
0072: 0004          data_length:             4
0074: 0163c022      Name:                    c[.dns[.jp]]

0078:               Answer[6]:
0078: c00c          Name:                    jp
007a: 0002          Type:                    NS(2)
007c: 0001          Class:                   IN(1)
007e: 0000545f      Time to live:            0 day 05:59:59(21599)
0082: 0004          data_length:             4
0084: 0166c022      Name:                    f[.dns[.jp]]

0088:               Answer[7]:
0088: c00c          Name:                    jp
008a: 0002          Type:                    NS(2)
008c: 0001          Class:                   IN(1)
008e: 0000545f      Time to live:            0 day 05:59:59(21599)
0092: 0004          data_length:             4
0094: 0168c022      Name:                    h[.dns[.jp]]
============================================================================================
Reception is complete.
```