【Node.js】dns.reverse()の解説:IPアドレスからホスト名を検索

2025-05-27

基本的な使い方

dns.reverse(ip, callback)

  • callback: 非同期処理の結果を受け取るコールバック関数です。このコールバック関数は、以下の2つの引数を受け取ります。
    • err: エラーオブジェクト。エラーが発生しなかった場合は null になります。
    • hostnames: IPアドレスに対応するホスト名の配列。逆引きに成功した場合、一つ以上のホスト名が配列で返されます。もし逆引きに失敗した場合や、対応するホスト名が見つからない場合は空の配列になることがあります。
  • ip: 逆引きしたいIPアドレスの文字列です(例: '8.8.8.8', '2001:4860:4860::8888')。

挙動と重要な点

  • 複数のホスト名
    1つのIPアドレスに対して複数のPTRレコードが設定されている場合、返される配列には複数のホスト名が含まれることがあります。
  • 逆引きDNSレコード (PTRレコード)
    逆引きDNSルックアップは、DNSサーバーに設定されたPTR (Pointer) レコードを参照します。IPアドレスに対応するPTRレコードが存在しない場合、hostnames は空の配列となる可能性があります。
  • 返り値
    dns.reverse() 自体は直接的な値を返しません。結果はコールバック関数の引数として渡されます。
  • コールバック関数
    結果はコールバック関数を通じて返されます。エラーハンドリングと成功時の処理は、このコールバック関数内で記述する必要があります。
  • 非同期処理
    dns.reverse() は非同期的に動作します。つまり、この関数が呼び出されると、Node.jsのイベントループは他の処理を実行し続け、逆引きDNSルックアップが完了した時点でコールバック関数が実行されます。
const dns = require('node:dns');

const ipAddress = '8.8.8.8';

dns.reverse(ipAddress, (err, hostnames) => {
  if (err) {
    console.error('逆引きDNSルックアップエラー:', err);
    return;
  }
  if (hostnames.length > 0) {
    console.log(`${ipAddress} のホスト名:`, hostnames);
  } else {
    console.log(`${ipAddress} に対応するホスト名は見つかりませんでした。`);
  }
});

const ipv6Address = '2001:4860:4860::8888';

dns.reverse(ipv6Address, (err, hostnames) => {
  if (err) {
    console.error('IPv6の逆引きDNSルックアップエラー:', err);
    return;
  }
  if (hostnames.length > 0) {
    console.log(`${ipv6Address} のホスト名:`, hostnames);
  } else {
    console.log(`${ipv6Address} に対応するホスト名は見つかりませんでした。`);
  }
});

console.log('逆引きDNSルックアップのリクエストを送信しました...');


一般的なエラー

  1. Error: getaddrinfo ENOTFOUND:

    • 原因
      指定されたIPアドレスに対応するPTRレコードがDNSサーバーに見つからない場合に発生します。これは、IPアドレスに逆引き情報が設定されていないことが原因です。
    • トラブルシューティング
      • 指定したIPアドレスが正しいか確認してください。タイプミスなどがないか注意が必要です。
      • そのIPアドレスの逆引きDNSレコードが存在するかどうかを確認する必要があります。dig -x <IPアドレス> (Linux/macOS) や nslookup -type=ptr <IPアドレス> (Windows) などのコマンドラインツールを使用して、PTRレコードの存在を確認できます。
      • もし自社ネットワーク内のIPアドレスであれば、ネットワーク管理者に対応するPTRレコードの設定を依頼する必要があります。
  2. Error: getaddrinfo EAI_NODATA:

    • 原因
      DNSサーバーに問い合わせた結果、データ(この場合はPTRレコード)が存在しないことが返された場合に発生します。ENOTFOUND と似ていますが、こちらはDNSサーバーが「そのIPアドレスについて何も情報がない」と応答した場合に起こりやすいです。
    • トラブルシューティング
      • 上記 ENOTFOUND のトラブルシューティングと同様に、IPアドレスの確認とPTRレコードの存在確認を行ってください。
      • 使用しているDNSサーバーに問題がないか確認することも有効です。別のDNSサーバーで試してみる(例えば、Google Public DNSの 8.8.8.88.8.4.4 など)ことで、問題が特定のDNSサーバーにあるかどうかを切り分けられます。
  3. Error: getaddrinfo ESERVFAIL:

    • 原因
      DNSサーバーがリクエストに応答できなかった場合に発生します。これは、DNSサーバーのダウン、過負荷、設定ミスなどが原因として考えられます。
    • トラブルシューティング
      • 一時的なネットワークの問題である可能性があるので、少し時間を置いて再度試してみてください。
      • 他のネットワークリソースへのアクセスは正常か確認し、ローカルネットワークに問題がないか確認します。
      • 使用しているDNSサーバーの状態を確認してください。もし自社で管理しているDNSサーバーであれば、サーバーのログなどを確認する必要があります。
      • 別のDNSサーバーを使用してみることも有効な切り分け方法です。
  4. Error: getaddrinfo ETIMEOUT:

    • 原因
      DNSサーバーへの接続がタイムアウトした場合に発生します。ネットワークの遅延、ファイアウォールの設定、DNSサーバーの応答遅延などが考えられます。
    • トラブルシューティング
      • ネットワーク接続が安定しているか確認してください。
      • ファイアウォールがDNS (通常はUDP/TCPポート53番) の通信をブロックしていないか確認します。
      • DNSサーバーまでのネットワーク経路に問題がないか (ping などで確認) 調べます。
      • 応答の速い別のDNSサーバーを試してみることも有効です。
  5. コールバックが呼ばれない

    • 原因
      何らかの理由で dns.reverse() の処理が完了せず、コールバック関数が実行されないことがあります。ネットワークの不安定さや、Node.jsのイベントループがブロックされているなどが考えられます。
    • トラブルシューティング
      • ネットワーク接続が安定しているか確認してください。
      • Node.jsのイベントループを長時間ブロックするような処理が他に実行されていないか確認します。もしそのような処理がある場合は、非同期処理に置き換えるなどの改善が必要です。
      • より詳細なエラー情報を得るために、try...catch ブロックで dns.reverse() の呼び出しを囲むことはできません(非同期処理のため)。エラーは必ずコールバック関数の err 引数で処理する必要があります。

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

  • タイムアウト設定を検討する (Node.jsの標準機能ではありませんが)
    Node.js自体には dns.reverse() のタイムアウトを設定する機能はありませんが、必要であればPromiseベースのラッパーを作成し、setTimeout などと組み合わせてタイムアウト処理を実装することを検討できます。
  • エラーハンドリングを適切に行う
    dns.reverse() のコールバック関数内で、必ず err オブジェクトをチェックし、エラーが発生した場合の処理を実装してください。
  • 他のDNSツールを試す
    dignslookup などのコマンドラインツールを使って、Node.jsの外部からDNSルックアップを試してみることで、問題がNode.jsのコードにあるのか、ネットワークやDNSサーバーにあるのかを切り分けられます。
  • DNSサーバーを確認する
    使用しているDNSサーバーが正常に動作しているか、応答が遅くないかなどを確認します。
  • ネットワーク環境を確認する
    安定したネットワーク接続はDNSルックアップの基本です。
  • エラーメッセージをよく読む
    エラーメッセージには、問題の原因の手がかりとなる情報が含まれていることが多いです。


例1: 単一のIPアドレスの逆引きと結果表示

これは、最も基本的な dns.reverse() の使い方を示す例です。指定したIPアドレスに対応するホスト名を取得し、成功または失敗に応じてメッセージを表示します。

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

const ipAddress = '8.8.8.8'; // Google Public DNS

dns.reverse(ipAddress, (err, hostnames) => {
  if (err) {
    console.error(`IPアドレス ${ipAddress} の逆引きに失敗しました:`, err.message);
    return;
  }
  if (hostnames.length > 0) {
    console.log(`IPアドレス ${ipAddress} のホスト名:`, hostnames.join(', '));
  } else {
    console.log(`IPアドレス ${ipAddress} に対応するホスト名は見つかりませんでした。`);
  }
});

console.log('逆引きDNSルックアップのリクエストを送信しました...');

このコードでは、'8.8.8.8' というIPアドレスに対して dns.reverse() を呼び出し、その結果をコールバック関数で処理しています。エラーが発生した場合はエラーメッセージを、成功した場合はホスト名の配列をコンソールに出力します。複数のホスト名が返ってきた場合は、, で区切って表示しています。

例2: エラーハンドリングの強化

前の例に加えて、より具体的なエラーコードに基づいて処理を分岐させる方法を示します。

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

const ipAddress = '192.0.2.1'; // ドキュメント用の予約済みIPアドレス (通常は逆引きできない)

dns.reverse(ipAddress, (err, hostnames) => {
  if (err) {
    console.error(`IPアドレス ${ipAddress} の逆引きエラー:`, err);
    if (err.code === 'ENOTFOUND') {
      console.log(`ホスト名が見つかりませんでした。`);
    } else if (err.code === 'ESERVFAIL') {
      console.log(`DNSサーバーが応答しませんでした。`);
    } else {
      console.log(`その他のエラーが発生しました: ${err.message}`);
    }
    return;
  }
  if (hostnames && hostnames.length > 0) {
    console.log(`IPアドレス ${ipAddress} のホスト名:`, hostnames.join(', '));
  } else {
    console.log(`IPアドレス ${ipAddress} に対応するホスト名は見つかりませんでした。`);
  }
});

console.log('逆引きDNSルックアップのリクエストを送信しました...');

ここでは、エラーオブジェクト errcode プロパティをチェックすることで、ENOTFOUNDESERVFAIL といった特定のエラーに対応したメッセージを表示しています。

例3: 複数のIPアドレスを処理する

複数のIPアドレスに対して逆引きルックアップを実行し、それぞれの結果を処理する例です。

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

const ipAddresses = ['8.8.8.8', '1.1.1.1', '203.0.113.45']; // Cloudflare, ドキュメント用

ipAddresses.forEach(ip => {
  dns.reverse(ip, (err, hostnames) => {
    if (err) {
      console.error(`IPアドレス ${ip} の逆引きに失敗しました:`, err.message);
      return;
    }
    if (hostnames.length > 0) {
      console.log(`IPアドレス ${ip} のホスト名:`, hostnames.join(', '));
    } else {
      console.log(`IPアドレス ${ip} に対応するホスト名は見つかりませんでした。`);
    }
  });
  console.log(`IPアドレス ${ip} の逆引きルックアップをリクエストしました...`);
});

forEach ループを使って配列内の各IPアドレスに対して dns.reverse() を呼び出し、それぞれの結果を個別のコールバック関数で処理しています。

例4: Promiseを使った非同期処理 (async/await)

dns.promises.reverse() を使用すると、Promiseベースの非同期処理が可能になり、async/await 構文を使ってより簡潔にコードを記述できます。

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

async function reverseLookup(ip) {
  try {
    const hostnames = await dns.reverse(ip);
    if (hostnames.length > 0) {
      console.log(`IPアドレス ${ip} のホスト名 (Promise):`, hostnames.join(', '));
    } else {
      console.log(`IPアドレス ${ip} に対応するホスト名 (Promise): 見つかりませんでした。`);
    }
  } catch (err) {
    console.error(`IPアドレス ${ip} の逆引きに失敗しました (Promise):`, err.message);
  }
}

const ipAddresses = ['8.8.8.8', '1.1.1.1', '203.0.113.45'];

ipAddresses.forEach(ip => {
  reverseLookup(ip);
  console.log(`IPアドレス ${ip} の逆引きルックアップ (Promise) を開始しました...`);
});

この例では、dns.promises.reverse() が返すPromiseを await で待ち、成功した場合はホスト名を表示し、エラーが発生した場合は catch ブロックで処理しています。これにより、非同期処理を同期的なコードのように記述できます。



Promise API (dns.promises.reverse)

既に前の例でも触れましたが、Node.jsの dns モジュール自体がPromiseベースのAPIを提供しています。コールバックベースの dns.reverse() の代わりに dns.promises.reverse() を使用することで、async/await 構文を利用したよりモダンで読みやすい非同期処理を記述できます。

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

async function getHostnames(ip) {
  try {
    const hostnames = await dns.reverse(ip);
    console.log(`IPアドレス ${ip} のホスト名 (Promise):`, hostnames.join(', '));
  } catch (error) {
    console.error(`IPアドレス ${ip} の逆引きに失敗しました (Promise):`, error.message);
  }
}

getHostnames('8.8.8.8');
getHostnames('203.0.113.45');

メリット

  • エラーハンドリングに try...catch 構文を使用できる。
  • Promiseチェーンによる複雑な非同期処理の制御が容易。
  • async/await による直感的で理解しやすい非同期処理の記述。

第三者製DNSライブラリの利用

Node.jsのエコシステムには、標準の dns モジュールよりも高度な機能や柔軟性を提供するサードパーティ製のDNSライブラリがいくつか存在します。

  • node-resolve: 名前解決に関連する様々な機能を提供しており、DNSルックアップもその一つです。より高度な設定やカスタマイズが必要な場合に検討できます。

これらのライブラリは、標準の dns モジュールでは提供されない機能(例えば、タイムアウトの詳細な設定、リトライ処理、特定のDNSサーバーの指定など)を提供することがあります。

メリット (サードパーティ製ライブラリ)

  • パフォーマンスの最適化がされている場合がある。
  • DNSクエリとレスポンスのより詳細な制御と解析。
  • タイムアウト、リトライなどの詳細な設定が可能。
  • より高度なDNSプロトコルや機能のサポート。

デメリット (サードパーティ製ライブラリ)

  • 標準モジュールと比較して、コミュニティのサポートや情報が少ない可能性がある。
  • APIの学習コストがかかる場合がある。
  • 追加の依存関係が発生する。

外部コマンドの実行 (child_process)

Node.jsの child_process モジュールを使用すると、dignslookup といった外部のDNS関連コマンドを実行し、その出力を解析することで逆引きルックアップの結果を得ることも可能です。

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

function reverseLookupExec(ip) {
  exec(`dig -x ${ip} +short`, (error, stdout, stderr) => {
    if (error) {
      console.error(`IPアドレス ${ip} の逆引きに失敗しました (exec):`, error);
      return;
    }
    if (stderr) {
      console.error(`IPアドレス ${ip} の逆引きエラー (stderr):`, stderr);
      return;
    }
    const hostnames = stdout.trim().split('\n').filter(Boolean);
    if (hostnames.length > 0) {
      console.log(`IPアドレス ${ip} のホスト名 (exec):`, hostnames.join(', '));
    } else {
      console.log(`IPアドレス ${ip} に対応するホスト名 (exec): 見つかりませんでした。`);
    }
  });
}

reverseLookupExec('8.8.8.8');

メリット (外部コマンド実行)

  • 場合によっては、より詳細なDNS情報を取得できる。
  • OSに標準搭載されているDNSツールの機能を利用できる。

デメリット (外部コマンド実行)

  • セキュリティ上のリスク (特にユーザーからの入力をそのままコマンドに含める場合)。
  • 出力の解析が必要になるため、実装が複雑になる可能性がある。
  • コマンド実行のオーバーヘッドが発生する。
  • プラットフォーム依存性がある (コマンドの可用性や構文が異なる場合がある)。

どの方法を選ぶべきか?

  • OSのDNSツール固有の機能を利用したい場合や、どうしても外部コマンドを使いたい場合
    child_process を利用することも可能ですが、プラットフォーム依存性やセキュリティに注意が必要です。
  • より高度なDNS機能やプロトコルを利用したい場合
    dns2 などのサードパーティ製ライブラリを検討すると良いでしょう。
  • 基本的な逆引き処理で、PromiseベースのAPIを利用したい場合
    dns.promises.reverse() がシンプルで推奨されます。