Node.jsのdnsPromises.lookupService()徹底解説:IPからホスト・サービス名解決
2025-05-27
この関数は、主に以下の目的で使用されます。
- ポート番号からサービス名への変換
ポート番号(例: 80番ポートはhttp
)から、そのポートが一般的に使用されるサービス名を取得します。 - 逆引きDNSルックアップ
IPアドレスから対応するホスト名(ドメイン名)を取得します。
使い方
dnsPromises.lookupService()
は、2つの引数を取ります。
address
: 解決したいIPアドレス(例:'127.0.0.1'
,'::1'
など)。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)であることを確認します。
- 引数が正しい型(
- 原因:
-
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クエリをブロックしていないかなども確認の対象になります。
- 原因:
-
EAI_AGAIN
(一時的な名前解決の失敗)- 原因: DNSサーバーが一時的に応答しない、またはリソース不足などの理由で名前解決が一時的に利用できない場合に発生します。これは通常、一時的なネットワークの問題やDNSサーバーの負荷によるものです。
- トラブルシューティング:
- リトライ: しばらく待ってから再度処理を試みます。一時的な問題であることが多いため、リトライ戦略(exponential backoffなど)を実装することが有効です。
- DNSサーバーの確認: 使用しているDNSサーバーが正常に動作しているか、または代替の信頼できるDNSサーバー(例: Google Public DNS
8.8.8.8
, Cloudflare1.1.1.1
)に変更することで改善するか試します。 - ネットワーク負荷: ネットワークに過度な負荷がかかっていないか確認します。
-
EAI_NODATA
(データなし)- 原因: 名前解決は成功したが、要求されたレコードタイプ(この場合はPTRレコード)のデータが存在しない場合に発生します。
ENOTFOUND
と似ていますが、より具体的なデータ不足を示します。 - トラブルシューティング:
ENOTFOUND
と同様に、IPアドレスの逆引きDNS設定を確認します。そのIPアドレスに逆引きエントリがない可能性があります。
- 原因: 名前解決は成功したが、要求されたレコードタイプ(この場合はPTRレコード)のデータが存在しない場合に発生します。
-
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トラフィックをブロックしていないか。 - ネットワークインターフェースが正しく設定されているか。
- DNSサーバーの設定(
- OSのコマンドラインツールでの確認: Node.jsの
dnsPromises.lookupService()
は、OSのgetnameinfo
関数に依存しています。そのため、問題が発生した場合、まずOSのコマンドラインツールで同様の解決が可能かどうかを確認することが非常に重要です。- 逆引きDNS:
dig -x <IPアドレス>
(Linux/macOS),nslookup <IPアドレス>
(Windows/Linux/macOS) - サービス名: ポートからサービス名への変換は、
getnameinfo
が参照するシステムファイル(/etc/services
)に依存します。このファイルの中身を直接確認するか、grep
などでポート番号を検索してみます。
- 逆引きDNS:
- エラーハンドリングの徹底:
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の古いバージョンでも利用可能です。
- コールバックベースのコードに慣れている場合、直感的に使えるかもしれません。