Node.js ネットワークプログラミング:dnsPromises.setServers() の役割と重要性

2025-05-27

dnsPromises.setServers(servers) は、Node.js の dns.promises モジュールに用意されているメソッドの一つです。このメソッドは、名前解決に使用する DNS サーバーのリストを設定するために使われます。

もう少し詳しく見ていきましょう。

  • 注意点

    • dnsPromises.setServers() で設定した DNS サーバーは、その Node.js プロセス全体に影響します。つまり、このメソッドを呼び出した後に実行される DNS 関連の操作は、設定されたサーバーを使用します。
    • 無効な IP アドレスや到達できない DNS サーバーを設定すると、名前解決が失敗する可能性があります。
    • このメソッドは、dns.lookup() などの低レベルな DNS 解決関数に影響を与えますが、httphttps モジュールなど、より高レベルな API が内部的にどのように DNS 解決を行っているかによっては、期待通りに動作しない場合もあります。
  • いつ使うか

    • 特定の DNS サーバーを使用したい場合
      例えば、パフォーマンスの向上や特定の機能(フィルタリングなど)を持つ DNS サーバーを利用したい場合。
    • テスト環境
      特定の DNS サーバー環境をシミュレートしたい場合。
    • トラブルシューティング
      DNS 関連の問題を調査する際に、特定の DNS サーバーに対してクエリを送信して動作を確認したい場合。
  • 役割
    通常、Node.js アプリケーションは、システムのデフォルト設定(オペレーティングシステムやネットワーク設定で指定された DNS サーバー)を使用して名前解決を行います。しかし、dnsPromises.setServers() を使用することで、アプリケーションが特定のリクエストに対して使用する DNS サーバーを明示的に指定できます。

  • DNS サーバーのリスト
    setServers() メソッドに渡す引数 servers は、DNS サーバーの IP アドレス(IPv4 または IPv6)の文字列からなる配列です。例えば、Google Public DNS を使用したい場合は、次のような配列を渡します。

    ['8.8.8.8', '8.8.4.4']
    
  • dns.promises モジュール
    Node.js には、非同期処理(Promise ベース)で DNS 関連の操作を行うための dns.promises モジュールがあります。このモジュールには、名前解決(ホスト名から IP アドレスへの変換など)を行うための様々な関数が含まれています。setServers() もその一つです。

使用例

const dns = require('dns').promises;

async function resolveHost() {
  // 使用する DNS サーバーを Google Public DNS に設定
  dns.setServers(['8.8.8.8', '8.8.4.4']);

  try {
    const address = await dns.lookup('example.com');
    console.log(`example.com の IP アドレス: ${address.address}`);
  } catch (err) {
    console.error('名前解決エラー:', err);
  }
}

resolveHost();

この例では、dns.setServers() を使って DNS サーバーを Google Public DNS に設定した後、dns.lookup()example.com の IP アドレスを解決しています。



一般的なエラー

    • エラー
      servers 配列に、IP アドレスとして不正な文字列(例: スペースを含む、形式が正しくない)や、IP アドレスではない文字列が含まれている場合。
    • 症状
      TypeError などのエラーが発生し、Node.js プロセスがクラッシュしたり、その後の DNS 解決処理が失敗したりする可能性があります。

    • dns.setServers(['8.8.8', 'invalid address', '2001:4860:4860::8888']); // '8.8.8' は短すぎる
      
  1. 到達不能なサーバーアドレスの指定

    • エラー
      指定した IP アドレスの DNS サーバーがダウンしている、ネットワーク的にアクセスできない(ファイアウォールでブロックされているなど)場合。
    • 症状
      dns.lookup() などの DNS 解決関数がタイムアウトしたり、ECONNREFUSED などのネットワーク関連のエラーを返したりする可能性があります。
    • 確認方法
      ping コマンドなどで、指定した DNS サーバーにネットワーク的に到達可能か確認してみると良いでしょう。
  2. 配列以外の値を servers に渡した場合

    • エラー
      dns.setServers() の引数 servers に、配列 (Array) 以外の値(文字列、数値、オブジェクトなど)を渡した場合。
    • 症状
      TypeError が発生し、Node.js プロセスがエラー終了する可能性があります。
  3. 権限の問題 (稀)

    • エラー
      特定の環境下では、DNS サーバーの設定変更に管理者権限が必要な場合があります。Node.js プロセスが適切な権限で実行されていない場合、設定が反映されない、またはエラーが発生する可能性があります。
    • 症状
      設定を試みても、その後の DNS 解決が依然としてデフォルトのサーバーを使用したり、エラーが発生したりする場合があります。

トラブルシューティング

  1. 指定したサーバーアドレスの確認

    • servers 配列に指定している IP アドレスが正しい形式であるか、タイプミスがないかなどを注意深く確認してください。IPv4 と IPv6 の形式を間違えないようにしましょう。
  2. ネットワーク接続の確認

    • 指定した DNS サーバーに対して、Node.js が動作している環境からネットワーク的に到達可能か確認してください。pingtraceroute などのコマンドラインツールが役立ちます。
    • ファイアウォールやネットワークポリシーによって、DNS (通常は UDP/TCP ポート 53) の通信がブロックされていないか確認してください。
  3. エラーメッセージの確認

    • DNS 解決関連のエラーが発生した場合、エラーメッセージを注意深く読み、何が問題なのか手がかりを探してください。ECONNREFUSED は接続拒否、ETIMEDOUT はタイムアウトを示唆しています。
  4. 設定のタイミング

    • dns.setServers() は、DNS 解決を行う前に呼び出す必要があります。非同期処理のタイミングによっては、意図したサーバーが使われない可能性があるので、注意が必要です。通常は、アプリケーションの初期化時など、早い段階で設定することが推奨されます。
  5. 他の DNS 解決方法との干渉

    • Node.js アプリケーション内で、dns.setServers() 以外にも DNS サーバーの設定を変更するようなライブラリや仕組みを使用している場合、それらが干渉し合っている可能性があります。設定が意図通りに反映されない場合は、他の DNS 関連の設定がないか確認してください。
  6. システムの DNS 設定の確認

    • dns.setServers() は Node.js プロセス内の DNS 解決に影響を与えますが、OS レベルの DNS 設定が誤っている場合、根本的な問題解決にはならないことがあります。必要に応じて、OS の DNS 設定も確認してください。
  7. ログ出力の活用

    • DNS 解決処理の前後にログを出力するようにして、実際にどの DNS サーバーが使用されているか、設定が正しく行われているかなどを確認するのも有効な手段です。
  8. 最小限のコードでのテスト

    • 複雑なアプリケーションで問題が発生している場合は、最小限のコードで dns.setServers() と DNS 解決処理を試してみて、問題が再現するかどうか切り分けることが重要です。

例: 無効なサーバーアドレスによるエラー

const dns = require('dns').promises;

async function resolveHost() {
  try {
    dns.setServers(['192.168.1.1', 'invalid-ip']); // 無効な IP アドレス
    const address = await dns.lookup('example.com');
    console.log(`example.com の IP アドレス: ${address.address}`);
  } catch (err) {
    console.error('エラー:', err); // TypeError が発生する可能性
  }
}

resolveHost();

この例では、'invalid-ip' という無効な文字列がサーバーリストに含まれているため、dns.setServers() の呼び出し時に TypeError が発生する可能性があります。



基本的な DNS サーバーの設定と名前解決

この例では、dnsPromises.setServers() を使って DNS サーバーを Google Public DNS に設定し、その後 dnsPromises.lookup() でホスト名の IP アドレスを解決します。

const dns = require('dns').promises;

async function resolveWithCustomDns() {
  // 使用する DNS サーバーを Google Public DNS に設定
  dns.setServers(['8.8.8.8', '8.8.4.4']);

  try {
    const address = await dns.lookup('example.com');
    console.log(`example.com の IP アドレス: ${address.address}`);
  } catch (err) {
    console.error('名前解決エラー:', err);
  }
}

resolveWithCustomDns();

解説

  • 成功した場合は IP アドレスが表示され、エラーが発生した場合はエラーメッセージが表示されます。
  • dns.lookup('example.com') は、設定された DNS サーバーに対して example.com の IP アドレスを問い合わせる非同期関数です。await を使って結果を待ちます。
  • dns.setServers(['8.8.8.8', '8.8.4.4']); で、DNS サーバーのリストを Google Public DNS のアドレスに設定します。
  • require('dns').promises;dns.promises モジュールをインポートし、非同期処理のための API を利用できるようにします。

複数の名前解決を異なる DNS サーバーで行う (注意点あり)

dns.setServers() はグローバルな設定であるため、連続して異なるサーバーを設定しても、最後の設定が有効になります。もし、特定のリクエストごとに異なる DNS サーバーを使いたい場合は、より低レベルなソケット処理などを検討する必要があります。以下の例は、意図した動作にならない可能性があることを示すためのものです。

const dns = require('dns').promises;

async function resolveWithMultipleDns() {
  // 最初の DNS サーバーを設定
  dns.setServers(['1.1.1.1']); // Cloudflare DNS
  try {
    const address1 = await dns.lookup('example.com');
    console.log(`example.com (Cloudflare): ${address1.address}`);
  } catch (err) {
    console.error('エラー (Cloudflare):', err);
  }

  // 次の DNS サーバーを設定
  dns.setServers(['9.9.9.9']); // Quad9 DNS
  try {
    const address2 = await dns.lookup('nodejs.org');
    console.log(`nodejs.org (Quad9): ${address2.address}`);
  } catch (err) {
    console.error('エラー (Quad9):', err);
  }
}

resolveWithMultipleDns();

解説

  • 重要な注意点
    dns.setServers() はグローバルな設定なので、2回目の dns.lookup() は Quad9 DNS を使用して行われます。最初の dns.lookup() も、完了する前に 2回目の dns.setServers() が実行された場合、Quad9 DNS を使用する可能性があります。
  • このコードは、Cloudflare DNS (1.1.1.1) で example.com を解決しようとした後、Quad9 DNS (9.9.9.9) に DNS サーバーを変更し、nodejs.org を解決しようとしています。

エラーハンドリングの例

DNS サーバーが到達不能な場合などのエラーをキャッチし、処理する方法を示します。

const dns = require('dns').promises;

async function resolveWithErrorHandling() {
  // 存在しない可能性のある DNS サーバーを設定
  dns.setServers(['192.0.2.1']); // ドキュメント用の予約済みアドレス

  try {
    const address = await dns.lookup('example.com', { timeout: 1000 }); // タイムアウトを設定
    console.log(`example.com の IP アドレス: ${address.address}`);
  } catch (err) {
    console.error('名前解決エラー:', err.code); // エラーコードを表示
    if (err.code === 'ETIMEDOUT') {
      console.log('DNS サーバーへの接続がタイムアウトしました。');
    } else if (err.code === 'ECONNREFUSED') {
      console.log('DNS サーバーに接続できませんでした。');
    } else {
      console.log('その他の DNS エラーが発生しました。');
    }
  }
}

resolveWithErrorHandling();

解説

  • catch ブロックでエラーを捕捉し、エラーオブジェクトの code プロパティに基づいて、タイムアウトや接続拒否などの特定のエラーを処理しています。
  • dns.lookup() のオプションで timeout を設定することで、DNS クエリが一定時間内に応答しない場合にエラーを発生させることができます。
  • 存在しない可能性のある IP アドレスを DNS サーバーとして設定しています。

システムのデフォルト DNS サーバーに戻す

dns.setServers() で設定した DNS サーバーを解除し、システムのデフォルト設定に戻す方法は直接的には提供されていません。アプリケーションのライフサイクル全体でカスタム DNS サーバーを使用するか、必要に応じてプロセスを再起動するなどの方法が考えられます。

  • DNS 関連の処理はネットワークに依存するため、エラーハンドリングを適切に行うことが重要です。
  • dns.setServers() は、Node.js プロセス全体に影響を与えるグローバルな設定です。複数の独立した DNS 設定が必要な場合は、より複雑なネットワーク処理を検討する必要があるかもしれません。


個別の DNS リゾルバーの実装 (サードパーティライブラリの利用)

Node.js の標準の dns モジュールは比較的低レベルな API を提供しており、より高度な機能や柔軟な設定を行うには限界があります。サードパーティの DNS クライアントライブラリを利用することで、個別の DNS リゾルバーを作成し、リクエストごとに異なる DNS サーバーを使用したり、より詳細な設定(タイムアウト、リトライ、プロトコルなど)を行ったりできます。

  • 例: dns-over-https ライブラリ このライブラリを使うと、DNS-over-HTTPS (DoH) プロトコルを利用して名前解決を行うことができます。DoH は HTTP/2 を介して DNS クエリを送信するため、ISP などによる DNS 傍受や改ざんを防ぐ効果が期待できます。

    const doh = require('dns-over-https');
    
    async function resolveWithDoH() {
      try {
        const result = await doh.resolve('example.com', 'https://cloudflare-dns.com/dns-query');
        console.log('example.com の IP アドレス (DoH):', result);
      } catch (err) {
        console.error('DoH を使用した名前解決エラー:', err);
      }
    }
    
    resolveWithDoH();
    
    • doh.resolve(hostname, url) 関数に、解決したいホスト名と DoH サーバーの URL を指定します。
    • この方法では、dns.setServers() のようにグローバルな DNS サーバー設定は行いません。各リクエストごとに使用するサーバーを指定できます。

child_process を使って外部の DNS 解決ツールを実行する

Node.js のプロセス内で、オペレーティングシステムの提供する DNS 解決ツール(例: dignslookup)を child_process モジュールを使って実行し、その結果を解析する方法です。

const { exec } = require('child_process').promises;

async function resolveWithDig(hostname, server) {
  try {
    const { stdout, stderr } = await exec(`dig +short @${server} ${hostname}`);
    if (stderr) {
      console.error('dig エラー:', stderr);
      return null;
    }
    const ipAddress = stdout.trim();
    console.log(`${hostname} の IP アドレス (${server}): ${ipAddress}`);
    return ipAddress;
  } catch (err) {
    console.error('dig の実行エラー:', err);
    return null;
  }
}

async function resolveMultipleWithExternal() {
  await resolveWithDig('example.com', '8.8.8.8');
  await resolveWithDig('nodejs.org', '1.1.1.1');
}

resolveMultipleWithExternal();

解説

  • ただし、外部プロセスを実行するため、オーバーヘッドが発生する可能性があります。また、実行環境に dig などのツールがインストールされている必要があります。
  • この方法では、Node.js の内部 DNS 設定には影響を与えず、各リクエストごとに異なる DNS サーバーを指定できます。
  • +short オプションは、結果を IP アドレスのみに簡略化するために使用しています。
  • exec 関数を使って dig コマンドを実行し、特定の DNS サーバー (@${server}) に対して指定されたホスト名 (${hostname}) の A レコード(IP アドレス)を問い合わせています。

低レベルなネットワーク API (dgram モジュールなど) を使用して DNS クエリを直接送信する (高度な方法)

Node.js の dgram モジュール(UDP/IP ソケット)などを使用して、DNS プロトコルに従って DNS クエリを自分で作成し、指定した DNS サーバーに直接送信してレスポンスを解析する方法です。これは非常に低レベルなアプローチであり、DNS プロトコルの深い理解が必要です。

const dgram = require('dgram');
const dns = require('dns'); // 定数や構造体の定義に利用

async function resolveWithRawUDP(hostname, server, port = 53) {
  return new Promise((resolve, reject) => {
    const client = dgram.createSocket('udp4');
    const query = dns.resolve4(hostname, (err) => {}); // クエリの構造を内部的に生成 (エラーは無視)
    const queryBuffer = query.questions[0].name.write('...'); // 実際のクエリ生成は複雑

    // 実際には DNS クエリのヘッダーや質問セクションを自分で構築する必要があります
    const message = Buffer.from('...', 'hex'); // これはプレースホルダー

    client.send(message, port, server, (err) => {
      if (err) {
        client.close();
        reject(err);
        return;
      }
      client.on('message', (msg) => {
        client.close();
        // 受信した DNS レスポンスを解析する処理 (複雑)
        console.log(`${hostname} の応答 (${server}):`, msg);
        resolve(msg); // 実際には解析後の IP アドレスを resolve する
      });
    });

    setTimeout(() => {
      client.close();
      reject(new Error('DNS クエリタイムアウト'));
    }, 1000); // タイムアウト設定
  });
}

// 注意: これは非常に基本的な概念を示すためのもので、実際の DNS クエリとレスポンスの処理はもっと複雑です。
// 実用的な実装には、DNS プロトコルの詳細な理解が必要です。
async function resolveWithRaw() {
  await resolveWithRawUDP('example.com', '8.8.8.8');
}

// resolveWithRaw(); // コメントアウト (実装が複雑なため)

解説

  • この方法は、DNS プロトコルの深い理解と実装の手間が必要ですが、最も柔軟性が高いと言えます。
  • サーバーからの応答を message イベントで受信し、DNS レスポンスの構造を解析して IP アドレスなどの情報を抽出します。
  • DNS クエリのメッセージを手動で作成し、指定された DNS サーバーとポートに送信します。
  • dgram.createSocket('udp4') で UDP ソケットを作成します。DNS は通常 UDP を使用します。

OS レベルの DNS 設定を利用する (間接的な方法)

Node.js アプリケーション自体では DNS サーバーを明示的に設定せず、実行環境のオペレーティングシステムやネットワーク設定で指定された DNS サーバーを使用する方法です。これは dns.setServers() を全く使用しない、最も一般的なデフォルトの動作です。

  • デフォルトの動作
    OS の DNS 設定に依存する。
  • 低レベルなネットワーク処理
    dgram モジュールなどを利用して DNS プロトコルを直接扱う(高度な知識が必要)。
  • システムに依存した DNS 解決
    child_process で外部の DNS ツールを実行する。
  • より柔軟な制御や DoH の利用
    サードパーティの DNS ライブラリを検討する。