Node.js dns.lookupService()とは?日本語解説とプログラミング例

2025-05-27

引数

  • callback (Function): 非同期処理が完了した後に呼び出されるコールバック関数です。このコールバック関数は3つの引数を受け取ります。
    • err (Error | null): エラーが発生した場合、エラーオブジェクトが渡されます。成功した場合は null が渡されます。
    • hostname (string): IPアドレスに対応するホスト名です。逆引きに失敗した場合や情報がない場合は、IPアドレスがそのまま返されることがあります。
    • service (string): ポート番号に対応するサービス名です。例えば、ポート 80 なら 'http'、ポート 443 なら 'https' のような文字列が返されます。サービス名が特定できない場合は、ポート番号が文字列として返されることがあります。
  • port (number): 関連付けるポート番号です。例えば、HTTPなら 80、HTTPSなら 443 などです。
  • address (string): 逆引きしたいIPアドレスです。例えば、'127.0.0.1''2001:db8::1' のようなIPv4またはIPv6のアドレスを指定します。

動作

dns.lookupService() を呼び出すと、Node.jsはオペレーティングシステムの基盤となるシステムルーチンを利用して、指定されたIPアドレスとポート番号に関連付けられたホスト名とサービス名の特定を試みます。この処理は非同期的に行われるため、Node.jsのイベントループをブロックしません。結果はコールバック関数を通じて通知されます。


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

async function lookupAndGetService() {
  try {
    const { hostname, service } = await dns.lookupService('127.0.0.1', 80);
    console.log(`ホスト名: ${hostname}, サービス名: ${service}`);
  } catch (err) {
    console.error(`エラーが発生しました: ${err}`);
  }
}

lookupAndGetService();

この例では、IPアドレス 127.0.0.1 とポート番号 80 に対して dns.lookupService() を呼び出し、成功すればホスト名とサービス名(通常は localhosthttp)をコンソールに出力します。エラーが発生した場合は、エラーメッセージを出力します。

  • Node.jsの dns モジュールには、コールバックベースのAPIとPromiseベースのAPIがあります。上記の例では、PromiseベースのAPI (dns.promises.lookupService) を使用しています。コールバックベースのAPIを使用する場合は、コールバック関数を直接 dns.lookupService() に渡します。
  • dns.lookupService() は、IPアドレスとポート番号からホスト名とサービス名を特定しようとしますが、常に成功するとは限りません。DNSの設定やネットワーク環境によっては、情報が見つからない場合があります。


一般的なエラー

  1. Error: getaddrinfo ENOTFOUND:

    • 原因
      指定された IP アドレスに対応するホスト名が見つからなかった場合に発生します。これは、DNS の逆引き設定がされていない、または DNS サーバーがその IP アドレスの PTR レコードを持っていない場合に起こりえます。
    • トラブルシューティング
      • 指定した IP アドレスが正しいか確認してください。タイプミスなどがないか注意が必要です。
      • ネットワーク環境が正常に動作しているか確認してください。DNS サーバーにアクセスできるかなどをテストします。
      • DNS サーバーの設定を確認してください。特に、逆引き (PTR レコード) の設定が正しく行われているかを確認します。
      • 別の DNS サーバーを試してみることも有効かもしれません。
  2. Error: getaddrinfo ENOSERV:

    • 原因
      指定されたポート番号に対応するサービス名が見つからなかった場合に発生します。オペレーティングシステムがそのポート番号に関連付けられた既知のサービスを持っていない可能性があります。
    • トラブルシューティング
      • 指定したポート番号が正しいか確認してください。
      • 一般的なポート番号(80, 443 など)で試してみて、エラーが発生するかどうかを確認します。もし発生しない場合は、特定のポート番号に関連する問題かもしれません。
      • オペレーティングシステムのサービス定義ファイル(/etc/services など)を確認し、関連するポート番号にサービス名が定義されているかを確認します。
  3. Error: getaddrinfo EAI_NODATA:

    • 原因
      これは ENOTFOUND に似ていますが、より詳細な情報がないことを示唆している場合があります。DNS サーバーは存在しますが、要求された情報(この場合は PTR レコード)が見つからなかった場合に発生することがあります。
    • トラブルシューティング
      • ENOTFOUND のトラブルシューティングと同様の手順を試してください。
      • DNS サーバーのログを確認し、逆引きクエリがどのように処理されているかを確認するのも有効です。
  4. Error: timeout (タイムアウト):

    • 原因
      DNS サーバーへの問い合わせが時間内に完了しなかった場合に発生します。ネットワークの遅延や DNS サーバーの応答の遅さなどが原因として考えられます。
    • トラブルシューティング
      • ネットワーク接続が安定しているか確認してください。
      • DNS サーバーの応答速度をテストしてみてください。ping コマンドなどで DNS サーバーの IP アドレスへの応答を確認できます。
      • より近く、応答速度の速い DNS サーバーを使用するようにネットワーク設定を変更することを検討してください。
      • Node.js の dns モジュールにはタイムアウトを設定するオプションはありませんが、ネットワーク全体のタイムアウト設定や、リトライ処理などを実装することで間接的に対処できます。

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

  • 異なる環境でのテスト
    可能であれば、異なるネットワーク環境やマシンで同じコードを実行し、問題が特定の環境に依存するものかどうかを切り分けます。
  • ログの確認
    アプリケーションやネットワーク関連のログを確認し、エラーの原因となる情報がないか探します。
  • コードの見直し
    dns.lookupService() の呼び出し方や、渡している引数が正しいか再度確認します。
  • エラーメッセージの正確な確認
    エラーメッセージを注意深く読み、どの種類のエラーが発生しているかを正確に把握することが重要です。
  • ファイアウォールの確認
    ローカルマシンやネットワークのファイアウォールが DNS クエリをブロックしていないか確認します。
  • DNS サーバーの確認
    使用している DNS サーバーのアドレスが正しいか、正常に動作しているかを確認します。
  • 他の DNS ツールでの確認
    nslookupdig などのコマンドライン DNS ツールを使用して、同じ IP アドレスとポート番号で逆引きができるか試してみます。これにより、Node.js 特有の問題なのか、ネットワークや DNS 設定の問題なのかを切り分けることができます。
  • 基本的なネットワーク接続の確認
    ping コマンドや traceroute コマンドを使用して、ネットワークの基本的な接続性を確認します。


例1: 基本的な逆引き(コールバック形式)

この例では、コールバック関数を使用して dns.lookupService() の結果を受け取ります。

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

const ipAddress = '8.8.8.8'; // Google Public DNS
const port = 53; // DNS ポート

dns.lookupService(ipAddress, port, (err, hostname, service) => {
  if (err) {
    console.error('エラーが発生しました:', err);
    return;
  }
  console.log(`IPアドレス: ${ipAddress}, ポート: ${port}`);
  console.log(`ホスト名: ${hostname}`);
  console.log(`サービス名: ${service}`);
});

console.log('逆引き処理を開始しました...');

解説

  • console.log('逆引き処理を開始しました...') は、非同期処理が開始されたことを示すために出力されます。
  • 成功した場合は、IP アドレス、ポート番号、逆引きされたホスト名、サービス名をコンソールに出力します。
  • エラーが発生した場合は、エラーメッセージをコンソールに出力して処理を終了します。
  • コールバック関数は、エラー (err)、ホスト名 (hostname)、サービス名 (service) を引数として受け取ります。
  • dns.lookupService() を呼び出し、IP アドレス、ポート番号、そして結果を受け取るコールバック関数を渡します。
  • 逆引きしたい IP アドレス (ipAddress) とポート番号 (port) を定義します。ここでは Google Public DNS のアドレスと DNS ポートを使用しています。
  • require('node:dns')dns モジュールを読み込みます。

例2: Promise を使用した逆引き(async/await 形式)

この例では、Promise ベースの API (dns.promises.lookupService) と async/await を使用して、よりモダンな非同期処理を行います。

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

async function lookupAndPrint(ipAddress, port) {
  try {
    const { hostname, service } = await dns.lookupService(ipAddress, port);
    console.log(`IPアドレス: ${ipAddress}, ポート: ${port}`);
    console.log(`ホスト名: ${hostname}`);
    console.log(`サービス名: ${service}`);
  } catch (err) {
    console.error('エラーが発生しました:', err);
  }
}

const ipToLookup1 = '192.168.1.1'; // ローカルネットワークのアドレス(結果は環境によって異なります)
const portToLookup1 = 80; // HTTP ポート
lookupAndPrint(ipToLookup1, portToLookup1);

const ipToLookup2 = '203.0.113.45'; // 例示用のIPアドレス
const portToLookup2 = 443; // HTTPS ポート
lookupAndPrint(ipToLookup2, portToLookup2);

console.log('逆引き処理を開始しました...');

解説

  • 複数の IP アドレスとポート番号に対して逆引き処理を実行しています。
  • エラーが発生した場合は、try...catch ブロックで捕捉し、エラーメッセージをコンソールに出力します。
  • 成功した場合、結果は { hostname, service } オブジェクトとして返され、それぞれの値をコンソールに出力します。
  • dns.lookupService() は Promise を返すため、await キーワードを使ってその解決を待ちます。
  • async 関数 lookupAndPrint を定義し、内部で await dns.lookupService(ipAddress, port) を呼び出します。
  • require('node:dns').promises で Promise ベースの API を読み込みます。

例3: エラーハンドリングの例

この例では、dns.lookupService() で発生する可能性のあるエラーを具体的にハンドリングする方法を示します。

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

async function lookupWithErrorHandling(ipAddress, port) {
  try {
    const { hostname, service } = await dns.lookupService(ipAddress, port);
    console.log(`IPアドレス: ${ipAddress}, ポート: ${port}`);
    console.log(`ホスト名: ${hostname}`);
    console.log(`サービス名: ${service}`);
  } catch (err) {
    console.error('エラーが発生しました:', err.message);
    if (err.code === 'ENOTFOUND') {
      console.log('指定されたIPアドレスに対応するホスト名が見つかりませんでした。');
    } else if (err.code === 'ENOSERV') {
      console.log('指定されたポート番号に対応するサービス名が見つかりませんでした。');
    } else {
      console.log('その他のエラーが発生しました。');
    }
  }
}

const unknownIp = '192.0.2.1'; // ドキュメント用のテストIPアドレス
const unknownPort = 99999; // 存在しない可能性の高いポート番号

lookupWithErrorHandling(unknownIp, 80);
lookupWithErrorHandling('127.0.0.1', unknownPort);
  • これにより、より具体的なエラー情報を提供し、問題の特定に役立てることができます。
  • err.code を参照することで、特定のエラータイプ(ENOTFOUND, ENOSERV など)に基づいて異なるエラーメッセージを出力できます。
  • エラーハンドリングの catch ブロック内で、エラーオブジェクト (err) の messagecode プロパティを確認しています。


外部 API の利用


  • const https = require('node:https');
    
    async function reverseLookupWithAPI(ipAddress) {
      return new Promise((resolve, reject) => {
        const url = `https://api.example.com/reverse-lookup?ip=${ipAddress}`; // 例示のAPI URL
        https.get(url, (res) => {
          let data = '';
          res.on('data', (chunk) => {
            data += chunk;
          });
          res.on('end', () => {
            try {
              const result = JSON.parse(data);
              resolve({ hostname: result.hostname, service: result.service || '' });
            } catch (error) {
              reject(error);
            }
          });
        }).on('error', (err) => {
          reject(err);
        });
      });
    }
    
    async function main() {
      try {
        const result = await reverseLookupWithAPI('8.8.8.8');
        console.log('APIによる逆引き結果:', result);
      } catch (error) {
        console.error('API呼び出しエラー:', error);
      }
    }
    
    main();
    
    (注意: 上記の URL https://api.example.com/reverse-lookup は架空のものです。実際に利用できる API のドキュメントを参照してください。)
  • 欠点
    • ネットワーク経由で通信が発生するため、遅延が生じる可能性があります。
    • API の利用には費用がかかる場合があります。
    • 外部サービスに依存するため、そのサービスの可用性に影響を受けます。
  • 利点
    • より詳細な情報が得られる場合があります(地理的な位置情報、組織情報など、API によって異なります)。
    • DNS サーバーの設定に依存しないため、環境による差異が少ない可能性があります。
    • レート制限や認証機能を備えた API を利用することで、DoS 攻撃などを防ぐことができます。

ローカルキャッシュの利用


  • const dns = require('node:dns').promises;
    const lookupCache = new Map();
    
    async function lookupServiceWithCache(ipAddress, port) {
      const cacheKey = `<span class="math-inline">\{ipAddress\}\:</span>{port}`;
      if (lookupCache.has(cacheKey)) {
        console.log('キャッシュから取得:', cacheKey);
        return lookupCache.get(cacheKey);
      }
    
      try {
        const result = await dns.lookupService(ipAddress, port);
        lookupCache.set(cacheKey, result);
        console.log('DNSルックアップ:', cacheKey, result);
        return result;
      } catch (error) {
        console.error('DNSルックアップエラー:', error);
        throw error;
      }
    }
    
    async function main() {
      const ip = '8.8.8.8';
      const port = 53;
    
      await lookupServiceWithCache(ip, port);
      await lookupServiceWithCache(ip, port); // 2回目はキャッシュから取得される可能性が高い
    }
    
    main();
    
  • 欠点
    • キャッシュの有効期限や更新ポリシーを適切に管理する必要があります。古い情報を提供してしまう可能性があります。
    • アプリケーションのメモリ使用量が増加します。
  • 利点
    • DNS サーバーへの不要な問い合わせを減らし、パフォーマンスを向上させることができます。
    • ネットワークの遅延を回避できます。

  • (具体的なコード例は、利用するツールやライブラリに大きく依存するため省略します。pcapnetstat などのネットワーク監視ツールや、それらを Node.js から操作するライブラリなどが考えられます。)
  • 欠点
    dns.lookupService() の直接的な代替にはならず、複雑な実装が必要になる場合があります。
  • 利点
    より広範なネットワーク情報を取得できる可能性があります。
  • 多くの場合、dns.lookupService() はシンプルで直接的な解決策を提供しますが、より高度なニーズや特定の問題を解決するためには、これらの代替方法を検討する価値があります。
  • どの方法を選択するかは、アプリケーションの要件(精度、パフォーマンス、外部依存性、コストなど)によって異なります。
  • これらの代替方法は、dns.lookupService() が提供する基本的な逆引き機能とは異なる特性を持っています。