Node.js開発者必見:DNS解決をマスターするdns.resolve*()活用術

2025-05-16

dns.resolve*() とは何か?

dns モジュールは、Node.js が提供するDNS機能の中心となるモジュールです。その中でも dns.resolve*() という形式のメソッドは、特定の種類のDNSレコードを解決するために使われます。

主なものとその用途は以下の通りです。

  • dns.resolveAny(hostname, callback):

    • 指定されたホスト名に関連付けられたすべてのリソースレコードを解決します。
    • コールバック関数には (err, addresses) が渡され、addresses はそれぞれのレコードタイプに応じたオブジェクトの配列になります。
  • dns.resolveCname(hostname, callback):

    • 正規名 (CNAME) レコードを解決します。
    • あるホスト名が別のホスト名のエイリアスであることを示します。
    • コールバック関数には (err, addresses) が渡され、addresses はエイリアスのホスト名を示す文字列の配列になります。
  • dns.resolveSrv(hostname, callback):

    • サービス (SRV) レコードを解決します。
    • 特定のサービスを提供しているサーバーのホスト名、ポート番号、重みなどの情報を取得するために使用されます。
    • コールバック関数には (err, addresses) が渡され、addresses[{ name: string, port: number, priority: number, weight: number }] の形式のオブジェクトの配列になります。
  • dns.resolveTxt(hostname, callback):

    • テキスト (TXT) レコードを解決します。
    • SPFレコードやDKIMレコードなど、ドメインに関連付けられた任意のテキスト情報を取得するために使用されます。
    • コールバック関数には (err, addresses) が渡され、addresses はテキスト文字列の配列の配列になります (例: [['v=spf1', 'include:_spf.google.com', '~all']])。
  • dns.resolveNs(hostname, callback):

    • ネームサーバー (NS) レコードを解決します。
    • そのドメインの権威ネームサーバーの情報を取得する際に使用されます。
    • コールバック関数には (err, addresses) が渡され、addresses はネームサーバーのホスト名を示す文字列の配列になります。
  • dns.resolveMx(hostname, callback):

    • メール交換 (MX) レコードを解決します。
    • メールの送信先となるメールサーバーの情報を取得する際に使用されます。
    • コールバック関数には (err, addresses) が渡され、addresses[{ priority: number, exchange: string }] の形式のオブジェクトの配列になります。
  • dns.resolve6(hostname[, options], callback):

    • IPv6アドレス (AAAA レコード) のみを解決します。
    • dns.resolve(hostname, 'AAAA', callback) と同じ結果を返しますが、より特化されています。
    • コールバック関数には (err, addresses) が渡され、addresses はIPv6アドレスの文字列の配列になります。
  • dns.resolve4(hostname[, options], callback):

    • IPv4アドレス (A レコード) のみを解決します。
    • dns.resolve(hostname, 'A', callback) と同じ結果を返しますが、より特化されています。
    • コールバック関数には (err, addresses) が渡され、addresses はIPv4アドレスの文字列の配列になります。
  • dns.resolve(hostname[, rrtype], callback):

    • これは汎用的なDNS解決メソッドです。
    • hostname で指定されたホスト名に対するDNSレコードを解決します。
    • rrtype (Resource Record Type) はオプションで、解決したいレコードの種類を指定します。例えば、'A' (IPv4アドレス)、'AAAA' (IPv6アドレス)、'MX' (メール交換レコード)、'TXT' (テキストレコード)、'NS' (ネームサーバーレコード) などがあります。
    • rrtype を省略した場合、デフォルトで A レコードが解決されます。
    • コールバック関数には (err, addresses) が渡され、addresses には解決されたレコードの配列が含まれます。その配列の要素の型は rrtype によって異なります。

dns.lookup() との違い

Node.js の dns モジュールには、dns.resolve*() 以外に dns.lookup() というメソッドもあります。これらは似ていますが、重要な違いがあります。

  • dns.lookup(hostname[, options], callback):

    • ホスト名をIPアドレスに解決する際に、OSの基盤となる**getaddrinfo**システムコールを利用します。
    • これは、/etc/hosts ファイル(Linux/macOS)やDNSキャッシュなど、OSレベルで設定された解決メカニズムに従います。
    • 通常、A または AAAA レコード(IPv4またはIPv6アドレス)を解決し、最初に見つかった1つを返します。
    • DNSプロトコルに直接アクセスするのではなく、OSのネームリゾルバに依存するため、dns.resolve*() よりも一般的に高速です。
    • 非同期で動作しますが、内部的にはスレッドプールを使用することがあります(一部のOSでは同期的に動作する場合がある)。
  • dns.resolve*():

    • DNSプロトコルを明示的に使用して、ネットワーク上のDNSサーバーに問い合わせを行います。
    • 指定されたレコードタイプに応じて、複数の異なる種類の情報を取得できます(例: A, AAAA, MX, TXT など)。
    • 結果はDNSサーバーからの生のレコード情報に近いです。
    • 非同期で動作します。

基本的な dns.resolve4 の使用例を以下に示します。

const dns = require('dns');

dns.resolve4('www.google.com', (err, addresses) => {
  if (err) {
    console.error('DNS解決エラー:', err);
    return;
  }
  console.log('IPv4アドレス:', addresses);
  // 出力例: IPv4アドレス: ['142.250.199.196', '142.250.199.228', ...]
});

dns.resolveMx('google.com', (err, addresses) => {
  if (err) {
    console.error('DNS解決エラー:', err);
    return;
  }
  console.log('MXレコード:', addresses);
  /* 出力例:
  MXレコード: [
    { priority: 10, exchange: 'alt1.aspmx.l.google.com' },
    { priority: 20, exchange: 'alt2.aspmx.l.google.com' },
    // ...
  ]
  */
});

// PromiseベースのAPIも利用可能 (dns.promises)
const dnsPromises = dns.promises;

async function resolveExample() {
  try {
    const addresses = await dnsPromises.resolveTxt('example.com');
    console.log('TXTレコード:', addresses);
    // 出力例: TXTレコード: [['v=spf1', 'include:spf.example.com', '~all']]
  } catch (err) {
    console.error('DNS解決エラー:', err);
  }
}

resolveExample();


NOTFOUND または ENODATA エラー

  • トラブルシューティング

    1. ホスト名のスペル確認
      最も基本的なことですが、再度スペルを確認してください。
    2. nslookup または dig コマンドでの確認
      • ターミナルを開き、以下のコマンドで手動で確認します。
        nslookup example.com
        dig example.com A
        dig example.com MX
        
      • これらのコマンドで期待するレコードが返されるか確認します。もし返されない場合は、Node.js の問題ではなく、DNS設定またはネットワークの問題です。
    3. レコードタイプの確認
      問い合わせている rrtype (例: A, MX, TXT) がそのドメインに設定されているか確認します。
    4. DNSキャッシュのクリア
      • ローカルPC
        OSによって方法は異なりますが、Windowsでは ipconfig /flushdns、macOSでは sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder など。
      • Node.jsアプリケーション
        Node.js自体にDNSキャッシュ機能はありませんが、dns.setServers() で一時的に別のDNSサーバーを指定してみることで、キャッシュの問題を回避できる場合があります。
    5. 伝播の確認
      DNSチェッカーサイト (例: whatsmydns.net) を利用して、ドメインのDNS伝播状況を確認します。
  • 原因

    • ドメイン名のスペルミス
      最も単純な原因です。
    • 存在しないホスト名/ドメイン
      問い合わせているドメインが登録されていないか、完全に間違っている。
    • 特定のレコードタイプの欠如
      ドメインは存在するが、例えばメールサーバーがないドメインに対して resolveMx を実行した場合など。
    • DNSキャッシュの問題
      ローカルのDNSキャッシュが古い、または無効な情報を保持している。
    • DNS伝播の遅延
      ドメインのDNS設定を変更したばかりで、まだ世界中のDNSサーバーに情報が伝播していない。
  • 発生する状況

    • 指定されたホスト名やドメインが存在しない場合。
    • ホスト名自体は存在するが、指定されたリソースレコードタイプ (例: A, MX, TXT) のレコードが存在しない場合。
    • 一時的なDNSの不具合や、ドメインの伝播が完了していない場合。
    • Error: queryTxt ENODATA example.com
    • Error: queryA NOTFOUND nonexist.domain

SERVFAIL エラー

  • トラブルシューティング

    1. 別のDNSサーバーの利用
      dns.setServers() を使用して、Google Public DNS (8.8.8.8, 8.8.4.4) や Cloudflare DNS (1.1.1.1, 1.0.0.1) など、信頼性の高い公開DNSサーバーを一時的に指定してみます。
      const dns = require('dns');
      dns.setServers(['8.8.8.8', '8.8.4.4']); // Google Public DNS
      // または ['1.1.1.1', '1.0.0.1'] // Cloudflare DNS
      
      dns.resolve4('example.com', (err, addresses) => {
        if (err) {
          console.error('DNS解決エラー:', err);
          return;
        }
        console.log('IPv4アドレス:', addresses);
      });
      
    2. ネットワーク接続の確認
      Node.jsアプリケーションが実行されているサーバーから、DNSサーバー(通常はルーターやISPのDNS)への基本的なネットワーク接続を確認します。ping コマンドなどでDNSサーバーに到達可能か確認します。
    3. ドメインのDNSSEC確認
      ドメインがDNSSECを有効にしている場合、その設定に問題がないかドメインレジストラやDNSプロバイダの管理画面で確認します。
    4. 時間をおいて再試行
      DNSサーバーの一時的な問題である可能性もあるため、しばらく待ってから再度試行します。
  • 原因

    • DNSサーバーの障害
      問い合わせ先のDNSサーバーが一時的に応答しないか、何らかの問題を抱えている。
    • ネットワーク接続の問題
      Node.jsアプリケーションがDNSサーバーに到達できない。
    • DNSSECの検証失敗
      ドメインがDNSSECで保護されており、その検証に失敗した場合。
    • 不正なDNS設定
      ドメインのDNS設定自体にエラーがある場合。
  • 発生する状況

    • DNSサーバーが、クエリの処理中に内部エラーを報告した場合。
    • 問い合わせ先のDNSサーバーが一時的にダウンしているか、応答しない場合。
    • DNSSEC検証エラーが発生した場合。
  • エラーメッセージの例

    • Error: queryA SERVFAIL example.com

CONNREFUSED または EADDRNOTAVAIL エラー

  • トラブルシューティング

    1. DNSサーバーアドレスの確認
      dns.setServers() を使用している場合は、IPアドレスが正しいか再確認します。デフォルトのDNSサーバーを使用している場合は、OSのネットワーク設定を確認します。
    2. ファイアウォールの確認
      • ローカルPC/サーバーのファイアウォール設定を確認し、UDPポート53が許可されていることを確認します。
      • ネットワーク機器(ルーター、企業ファイアウォールなど)でDNSクエリがブロックされていないか確認します。
    3. DNSサーバーの稼働状況確認
      指定したDNSサーバーに対して ping コマンドで到達可能性を確認します。可能であれば、そのDNSサーバーが実際に稼働しているかを確認します。
  • 原因

    • 不正なDNSサーバーアドレス
      dns.setServers() で設定したDNSサーバーのIPアドレスが間違っている。
    • ファイアウォール
      ローカルまたはネットワークのファイアウォールが、DNSクエリ(通常はUDPポート53)をブロックしている。
    • DNSサーバーがダウン
      問い合わせ先のDNSサーバーが完全にダウンしているか、サービスが停止している。
    • リソース不足
      サーバーのリソース(ポートなど)が枯渇している。
  • 発生する状況

    • Node.jsアプリケーションがDNSサーバーへの接続を試みたが、接続が拒否された場合。
    • 指定されたDNSサーバーが存在しないか、不正なIPアドレスが指定されている場合。
    • ファイアウォールがDNSクエリをブロックしている場合。
  • エラーメッセージの例

    • Error: queryA CONNREFUSED 192.168.1.1 (DNSサーバーへの接続拒否)
    • Error: queryA EADDRNOTAVAIL 0.0.0.0 (利用可能なアドレスがない)

TIMEOUT エラー

  • トラブルシューティング

    1. より信頼性の高いDNSサーバーの利用
      前述のGoogle Public DNSやCloudflare DNSなど、応答性の良い公開DNSサーバーを試します。
    2. ネットワーク接続の診断
      pingtraceroute (Windowsでは tracert) コマンドを使って、Node.jsアプリケーションからDNSサーバーまでの経路の遅延やパケットロスを確認します。
    3. DNSサーバーの稼働状況と負荷
      問い合わせ先のDNSサーバーが正常に機能しているか、負荷が高すぎないかを確認します。
    4. タイムアウト値の調整
      dns.setResolver() (Node.js v15.0.0以降) または dns.setServers() に関連する設定で、タイムアウト値を調整できる場合がありますが、これは根本的な解決策ではありません。通常、デフォルトのタイムアウトで十分なはずです。
  • 原因

    • ネットワーク遅延
      DNSサーバーへのネットワーク経路に大きな遅延がある。
    • DNSサーバーの応答遅延
      DNSサーバーが負荷過多であるか、処理が遅い。
    • パケットロス
      ネットワーク上でDNSクエリのパケットが失われている。
    • 遠隔地にあるDNSサーバー
      物理的に遠い場所にあるDNSサーバーを使用している。
  • 発生する状況

    • DNSクエリを送信したが、設定されたタイムアウト時間内に応答がなかった場合。
  • エラーメッセージの例

    • Error: queryA Timeout after 5000ms for example.com

EBADSTR エラー

  • トラブルシューティング

    1. ホスト名の検証
      dns.resolve*() メソッドに渡す前に、ホスト名が有効な形式であることを確認します。正規表現などを使用して入力値をサニタイズすると良いでしょう。
  • 原因

    • 不正なホスト名
      ホスト名に許可されていない文字が含まれている、または形式が正しくない。
    • 空のホスト名
      空文字列をホスト名として渡した場合。
  • 発生する状況

    • 無効なホスト名や、不正な形式のドメイン名を渡した場合。
  • エラーメッセージの例

    • Error: queryA EBADSTR
  • dns.lookup() との比較
    IPアドレスを解決したいだけで、特定のレコードタイプにこだわらない場合は、よりOSに依存する dns.lookup() を試してみるのも一つの手です。dns.lookup() は通常、より高速で、ローカルの /etc/hosts ファイルなどの設定も利用します。
  • 環境の確認
    Node.jsが実行されている環境(開発環境、テスト環境、本番環境)のDNS設定、ネットワーク設定、ファイアウォールルールなどが一貫しているか確認します。
  • ログの活用
    エラーメッセージとスタックトレースをログに出力し、問題の特定に役立てます。
  • PromiseベースのAPIの使用
    Node.js v10.0.0 から導入された dns.promises を利用すると、async/await でより現代的で読みやすいコードを書け、エラーハンドリングも容易になります。
    const dnsPromises = require('dns').promises;
    
    async function resolveDomain(domain, type) {
      try {
        const result = await dnsPromises.resolve(domain, type);
        console.log(`${domain}${type} レコード:`, result);
      } catch (err) {
        console.error(`${domain}${type} レコード解決エラー:`, err.message);
      }
    }
    
    resolveDomain('example.com', 'A');
    resolveDomain('nonexist.domain', 'A'); // エラーが発生する例
    
  • エラーハンドリングの徹底
    dns.resolve*() は非同期であり、エラーが発生する可能性があるため、必ずコールバック関数やPromiseの catch ブロックでエラーを適切に処理するようにしてください。


const dns = require('dns'); // コールバックベースのAPI
const dnsPromises = require('dns').promises; // PromiseベースのAPI (Node.js v10.0.0以降)

dns.resolve(hostname[, rrtype], callback): 汎用的な解決

このメソッドは最も汎用的な解決方法で、特定のレコードタイプを指定してDNS情報を取得します。rrtype を省略すると、デフォルトで A レコード(IPv4アドレス)を解決します。

コールバックベースの例

// IPv4アドレス (Aレコード) を解決する例
dns.resolve('example.com', 'A', (err, addresses) => {
  if (err) {
    console.error('Aレコード解決エラー:', err.message);
    return;
  }
  console.log('example.com の IPv4アドレス (Aレコード):', addresses);
  // 出力例: example.com の IPv4アドレス (Aレコード): [ '93.184.216.34' ]
});

// メール交換 (MXレコード) を解決する例
dns.resolve('google.com', 'MX', (err, addresses) => {
  if (err) {
    console.error('MXレコード解決エラー:', err.message);
    return;
  }
  console.log('google.com の MXレコード:', addresses);
  /* 出力例:
  google.com の MXレコード: [
    { priority: 5, exchange: 'alt1.aspmx.l.google.com' },
    { priority: 10, exchange: 'alt2.aspmx.l.google.com' },
    { priority: 5, exchange: 'aspmx.l.google.com' },
    { priority: 50, exchange: 'alt4.aspmx.l.google.com' },
    { priority: 30, exchange: 'alt3.aspmx.l.google.com' }
  ]
  */
});

Promiseベースの例 (dnsPromises.resolve)

async function resolveWithType(hostname, rrtype) {
  try {
    const result = await dnsPromises.resolve(hostname, rrtype);
    console.log(`${hostname}${rrtype} レコード:`, result);
  } catch (err) {
    console.error(`${hostname}${rrtype} レコード解決エラー:`, err.message);
  }
}

resolveWithType('nodejs.org', 'AAAA'); // IPv6アドレス
resolveWithType('google.com', 'TXT'); // テキストレコード
resolveWithType('nonexistent.domain', 'A'); // 存在しないドメインの例 (エラー)

dns.resolve4(hostname[, options], callback): IPv4アドレス解決

A レコードのみを解決するための特化されたメソッドです。

dns.resolve4('github.com', (err, addresses) => {
  if (err) {
    console.error('GitHubのIPv4アドレス解決エラー:', err.message);
    return;
  }
  console.log('GitHubのIPv4アドレス:', addresses);
  // 出力例: GitHubのIPv4アドレス: [ '140.82.113.3' ]
});
async function getIPv4(hostname) {
  try {
    const addresses = await dnsPromises.resolve4(hostname);
    console.log(`${hostname} の IPv4アドレス:`, addresses);
  } catch (err) {
    console.error(`${hostname} の IPv4アドレス解決エラー:`, err.message);
  }
}

getIPv4('wikipedia.org');
dns.resolve6('ipv6.google.com', (err, addresses) => {
  if (err) {
    console.error('IPv6 Googleアドレス解決エラー:', err.message);
    return;
  }
  console.log('IPv6 Googleアドレス:', addresses);
  // 出力例: IPv6 Googleアドレス: [ '2607:f8b0:4004:803::200e' ]
});
async function getIPv6(hostname) {
  try {
    const addresses = await dnsPromises.resolve6(hostname);
    console.log(`${hostname} の IPv6アドレス:`, addresses);
  } catch (err) {
    console.error(`${hostname} の IPv6アドレス解決エラー:`, err.message);
  }
}

getIPv6('facebook.com');

dns.resolveMx(hostname, callback): メール交換 (MX) レコード解決

メールサーバーの情報を取得します。

dns.resolveMx('microsoft.com', (err, addresses) => {
  if (err) {
    console.error('Microsoft MXレコード解決エラー:', err.message);
    return;
  }
  console.log('Microsoft の MXレコード:', addresses);
  /* 出力例:
  Microsoft の MXレコード: [
    { priority: 10, exchange: 'microsoft-com.mail.protection.outlook.com' }
  ]
  */
});
async function getMxRecords(hostname) {
  try {
    const mxRecords = await dnsPromises.resolveMx(hostname);
    console.log(`${hostname} の MXレコード:`, mxRecords);
  } catch (err) {
    console.error(`${hostname} の MXレコード解決エラー:`, err.message);
  }
}

getMxRecords('example.org');

dns.resolveNs(hostname, callback): ネームサーバー (NS) レコード解決

ドメインの権威ネームサーバーの情報を取得します。

dns.resolveNs('nodejs.org', (err, addresses) => {
  if (err) {
    console.error('Node.js NSレコード解決エラー:', err.message);
    return;
  }
  console.log('Node.js の NSレコード:', addresses);
  /* 出力例:
  Node.js の NSレコード: [
    'ns1.p31.dynect.net',
    'ns3.p31.dynect.net',
    'ns4.p31.dynect.net',
    'ns2.p31.dynect.net'
  ]
  */
});
async function getNsRecords(hostname) {
  try {
    const nsRecords = await dnsPromises.resolveNs(hostname);
    console.log(`${hostname} の NSレコード:`, nsRecords);
  } catch (err) {
    console.error(`${hostname} の NSレコード解決エラー:`, err.message);
  }
}

getNsRecords('apple.com');

dns.resolveTxt(hostname, callback): テキスト (TXT) レコード解決

SPF、DKIM、DMARCなどのテキスト情報を取得します。

dns.resolveTxt('google.com', (err, addresses) => {
  if (err) {
    console.error('Google TXTレコード解決エラー:', err.message);
    return;
  }
  console.log('Google の TXTレコード:', addresses);
  /* 出力例 (一部):
  Google の TXTレコード: [
    [ 'v=spf1 include:_spf.google.com ~all' ],
    [ 'google-site-verification=some_code' ],
    [ 'docusign=some_value' ]
    // ...
  ]
  */
});
async function getTxtRecords(hostname) {
  try {
    const txtRecords = await dnsPromises.resolveTxt(hostname);
    console.log(`${hostname} の TXTレコード:`, txtRecords);
  } catch (err) {
    console.error(`${hostname} の TXTレコード解決エラー:`, err.message);
  }
}

getTxtRecords('cloudflare.com');

dns.resolveSrv(hostname, callback): サービス (SRV) レコード解決

特定のサービスを提供しているサーバーのホスト名、ポート、優先度などを取得します。Microsoft Active Directoryなどでよく使用されます。

// SIPサービス(例)のSRVレコードを解決する一般的なパターン
// _service._proto.name.domain
dns.resolveSrv('_sip._tcp.example.com', (err, addresses) => {
  if (err) {
    console.error('SIP SRVレコード解決エラー:', err.message);
    // ほとんどの公開ドメインでは存在しないため、エラーが出ることが多い
    return;
  }
  console.log('SIP SRVレコード:', addresses);
  /* 出力例 (存在する場合):
  SIP SRVレコード: [
    { name: 'sip.example.com', port: 5060, priority: 10, weight: 100 }
  ]
  */
});
async function getSrvRecords(hostname) {
  try {
    const srvRecords = await dnsPromises.resolveSrv(hostname);
    console.log(`${hostname} の SRVレコード:`, srvRecords);
  } catch (err) {
    console.error(`${hostname} の SRVレコード解決エラー:`, err.message);
  }
}

// 例として、一部のVoIPサービスなどで使用されるSRVレコードを想定
getSrvRecords('_xmpp-client._tcp.google.com');

dns.resolveCname(hostname, callback): 正規名 (CNAME) レコード解決

ホスト名が別のホスト名のエイリアスであることを示します。

dns.resolveCname('www.example.com', (err, addresses) => {
  if (err) {
    console.error('CNAMEレコード解決エラー:', err.message);
    // www が CNAME ではない場合、エラーになることがあります
    return;
  }
  console.log('www.example.com の CNAMEレコード:', addresses);
  // 出力例: www.example.com の CNAMEレコード: [ 'example.com' ]
});

Promiseベースの例 (dnsPromises.resolveCname)

async function getCnameRecords(hostname) {
  try {
    const cnameRecords = await dnsPromises.resolveCname(hostname);
    console.log(`${hostname} の CNAMEレコード:`, cnameRecords);
  } catch (err) {
    console.error(`${hostname} の CNAMEレコード解決エラー:`, err.message);
  }
}

getCnameRecords('mail.google.com'); // 通常はCNAMEレコードを返します

指定されたホスト名に関連付けられたすべてのリソースレコードを解決します。

dns.resolveAny('example.com', (err, addresses) => {
  if (err) {
    console.error('Anyレコード解決エラー:', err.message);
    return;
  }
  console.log('example.com の すべてのレコード:', addresses);
  /* 出力例:
  example.com の すべてのレコード: [
    { address: '93.184.216.34', ttl: 86400, type: 'A' },
    { priority: 0, exchange: 'mail.example.com', type: 'MX' }, // MXレコードは通常このような形式
    { data: [ 'v=spf1 -all' ], type: 'TXT' }
    // ...
  ]
  */
});
async function getAllRecords(hostname) {
  try {
    const allRecords = await dnsPromises.resolveAny(hostname);
    console.log(`${hostname} の すべてのレコード:`, allRecords);
  } catch (err) {
    console.error(`${hostname} の すべてのレコード解決エラー:`, err.message);
  }
}

getAllRecords('amazon.com');


これは最も一般的な代替手段であり、dns.resolve*() とは異なる動作をします。