【Node.js】dns.resolveSrv()でのエラー解決ガイド:よくある問題と対処法
SRVレコードとは?
SRVレコードは、特定のサービスを提供するサーバーのホスト名とポート番号をDNS上で公開するためのレコードタイプです。例えば、IP電話(SIP)やXMPP(Jabber)などのサービスでは、クライアントがサービスに接続する際にどのサーバーとポートを使用すべきかをSRVレコードで発見することが一般的です。
dns.resolveSrv()
の使い方
dns.resolveSrv()
メソッドの基本的な書式は以下の通りです。
dns.resolveSrv(hostname, callback)
callback
: DNSクエリが完了したときに呼び出されるコールバック関数。以下の引数を受け取ります。err
: エラーが発生した場合にErrorオブジェクトが含まれます。addresses
: SRVレコードの情報を格納したオブジェクトの配列。各オブジェクトには以下のプロパティが含まれます。priority
: 優先度。値が小さいほど優先度が高い。weight
: 同じ優先度のレコード間での重み。port
: サービスが提供されているポート番号。name
: サービスを提供するターゲットホスト名。
hostname
: 解決したいホスト名(文字列)。通常、_service._protocol.domain.com
のような形式で指定します。例えば、SIPサービスであれば_sip._tcp.example.com
のようになります。
const dns = require('dns');
// _sip._tcp.example.com のSRVレコードを解決する例
dns.resolveSrv('_sip._tcp.example.com', (err, addresses) => {
if (err) {
console.error('SRVレコードの解決中にエラーが発生しました:', err);
return;
}
console.log('SRVレコード:', addresses);
// addresses の出力例:
// [
// { priority: 10, weight: 5, port: 5060, name: 'sip-server1.example.com' },
// { priority: 20, weight: 5, port: 5060, name: 'sip-server2.example.com' }
// ]
});
// promises API を使用した例 (Node.js v10.0.0以降)
const { promises: dnsPromises } = require('dns');
async function resolveSrvRecords() {
try {
const addresses = await dnsPromises.resolveSrv('_xmpp-client._tcp.jabber.org');
console.log('XMPP SRVレコード:', addresses);
} catch (err) {
console.error('XMPP SRVレコードの解決中にエラーが発生しました:', err);
}
}
resolveSrvRecords();
一般的なDNS解決エラー
dns.resolveSrv()
を含むNode.jsのDNS関連メソッドで発生しうる一般的なエラーは、err
オブジェクトのcode
プロパティで識別できます。
エラーコードと意味
- ネットワーク関連のエラー (例:
ECONNREFUSED
,EHOSTUNREACH
): ネットワーク接続自体に問題がある場合に発生します。DNSサーバーに到達できない可能性があります。 dns.FORMERR
(EFORMERR): DNSクエリが誤ってフォーマットされている場合に発生します。これはNode.js内部の問題か、非常にまれなケースです。dns.REFUSED
(EREFUSED): DNSサーバーがクエリを拒否した場合に発生します。これは通常、DNSサーバーが特定のクエリを許可していない、またはアクセス制限がある場合に起こります。dns.TIMEOUT
(ETIMEOUT): DNSクエリがタイムアウトした場合に発生します。DNSサーバーが応答しない、またはネットワーク接続が不安定な場合に起こりえます。dns.SERVFAIL
(ESERVFAIL): DNSサーバーがクエリを処理できなかった場合に発生します。これはDNSサーバー自身の一時的な問題や設定ミスが原因であることがあります。dns.NODATA
(ENODATA): ドメイン名自体は存在するが、指定されたレコードタイプ(この場合はSRVレコード)が見つからなかった場合に発生します。- SRVレコードのホスト名が間違っている可能性があります。(例:
_sip._tcp.example.com
の代わりにexample.com
を指定しているなど) - そのドメインにSRVレコードが設定されていない可能性があります。
- SRVレコードのホスト名が間違っている可能性があります。(例:
dns.NOTFOUND
(ENOTFOUND): 指定されたドメイン名(またはSRVレコードのホスト名)が見つからない、つまりDNSに登録されていない場合に発生します。これはタイプミスや、そもそも存在しないドメイン名を指定している可能性が高いです。
トラブルシューティング(共通)
- ホスト名の確認: まず、
dns.resolveSrv()
に渡しているホスト名が正しいか、タイプミスがないかを徹底的に確認してください。SRVレコードの場合、_service._protocol.domain.com
の形式が非常に重要です。 - ネットワーク接続の確認:
- アプリケーションが動作しているサーバーから外部へのネットワーク接続が正常かを確認します(例:
ping google.com
)。 - DNSサーバーが適切に設定されているかを確認します。通常、
/etc/resolv.conf
(Linux/macOS) やネットワークアダプターの設定 (Windows) を確認します。
- アプリケーションが動作しているサーバーから外部へのネットワーク接続が正常かを確認します(例:
- DNSサーバーの確認:
dig
やnslookup
コマンドを使用して、問題のSRVレコードが実際に解決できるかを確認します。dig SRV _sip._tcp.example.com @your_dns_server nslookup -type=SRV _sip._tcp.example.com your_dns_server
your_dns_server
を省略すると、システムのデフォルトDNSサーバーが使われます。- もし特定のDNSサーバーで解決できない場合は、別の信頼できるDNSサーバー(例: Google Public DNS
8.8.8.8
や Cloudflare DNS1.1.1.1
)を指定して試してみます。 - Node.jsの
dns.setServers()
を使って、プログラム内で一時的にDNSサーバーを変更して試すこともできます。const dns = require('dns'); dns.setServers(['8.8.8.8', '1.1.1.1']); // Google Public DNS を設定 dns.resolveSrv('_sip._tcp.example.com', (err, addresses) => { // ... });
- 対象ドメインのDNS設定を確認: SRVレコードが設定されているはずなのに
ENODATA
やNOTFOUND
が出る場合は、そのドメインのDNSゾーンファイル自体にSRVレコードが正しく設定されているか、またはDNS伝播が完了しているかを確認してください。DNSの変更は反映に時間がかかる場合があります。 - DNSサーバーの負荷/応答遅延:
ETIMEOUT
が頻繁に発生する場合は、使用しているDNSサーバーが過負荷状態にあるか、地理的に遠いなどの理由で応答が遅れている可能性があります。
dns.resolveSrv()特有の考慮事項とトラブルシューティング
SRVレコードの形式の誤り
- 解決策:
- サービスとプロトコルの正しい命名規則を確認します(例: SIPは通常
_sip._tcp
または_sip._udp
)。 - 対象のサービスが使用するSRVレコードのホスト名の正確な形式をドキュメント等で確認してください。
- サービスとプロトコルの正しい命名規則を確認します(例: SIPは通常
- 問題:
_service._protocol.domain.com
の形式を間違えている。よくある間違いは、サービス名やプロトコル名のアンダースコア(_
)を忘れる、または存在しないサービス名やプロトコル名を使用することです。
SRVレコードの優先度と重み
- 解決策: アプリケーション側で、返された
addresses
配列の各要素をpriority
でソートし、同じpriority
の要素間ではweight
に基づいて処理ロジックを実装する必要があります。 - 問題:
dns.resolveSrv()
はSRVレコードをpriority
(優先度)とweight
(重み)に基づいてソートして返しません。返された配列の順序は保証されません。
非同期処理と同期的なボトルネック(上級者向け)
Node.jsのDNSモジュールは非同期APIを提供していますが、内部的にはlibuvのスレッドプールで同期的なDNS解決を実行しています。
- 解決策:
- DNSクエリを多用する設計を見直し、可能な限りDNSルックアップの結果をキャッシュすることを検討します。カスタムのDNSキャッシング層を実装するか、既存のライブラリを使用します。
- DNSクエリのレートリミットを設ける。
- Node.jsの
dns.Resolver
クラスを使用して、カスタムのDNS設定やより高度な制御を行うことも検討できますが、これは複雑になります。
- 問題: 短時間に多数のDNSクエリ(
dns.resolveSrv()
を含む)を並行して実行すると、libuvスレッドプール(デフォルトで4スレッド)を使い果たし、Node.jsアプリケーション全体のI/O処理(ファイルI/Oなど)もブロックされる可能性があります。これにより、アプリケーションが遅くなったり、タイムアウトが発生したりすることがあります。
- 外部ツールとの比較:
dig
やnslookup
などのコマンドラインツールで同じSRVレコードを解決してみて、Node.jsの結果と比較することが非常に有効です。これにより、DNSサーバー側の問題か、Node.jsアプリケーション側の問題かを切り分けることができます。 - ログ出力:
dns.resolveSrv()
の呼び出し前後にログを出力し、どのホスト名を解決しようとしているか、エラーが発生したタイミングなどを追跡できるようにします。 - エラーオブジェクトの詳細確認:
err
オブジェクトには、エラーコード(err.code
)だけでなく、追加情報が含まれている場合があります。console.error(err)
で全体を出力し、詳細を確認してください。
基本的な使い方 (Callback-based)
最も基本的な使用例です。指定したホスト名のSRVレコードを解決し、結果をコールバック関数で受け取ります。
const dns = require('dns');
// 例: Google Talk (XMPP) クライアントサービスのSRVレコードを解決
const hostname = '_xmpp-client._tcp.gmail.com';
dns.resolveSrv(hostname, (err, addresses) => {
if (err) {
console.error(`SRVレコード解決エラー (${hostname}):`, err.message);
// エラーコードによって分岐することも可能
if (err.code === 'ENOTFOUND') {
console.error('指定されたホスト名が見つかりません。タイプミスを確認してください。');
} else if (err.code === 'ENODATA') {
console.error('ホスト名は存在しますが、SRVレコードが見つかりません。');
}
return;
}
console.log(`SRVレコード (${hostname}):`);
if (addresses.length === 0) {
console.log(' SRVレコードは見つかりませんでした。');
} else {
// 取得したSRVレコードをログに出力
addresses.forEach((addr, index) => {
console.log(` [${index + 1}]`);
console.log(` ターゲット: ${addr.name}`);
console.log(` ポート: ${addr.port}`);
console.log(` 優先度: ${addr.priority}`);
console.log(` 重み: ${addr.weight}`);
});
}
});
console.log('SRV解決のリクエストを送信しました...');
解説
- 成功した場合、
addresses
配列にSRVレコードのオブジェクトが含まれます。各オブジェクトはname
,port
,priority
,weight
プロパティを持ちます。 - エラーが発生した場合、
err
オブジェクトにエラー情報が含まれます。err.code
でエラーの種類を判断できます。 dns.resolveSrv(hostname, callback)
の形式で呼び出します。
Promises API を使用した現代的な書き方 (Async/Await)
Node.js v10.0.0以降では、dns.promises
を使用することで、async/await構文を使ったよりクリーンなコードを書くことができます。
const { promises: dnsPromises } = require('dns');
// 例: Microsoft Teams (SIP) サービスのSRVレコードを解決
const hostname = '_sipfederationtls._tcp.lync.com'; // SIPフェデレーション用
async function resolveSrvRecords(host) {
try {
console.log(`SRVレコードを解決中: ${host}...`);
const addresses = await dnsPromises.resolveSrv(host);
console.log(`SRVレコード (${host}):`);
if (addresses.length === 0) {
console.log(' SRVレコードは見つかりませんでした。');
} else {
// 取得したSRVレコードをログに出力
// 優先度と重みでソートすることも可能 (後述)
addresses.forEach((addr, index) => {
console.log(` [${index + 1}]`);
console.log(` ターゲット: ${addr.name}`);
console.log(` ポート: ${addr.port}`);
console.log(` 優先度: ${addr.priority}`);
console.log(` 重み: ${addr.weight}`);
});
}
} catch (error) {
console.error(`SRVレコード解決エラー (${host}):`, error.message);
if (error.code === 'ENOTFOUND') {
console.error(' 指定されたホスト名またはSRVレコードが見つかりません。');
} else if (error.code === 'ENODATA') {
console.error(' ホスト名は存在しますが、SRVレコードがありません。');
} else if (error.code === 'ETIMEOUT') {
console.error(' DNSクエリがタイムアウトしました。ネットワーク接続を確認してください。');
}
}
}
resolveSrvRecords(hostname);
resolveSrvRecords('_sip._tcp.example.com'); // 存在しない可能性のあるドメインの例
解説
- エラーは
try...catch
ブロックで捕捉します。 await dnsPromises.resolveSrv(host)
で非同期処理の結果を待ちます。require('dns').promises
を使ってdnsPromises
オブジェクトを取得します。
取得したSRVレコードのソートと優先順位付け
SRVレコードはpriority
(優先度)とweight
(重み)という概念を持ちます。通常、priority
が低いほど優先され、同じpriority
のレコード間ではweight
が高いほど優先されます(ただし、クライアントは通常weight
を考慮してランダムに選択します)。dns.resolveSrv()
はソートされていない配列を返すため、アプリケーション側でソートして利用するのが一般的です。
const { promises: dnsPromises } = require('dns');
const hostname = '_ldap._tcp.gc._msdcs.contoso.com'; // Active Directory LDAPの例
async function resolveAndSortSrvRecords(host) {
try {
console.log(`SRVレコードを解決中 (ソートあり): ${host}...`);
const addresses = await dnsPromises.resolveSrv(host);
if (addresses.length === 0) {
console.log(' SRVレコードは見つかりませんでした。');
return;
}
// 優先度 (priority) で昇順、次に重み (weight) で降順にソート
// priorityが低いほど優先度が高い
// 同じpriorityの場合、weightが高いほど優先される (クライアントがランダム選択する際の重み)
addresses.sort((a, b) => {
if (a.priority !== b.priority) {
return a.priority - b.priority;
}
return b.weight - a.weight; // 同じ優先度の場合は重みで降順
});
console.log(`ソートされたSRVレコード (${host}):`);
addresses.forEach((addr, index) => {
console.log(` [${index + 1}]`);
console.log(` ターゲット: ${addr.name}`);
console.log(` ポート: ${addr.port}`);
console.log(` 優先度: ${addr.priority}`);
console.log(` 重み: ${addr.weight}`);
});
// 最も優先されるサーバーに接続する例
if (addresses.length > 0) {
const primaryServer = addresses[0];
console.log(`\n最も優先されるサーバー: ${primaryServer.name}:${primaryServer.port}`);
// ここで、例えばソケット接続を試みるなどの処理に進む
}
} catch (error) {
console.error(`SRVレコード解決エラー (${host}):`, error.message);
}
}
resolveAndSortSrvRecords(hostname);
解説
priority
が同じ場合は、weight
が大きい方が前に来るように降順でソートします。これは、クライアントが複数のサーバーから選択する際に、重み付けされたロードバランシングを行うためのものです。priority
が異なる場合は、小さい方が前に来るように昇順でソートします。addresses.sort()
メソッドを使って、カスタムの比較関数を渡しています。
デフォルトのシステムDNSサーバーではなく、特定のDNSサーバー(例: Google Public DNSやISPのDNS)を使用して解決したい場合。
const dns = require('dns');
const { Resolver } = dns;
const hostname = '_sip._tcp.example.com'; // 例のドメイン
// 新しいResolverインスタンスを作成
const resolver = new Resolver();
// 任意のDNSサーバーを設定
resolver.setServers(['8.8.8.8', '8.8.4.4']); // Google Public DNS
resolver.resolveSrv(hostname, (err, addresses) => {
if (err) {
console.error(`カスタムDNSサーバーでのSRVレコード解決エラー (${hostname}):`, err.message);
return;
}
console.log(`カスタムDNSサーバー (${resolver.getServers().join(', ')}) で解決したSRVレコード (${hostname}):`);
addresses.forEach((addr, index) => {
console.log(` [${index + 1}] ${addr.name}:${addr.port} (Priority: ${addr.priority}, Weight: ${addr.weight})`);
});
});
// デフォルトのDNSサーバー設定を元に戻すことも可能
// dns.setServers(originalServers);
- この
resolver
インスタンスのresolveSrv()
メソッドを使用します。 resolver.setServers()
でDNSサーバーのIPアドレスを配列で指定します。dns.Resolver
クラスのインスタンスを作成することで、独自のDNSサーバー設定を持つリゾルバを作成できます。
外部DNSクライアントライブラリの使用
Node.jsの標準dns
モジュールは比較的シンプルな機能を提供しますが、より高度なDNS機能(例えば、特定のDNSSEC検証、DANE TLSAレコード、より詳細なDNSプロトコル制御、または非標準的なDNSレコードタイプ)が必要な場合、専用のDNSクライアントライブラリを検討することがあります。
例: dns-packet
やnative-dns
(あまり活発ではない)
厳密にはresolveSrv
の直接的な代替というよりは、より低レベルでDNSクエリを構築・パースするためのライブラリです。これらを使うことで、dns.resolveSrv()
が提供しないようなカスタムなSRVレコードクエリや、他のレコードタイプとの組み合わせ、DNSサーバーへの直接的なUDP/TCP接続など、高度な処理を実装できます。
ただし、これらのライブラリは通常、より多くのコードとDNSプロトコルに関する深い知識を必要とします。
メリット
- Node.jsの標準ライブラリではサポートされていない、より高度なDNS機能を利用できる可能性がある。
- DNSプロトコルに対する低レベルな制御が可能。
デメリット
- 多くの場合、SRVレコードを解決するだけであればオーバーキル。
- ライブラリによってはメンテナンスが活発でない場合がある。
- 実装が複雑になりがちで、DNSプロトコルの知識が必須。
使用場面
- カスタムのDNSサーバー実装や、DNSプロトコルに関する研究開発を行う場合。
- Node.jsの
dns
モジュールが提供しない特定のDNSレコードタイプを解決する必要がある場合。
厳密にはdns.resolveSrv()
の代替ではありませんが、ごく一部のレガシーなシステムでは、SRVレコードを直接解決する代わりに、SRVレコードが示すサービスのエンドポイント(ホスト名とポート)が別の方法で提供される場合があります。