Node.jsのdnsPromises.reverse()徹底解説:IPアドレスからホスト名を逆引き

2025-05-27

通常のDNSクエリがホスト名からIPアドレスを解決する(正引き)のに対し、reverse()はIPアドレスからそのIPアドレスに関連付けられているホスト名を検索します。これは「リバースDNSルックアップ」または「逆引きDNS」と呼ばれます。

主な特徴と使い方

  • 用途
    • ログ分析:Webサーバーのアクセスログなどに記録されたIPアドレスから、どのドメインがアクセスしてきたかを特定する。
    • スパム対策:メールサーバーなどが、送信元IPアドレスの逆引きを行うことで、正当なドメインからの送信かどうかを確認する。
    • ネットワーク診断:特定のIPアドレスがどのサービスやドメインに関連付けられているかを調べる。
  • 戻り値
    • 成功した場合、Promiseは解決され、そのIPアドレスに関連付けられたホスト名の配列を返します。通常は1つのホスト名が返されますが、場合によっては複数返されることもあります。
    • 逆引きに失敗した場合(該当するホスト名が見つからない、DNSサーバーエラーなど)、Promiseは拒否され、Errorオブジェクトが返されます。
  • 引数
    逆引きしたいIPv4またはIPv6アドレスを文字列として1つだけ引数に取ります。
  • Promiseベース
    dnsPromisesは、Node.jsのdnsモジュールが提供するPromiseベースのAPIの一部です。そのため、コールバック関数ではなく、Promiseを返します。これにより、async/await構文を使って、より現代的で読みやすい非同期処理を記述できます。

コード例

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

async function reverseDnsLookup(ipAddress) {
  try {
    const hostnames = await dnsPromises.reverse(ipAddress);
    console.log(`${ipAddress} のホスト名:`, hostnames);
  } catch (error) {
    console.error(`逆引きエラー (${ipAddress}):`, error.message);
  }
}

// 例: GoogleのDNSサーバーのIPアドレスを逆引き
reverseDnsLookup('8.8.8.8'); 

// 例: 存在しないIPアドレスを逆引き(エラーになる可能性が高い)
reverseDnsLookup('192.0.2.1'); // これはテスト用のIPアドレスで、通常は逆引きできません
  1. const dns = require('dns');dnsモジュールを読み込みます。
  2. const dnsPromises = dns.promises; でPromiseベースのAPIにアクセスします。
  3. async function reverseDnsLookup(ipAddress) のようにasync関数を定義し、その中でawait dnsPromises.reverse(ipAddress); を使って非同期に逆引きを実行します。
  4. try...catchブロックでエラーハンドリングを行い、逆引きが成功した場合はホスト名を表示し、失敗した場合はエラーメッセージを表示します。


一般的なエラー

dnsPromises.reverse()が返すPromiseが拒否される(rejectされる)際、Errorオブジェクトが渡されます。このErrorオブジェクトには、codeプロパティが含まれており、これがエラーの種類を示します。

  • 一般的なPromiseのエラーハンドリングの不足

    • dnsPromises.reverse()はPromiseを返すため、try...catchブロックで適切にエラーを捕捉する必要があります。これを怠ると、Promiseが拒否された際に未処理のPromise拒否エラーが発生し、アプリケーションがクラッシュする可能性があります。
    • トラブルシューティング
      • 必ずasync/awaittry...catch、または.then().catch()を使用してエラーを適切に処理します。
  • Error: EINVAL

    • 意味
      dnsPromises.reverse()に無効な引数が渡された場合に発生します。これは、IPアドレスとして認識できない文字列や、空文字列などを渡した場合に起こりえます。
    • 原因
      • reverse()メソッドの引数にIPアドレスではない文字列、undefinednull、空文字列などが渡された。
    • トラブルシューティング
      • 引数の検証
        dnsPromises.reverse()に渡す前に、引数が有効なIPv4またはIPv6アドレスの形式であることを確認します。
  • Error: queryA ETIMEOUT

    • 意味
      DNSサーバーからの応答がタイムアウトした場合に発生します。
    • 原因
      • DNSサーバーがダウンしている、または過負荷になっている。
      • ネットワーク接続が不安定。
      • ファイアウォールがDNSクエリをブロックしている。
    • トラブルシューティング
      • ネットワーク接続の確認
        インターネット接続が正常であることを確認します。
      • ファイアウォールの確認
        Node.jsプロセスからのDNSクエリがファイアウォールによってブロックされていないか確認します。
      • DNSサーバーの変更
        システムのDNSサーバー設定を見直すか、dns.setServers()を使用してNode.js内で明示的に別のDNSサーバー(例: 8.8.8.81.1.1.1)を指定してみる。
    • 意味
      指定されたIPアドレスに対応するPTRレコード(逆引き情報)が見つからない、またはDNSサーバーがその情報を解決できない場合に発生します。
    • 原因
      • そもそもそのIPアドレスに逆引きエントリが設定されていない(よくあること)。
      • DNSサーバーが一時的に応答しない、またはタイムアウトした。
      • DNSサーバーの設定が間違っている(Node.jsが使用するDNSサーバー)。
      • IPアドレスがプライベートネットワーク(例: 192.168.x.x)のもので、外部のDNSサーバーでは逆引きできない。
      • IPアドレスの形式が正しくない。
    • トラブルシューティング
      • IPアドレスの確認
        逆引きしようとしているIPアドレスが正しい形式であり、インターネット上でルーティング可能な公開IPアドレスであることを確認します。プライベートIPアドレスは通常、外部のDNSサーバーでは逆引きできません。
      • 別のIPアドレスでテスト
        確実に逆引きできると分かっているIPアドレス(例: 8.8.8.8 (Google DNS))で試して、コード自体に問題がないか確認します。
      • DNSサーバーの確認
        システムが使用しているDNSサーバーが正しく機能しているか確認します。nslookupdigコマンド(Linux/macOS)で該当IPアドレスのPTRレコードを直接問い合わせてみます。
        # 例: Google Public DNS (8.8.8.8) を逆引き
        nslookup 8.8.8.8
        # または
        dig -x 8.8.8.8
        
        ここで結果が得られない場合、そのIPアドレスには逆引き情報がないか、使用しているDNSサーバーが参照できない状態です。

<!-- end list -->

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

async function performReverseDns(ipAddress) {
  try {
    const hostnames = await dnsPromises.reverse(ipAddress);
    if (hostnames.length > 0) {
      console.log(`${ipAddress} の逆引き結果:`, hostnames);
    } else {
      console.log(`${ipAddress} の逆引き情報は見つかりませんでした。`);
    }
  } catch (error) {
    console.error(`逆引きエラー (${ipAddress}):`);
    console.error(`  コード: ${error.code}`);
    console.error(`  メッセージ: ${error.message}`);
    // エラーコードに応じてさらに詳細な処理を行う
    if (error.code === 'ENOTFOUND') {
      console.error('  ヒント: このIPアドレスに逆引きエントリがないか、DNSサーバーが解決できません。');
    } else if (error.code === 'ETIMEDOUT') {
      console.error('  ヒント: DNSサーバーからの応答がタイムアウトしました。ネットワークまたはDNSサーバーを確認してください。');
    } else if (error.code === 'EINVAL') {
        console.error('  ヒント: 無効なIPアドレス形式が入力されました。');
    }
  }
}

// 正常に逆引きできる例 (Google Public DNS)
performReverseDns('8.8.8.8');

// 逆引きできない可能性のある例 (存在しないIPアドレス、または逆引きエントリがないIPアドレス)
performReverseDns('192.0.2.1'); 

// 無効なIPアドレスの例
performReverseDns('invalid-ip-address');

// プライベートIPアドレスの例(通常、外部DNSでは逆引きできない)
performReverseDns('192.168.1.1'); 
  • Node.jsのバージョン
    使用しているNode.jsのバージョンによっては、古いバージョンにバグが存在する可能性もあります。最新の安定版にアップデートしてみることも検討してください。
  • エラーメッセージのログ
    error.codeだけでなく、error.messageもログに出力することで、より詳細な情報が得られる場合があります。
  • 同時実行数の制限 (スレッドプール)
    Node.jsのDNS操作は、内部的にlibuvのスレッドプールを使用します。デフォルトのスレッドプールサイズは小さいため、非常に多くのDNSクエリを同時に実行しようとすると、他のI/O操作がブロックされ、パフォーマンスの問題やタイムアウトが発生する可能性があります。
    • 大量の逆引きが必要な場合は、キューイングやレートリミットを導入するか、UV_THREADPOOL_SIZE環境変数を増やしてスレッドプールサイズを調整することを検討してください(ただし、これは慎重に行うべきです)。
  • キャッシュ
    DNSの解決結果は、OSやDNSサーバーによってキャッシュされることがあります。問題が一時的なものであれば、キャッシュがクリアされるのを待つか、OSのDNSキャッシュをフラッシュすることで解決する場合があります。
  • dns.setServers()の利用
    特定のDNSサーバー(例: 8.8.8.8, 1.1.1.1)を使いたい場合は、dns.setServers()で明示的に指定できます。これにより、システム設定とは独立したDNS解決が可能になります。
    const dns = require('dns');
    const dnsPromises = dns.promises;
    
    // Google Public DNS を使用するように設定
    dns.setServers(['8.8.8.8', '8.8.4.4']);
    
    async function testReverse() {
      try {
        const hostnames = await dnsPromises.reverse('8.8.8.8');
        console.log('設定後の逆引き:', hostnames);
      } catch (error) {
        console.error('設定後のエラー:', error);
      }
    }
    
    testReverse();
    
    注意点: setServers()はグローバルな設定であり、一度設定すると以降のすべてのdnsモジュールのクエリに影響します。
  • システムDNS設定の確認
    Node.jsのdnsモジュールは、デフォルトでOSのDNS設定を使用します。システムレベルでDNS設定に問題がないか確認してください。


基本的な逆引き処理

最も基本的な使い方です。特定のIPアドレスを逆引きし、ホスト名を取得します。

const dns = require('dns');
const dnsPromises = dns.promises; // PromiseベースのAPIを取得

async function lookupAndReverse(ipAddress) {
  console.log(`--- IPアドレス: ${ipAddress} の逆引き ---`);
  try {
    const hostnames = await dnsPromises.reverse(ipAddress);
    
    if (hostnames.length > 0) {
      console.log(`  見つかったホスト名:`);
      hostnames.forEach(hostname => console.log(`    - ${hostname}`));
    } else {
      console.log(`  このIPアドレスに関連付けられたホスト名はありませんでした。`);
    }
  } catch (error) {
    console.error(`  逆引きエラー (${ipAddress}):`);
    console.error(`    コード: ${error.code}`);
    console.error(`    メッセージ: ${error.message}`);
    if (error.code === 'ENOTFOUND') {
      console.error(`    ヒント: PTRレコードが見つからないか、DNSサーバーが解決できません。`);
    } else if (error.code === 'ETIMEDOUT') {
      console.error(`    ヒント: DNSサーバーからの応答がタイムアウトしました。`);
    }
  }
  console.log('\n'); // 見やすくするために改行
}

// 例1: 正常に逆引きできるIPアドレス (Google Public DNS)
lookupAndReverse('8.8.8.8');

// 例2: 逆引きできない可能性のあるIPアドレス (RFC5737でドキュメント化用のIPアドレス)
lookupAndReverse('192.0.2.1'); 

// 例3: 無効な形式の入力
lookupAndReverse('invalid-ip');

// 例4: 複数のホスト名が登録されている可能性があるIPアドレス (状況による)
// この例は環境や設定によって結果が変わります。
// 適当なWebサイトのIPアドレスなどを調べて試してみてください。
// 例えば '203.0.113.1' (RFC5737) は通常逆引きできませんが、
// 実際にウェブサイトが稼働しているIPアドレスを調べて試すと良いでしょう。
// 例: example.com のIPアドレスを取得して逆引き
/*
(async () => {
  try {
    const addresses = await dnsPromises.resolve4('example.com');
    if (addresses.length > 0) {
      console.log(`example.com のIPアドレス: ${addresses[0]}`);
      await lookupAndReverse(addresses[0]);
    }
  } catch (err) {
    console.error(`example.com のIP解決エラー: ${err.message}`);
  }
})();
*/

解説

  • エラーの種類に応じて、error.codeを使って異なるメッセージを表示しています。
  • async/awaitを使って非同期処理を直線的に記述し、try...catchでエラーハンドリングをしっかり行います。
  • require('dns')dnsモジュールをインポートし、dns.promisesでPromiseベースのAPIにアクセスします。

複数のIPアドレスを並行して逆引きする

複数のIPアドレスに対して同時に逆引きを実行したい場合に便利です。Promise.allSettledを使うことで、全ての結果(成功・失敗問わず)を待つことができます。

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

async function bulkReverseLookup(ipAddresses) {
  console.log(`--- 複数IPアドレスの並行逆引き ---`);
  const lookupPromises = ipAddresses.map(async (ipAddress) => {
    try {
      const hostnames = await dnsPromises.reverse(ipAddress);
      return { ip: ipAddress, status: 'fulfilled', value: hostnames };
    } catch (error) {
      return { ip: ipAddress, status: 'rejected', reason: error.message, code: error.code };
    }
  });

  const results = await Promise.allSettled(lookupPromises);

  results.forEach(result => {
    if (result.status === 'fulfilled') {
      console.log(`  ${result.value.ip}: ホスト名 -> ${result.value.value.join(', ') || '(なし)'}`);
    } else {
      console.error(`  ${result.value.ip}: エラー -> ${result.value.reason} (コード: ${result.value.code})`);
    }
  });
  console.log('\n');
}

const targetIPs = [
  '8.8.8.8',          // Google Public DNS
  '1.1.1.1',          // Cloudflare DNS
  '192.0.2.1',        // ドキュメント用IPアドレス(通常逆引き不可)
  '203.0.113.5',      // ドキュメント用IPアドレス(通常逆引き不可)
  'invalid-ip',       // 無効な形式
  '2001:4860:4860::8888', // Google Public DNS (IPv6)
];

bulkReverseLookup(targetIPs);

解説

  • 結果のstatusプロパティがfulfilledなら成功、rejectedなら失敗です。
  • Promise.allSettled()は、渡されたすべてのPromiseが解決または拒否されるまで待ち、それぞれの結果をオブジェクトの配列として返します。これにより、個々の逆引きが失敗しても全体の処理は停止しません。
  • ipAddresses.mapを使って、各IPアドレスに対してdnsPromises.reverse()を実行するPromiseの配列を作成します。

OSのデフォルトDNSサーバーではなく、明示的に特定のDNSサーバー(例: Google Public DNS)を使って逆引きを行いたい場合の例です。

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

async function reverseWithSpecificDnsServer(ipAddress, dnsServers) {
  console.log(`--- IPアドレス: ${ipAddress} をDNSサーバー [${dnsServers.join(', ')}] で逆引き ---`);
  
  // 元のDNSサーバー設定を保存
  const originalServers = dns.getServers();
  
  try {
    // 特定のDNSサーバーを設定
    dns.setServers(dnsServers);
    console.log(`  DNSサーバーを ${dns.getServers().join(', ')} に設定しました。`);

    const hostnames = await dnsPromises.reverse(ipAddress);

    if (hostnames.length > 0) {
      console.log(`  見つかったホスト名: ${hostnames.join(', ')}`);
    } else {
      console.log(`  ホスト名は見つかりませんでした。`);
    }
  } catch (error) {
    console.error(`  逆引きエラー (${ipAddress}):`);
    console.error(`    コード: ${error.code}`);
    console.error(`    メッセージ: ${error.message}`);
  } finally {
    // 処理後、元のDNSサーバー設定に戻す(重要)
    dns.setServers(originalServers);
    console.log(`  DNSサーバーを元の ${dns.getServers().join(', ')} に戻しました。\n`);
  }
}

// Google Public DNS を使用して逆引き
reverseWithSpecificDnsServer('8.8.8.8', ['8.8.8.8', '8.8.4.4']);

// Cloudflare DNS を使用して逆引き
reverseWithSpecificDnsServer('1.1.1.1', ['1.1.1.1', '1.0.0.1']);

// プライベートIPアドレスをカスタムDNSで試す(設定によっては逆引きできる可能性あり)
// 例えば、内部DNSサーバーが設定されている企業ネットワーク内など
// reverseWithSpecificDnsServer('192.168.1.100', ['192.168.1.1']); 
  • dns.getServers() で現在のDNSサーバー設定を取得できます。
  • 非常に重要
    setServers() はグローバルな設定を変更するため、処理が終わったら必ず元のDNSサーバー設定 (originalServers) に戻すように finally ブロックで処理しています。これを怠ると、アプリケーションの他の部分でのDNSクエリに予期せぬ影響を与える可能性があります。
  • dns.setServers() を使用して、Node.jsがDNSクエリに使用するDNSサーバーを指定します。


dns.resolvePtr() (Promiseベースの同等機能)

dnsPromises.reverse()は、内部的にはdns.resolvePtr()を使用しています。dns.resolvePtr()は、DNSプロトコルを使用してPTRレコード(ポインタレコード)を直接解決するためのメソッドです。PTRレコードは、IPアドレスからホスト名を逆引きするために使われるレコードタイプです。

dnsPromises.reverse()dns.resolvePtr()のPromiseベースのラッパーであり、より高レベルで使いやすい抽象化を提供します。しかし、もし何らかの理由でより低レベルの制御が必要な場合は、dns.resolvePtr()を直接使用することも可能です。

違い

  • dnsPromises.resolvePtr(name): 引数には、逆引き形式に変換されたドメイン名(例: 8.8.8.8.in-addr.arpa)を渡す必要があります。これはユーザーが手動で構築する必要があります。
  • dnsPromises.reverse(ipAddress): 引数にIPアドレスを直接渡します。Node.jsがIPアドレスを逆引き形式(例: 8.8.8.8 -> 8.8.8.8.in-addr.arpa)に変換してクエリを実行します。

コード例

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

async function reverseUsingResolvePtr(ipAddress) {
  console.log(`--- resolvePtr を使用した逆引き (IP: ${ipAddress}) ---`);
  
  // IPアドレスを逆引き形式のドメイン名に変換
  const parts = ipAddress.split('.');
  if (parts.length !== 4) {
    console.error('  IPv4アドレスのみサポートしています。');
    return;
  }
  const reverseDomain = parts.reverse().join('.') + '.in-addr.arpa';

  try {
    const hostnames = await dnsPromises.resolvePtr(reverseDomain);
    if (hostnames.length > 0) {
      console.log(`  見つかったホスト名: ${hostnames.join(', ')}`);
    } else {
      console.log(`  ホスト名は見つかりませんでした。`);
    }
  } catch (error) {
    console.error(`  エラー (${ipAddress}):`);
    console.error(`    コード: ${error.code}`);
    console.error(`    メッセージ: ${error.message}`);
  }
  console.log('\n');
}

reverseUsingResolvePtr('8.8.8.8');
reverseUsingResolvePtr('192.0.2.1');

考察
通常はdnsPromises.reverse()を使用すべきです。dnsPromises.resolvePtr()は、特定のDNSレコードタイプ(PTR)に焦点を当てた低レベルの操作であり、IPアドレスの逆引き形式への変換を自分で管理する必要があります。

dns.lookupService() (ローカルシステムによるサービス名の解決)

dns.lookupService(address, port, callback)は、IPアドレスとポート番号からホスト名とサービス名(例: http, ssh)を解決します。これはDNSクエリとは異なり、OSのgetnameinfo実装を使用します。つまり、インターネット上のDNSサーバーに問い合わせるのではなく、ローカルシステムの設定(/etc/hostsなど)やOSのDNSキャッシュに依存する可能性があります。

用途

  • ローカルネットワーク内の名前解決。
  • サーバーログに記録されたIPアドレスとポートから、どのサービスに接続されたかを判別する。

コード例

const dns = require('dns');

function lookupServiceExample(address, port) {
  console.log(`--- lookupService を使用した解決 (IP: ${address}, Port: ${port}) ---`);
  dns.lookupService(address, port, (err, hostname, service) => {
    if (err) {
      console.error(`  エラー: ${err.message} (コード: ${err.code})`);
      return;
    }
    console.log(`  ホスト名: ${hostname}`);
    console.log(`  サービス: ${service}`);
  });
  console.log('\n');
}

lookupServiceExample('127.0.0.1', 22); // ローカルホストのSSH
lookupServiceExample('8.8.8.8', 53);   // Google DNSのポート53(DNSサービス)
lookupServiceExample('192.168.1.1', 80); // ルーターなどのWebサーバー(設定による)

考察
dnsPromises.reverse()が純粋なDNS逆引きを行うのに対し、dns.lookupService()はより「ローカルな」解決であり、サービス名も取得できる点が異なります。目的が単にIPアドレスからホスト名を得ることであれば、reverse()が適切です。

外部ライブラリの利用

Node.jsの組み込みdnsモジュールは基本的なDNS操作を提供しますが、より高度な機能や異なるアプローチを求める場合は、npmのエコシステムで利用可能な外部ライブラリを検討できます。

  • dns-over-http-resolvertangerine: DNS over HTTPS (DoH) を利用して名前解決を行うライブラリです。プライバシー保護や検閲回避の目的で、標準のUDP/TCPベースのDNSではなくHTTP経由でDNSクエリを実行したい場合に検討されます。

Node.jsのchild_processモジュールを使用して、OSのコマンド(例: nslookupdig)を実行し、その出力をパースする方法も技術的には可能です。しかし、これはNode.jsのネイティブなAPIを使用するよりも複雑で、OS間の互換性の問題、セキュリティリスク、パフォーマンスのオーバーヘッドがあるため、ほとんどの場合で推奨されません。

コード例 (nslookup を使用)

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

function reverseUsingNslookup(ipAddress) {
  console.log(`--- nslookup を使用した逆引き (IP: ${ipAddress}) ---`);
  exec(`nslookup ${ipAddress}`, (error, stdout, stderr) => {
    if (error) {
      console.error(`  エラー: ${error.message}`);
      return;
    }
    if (stderr) {
      console.error(`  nslookup エラー出力: ${stderr}`);
      // nslookup は逆引きできない場合もstderrに出力することがある
    }

    const lines = stdout.split('\n');
    let hostname = null;
    for (const line of lines) {
      if (line.includes('name = ')) {
        hostname = line.split('name = ')[1].trim().replace(/\.$/, ''); // 末尾のドットを削除
        break;
      }
    }

    if (hostname) {
      console.log(`  見つかったホスト名: ${hostname}`);
    } else {
      console.log(`  ホスト名は見つかりませんでした。(nslookup出力からパース失敗)`);
    }
  });
  console.log('\n');
}

reverseUsingNslookup('8.8.8.8');
reverseUsingNslookup('192.0.2.1');

考察
この方法は、最終手段として、または特定のツール(nslookupなど)の機能をそのまま利用したい場合にのみ検討すべきです。出力のパースが複雑でエラー処理も煩雑になりがちです。

ほとんどのNode.jsアプリケーションでは、IPアドレスの逆引きには**dnsPromises.reverse()が最も推奨される方法**です。これはPromiseベースで使いやすく、Node.jsのDNS解決のベストプラクティスに従っています。

代替手段は、以下のような特定のニーズがある場合に検討されます。

  • 外部コマンド実行: 非常に特殊なケースで、Node.jsのネイティブAPIでは不可能な要件がある場合(ただし非推奨)。
  • 外部ライブラリ (dns2など): DNS over HTTPSなどの高度な機能、またはDNSクライアントのカスタマイズが必要な場合。
  • dns.lookupService(): IPアドレスとポートからホスト名とサービス名をOSの機能で解決したい場合。
  • dnsPromises.resolvePtr(): より低レベルなPTRレコードクエリが必要な場合。