Node.js 非同期処理: dns.resolveSoa() を使ったDNS情報取得のベストプラクティス

2025-05-16

dns.resolveSoa() は、Node.jsの dns モジュールに含まれる非同期関数の一つです。この関数は、指定されたドメイン名の SOA (Start of Authority) レコード をDNSサーバーに問い合わせ、その結果を返します。

SOAレコードとは?

SOAレコードは、DNSゾーンに関する権威情報を含むDNSリソースレコードです。各DNSゾーンには、必ず1つのSOAレコードが存在します。SOAレコードには、主に以下のような情報が含まれています。

  • 最小TTL (Minimum TTL)
    このゾーン内のリソースレコードがキャッシュされる最小時間。
  • 有効期限 (Expire Limit)
    セカンダリDNSサーバーがプライマリDNSサーバーから応答を得られなくなった場合に、そのゾーン情報を無効と見なすまでの時間。
  • リトライ間隔 (Retry Interval)
    セカンダリDNSサーバーがプライマリDNSサーバーへの接続に失敗した場合、再試行するまでの待機時間。
  • リフレッシュ間隔 (Refresh Interval)
    セカンダリDNSサーバーがプライマリDNSサーバーにゾーン情報の更新を確認する頻度。
  • シリアル番号 (Serial Number)
    ゾーンファイルが更新されるたびに増加する番号。セカンダリDNSサーバーがゾーン転送が必要かどうかを判断するために使用されます。
  • 責任者のメールアドレス (Responsible Person's Mailbox)
    そのゾーンの管理者のメールアドレス(@は.に置き換えられています)。
  • プライマリネームサーバー (Primary Name Server)
    そのゾーンの権威を持つ主要なDNSサーバーのホスト名。

dns.resolveSoa() の使い方

dns.resolveSoa() 関数は、コールバック関数またはPromiseを使用して結果を受け取ることができます。

コールバック関数を使用する場合

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

dns.resolveSoa('example.com', (err, record) => {
  if (err) {
    console.error('SOAレコードの解決に失敗しました:', err);
    return;
  }
  console.log('SOAレコード:', record);
  // record オブジェクトには、SOAレコードの各フィールドが含まれています。
  console.log('プライマリネームサーバー:', record.nsname);
  console.log('責任者のメールアドレス:', record.hostmaster);
  console.log('シリアル番号:', record.serial);
  console.log('リフレッシュ間隔:', record.refresh);
  console.log('リトライ間隔:', record.retry);
  console.log('有効期限:', record.expire);
  console.log('最小TTL:', record.minttl);
});

Promiseを使用する場合 (Node.js 15.0.0 以降)

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

async function getSoaRecord() {
  try {
    const record = await dns.resolveSoa('example.com');
    console.log('SOAレコード:', record);
    console.log('プライマリネームサーバー:', record.nsname);
    console.log('責任者のメールアドレス:', record.hostmaster);
    // ... 他のフィールド
  } catch (err) {
    console.error('SOAレコードの解決に失敗しました:', err);
  }
}

getSoaRecord();

引数

  • 第2引数 (コールバック関数を使用する場合):
    • err (Error | null) - エラーが発生した場合にエラーオブジェクトが渡されます。成功した場合は null
    • record (Object) - SOAレコードの情報を含むオブジェクト。
  • 第1引数: hostname (文字列) - 解決したいドメイン名。

戻り値 (Promiseを使用する場合)

  • 失敗した場合: エラーオブジェクトで拒否されるPromise。
  • 成功した場合: SOAレコードの情報を含むオブジェクトを解決するPromise。

利用場面

dns.resolveSoa() は、以下のような場合に利用できます。

  • ネットワーク関連の診断やトラブルシューティングを行う場合。
  • DNSサーバーの構成や管理情報を確認するツールを開発する場合。
  • 特定のドメインのDNSゾーンに関する基本的な情報をプログラムで取得したい場合。


Error: getaddrinfo ENOTFOUND <hostname>

  • トラブルシューティング
    • 指定したドメイン名が正しいかどうか、スペルミスがないかなどを確認してください。
    • インターネット接続が正常に機能しているか確認してください。他のウェブサイトにアクセスできるかなどを試すと良いでしょう。
    • 別のDNS解決方法(例えば dns.lookup()ping コマンド)で同じホスト名を試してみて、DNSサーバーの問題かどうかを切り分けてください。
    • もし特定のDNSサーバーを使用している場合は、そのサーバーの設定や状態を確認してください。
  • 原因
    • 指定したドメイン名が間違っている、または存在しない。
    • ネットワーク接続に問題があり、DNSサーバーにアクセスできない。
    • DNSサーバー自体に問題が発生している。
  • エラー内容
    指定されたホスト名が見つからなかった場合に発生します。DNSサーバーがそのホスト名に対応するIPアドレスを解決できなかったことを意味します。

Error: queryA ENODATA <hostname> (または類似のエラー)

  • トラブルシューティング
    • 別のDNS解決ツール(dignslookup コマンドなど)を使用して、同じドメインの SOA レコードを問い合わせてみてください。もし他のツールでもエラーが発生する場合は、DNSサーバー側の問題である可能性が高いです。
    • DNSサーバーの管理者またはプロバイダーに連絡して、状況を確認してもらう必要があります。
  • 原因
    • DNSサーバーのゾーンファイルが破損しているか、正しく構成されていない。
    • DNSサーバーソフトウェアの不具合。
  • エラー内容
    DNSサーバーはホスト名を見つけられたものの、要求されたレコードタイプ(この場合は SOA レコード)が存在しない場合に発生することがあります。しかし、SOA レコードはすべての DNS ゾーンに必須であるため、このエラーが dns.resolveSoa() で直接発生することは稀です。より一般的なのは、他のレコードタイプ (A, MX など) の解決で発生するエラーです。もしこのエラーが dns.resolveSoa() で発生する場合は、DNSサーバーの構成に深刻な問題がある可能性があります。

Error: timeout (または Error: ESOCKETTIMEDOUT)

  • トラブルシューティング
    • ネットワーク接続を確認し、安定しているかを確認してください。
    • 別のDNSサーバー(例えば Google Public DNS の 8.8.8.8 や Cloudflare DNS の 1.1.1.1)を使用するように設定を変更して、問題が特定のDNSサーバーにあるかどうかを試してみてください。Node.js の dns モジュールで使用するDNSサーバーは、通常はシステムのDNS設定に従います。
    • ファイアウォールやルーターの設定を確認し、DNSリクエストがブロックされていないか確認してください。
  • 原因
    • ネットワークの遅延や不安定さ。
    • DNSサーバーが過負荷状態であるか、応答に時間がかかっている。
    • ファイアウォールがDNSリクエスト(通常は UDP/TCP ポート 53)をブロックしている。
  • エラー内容
    DNSサーバーへの問い合わせがタイムアウトした場合に発生します。
  • エラーオブジェクトには、エラーコード (err.code) や詳細なメッセージ (err.message) が含まれているため、これらを手がかりに原因を調査してください。
  • 上記以外にも、ネットワーク環境やDNSサーバーの状態によって様々なエラーが発生する可能性があります。エラーメッセージをよく読み、具体的な状況を把握することが重要です。
  • Node.js のバージョンを確認する
    まれに、Node.js の特定のバージョンに起因する問題が存在する可能性があります。最新の安定版にアップデートしてみるのも有効な場合があります。
  • ファイアウォールやセキュリティソフトの設定を確認する
    DNSリクエストがファイアウォールやセキュリティソフトによってブロックされていないか確認してください。
  • 異なるDNSサーバーを試す
    システムのDNS設定を変更するか、一時的に別のDNSサーバーを指定して、問題が特定のDNSサーバーにあるかどうかを確認してください。
  • 別のDNS解決ツールを試す
    dignslookupping などのコマンドラインツールを使用して、DNSの解決を試してみることで、問題が Node.js の dns.resolveSoa() 特有のものなのか、それともより一般的なDNSの問題なのかを切り分けることができます。
  • ネットワーク接続を確認する
    DNS解決はネットワークに依存するため、ネットワーク接続が正常であることを確認してください。
  • エラーメッセージを注意深く読む
    エラーメッセージには、問題の原因を示唆する重要な情報が含まれています。


基本的な SOA レコードの解決 (コールバック関数を使用)

この例では、example.com の SOA レコードを非同期的に解決し、その結果をコンソールに出力します。

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

dns.resolveSoa('example.com', (err, record) => {
  if (err) {
    console.error('SOAレコードの解決に失敗しました:', err);
    return;
  }
  console.log('example.com の SOA レコード:');
  console.log('プライマリネームサーバー (nsname):', record.nsname);
  console.log('責任者のメールアドレス (hostmaster):', record.hostmaster);
  console.log('シリアル番号 (serial):', record.serial);
  console.log('リフレッシュ間隔 (refresh):', record.refresh);
  console.log('リトライ間隔 (retry):', record.retry);
  console.log('有効期限 (expire):', record.expire);
  console.log('最小TTL (minttl):', record.minttl);
});

console.log('SOAレコードの解決を要求しました...');

このコードを実行すると、「SOAレコードの解決を要求しました...」が最初に表示され、その後(非同期処理が完了した後)、example.com の SOA レコードの詳細が出力されます。エラーが発生した場合は、エラーメッセージが表示されます。

基本的な SOA レコードの解決 (Promise を使用)

Node.js 15.0.0 以降では、Promise ベースの API を使用することもできます。

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

async function getSoaRecord() {
  try {
    const record = await dns.resolveSoa('example.com');
    console.log('example.com の SOA レコード:');
    console.log('プライマリネームサーバー (nsname):', record.nsname);
    console.log('責任者のメールアドレス (hostmaster):', record.hostmaster);
    console.log('シリアル番号 (serial):', record.serial);
    console.log('リフレッシュ間隔 (refresh):', record.refresh);
    console.log('リトライ間隔 (retry):', record.retry);
    console.log('有効期限 (expire):', record.expire);
    console.log('最小TTL (minttl):', record.minttl);
  } catch (err) {
    console.error('SOAレコードの解決に失敗しました:', err);
  }
}

getSoaRecord();
console.log('SOAレコードの解決を要求しました...');

この例では、async/await 構文を使用して非同期処理をより同期的なスタイルで記述しています。try...catch ブロックでエラーを処理します。

複数のドメインの SOA レコードを解決する

複数のドメインの SOA レコードを同時に解決したい場合は、Promise.all を使用できます。

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

async function getMultipleSoaRecords(domains) {
  try {
    const results = await Promise.all(domains.map(domain => dns.resolveSoa(domain)));
    results.forEach((record, index) => {
      console.log(`${domains[index]} の SOA レコード:`);
      console.log('プライマリネームサーバー:', record.nsname);
      console.log('責任者のメールアドレス:', record.hostmaster);
      // ... 他のフィールド
    });
  } catch (err) {
    console.error('SOAレコードの解決中にエラーが発生しました:', err);
  }
}

const domainsToResolve = ['example.com', 'google.com', 'nodejs.org'];
getMultipleSoaRecords(domainsToResolve);

このコードは、指定されたドメインの配列に対して dns.resolveSoa() を実行し、すべての Promise が完了したら結果を出力します。

エラー処理の例

SOA レコードの解決に失敗した場合のエラー処理を示す例です。

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

async function getSoaRecordWithErrorHandling(domain) {
  try {
    const record = await dns.resolveSoa(domain);
    console.log(`${domain} の SOA レコード:`, record);
  } catch (err) {
    console.error(`${domain} の SOA レコードの解決に失敗しました:`, err.message);
    if (err.code === 'ENOTFOUND') {
      console.log('ホスト名が見つかりませんでした。');
    } else if (err.code === 'TIMEOUT') {
      console.log('DNSサーバーへの問い合わせがタイムアウトしました。');
    } else {
      console.log('その他のエラー:', err.code);
    }
  }
}

getSoaRecordWithErrorHandling('invalid-domain-example-that-does-not-exist.xyz');
getSoaRecordWithErrorHandling('example.com');


dns.resolve() 関数とレコードタイプ 'SOA' を使用する

dns.resolve() 関数は、指定されたホスト名とレコードタイプに対して DNS クエリを実行し、その結果を配列で返します。SOA レコードの場合、配列には1つのオブジェクトが含まれます。

コールバック関数を使用する場合

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

dns.resolve('example.com', 'SOA', (err, records) => {
  if (err) {
    console.error('SOAレコードの解決に失敗しました:', err);
    return;
  }
  if (records && records.length > 0) {
    const record = records[0];
    console.log('example.com の SOA レコード:');
    console.log('プライマリネームサーバー (nsname):', record.nsname);
    console.log('責任者のメールアドレス (hostmaster):', record.hostmaster);
    console.log('シリアル番号 (serial):', record.serial);
    console.log('リフレッシュ間隔 (refresh):', record.refresh);
    console.log('リトライ間隔 (retry):', record.retry);
    console.log('有効期限 (expire):', record.expire);
    console.log('最小TTL (minttl):', record.minttl);
  } else {
    console.log('SOAレコードが見つかりませんでした。');
  }
});

Promise を使用する場合

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

async function getSoaRecordWithResolve() {
  try {
    const records = await dns.resolve('example.com', 'SOA');
    if (records && records.length > 0) {
      const record = records[0];
      console.log('example.com の SOA レコード:');
      console.log('プライマリネームサーバー (nsname):', record.nsname);
      console.log('責任者のメールアドレス (hostmaster):', record.hostmaster);
      // ... 他のフィールド
    } else {
      console.log('SOAレコードが見つかりませんでした。');
    }
  } catch (err) {
    console.error('SOAレコードの解決に失敗しました:', err);
  }
}

getSoaRecordWithResolve();

利点

  • dns.resolve() は様々なレコードタイプに対応しており、一貫したAPIで使用できます。

欠点

  • 型安全性の観点からは、dns.resolveSoa() の方が SOA レコードの構造を明確に示しています。
  • 結果が配列で返されるため、SOA レコードの場合でもインデックス [0] でアクセスする必要があります。

外部の DNS クライアントライブラリを使用する

Node.js の標準の dns モジュール以外にも、より高度な機能や柔軟性を提供するサードパーティの DNS クライアントライブラリが存在します。例えば、dns-packetnode-dns などがあります。これらのライブラリを使用すると、より低レベルな DNS プロトコルを操作したり、より複雑なクエリやレスポンスの処理が可能になります。

例 (概念的なもの)

// 例: node-dns ライブラリを使用する場合 (インストールが必要: npm install node-dns)
const dns = require('node-dns');

const query = dns.Request({
  question: dns.Question({ name: 'example.com', type: 'SOA' }),
});

query.send().then(response => {
  const answer = response.answer.find(a => a.type === dns.consts.NAME_TO_QTYPE.SOA);
  if (answer) {
    console.log('example.com の SOA レコード:', answer.data);
    // answer.data に SOA レコードの各フィールドが含まれます
  } else {
    console.log('SOAレコードが見つかりませんでした。');
  }
}).catch(err => {
  console.error('DNSクエリに失敗しました:', err);
});

利点

  • DNS プロトコルの深い理解がある場合には、より効率的な実装や特殊な要件への対応が可能です。
  • より細やかな制御が可能になり、標準モジュールでは提供されない機能を利用できる場合があります。

欠点

  • ライブラリのメンテナンス状況や信頼性を考慮する必要があります。
  • 標準モジュールよりも学習コストが高い場合があります。
  • 外部ライブラリへの依存性が増えます。

OS のコマンドラインツールを実行する

child_process モジュールを使用して、dignslookup などの OS に組み込まれている DNS lookup ツールを実行し、その出力を解析する方法も考えられます。

const { exec } = require('node:child_process');

exec('dig soa example.com', (error, stdout, stderr) => {
  if (error) {
    console.error('コマンド実行エラー:', error);
    return;
  }
  if (stderr) {
    console.error('エラー出力:', stderr);
    return;
  }
  // stdout を解析して SOA レコードの情報を抽出する
  const lines = stdout.split('\n');
  let soaInfo = {};
  for (const line of lines) {
    if (line.includes('IN SOA')) {
      const parts = line.split(/\s+/);
      soaInfo.nsname = parts[5];
      soaInfo.hostmaster = parts[6];
      soaInfo.serial = parseInt(parts[7], 10);
      soaInfo.refresh = parseInt(parts[8], 10);
      soaInfo.retry = parseInt(parts[9], 10);
      soaInfo.expire = parseInt(parts[10], 10);
      soaInfo.minttl = parseInt(parts[11], 10);
      break;
    }
  }
  if (soaInfo) {
    console.log('example.com の SOA レコード:', soaInfo);
  } else {
    console.log('SOAレコードが見つかりませんでした。');
  }
});

利点

  • OS 標準のツールであるため、信頼性が高いと考えられます。
  • ほとんどの環境で追加のライブラリなしで利用できます。

欠点

  • セキュリティ上の考慮が必要となる場合があります(特にユーザーからの入力をコマンドに含める場合)。
  • エラー処理や結果の抽出が煩雑になることがあります。
  • 出力のフォーマットが OS やツールのバージョンによって異なる可能性があり、解析が複雑になる場合があります。

dns.resolveSoa() は SOA レコードを直接的かつ型安全に取得するための便利な関数であり、通常はこの関数を使用するのが最も簡潔で推奨される方法です。

代替案としては、より汎用的な dns.resolve() 関数を使用する方法や、より高度な制御が必要な場合には外部の DNS クライアントライブラリを検討する方法があります。OS のコマンドラインツールを実行する方法は、最後の手段として考えられます。