Dart で プログラマブルなメールサービスを作ろう (2) DNS 用のQuery を組んでみる 


[Dart で プログラマブルなメールサービスを作ろう](https://qiita.com/kyorohiro/items/545372fc919f18d4ce0d) の連載記事です。


Dart で DNS リクエストする機能を組んでいきます。メールを送信する際に、メールアドレスから、そのメールを管理しているメールプロバイダーを探して、
そのプロバイダーにメールを送信します。

- 1. DNS に 依頼して、MXレコードを取得する。
- 2. MXレコードから、Aレコードを取得する。
といった流れを組みます。

例えば、tetorica.net の場合だと、

![スクリーンショット 2021-12-26 17.10.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54192/b33c8475-7afb-cd57-70b6-e396875f1e9c.png)

`118.27.109.25` の IPアドレスを取得する必要があります。


# DNS の仕様は REF1035 に書かれています。

https://datatracker.ietf.org/doc/html/rfc1035


# Dart で Aレコードをリクエストしてみる。

Google の Public DNS を利用してみます。

```https://dns.google/dns-query?dns=${base64化したDNS Message}```
にGet リクエスを飛ばすことで、DNSから Aレコード MXレコード を取得する事ができます。


https://developers.google.com/speed/public-dns/docs/doh/


## メッセージのFormat

DNSの Format は以下の通り、Header、Question、Answer、Authority、Additional
と5つのSectionからなっています。

```
4. MESSAGES

4.1. Format

    +---------------------+
    |        Header       |
    +---------------------+
    |       Question      | the question for the name server
    +---------------------+
    |        Answer       | RRs answering the question
    +---------------------+
    |      Authority      | RRs pointing toward an authority
    +---------------------+
    |      Additional     | RRs holding additional information
    +---------------------+
```

ただし、リクエストする場合は、 Answer、Authority、Additional のデーターが0バイトになります。


### リクエスする場合のHeader 


Header の SPECは以下の通り
```

4.1.1. Header section format

```
                                    1  1  1  1  1  1
      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                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
```

IDはなんでも良いです。
RDは、１になります。
QDCOUNTは、1になります。
それ以外は、0でOKです。


### リクエスする場合のHeader をDartで書く

```dart:request_header.dart
var buffer = DNSBuffer(12);

void setInt16AtBE(Uint8List _buffer, int index, int value) {
  _buffer[index + 0] = (value >> 8) & 0xFF;
  _buffer[index + 1] = (value >> 0) & 0xFF;
}

void main() {
  var buffer = Uint8List(12);
  for (var i = 0; i < 12; i++) {
    buffer[i] = 0;
  }
  setInt16AtBE(buffer, 0, 0x1234);
  buffer[2] = 0x01;
  setInt16AtBE(buffer, 5, 0x01);
  print(toHex(buffer)); // 123401000001000000000000
}

```


### リクエスする場合のQuestion


```
                                    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                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
```

Question の Section の Format は以上のような感じで、QNAMEに関しては、
A RECORDを取得する場合は、

`QTYPE` は 1
`QCLASS` は 1

を設定します、
`QNAME` には、ドメイン名を設定しますが、`github.com`のような文字列ではないです・

## QNAME

```
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    12 |           6           |           g           |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    14 |           i           |           t           |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    16 |           h           |           u           |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    26 |           b           |           3           |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    28 |           c           |           o           |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    30 |           m           |           0           |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
```

のような、`1バイトの数字、アスキー文字列`、`1バイトの数字、アスキー文字列`.`Null文字(数字の0)`が してされます。


### リクエスする場合のQuestion をDartで書いてみる

```dart:request_question.dart

void main() {
  var host = 'github.com';
  var splitHost = host.split('.');
  // Calc Buffer Size
  var length = splitHost.length;
  splitHost.forEach((e) {
    length += e.length;
  });
  length += 1; // NULL CHAR
  length += 4; // CLASS  AND TYPE

  // Set Value
  var buffer = Uint8List(length);
  for (var i = 0; i < 12; i++) {
    buffer[i] = 0;
  }

  var index = 0;
  splitHost.forEach((e) {
    buffer[index++] = e.length;
    for (var i = 0; i < e.length; i++) {
      buffer[index++] = ascii.encode(e.substring(i, i + 1))[0];
    }
  });

  setInt16AtBE(buffer, length - 4, 0x01);
  setInt16AtBE(buffer, length - 2, 0x01);
  print(toHex(buffer)); // 0667697468756203636f6d0000010001
}
```
 

## ブラウザーからRequestしてみる。
これらの、Header と Question を合わせると、
`1234010000010000000000000667697468756203636f6d0000010001` の バイトデータができます、これを Base64に変換すると、

```dart:request_base64.dart
import 'dart:typed_data' show Uint8List;
import 'dart:convert' show base64;

Uint8List fromHexString(String hexSrc) {
  var _buffer = Uint8List(hexSrc.length ~/ 2);
  for (var i = 0, j = 0; i < hexSrc.length; i += 2, j++) {
    var v = int.parse(hexSrc.substring(i, i + 2), radix: 16);
    _buffer[j] = v & 0xFF;
  }
  return _buffer;
}

void main() {
  var buffer = fromHexString('1234010000010000000000000667697468756203636f6d0000010001');
  print(base64.encode(buffer)); // EjQBAAABAAAAAAAABmdpdGh1YgNjb20AAAEAAQ==
}
```

`EjQBAAABAAAAAAAABmdpdGh1YgNjb20AAAEAAQ==`になります。
これから、 `==` を取り除いて。

Google Public DNS のアドレスに当てはめると、以下のようになります。

`https://dns.google/dns-query?dns=EjQBAAABAAAAAAAABmdpdGh1YgNjb20AAAEAAQ`

すると、DNSから結果を取得できます。

```
1234818000010001000000000667697468756203636f6d0000010001c00c000100010000003c000434c04859
```

# 次回

今回受け取った、値をParseしていきます。


# REF

今回のコードなどは以下を参照してください。
https://github.com/kyorohiro/dart2.dns