Node.jsでNSレコードを解決!dnsPromises.resolveNs()徹底解説

2025-05-27

ネームサーバ(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 ブロックで捕捉する必要があります。発生する可能性のある主なエラーコードと、その意味は以下の通りです。

  1. ENOTFOUND (No such host)
    • 意味
      指定されたホスト名(ドメイン名)に対応するNSレコードが見つからない場合に発生します。これは、ドメイン名が間違っている、存在しない、またはDNSに登録されていない場合に起こりえます。

    • dnsPromises.resolveNs('nonexistent-domain-12345.com') のような存在しないドメイン名を問い合わせた場合。
  2. SERVFAIL (Server failure)
    • 意味
      DNSサーバー自体がエラーを返した場合に発生します。これは、DNSサーバーが一時的にダウンしている、設定ミスがある、またはドメインのDNSSEC署名が無効であるなどの理由が考えられます。
  3. NODATA (No address records of the requested type)
    • 意味
      ホスト名は存在するものの、NSレコードが存在しない場合に発生します。これは、例えばIPアドレスを問い合わせるためのAレコードは存在するが、NSレコードは設定されていないような場合に起こります。
  4. TIMEOUT (Query timed out)
    • 意味
      DNSクエリが指定された時間内に応答しなかった場合に発生します。これは、ネットワーク接続の問題、DNSサーバーが応答しない、または非常に遅い場合に起こります。
  5. REFUSED (Query refused by DNS server)
    • 意味
      DNSサーバーがクエリを拒否した場合に発生します。これは、DNSサーバーが特定のIPアドレスからのクエリをブロックしている、または不正なクエリと判断した場合に起こりえます。
  6. FORMERR (Format error)
    • 意味
      DNSサーバーがクエリの形式が不正であると判断した場合に発生します。これは通常、Node.jsの内部的な問題か、非常に特殊なDNS設定の場合にのみ発生します。
  7. CONNREFUSED (Connection refused)
    • 意味
      DNSサーバーへの接続が拒否された場合に発生します。これは、DNSサーバーがダウンしている、ファイアウォールでブロックされている、またはIPアドレスが間違っている場合に起こりえます。

dnsPromises.resolveNs() で問題が発生した場合のトラブルシューティングは、以下のステップで進めることができます。

    • 問題
      Node.jsが実行されているサーバーからDNSサーバーへのネットワーク接続が確立されているか。
    • 確認方法
      • インターネット接続が正常か確認する。
      • ファイアウォール(OSのファイアウォール、クラウドセキュリティグループなど)がDNS(UDP/TCPポート53番)の通信をブロックしていないか確認する。
      • もし特定のDNSサーバーを使用している場合は、そのDNSサーバーへのpingが通るか確認する。
  1. 使用している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
      
  2. DNSSEC関連の問題

    • 問題
      ドメインがDNSSECで署名されている場合、署名が無効であったり、DNSサーバーがDNSSEC検証に失敗したりすると、SERVFAIL などのエラーが発生することがあります。
    • 対策
      これはサーバー側の問題なので、直接的な解決策は難しいですが、情報として知っておくと原因究明に役立ちます。DNSSEC検証をサポートしないDNSサーバーを使用している場合、問題が発生する可能性があります。
  3. 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の組み込み機能であるため外部依存がなく、一般的なユースケースに十分対応できるからです。