Node.jsでNSレコードを解決!dnsPromises.resolveNs()徹底解説
ネームサーバ(NS)レコードとは?
DNS(Domain Name System)において、あるドメイン名がどのDNSサーバーによって管理されているかを示すのがNSレコードです。例えば、「example.com」というドメインのNSレコードは、「ns1.example.com」や「ns2.example.com」といったネームサーバがこのドメインの情報を管理していることを示します。
このメソッドは、指定されたhostname
のNSレコードをDNSに対して問い合わせ、その結果をPromiseとして返します。
基本的な使い方
const dns = require('dns');
const dnsPromises = dns.promises; // promises APIを利用
async function getNsRecords(hostname) {
try {
const records = await dnsPromises.resolveNs(hostname);
console.log(`${hostname} のNSレコード:`, records);
} catch (err) {
console.error(`NSレコードの取得中にエラーが発生しました: ${err.message}`);
}
}
getNsRecords('google.com');
getNsRecords('example.com');
返される値
dnsPromises.resolveNs()
が成功すると、Promiseは文字列の配列で解決されます。この配列には、指定されたホスト名に対応するネームサーバのホスト名が格納されます。
例:['ns1.example.com', 'ns2.example.com']
よくあるエラー
dnsPromises.resolveNs()
は Promise を返すため、エラーが発生した場合は .catch()
ブロックで捕捉するか、async/await
を使用している場合は try...catch
ブロックで捕捉する必要があります。発生する可能性のある主なエラーコードと、その意味は以下の通りです。
ENOTFOUND
(No such host)- 意味
指定されたホスト名(ドメイン名)に対応するNSレコードが見つからない場合に発生します。これは、ドメイン名が間違っている、存在しない、またはDNSに登録されていない場合に起こりえます。 - 例
dnsPromises.resolveNs('nonexistent-domain-12345.com')
のような存在しないドメイン名を問い合わせた場合。
- 意味
SERVFAIL
(Server failure)- 意味
DNSサーバー自体がエラーを返した場合に発生します。これは、DNSサーバーが一時的にダウンしている、設定ミスがある、またはドメインのDNSSEC署名が無効であるなどの理由が考えられます。
- 意味
NODATA
(No address records of the requested type)- 意味
ホスト名は存在するものの、NSレコードが存在しない場合に発生します。これは、例えばIPアドレスを問い合わせるためのAレコードは存在するが、NSレコードは設定されていないような場合に起こります。
- 意味
TIMEOUT
(Query timed out)- 意味
DNSクエリが指定された時間内に応答しなかった場合に発生します。これは、ネットワーク接続の問題、DNSサーバーが応答しない、または非常に遅い場合に起こります。
- 意味
REFUSED
(Query refused by DNS server)- 意味
DNSサーバーがクエリを拒否した場合に発生します。これは、DNSサーバーが特定のIPアドレスからのクエリをブロックしている、または不正なクエリと判断した場合に起こりえます。
- 意味
FORMERR
(Format error)- 意味
DNSサーバーがクエリの形式が不正であると判断した場合に発生します。これは通常、Node.jsの内部的な問題か、非常に特殊なDNS設定の場合にのみ発生します。
- 意味
CONNREFUSED
(Connection refused)- 意味
DNSサーバーへの接続が拒否された場合に発生します。これは、DNSサーバーがダウンしている、ファイアウォールでブロックされている、またはIPアドレスが間違っている場合に起こりえます。
- 意味
dnsPromises.resolveNs()
で問題が発生した場合のトラブルシューティングは、以下のステップで進めることができます。
-
- 問題
Node.jsが実行されているサーバーからDNSサーバーへのネットワーク接続が確立されているか。 - 確認方法
- インターネット接続が正常か確認する。
- ファイアウォール(OSのファイアウォール、クラウドセキュリティグループなど)がDNS(UDP/TCPポート53番)の通信をブロックしていないか確認する。
- もし特定のDNSサーバーを使用している場合は、そのDNSサーバーへのpingが通るか確認する。
- 問題
-
使用しているDNSサーバーの確認
-
問題
Node.jsが使用しているDNSサーバーが、正しく設定されており、応答しているか。デフォルトではOSのDNS設定を使用しますが、dns.setServers()
を使って明示的に設定している場合もあります。 -
確認方法
- OSのDNS設定(
/etc/resolv.conf
など)を確認する。 dns.getServers()
を使って、Node.jsが現在どのDNSサーバーを使用しているか確認する。dns.promises.Resolver
クラスをインスタンス化して、特定のDNSサーバーを指定し、それを使って問い合わせてみる。
<!-- end list -->
const dns = require('dns'); const dnsPromises = dns.promises; async function resolveWithSpecificServer(hostname, serverIp) { const resolver = new dnsPromises.Resolver(); resolver.setServers([serverIp]); // 例: Google Public DNS try { const records = await resolver.resolveNs(hostname); console.log(`${hostname} のNSレコード (サーバー ${serverIp} 経由):`, records); } catch (err) { console.error(`エラー (${serverIp} 経由): ${err.message}`); } } resolveWithSpecificServer('example.com', '8.8.8.8'); // Google Public DNS resolveWithSpecificServer('example.com', '1.1.1.1'); // Cloudflare DNS
- OSのDNS設定(
-
-
DNSSEC関連の問題
- 問題
ドメインがDNSSECで署名されている場合、署名が無効であったり、DNSサーバーがDNSSEC検証に失敗したりすると、SERVFAIL
などのエラーが発生することがあります。 - 対策
これはサーバー側の問題なので、直接的な解決策は難しいですが、情報として知っておくと原因究明に役立ちます。DNSSEC検証をサポートしないDNSサーバーを使用している場合、問題が発生する可能性があります。
- 問題
-
localhost の解決問題
- 問題
localhost
を解決しようとすると、ENOTFOUND
エラーやハングアップ(応答なし)が発生することがあります。 - 原因
dns.resolve*
系メソッドは、/etc/hosts
ファイル(WindowsではC:\Windows\System32\drivers\etc\hosts
)を直接参照しません。DNSサーバーに問い合わせを行います。localhost
は通常、DNSサーバーには登録されていないため、ENOTFOUND
となります。 - 対策
localhost
のようなローカルホスト名の解決には、dns.lookup()
を使用することを検討してください。dns.lookup()
はOSのホストファイルや他のローカルな解決メカニズムを利用します。
const dns = require('dns'); dns.lookup('localhost', (err, address, family) => { if (err) { console.error('localhost の解決中にエラー:', err.message); } else { console.log(`localhost は <span class="math-inline">\{address\} \(IPv</span>{family}) に解決されました。`); } });
- 問題
基本的な使い方
最も基本的な例で、指定されたドメインのNSレコードを取得します。
const dns = require('dns');
const dnsPromises = dns.promises; // Promise版APIを使用
async function getNsRecords(domain) {
try {
console.log(`--- ${domain} のNSレコードを取得中 ---`);
const records = await dnsPromises.resolveNs(domain);
console.log(`NSレコード:`);
records.forEach(record => {
console.log(` - ${record}`);
});
} catch (error) {
console.error(`エラーが発生しました (${domain}): ${error.message}`);
// エラーコードも表示するとデバッグに役立ちます
if (error.code) {
console.error(` エラーコード: ${error.code}`);
}
}
}
// 例の実行
getNsRecords('google.com');
getNsRecords('cloudflare.com');
getNsRecords('nonexistent-domain-12345.xyz'); // 存在しないドメインの例
解説
try...catch
ブロックでエラーを適切に捕捉し、ユーザーフレンドリーなメッセージを表示しています。- 結果はNSレコードのホスト名(文字列)の配列として返されます。
await dnsPromises.resolveNs(domain)
でNSレコードの解決を待ちます。dns.promises
を使用することで、Promiseベースの非同期APIを利用できます。これにより、async/await
構文でコードをすっきりと記述できます。require('dns')
でdns
モジュールを読み込みます。
複数のドメインを並行して解決する
複数のドメインのNSレコードを同時に解決したい場合、Promise.all
を使用すると効率的です。
const dns = require('dns');
const dnsPromises = dns.promises;
async function getAllNsRecords(domains) {
console.log(`--- 複数のドメインのNSレコードを並行して取得中 ---`);
const promises = domains.map(async (domain) => {
try {
const records = await dnsPromises.resolveNs(domain);
return { domain, records, error: null };
} catch (error) {
return { domain, records: [], error: error.message, errorCode: error.code };
}
});
const results = await Promise.all(promises);
results.forEach(result => {
if (result.error) {
console.error(` ${result.domain}: エラー - ${result.error} (コード: ${result.errorCode})`);
} else {
console.log(` ${result.domain} のNSレコード:`);
result.records.forEach(record => {
console.log(` - ${record}`);
});
}
console.log(''); // 空行で区切り
});
}
// 例の実行
const domainsToResolve = [
'amazon.com',
'microsoft.com',
'invalid-domain-abcde.co', // 存在しないドメイン
'example.com',
'akamai.com'
];
getAllNsRecords(domainsToResolve);
解説
- 結果をループ処理し、成功したドメインと失敗したドメインを区別して表示します。
Promise.all(promises)
は、すべてのPromiseが解決された後に、結果オブジェクトの配列を返します。- 各Promiseは、成功または失敗に関わらず、結果オブジェクト(
{ domain, records, error }
)を返します。これにより、Promise.all
がすべてのPromiseの解決を待つことができます。 domains.map()
を使用して、各ドメインに対してdnsPromises.resolveNs()
を呼び出すPromiseの配列を作成します。
特定のDNSサーバーを使用して解決する
デフォルトでは、Node.js はオペレーティングシステムに設定されているDNSサーバーを使用します。しかし、dnsPromises.Resolver
クラスを使用すると、特定のDNSサーバーを指定してクエリを実行できます。これは、DNSサーバーの応答性をテストしたり、特定のDNS設定が必要な場合に便利です。
const dns = require('dns');
const dnsPromises = dns.promises;
async function resolveNsWithSpecificServer(domain, dnsServerIp) {
const resolver = new dnsPromises.Resolver();
resolver.setServers([dnsServerIp]); // 使用するDNSサーバーのIPアドレスを設定
try {
console.log(`--- ${domain} のNSレコードを ${dnsServerIp} 経由で取得中 ---`);
const records = await resolver.resolveNs(domain);
console.log(`NSレコード (${dnsServerIp}):`);
records.forEach(record => {
console.log(` - ${record}`);
});
} catch (error) {
console.error(`エラーが発生しました (${domain}, ${dnsServerIp} 経由): ${error.message}`);
if (error.code) {
console.error(` エラーコード: ${error.code}`);
}
}
}
// 例の実行
// Google Public DNS (8.8.8.8) と Cloudflare DNS (1.1.1.1) を使用
resolveNsWithSpecificServer('nodejs.org', '8.8.8.8');
resolveNsWithSpecificServer('nodejs.org', '1.1.1.1');
resolveNsWithSpecificServer('some-bad-domain.com', '8.8.8.8'); // 存在しないドメイン
resolveNsWithSpecificServer('google.com', '192.0.2.1'); // 応答しない/存在しないDNSサーバーの例
解説
- その後、
resolver.resolveNs(domain)
を呼び出すと、設定されたDNSサーバーにクエリが送信されます。 resolver.setServers([dnsServerIp])
で、このリゾルバが使用するDNSサーバーのIPアドレス(配列)を設定します。new dnsPromises.Resolver()
で新しいリゾルバインスタンスを作成します。
dnsPromises.resolveNs()
自体には直接的なタイムアウトオプションがありません。特定の時間内に応答がない場合に処理を中断したい場合は、AbortController
を使用して Promise.race
と組み合わせるのが一般的な方法です。
const dns = require('dns');
const dnsPromises = dns.promises;
async function resolveNsWithTimeout(domain, timeoutMs) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs); // 指定時間後に中断信号を送る
try {
console.log(`--- ${domain} のNSレコードを ${timeoutMs}ms 以内で取得中 ---`);
const records = await dnsPromises.resolveNs(domain, { signal: controller.signal });
clearTimeout(timeoutId); // 成功したらタイムアウトタイマーをクリア
console.log(`NSレコード:`);
records.forEach(record => {
console.log(` - ${record}`);
});
} catch (error) {
clearTimeout(timeoutId); // エラーが発生してもタイマーをクリア
if (error.name === 'AbortError') {
console.error(`タイムアウトしました (${domain}): ${timeoutMs}ms 以内に応答がありませんでした。`);
} else {
console.error(`エラーが発生しました (${domain}): ${error.message}`);
if (error.code) {
console.error(` エラーコード: ${error.code}`);
}
}
}
}
// 例の実行
resolveNsWithTimeout('google.com', 2000); // 2秒以内に解決
resolveNsWithTimeout('very-slow-or-nonexistent-dns-target.net', 500); // 応答が遅い、または存在しないドメインで短めのタイムアウト
- 成功した場合や他のエラーが発生した場合は、
clearTimeout(timeoutId)
で不要になったタイマーをクリアします。 - 操作がシグナルによって中断された場合、
AbortError
がスローされます。 dnsPromises.resolveNs(domain, { signal: controller.signal })
のように、signal
オプションを渡すことで、このシグナルがresolveNs
操作に伝達されます。setTimeout
で指定した時間後にcontroller.abort()
を呼び出し、中断信号を送ります。AbortController
は、Web APIから導入された機能で、非同期操作をキャンセルするためのシグナルを提供します。
コールバックベースの dns.resolveNs() (非推奨だが互換性維持のため)
Node.js の dns
モジュールには、PromiseベースのAPIが導入される前から存在するコールバックベースのメソッドがあります。新しいコードではPromise版の使用が強く推奨されますが、古いコードベースや特定の要件で必要になる場合があります。
const dns = require('dns');
function getNsRecordsCallback(domain) {
console.log(`--- (Callback) ${domain} のNSレコードを取得中 ---`);
dns.resolveNs(domain, (err, records) => {
if (err) {
console.error(`エラーが発生しました (${domain}): ${err.message}`);
if (err.code) {
console.error(` エラーコード: ${err.code}`);
}
return;
}
console.log(`NSレコード:`);
records.forEach(record => {
console.log(` - ${record}`);
});
});
}
// 例の実行
getNsRecordsCallback('google.com');
getNsRecordsCallback('nonexistent-domain-callback.xyz');
利点
- 既存の古いコードとの互換性。
欠点
async/await
のような現代的な非同期制御構文と組み合わせにくい。- エラーハンドリングが煩雑になりがち。
- コールバック地獄 (Callback Hell) に陥りやすく、コードが読みにくくなる。
dns.lookup() (ホスト名からIPアドレスへの解決)
dns.lookup()
は、NSレコードの解決とは異なりますが、ホスト名解決という広い意味では代替となり得ます。これは、名前解決の最も基本的な形式であり、OSのホストファイルやネットワーク設定に依存してIPアドレスを解決します。NSレコードは直接解決しません。
const dns = require('dns');
async function lookupHost(hostname) {
try {
console.log(`--- ${hostname} のIPアドレスをルックアップ中 ---`);
// dns.lookup() はPromiseを返さないため、util.promisifyを使うか、コールバックで処理します。
// ここではNode.js v10以降で推奨される dns.promises.lookup() を使用します。
const { address, family } = await dns.promises.lookup(hostname);
console.log(`IPアドレス: ${address} (IPv${family})`);
} catch (error) {
console.error(`エラーが発生しました (${hostname}): ${error.message}`);
if (error.code) {
console.error(` エラーコード: ${error.code}`);
}
}
}
// 例の実行
lookupHost('example.com');
lookupHost('localhost'); // dns.resolveNs() と異なり、localhost も解決できる
lookupHost('nonexistent-host-lookup.test');
利点
- ホスト名からIPアドレスへの基本的な解決に用いられる。
- OSのホストファイルなどを参照するため、
localhost
などのローカルな解決に最適。
欠点
- DNSのキャッシュや再帰問い合わせの動作が
dns.resolve*
系とは異なる場合がある。 - DNSの特定のレコードタイプ(NS、MX、TXTなど)を解決する目的には適さない。
外部のDNSライブラリの利用
Node.js の組み込み dns
モジュールは基本的な機能を提供しますが、より高度な機能(例: DNSSEC検証の詳細な制御、特定のDNSレコードタイプに対するより多くのオプション、DNS over HTTPS/TLSのサポートなど)が必要な場合、外部ライブラリの利用を検討できます。
例として、dns-packet
のようなライブラリはDNSパケットのエンコード/デコードに役立ち、カスタムDNSクライアントを構築する際に利用できます。ただし、これらは通常、非常に低レベルな操作を伴います。
より高レベルなDNSクライアントライブラリとして、以下のようなものが挙げられます(ただし、NSレコード解決に特化してdnsPromises.resolveNs()
より「良い」代替かというと、目的によって異なります)。
- dns-query
カスタムDNSクエリを送信するためのモジュール。低レベルなクエリを作成できます。 - dane (DNS-based Authentication of Named Entities)
DNSSECとTLS証明書検証に特化しており、NSレコード直接解決のためではありません。
例 (概念的、具体的なコードはライブラリによる)
// これは架空の外部DNSライブラリの例であり、実際のコードではありません
// npm install some-dns-client のようにインストールが必要です
// const SomeDnsClient = require('some-dns-client');
// const client = new SomeDnsClient({ server: '8.8.8.8' });
// async function getNsRecordsViaExternalLibrary(domain) {
// try {
// console.log(`--- (External Lib) ${domain} のNSレコードを取得中 ---`);
// const records = await client.queryNs(domain); // 仮定のメソッド
// console.log(`NSレコード:`, records);
// } catch (error) {
// console.error(`エラーが発生しました (${domain}): ${error.message}`);
// }
// }
// getNsRecordsViaExternalLibrary('example.com');
利点
- よりきめ細かい制御が可能。
- 組み込みモジュールでは提供されない高度な機能(例: DNSSECの詳細、DOHTなど)。
欠点
- 多くの場合、
dnsPromises
よりも低レベルなAPIになるため、実装が複雑になる。 - 学習コストが発生する。
- 依存関係が増える。
これはNode.jsのプログラミングというよりは、システムレベルの代替方法ですが、DNSサーバーソフトウェア自体(例:Bind, PowerDNS, dnsmasqなど)に対して直接クエリを実行する方法も考えられます。これは、スクリプトから外部コマンドとして実行されることがあります。
# コマンドラインからnslookupでNSレコードを問い合わせる
nslookup -type=ns example.com
# コマンドラインからdigでNSレコードを問い合わせる
dig ns example.com @8.8.8.8 # 特定のDNSサーバーを指定
利点
- Node.jsプロセスとは独立して動作するため、デバッグなどに役立つ。
- 環境に依存せず、基本的なDNS情報を確認できる。
欠点
- OSや環境に依存する。
- Node.jsアプリケーション内から直接利用するには、
child_process
モジュールを使って外部コマンドを実行する必要があり、結果のパースが複雑になる。
ほとんどのNode.jsアプリケーションにおいて、NSレコードを解決する目的であれば、dnsPromises.resolveNs()
が最も推奨される方法です。これはPromiseベースで扱いやすく、Node.jsの組み込み機能であるため外部依存がなく、一般的なユースケースに十分対応できるからです。