DNSレスポンスのAnswerSectionが一致しないとNode.jsはHTTPリクエストを諦めちゃう問題
https://4.bp.blogspot.com/-7ULWjdTXmaI/V2ubU506oDI/AAAAAAAA7nw/DuqYHWKZbIE8PKkRoMV-WxqdQWnSRMxpgCLcB/s400/computer_internet_disconnected.png
CoreDNS:リクエストをrewriteする という記事でも最後の方に触りましたが、DNSレスポンス中の Answer Section に記載されている名前と、リクエストしたときの名前が一致していないと正常に名前解決ができない場合があります。 Node.js では axios など組み込みの http パッケージに依存する通信を行う仕組みにおいては上記のような名前解決の問題が発生します。ちなみに dns パッケージを使う場合においては IP がきちんと帰ってくるので余計にややこしかった。 問題の再現方法
検証用の DNS 設定はこちら。CoreDNS を用意して Corefile として設定してみてください。
CoreDNS 起動後は PC のDNSサーバーを一次的に localhost とかに変えてあげましょう。
code:Corefile(bash)
. {
# example.test が来たら httpbin.org として処理する
rewrite name regex example\.test httpbin.org
# 8.8.8.8 にリクエストする
forward . 8.8.8.8
}
検証用のスクリプトはこちら。
環境がない場合は Node.js をインストールして ts-starter とか使うと環境ができます。 code:index.ts
import { promisify } from 'util'
import { resolve } from 'dns'
import axios from 'axios'
(async () => {
const target = 'example.test'
console.log(target: ${target})
// dns パッケージを使って名前解決を試みる
const x = await promisify(resolve)(target)
console.log(ip: ${x.join(' ')})
// axios(http) を使って通信してみる
const y = await axios.get(http://${target}/status/200)
console.log(status: ${y.status})
})()
実行してみるとわかりますが、名前解決で queryA ENODATA として失敗しています。
code:エラー結果(bash)
$ ts-node ./index.ts
target: example.test
(node:50491) UnhandledPromiseRejectionWarning: Error: queryA ENODATA example.test
なおこのときの dig はこのような結果になります。
code: dig結果-1(bash)
❯ dig example.test @localhost
# ...
;; QUESTION SECTION:
;example.test. IN A
;; ANSWER SECTION:
httpbin.org. 41 IN A 35.170.216.115
httpbin.org. 41 IN A 34.230.193.231
# ...
これを正常にするには先程の CoreDNS の設定に answer name の指定を入れる必要があります。
code:Corefile.diff
. {
- rewrite name regex example\.test httpbin.org
+ rewrite name regex example\.test httpbin.org answer name httpbin\.org example.test
forward . 8.8.8.8
}
書き換えて CoreDNS を再起動後、同じスクリプトを実行すると正常に完了することがわかると思います。
正常に動くときの dig の結果はこのようになっています。
code:dig結果-2(bash)
❯ dig example.test @localhost
# ...
;; QUESTION SECTION:
;example.test. IN A
;; ANSWER SECTION:
example.test. 58 IN A 34.230.193.231 # httpbin.org の IP
example.test. 58 IN A 35.170.216.115 # httpbin.org の IP
# ...
なぜこうなるのか?
レスポンス中にある dns.js の 203行目はこちらのようになっており onresolve が実行されたタイミングではすでに err が何らかの値になっていることがわかる。(コアファイルなので外から見ることはできない)
code:dns.js-203(js)
if (err)
this.callback(dnsException(err, this.bindingName, this.hostname));
この Issue ではにたものとして Answer Section が無い場合に再現と同じく ENODATA が帰ってくることが言及されている。
Node.js ではかつて名前解決に c-are というライブラリを利用していたがそれを自前の実装に置き換えるプロジェクトが立ち上げられており、今も進行中のようだ。 dns のパッケージが問題なくて http のパッケージの利用で問題が出てくるのはこのあたりが関係してそう。
完全なエスパーだけど DNS に問い合わせた結果 dig 上 Answer Section とされている部分が、自分がほしいものと異なるなら使わないというのは、セキュリティ的な部分からも正しいように思える。
例えば example.test をリクエストして evil.test のレコードが帰ってくるようなそういうやばいDNSが仮にあったとしたら、結果をチェックするという意味では妥当な挙動だと思う。(チェックしてなければ全然関係ないレコードの結果をそのまま使ってしまい意図しないところにリクエストが飛んじゃう可能性があるから。)
多分そういう安全のための何らかのしくみが入ってそうな感じがした。
時間があったら続きはまた今度ってことで。今日はもう疲れた。。。