パフォーマンス改善に!Node.js非同期dns.resolve6()の実践ガイド

2025-05-16

基本的な動作

  1. dns.resolve6() 関数に、解決したいホスト名(例えば、www.example.com)を文字列として渡します。
  2. 内部的に、Node.js はシステムの DNS サーバーに対して、そのホスト名に対応する IPv6 レコード(AAAAレコード)の問い合わせを行います。
  3. DNS サーバーからの応答として、そのホスト名に関連付けられた一つまたは複数の IPv6 アドレスが返されます。
  4. dns.resolve6() に渡されたコールバック関数が、以下の2つの引数とともに呼び出されます。
    • err: エラーオブジェクト。DNS の解決に失敗した場合(例えば、ホスト名が見つからない場合など)にエラー情報が格納されます。成功した場合は null になります。
    • addresses: IPv6 アドレスの配列。解決に成功した場合、ホスト名に対応する IPv6 アドレスが文字列の配列として格納されます。

コード例

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

async function resolveIPv6(hostname) {
  try {
    const addresses = await dns.resolve6(hostname);
    console.log(`${hostname} の IPv6 アドレス:`, addresses);
  } catch (err) {
    console.error(`${hostname} の IPv6 アドレス解決に失敗しました:`, err);
  }
}

resolveIPv6('www.google.com');
resolveIPv6('invalid-hostname-example.com');

このコード例の解説

  1. const dns = require('node:dns').promises; は、dns モジュールをインポートし、Promise ベースの API を利用できるようにしています。
  2. async function resolveIPv6(hostname) { ... } は、非同期処理を行う関数を定義しています。
  3. const addresses = await dns.resolve6(hostname); は、指定された hostname の IPv6 アドレスを非同期的に解決し、その結果を addresses 変数に格納します。await キーワードは、Promise が解決されるまで処理を一時停止させます。
  4. console.log(...) は、解決された IPv6 アドレスの配列をコンソールに出力します。
  5. try...catch ブロックは、DNS 解決中に発生したエラーを捕捉し、エラー情報をコンソールに出力します。

dns.resolve6() の利点

  • 非同期処理
    I/O 操作である DNS 問い合わせを非同期的に行うため、Node.js のノンブロッキング I/O モデルを活かすことができます。これにより、DNS 解決の待ち時間に他の処理を実行できます。
  • IPv6 対応
    IPv6 ネットワーク環境において、ホスト名の IPv6 アドレスを取得するために使用されます。
  • エラー処理を適切に行う必要があります。
  • DNS サーバーの設定やネットワーク環境によっては、IPv6 アドレスが解決できない場合があります。


一般的なエラー

    • 原因
      指定されたホスト名が見つからなかった場合に発生します。DNS サーバーがそのホスト名の IPv6 レコード(AAAAレコード)を持っていないか、ホスト名が正しくない可能性があります。
    • トラブルシューティング
      • ホスト名が正しく入力されているか再度確認してください(スペルミスなど)。
      • そのホスト名が実際に IPv6 アドレスを持っているか、他のツール(例: ping6 <hostname>、オンラインの DNS ルックアップツール)で確認してみてください。
      • ネットワーク接続に問題がないか確認してください。DNS サーバーにアクセスできる状態である必要があります。
      • 使用している DNS サーバーが IPv6 レコードの解決に対応しているか確認してください。
  1. Error: getaddrinfo ENODATA <hostname> (または同様のエラー)

    • 原因
      指定されたホスト名は存在するものの、IPv6 アドレス(AAAAレコード)が存在しない場合に発生します。
    • トラブルシューティング
      • ホスト名が正しいことを確認してください。
      • そのホスト名が IPv4 アドレス(Aレコード)のみを持っている可能性があります。IPv6 アドレスが必要な場合は、ホストの管理者に IPv6 レコードの登録を依頼する必要があるかもしれません。
      • 他のツールで DNS 情報を確認し、AAAAレコードが存在しないことを確認してください。
  2. Error: getaddrinfo EAI_NODNS (または同様のエラー)

    • 原因
      DNS サーバーに到達できない、または DNS サーバーの設定に問題がある場合に発生します。
    • トラブルシューティング
      • ネットワーク接続を確認してください。インターネットに接続されているか、DNS サーバーへの経路が確立されているかを確認します。
      • システムの DNS 設定を確認してください。/etc/resolv.conf (Linux/macOS) やネットワーク設定 (Windows) を確認し、正しい DNS サーバーが設定されているか確認します。
      • ファイアウォールが DNS (通常 UDP/TCP ポート 53) の通信をブロックしていないか確認してください。
      • 一時的なネットワークの問題である可能性もあるため、しばらく待ってから再度試してみてください。
  3. タイムアウトエラー

    • 原因
      DNS サーバーからの応答が指定された時間内に返ってこない場合に発生します。ネットワークの遅延や DNS サーバーの負荷が高いなどが考えられます。
    • トラブルシューティング
      • ネットワーク接続の状態を確認してください。
      • 別の DNS サーバーを試してみることを検討してください(例: Google Public DNS (2001:4860:4860::8888, 2001:4860:4860::8844) など)。
      • dns.setServers() を使用して、使用する DNS サーバーを指定できます。
      • アプリケーションのタイムアウト設定を見直してみてください。ただし、DNS のタイムアウト自体は Node.js の内部設定に依存する部分が大きいです。

トラブルシューティングの一般的な手順

  1. エラーメッセージを正確に理解する
    エラーメッセージには、問題の原因の手がかりが含まれています。
  2. ネットワーク接続を確認する
    インターネットに接続されているか、DNS サーバーにアクセスできるかを確認します。
  3. DNS 設定を確認する
    システムの DNS サーバー設定が正しいか確認します。
  4. 他のツールで DNS 解決を試す
    ping6 コマンドやオンラインの DNS ルックアップツールを使用して、同じホスト名の IPv6 アドレスが解決できるか確認します。これにより、Node.js の問題なのか、ネットワークや DNS サーバーの問題なのかを切り分けることができます。
  5. 異なるホスト名で試す
    特定のホスト名でのみエラーが発生するのか、他のホスト名でも同様のエラーが発生するのかを確認します。
  6. Node.js のバージョンを確認する
    古い Node.js のバージョンを使用している場合、既知のバグが存在する可能性があります。最新の安定版にアップデートすることを検討してください。
  7. エラーハンドリングを実装する
    try...catch ブロックやコールバック関数のエラー引数 (err) を適切に処理し、エラー発生時の挙動を制御できるようにします。


Promise を使用した基本的な IPv6 アドレス解決

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

async function resolveIPv6Address(hostname) {
  try {
    const addresses = await dns.resolve6(hostname);
    console.log(`${hostname} の IPv6 アドレス:`, addresses);
    return addresses;
  } catch (err) {
    console.error(`${hostname} の IPv6 アドレス解決に失敗しました:`, err);
    return null;
  }
}

// 例: www.google.com の IPv6 アドレスを解決
resolveIPv6Address('www.google.com')
  .then(ipv6Addresses => {
    if (ipv6Addresses) {
      console.log('解決成功:', ipv6Addresses);
    } else {
      console.log('解決失敗');
    }
  });

// 例: 存在しないホスト名の解決を試みる
resolveIPv6Address('invalid-hostname-example.com');

このコードの解説

  • .then() を使用して、Promise が成功した場合の処理を行っています。
  • 解決された IPv6 アドレスの配列をコンソールに出力し、成功または失敗を示すメッセージを表示しています。
  • try...catch ブロックで、DNS 解決中に発生する可能性のあるエラーを捕捉しています。
  • await dns.resolve6(hostname) は、指定された hostname の IPv6 アドレスを非同期的に解決し、その結果を addresses に格納します。
  • async function resolveIPv6Address(hostname) は、非同期処理を行う関数です。
  • require('node:dns').promises; で、Promise ベースの dns API をインポートしています。

コールバック関数を使用した IPv6 アドレス解決

const dns = require('node:dns');

function resolveIPv6AddressCallback(hostname) {
  dns.resolve6(hostname, (err, addresses) => {
    if (err) {
      console.error(`${hostname} の IPv6 アドレス解決に失敗しました:`, err);
      return;
    }
    console.log(`${hostname} の IPv6 アドレス (コールバック):`, addresses);
  });
}

// 例: ipv6.google.com の IPv6 アドレスを解決
resolveIPv6AddressCallback('ipv6.google.com');

// 例: 無効なホスト名の解決を試みる
resolveIPv6AddressCallback('another-invalid-hostname.xyz');

このコードの解説

  • エラーが発生した場合はエラーメッセージをコンソールに出力し、成功した場合は IPv6 アドレスの配列をコンソールに出力します。
  • コールバック関数の二番目の引数 addresses は、解決された IPv6 アドレスの配列です。
  • コールバック関数の最初の引数 err は、エラーが発生した場合にエラーオブジェクトを持ちます。成功した場合は null になります。
  • dns.resolve6(hostname, (err, addresses) => { ... }); は、指定された hostname の IPv6 アドレスを非同期的に解決し、結果をコールバック関数に渡します。
  • require('node:dns'); で、コールバックベースの dns API をインポートしています。

複数の IPv6 アドレスが返される場合の処理

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

async function resolveMultipleIPv6(hostname) {
  try {
    const addresses = await dns.resolve6(hostname);
    if (addresses && addresses.length > 0) {
      console.log(`${hostname} の IPv6 アドレス (複数):`);
      addresses.forEach((address, index) => {
        console.log(`  ${index + 1}: ${address}`);
      });
    } else {
      console.log(`${hostname} は IPv6 アドレスを持っていません。`);
    }
    return addresses;
  } catch (err) {
    console.error(`${hostname} の IPv6 アドレス解決に失敗しました:`, err);
    return null;
  }
}

// 例: 複数の IPv6 アドレスを持つ可能性のあるホストを解決
resolveMultipleIPv6('www.cloudflare.com');

このコードの解説

  • 解決されたアドレスが存在しない場合は、その旨を通知するメッセージを表示しています。
  • 解決された IPv6 アドレスの配列が複数返ってきた場合に、それぞれの IP アドレスを順番にコンソールに出力しています。

エラー処理の例

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

async function handleResolve6Error(hostname) {
  try {
    const addresses = await dns.resolve6(hostname);
    console.log(`${hostname} の IPv6 アドレス:`, addresses);
  } catch (err) {
    if (err.code === 'ENOTFOUND') {
      console.error(`エラー: ホスト名 "${hostname}" は見つかりませんでした。`);
    } else if (err.code === 'ENODATA') {
      console.error(`エラー: ホスト名 "${hostname}" には IPv6 アドレスが登録されていません。`);
    } else {
      console.error(`不明なエラーが発生しました:`, err);
    }
  }
}

// 例: 存在しないホスト名を処理
handleResolve6Error('nonexistent-host.example');

// 例: IPv6 アドレスを持たない可能性のあるホストを処理
handleResolve6Error('example.com');
  • ENOTFOUND はホスト名が見つからない場合、ENODATA はホスト名が存在するが IPv6 アドレスがない場合に発生する一般的なエラーコードです。
  • catch ブロック内で、エラーオブジェクトの code プロパティをチェックすることで、特定のエラータイプに基づいて異なるエラーメッセージを表示しています。


dns.resolve() 関数を使用する

dns.resolve() 関数は、指定されたホスト名のすべてのタイプ(A、AAAA、MX、TXT など)の DNS レコードを解決できます。IPv6 アドレスのみを取得したい場合は、rrtype オプションに 'AAAA' を指定することで、dns.resolve6() と同様の動作をさせることが可能です。

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

async function resolveIPv6Alternative(hostname) {
  try {
    const addresses = await dns.resolve(hostname, 'AAAA');
    console.log(`${hostname} の IPv6 アドレス (dns.resolve):`, addresses);
    return addresses;
  } catch (err) {
    console.error(`${hostname} の IPv6 アドレス解決に失敗しました (dns.resolve):`, err);
    return null;
  }
}

resolveIPv6Alternative('www.google.com');
resolveIPv6Alternative('invalid-hostname-example.com');

このコードの解説

  • エラーハンドリングも dns.resolve6() と同様に行うことができます。
  • 結果として返される addresses は、dns.resolve6() と同様に IPv6 アドレスの配列となります。
  • dns.resolve(hostname, 'AAAA') のように、第二引数に 'AAAA' を指定することで、IPv6 レコードのみを要求しています。

利点

  • 単一の関数 dns.resolve() で様々なレコードタイプを扱えるため、柔軟性があります。

注意点

  • 'AAAA' を指定し忘れると、他のレコードタイプも返ってくる可能性があるため、注意が必要です。

第三者製 DNS クライアントライブラリの使用

Node.js の標準 dns モジュールは低レベルな API であり、より高度な機能やカスタマイズが必要な場合は、第三者製の DNS クライアントライブラリを検討できます。

  • dns-lookup
    より高レベルな抽象化を提供し、Promise ベースの API を持ち、タイムアウトなどのオプションも設定できます。

    const dnsLookup = require('dns-lookup');
    
    async function resolveIPv6WithLookup(hostname) {
      try {
        const result = await dnsLookup.resolve(hostname, 'AAAA');
        console.log(`${hostname} の IPv6 アドレス (dns-lookup):`, result);
        return result;
      } catch (err) {
        console.error(`${hostname} の IPv6 アドレス解決に失敗しました (dns-lookup):`, err);
        return null;
      }
    }
    
    resolveIPv6WithLookup('www.google.com');
    resolveIPv6WithLookup('invalid-hostname-example.com');
    

利点

  • DNS クエリの詳細な制御が可能な場合があります。
  • Promise ベースの API を提供しているライブラリが多く、非同期処理をより扱いやすくできます。
  • より高度な機能(タイムアウト、リトライ、カスタム DNS サーバー設定など)を提供することがあります。

注意点

  • 標準モジュールよりも学習コストが高い場合があります。
  • 外部ライブラリへの依存性が増えます。

オペレーティングシステムのコマンド実行 (child_process モジュール)

Node.js の child_process モジュールを使用して、オペレーティングシステムの DNS 解決コマンド(例: dig AAAA <hostname>nslookup -type=aaaa <hostname>ping6 -n 1 <hostname>) を実行し、その出力を解析することで IPv6 アドレスを取得することも理論的には可能です。

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

async function resolveIPv6WithCommand(hostname) {
  try {
    const { stdout, stderr } = await exec(`dig AAAA ${hostname} +short`);
    if (stderr) {
      console.error(`コマンド実行エラー: ${stderr}`);
      return null;
    }
    const addresses = stdout.trim().split('\n').filter(Boolean);
    console.log(`${hostname} の IPv6 アドレス (コマンド実行):`, addresses);
    return addresses;
  } catch (err) {
    console.error(`コマンド実行に失敗しました:`, err);
    return null;
  }
}

resolveIPv6WithCommand('www.google.com');
resolveIPv6WithCommand('invalid-hostname-example.com');

利点

  • オペレーティングシステムの DNS 解決機能を利用するため、環境によってはより正確な結果が得られる可能性があります。

注意点

  • 標準モジュールや専用ライブラリよりもパフォーマンスが劣る可能性があります。
  • セキュリティ上のリスク(ユーザー入力のサニタイズなど)に注意が必要です。
  • コマンドの出力形式を解析する必要があり、実装が複雑になる可能性があります。
  • プラットフォーム依存性が高く、異なるオペレーティングシステムでコマンドが異なる場合があります。

どの方法を選ぶべきか

  • 最後の手段または特殊な要件
    オペレーティングシステムのコマンド実行は、プラットフォーム依存性や複雑さを考慮して慎重に使用してください。
  • より高度な機能やカスタマイズ
    第三者製の DNS クライアントライブラリ (dns-lookup, node-dns など) を検討してください。
  • 基本的な IPv6 アドレス解決
    標準の dns.resolve6() または dns.resolve(hostname, 'AAAA') で十分です。Promise ベースの API が推奨されます。