Node.jsのdnsPromises.lookupService()徹底解説:IPからホスト・サービス名解決

2025-05-27

この関数は、主に以下の目的で使用されます。

  • ポート番号からサービス名への変換
    ポート番号(例: 80番ポートはhttp)から、そのポートが一般的に使用されるサービス名を取得します。
  • 逆引きDNSルックアップ
    IPアドレスから対応するホスト名(ドメイン名)を取得します。

使い方

dnsPromises.lookupService()は、2つの引数を取ります。

  1. address: 解決したいIPアドレス(例: '127.0.0.1', '::1'など)。
  2. port: 解決したいポート番号(例: 80, 22など)。

この関数はPromiseを返すため、async/await構文や.then().catch()構文を使って結果を処理できます。

返されるPromiseは、解決に成功した場合、以下のようなオブジェクトを返します。

{
  hostname: 'localhost', // 解決されたホスト名
  service: 'ssh'       // 解決されたサービス名
}

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

async function resolveService() {
  try {
    const result = await dnsPromises.lookupService('127.0.0.1', 22);
    console.log(`IPアドレス 127.0.0.1, ポート 22 のホスト名とサービス名:`);
    console.log(`  ホスト名: ${result.hostname}`); // 例: 'localhost'
    console.log(`  サービス名: ${result.service}`); // 例: 'ssh'

    const resultHttp = await dnsPromises.lookupService('192.168.1.100', 80);
    console.log(`\nIPアドレス 192.168.1.100, ポート 80 のホスト名とサービス名:`);
    console.log(`  ホスト名: ${resultHttp.hostname}`); // 例: 'my-web-server.local'
    console.log(`  サービス名: ${resultHttp.service}`); // 例: 'http'

  } catch (err) {
    console.error('エラーが発生しました:', err);
  }
}

resolveService();
  • Promiseベース: dnsPromises.lookupService()はPromiseを返すため、コールバックベースのdns.lookupService()と比較して、非同期処理の記述がより簡潔になります。
  • エラーハンドリング: 解決に失敗した場合(例: 無効なIPアドレスやポート、DNSサーバーへの到達不能など)、Promiseは拒否され、Errorオブジェクトが返されます。適切なエラーハンドリングを行うことが重要です。
  • オペレーティングシステムに依存: lookupService()は、オペレーティングシステムの基盤となるgetnameinfo実装を使用します。このため、結果はOSの設定やローカルのホストファイル(/etc/hostsなど)に影響される可能性があります。


よくあるエラーとその原因

dnsPromises.lookupService()は、解決に失敗するとPromiseを拒否し、Errorオブジェクトを返します。このErrorオブジェクトには、エラーコード(err.code)が含まれており、これがエラーの原因を特定するのに役立ちます。

    • 原因: address引数が有効なIPアドレス文字列ではない場合、またはport引数が有効な数値ではない場合に発生します。
    • :
      await dnsPromises.lookupService('invalid-ip-address', 80); // TypeError
      await dnsPromises.lookupService('127.0.0.1', 'not-a-number'); // TypeError
      
    • トラブルシューティング:
      • 引数が正しい型(addressは文字列、portは数値)であることを確認します。
      • addressが有効なIPv4またはIPv6アドレスの形式であることを確認します。
      • portが有効なポート番号の範囲内(1〜65535)であることを確認します。
  1. ENOTFOUND (ホスト名またはサービスが見つからない)

    • 原因:
      • 指定されたIPアドレスに対応するホスト名が見つからない場合(逆引きDNSルックアップに失敗)。
      • 指定されたポート番号に対応するサービス名がOSのサービスデータベース(/etc/servicesなど)に見つからない場合。
      • 一時的なDNSサーバーの障害やネットワークの問題。
    • トラブルシューティング:
      • IPアドレスの確認: 指定されたIPアドレスが正しく、実際に存在するデバイスのものであるかを確認します。
      • 逆引きDNS: そのIPアドレスが逆引きDNSに登録されているかを確認します。例えば、Linux/macOSではdig -x <IPアドレス>コマンドで確認できます。
      • ポート番号: ポート番号が一般的でない、あるいはOSのサービスデータベースに登録されていないポートの場合、サービス名は解決できません。例えば、カスタムアプリケーションが使用するポートなど。
      • DNS設定: 実行しているNode.jsアプリケーションのOSのDNS設定が正しいか確認します。DNSサーバーに到達可能か、名前解決が正常に行えるかを確認します。
      • ネットワーク接続: ネットワーク接続が安定しているか確認します。ファイアウォールがDNSクエリをブロックしていないかなども確認の対象になります。
  2. EAI_AGAIN (一時的な名前解決の失敗)

    • 原因: DNSサーバーが一時的に応答しない、またはリソース不足などの理由で名前解決が一時的に利用できない場合に発生します。これは通常、一時的なネットワークの問題やDNSサーバーの負荷によるものです。
    • トラブルシューティング:
      • リトライ: しばらく待ってから再度処理を試みます。一時的な問題であることが多いため、リトライ戦略(exponential backoffなど)を実装することが有効です。
      • DNSサーバーの確認: 使用しているDNSサーバーが正常に動作しているか、または代替の信頼できるDNSサーバー(例: Google Public DNS 8.8.8.8, Cloudflare 1.1.1.1)に変更することで改善するか試します。
      • ネットワーク負荷: ネットワークに過度な負荷がかかっていないか確認します。
  3. EAI_NODATA (データなし)

    • 原因: 名前解決は成功したが、要求されたレコードタイプ(この場合はPTRレコード)のデータが存在しない場合に発生します。ENOTFOUNDと似ていますが、より具体的なデータ不足を示します。
    • トラブルシューティング:
      • ENOTFOUNDと同様に、IPアドレスの逆引きDNS設定を確認します。そのIPアドレスに逆引きエントリがない可能性があります。
  4. EAI_NONAME (ホスト名またはサービス名が不明)

    • 原因: ENOTFOUNDと似ていますが、通常、OSのgetnameinfo関数が「ホストが見つからない」または「サービスが見つからない」と明確に報告した場合に発生します。
    • トラブルシューティング:
      • IPアドレスとポート番号が正しいことを再確認します。
      • OSのネットワーク設定や/etc/hosts, /etc/servicesなどのファイルを確認します。
  • Node.jsのバージョン: まれにNode.jsの特定のバージョンでDNS関連のバグが存在する可能性があります。最新のLTSバージョンにアップデートすることで問題が解決する場合もあります。
  • ネットワーク設定の確認:
    • DNSサーバーの設定(/etc/resolv.confなど)。
    • ファイアウォールルール(iptables, Windows Defender Firewallなど)がDNSトラフィックをブロックしていないか。
    • ネットワークインターフェースが正しく設定されているか。
  • OSのコマンドラインツールでの確認: Node.jsのdnsPromises.lookupService()は、OSのgetnameinfo関数に依存しています。そのため、問題が発生した場合、まずOSのコマンドラインツールで同様の解決が可能かどうかを確認することが非常に重要です。
    • 逆引きDNS: dig -x <IPアドレス> (Linux/macOS), nslookup <IPアドレス> (Windows/Linux/macOS)
    • サービス名: ポートからサービス名への変換は、getnameinfoが参照するシステムファイル(/etc/services)に依存します。このファイルの中身を直接確認するか、grepなどでポート番号を検索してみます。
  • エラーハンドリングの徹底: dnsPromises.lookupService()はPromiseを返すため、必ずtry...catchブロックを使用するか、.catch()メソッドでエラーを捕捉し、エラーコードをログに出力するようにします。これにより、問題の特定が容易になります。
    const dnsPromises = require('node:dns').promises;
    
    async function resolveService(ip, port) {
      try {
        const result = await dnsPromises.lookupService(ip, port);
        console.log(`Resolved <span class="math-inline">\{ip\}\:</span>{port} to <span class="math-inline">\{result\.hostname\}\:</span>{result.service}`);
      } catch (err) {
        console.error(`Error resolving <span class="math-inline">\{ip\}\:</span>{port}:`);
        console.error(`  Code: ${err.code}`);
        console.error(`  Message: ${err.message}`);
        // 追加のデバッグ情報:
        if (err.syscall) console.error(`  Syscall: ${err.syscall}`);
        if (err.hostname) console.error(`  Hostname: ${err.hostname}`); // 逆引きの場合、ここにはアドレスが入る
      }
    }
    
    resolveService('127.0.0.1', 22);
    resolveService('192.0.2.1', 80); // 存在しないIPアドレスの例
    resolveService('invalid-ip', 80); // 無効なIPアドレスの例
    


例1:基本的な使い方と成功時の処理

この例では、一般的なIPアドレスとポート番号を使って、ホスト名とサービス名を解決します。

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

async function basicLookupService() {
  const ipAddress = '127.0.0.1'; // ローカルホストのIPアドレス
  const port = 22;               // SSHの標準ポート

  try {
    const result = await dnsPromises.lookupService(ipAddress, port);
    console.log(`IPアドレス: ${ipAddress}, ポート: ${port}`);
    console.log(`  ホスト名: ${result.hostname}`); // 例: 'localhost'
    console.log(`  サービス名: ${result.service}`);   // 例: 'ssh'

    // 別の例: HTTPポート
    const ipAddress2 = '192.168.1.1'; // ルーターなどのIPアドレスを想定
    const port2 = 80;                 // HTTPの標準ポート
    const result2 = await dnsPromises.lookupService(ipAddress2, port2);
    console.log(`\nIPアドレス: ${ipAddress2}, ポート: ${port2}`);
    console.log(`  ホスト名: ${result2.hostname}`); // 例: 'router.local' またはルーターのホスト名
    console.log(`  サービス名: ${result2.service}`);   // 例: 'http'

  } catch (err) {
    console.error('エラーが発生しました:', err);
  }
}

basicLookupService();

解説:

  • service はポート番号に対応するサービス名(例: ssh, http)です。これはオペレーティングシステムのサービスデータベース(Linux/macOS の /etc/services など)に基づいています。
  • hostname はIPアドレスの逆引きDNSの結果(例: localhost)です。
  • lookupService(ipAddress, port) は解決が成功すると { hostname: string, service: string } 形式のオブジェクトを返します。
  • async/await を使用して非同期処理を同期的に記述しています。
  • require('node:dns').promises で、Promise ベースの dns モジュールを取得します。

例2:エラーハンドリング(存在しないIPアドレスとポート)

この例では、存在しないIPアドレスや、サービス名が登録されていないポート番号を使った場合に発生するエラーを処理する方法を示します。

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

async function handleErrorCases() {
  // 存在しない(または逆引きDNSが設定されていない)IPアドレスの例
  const nonExistentIp = '192.0.2.1'; // テスト用のReserved IP (TEST-NET-1)
  const portForNonExistent = 80;

  try {
    console.log(`--- ${nonExistentIp}:${portForNonExistent} を試行 ---`);
    const result = await dnsPromises.lookupService(nonExistentIp, portForNonExistent);
    console.log(`成功: ${result.hostname}:${result.service}`);
  } catch (err) {
    console.error(`エラーが発生しました:`);
    console.error(`  IP: ${nonExistentIp}, ポート: ${portForNonExistent}`);
    console.error(`  エラーコード: ${err.code}`); // 例: 'ENOTFOUND' または 'EAI_NONAME'
    console.error(`  メッセージ: ${err.message}`);
  }

  // サービス名が登録されていない可能性のあるポートの例
  const localIp = '127.0.0.1';
  const customPort = 60000; // 一般的ではない高いポート番号

  try {
    console.log(`\n--- ${localIp}:${customPort} を試行 ---`);
    const result = await dnsPromises.lookupService(localIp, customPort);
    console.log(`成功: ${result.hostname}:${result.service}`);
  } catch (err) {
    console.error(`エラーが発生しました:`);
    console.error(`  IP: ${localIp}, ポート: ${customPort}`);
    console.error(`  エラーコード: ${err.code}`); // 例: 'ENOTFOUND' または 'EAI_NONAME'
    console.error(`  メッセージ: ${err.message}`);
  }

  // 無効な引数の例
  try {
    console.log(`\n--- 無効なIPアドレス形式を試行 ---`);
    await dnsPromises.lookupService('invalid-ip-string', 80);
  } catch (err) {
    console.error(`エラーが発生しました (無効な引数):`);
    console.error(`  エラーコード: ${err.code}`); // 通常は undefined または TypeError
    console.error(`  メッセージ: ${err.message}`); // 例: 'The "address" argument must be of type string.'
  }
}

handleErrorCases();

解説:

  • 高位のポート番号(例: 60000)は、OSのサービスデータベースに登録されていないことがほとんどのため、service は解決されずエラーになる可能性があります。
  • err.code はエラーの種類を示す文字列です(例: ENOTFOUND, EAI_NONAME)。
    • ENOTFOUND: ホスト名が見つからない、またはサービス名が見つからない。
    • EAI_NONAME: ホスト名またはサービス名が不明。
    • TypeError: 引数の型が間違っている。
  • try...catch ブロックは必須です。lookupService() は失敗時にPromiseを拒否するため、エラーを適切に捕捉する必要があります。

複数のIPアドレスとポートの組み合わせを同時に解決したい場合、Promise.all() を使うと効率的です。

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

async function concurrentLookups() {
  const targets = [
    { ip: '127.0.0.1', port: 22 },
    { ip: '8.8.8.8', port: 53 }, // Google Public DNS, DNSサービス
    { ip: '192.168.1.1', port: 80 }, // ルーターのHTTP
    { ip: '192.0.2.1', port: 443 } // 存在しないIPアドレスのHTTPS
  ];

  const lookupPromises = targets.map(async (target) => {
    try {
      const result = await dnsPromises.lookupService(target.ip, target.port);
      return {
        ip: target.ip,
        port: target.port,
        status: 'success',
        hostname: result.hostname,
        service: result.service
      };
    } catch (err) {
      return {
        ip: target.ip,
        port: target.port,
        status: 'error',
        code: err.code,
        message: err.message
      };
    }
  });

  console.log('--- 複数のルックアップを並行して実行中 ---');
  const results = await Promise.all(lookupPromises);

  results.forEach(res => {
    if (res.status === 'success') {
      console.log(` ${res.ip}:${res.port} -> ホスト名: ${res.hostname}, サービス: ${res.service}`);
    } else {
      console.error(` ${res.ip}:${res.port} -> エラー: ${res.code} - ${res.message}`);
    }
  });
}

concurrentLookups();

解説:

  • 結果は、各ルックアップの成功/失敗ステータスと、対応する情報(ホスト名、サービス、エラーコードなど)を含むオブジェクトの配列として表示されます。
  • Promise.all(lookupPromises) は、すべてのPromiseが解決(成功または拒否)されるまで待機し、結果の配列を返します。これにより、処理が大幅に高速化されます。
  • targets.map() を使って、各ターゲットに対して dnsPromises.lookupService() を実行するPromiseを生成します。各Promiseは、成功またはエラーのいずれかの結果を含むオブジェクトを返します。
  • targets 配列に、解決したいIPアドレスとポートのペアを定義します。


dns.lookupService() (コールバックベース)

  • 使用例:
    const dns = require('node:dns');
    
    dns.lookupService('127.0.0.1', 22, (err, hostname, service) => {
      if (err) {
        console.error('エラー:', err);
        return;
      }
      console.log(`ホスト名: ${hostname}, サービス: ${service}`);
    });
    
  • 欠点:
    • 非同期処理が深くネストする「コールバック地獄」になりやすいです。
    • async/awaitと組み合わせるのが難しいです。
  • 利点:
    • Node.jsの古いバージョンでも利用可能です。
    • コールバックベースのコードに慣れている場合、直感的に使えるかもしれません。