開発者が知るべきNode.js DNS:getDefaultResultOrder()によるIPアドレス制御と最適化

2025-05-27

なぜこの関数が必要なのか?

DNSは、あるドメイン名(例: example.com)に対して、複数のIPアドレス(IPv4アドレスとIPv6アドレスの両方、またはそれぞれ複数のアドレス)を返すことがあります。例えば、ウェブサイトが複数のサーバーでホストされている場合や、IPv4とIPv6の両方に対応している場合などです。

dns.lookup()のようなNode.jsのDNS解決関数は、これらのIPアドレスをどのようにアプリケーションに返すかについて、デフォルトの順序を持っています。dns.getDefaultResultOrder()は、このデフォルトの順序が現在どのように設定されているかを確認するために使われます。

返される値

この関数は以下のいずれかの文字列を返します。

  • 'verbatim': IPアドレスが解決された際、DNSリゾルバーが返した順序そのままにIPアドレスが返されることを示します。通常、この場合、システムが提供するデフォルトの順序(OSの設定などに基づく)が適用されます。
  • 'ipv4first': IPアドレスが解決された際、IPv4アドレスがIPv6アドレスよりも優先して返されることを示します。
const dns = require('node:dns');

const defaultOrder = dns.getDefaultResultOrder();
console.log(`現在のデフォルトのDNS結果の順序: ${defaultOrder}`);

// 必要であれば、デフォルトの順序を変更することもできます
// dns.setDefaultResultOrder('ipv4first'); // IPv4を優先する設定にする
// console.log(`新しいデフォルトのDNS結果の順序: ${dns.getDefaultResultOrder()}`);
  • dns.setDefaultResultOrder()を使って、このデフォルトの順序をプログラム的に変更することもできます。また、Node.jsの起動時に--dns-result-orderフラグを使って設定することも可能です。
  • Node.js 16.4.0以降で利用可能です。
  • この設定は、dns.lookup()dnsPromises.lookup()といった関数に影響を与えます。dns.resolve*系の関数(例: dns.resolve4, dns.resolve6)は、特定のレコードタイプ(IPv4のみ、IPv6のみなど)を明示的に解決するため、この設定の影響を受けません。


dns.getDefaultResultOrder()自体に起因する問題(稀)

  • Node.jsのバージョンが古い
    dns.getDefaultResultOrder()はNode.js 16.4.0以降で導入されました。それ以前のバージョンを使用している場合、この関数は存在せず、TypeErrorなどのエラーが発生します。
    • トラブルシューティング
      Node.jsのバージョンを更新する。

デフォルトの順序が期待と異なることによる問題

dns.getDefaultResultOrder()が返す値は正しいが、その値がアプリケーションの期待する動作と異なっている場合に、間接的に問題が発生することがあります。

    • 現象
      アプリケーションが特定のIPバージョン(例えばIPv4)での接続を期待しているのに、dns.lookup()などがIPv6アドレスを先に返してしまうため、接続に失敗したり、遅延が発生したりする。
    • 原因
      dns.getDefaultResultOrder()'verbatim'または'ipv6first'を返しているにもかかわらず、アプリケーションがIPv4のみを想定している場合。特に、古いネットワークインフラやIPv6のルーティングに問題がある環境で発生しやすいです。
    • トラブルシューティング
      • dns.setDefaultResultOrder('ipv4first') を使用して、明示的にIPv4を優先するように設定する。
      • dns.lookup()のオプションに{ family: 4 }を指定して、IPv4のみを解決するようにする。
      • アプリケーションがIPv4とIPv6の両方に対応できるように設計されているか確認する。
  1. DNS結果の順序の不整合

    • 現象
      開発環境と本番環境でDNS解決の順序が異なり、動作が不安定になる。
    • 原因
      開発環境では'verbatim'(OSのデフォルト順)が設定されているが、本番環境では'ipv4first'が設定されている、あるいはその逆の場合など。OSやNode.jsのバージョン、起動オプションによってデフォルトの挙動が異なることがあります。
    • トラブルシューティング
      • dns.setDefaultResultOrder()を明示的に使用して、すべての環境で同じ順序を設定する。
      • Node.jsの起動時に--dns-result-orderフラグを使用して、環境間で一貫した設定を強制する。

dns.getDefaultResultOrder()自体は直接DNS解決を行うわけではありませんが、DNS解決の順序が関係するため、DNS解決における一般的なエラーも関連してきます。

  1. ENOTFOUND (Name not found)

    • 現象
      ホスト名が見つからないエラー。DNSサーバーがそのドメイン名を解決できなかったことを意味します。
    • 原因
      • タイプミス: ドメイン名が間違っている。
      • ドメインが存在しない: 登録されていない、または期限切れのドメイン。
      • DNSサーバーの問題: 設定されたDNSサーバーが正しく機能していない、または到達できない。
      • ネットワークの問題: DNSサーバーへの接続ができない。
      • ローカルのhostsファイルにエントリがない。
    • トラブルシューティング
      • ドメイン名が正しいか再確認する。
      • pingコマンドやnslookup/digコマンド(Windows/Linux/macOS)で、OSレベルでのDNS解決を試す。
      • DNSサーバーの設定(/etc/resolv.confなど)を確認する。
      • ファイアウォールがDNSポート(UDP/TCP 53)をブロックしていないか確認する。
      • 一時的な問題の可能性もあるため、少し待ってから再試行する。
  2. ETIMEOUT (Connection timeout)

    • 現象
      DNSサーバーからの応答がタイムアウトした。
    • 原因
      • DNSサーバーがダウンしている、または過負荷。
      • ネットワークの遅延や混雑。
      • ファイアウォールがDNSクエリをブロックしている。
    • トラブルシューティング
      • 他のDNSサーバー(例: Google Public DNS 8.8.8.8)を試してみる。
      • ネットワーク接続を確認する。
      • ファイアウォール設定を確認する。
      • Node.jsのDNSリクエストのタイムアウト値を調整する(dns.lookupのオプションなど)。
  3. EAI_AGAIN (Temporary failure in name resolution)

    • 現象
      DNS解決が一時的に失敗したことを示すエラー。通常は再試行することで解決する可能性があります。
    • 原因
      • DNSサーバーの一時的な過負荷または不安定。
      • ネットワークの一時的な問題。
    • トラブルシューティング
      • リトライロジックを実装する。
      • DNSサーバーの負荷状況を確認する。
  4. ECONNREFUSED (Connection refused)

    • 現象
      DNSサーバーへの接続が拒否された。
    • 原因
      • DNSサーバーが起動していない。
      • DNSサーバーが設定されたポートでリッスンしていない。
      • ファイアウォールが接続を積極的に拒否している。
    • トラブルシューティング
      • DNSサーバーの状態を確認する。
      • ファイアウォール設定を確認する。
  • ネットワーク設定の確認
    • サーバーが正しいDNSサーバーを参照しているか(/etc/resolv.confなど)。
    • インターネット接続が正常か。
    • ファイアウォールやセキュリティグループの設定。
  • DNSキャッシュ
    • Node.js自体はデフォルトでDNSキャッシュを行いません。OSレベルのキャッシュやネットワーク機器のキャッシュが影響することがあります。
    • 古いDNSエントリがキャッシュされている場合、意図しないIPアドレスに接続しようとすることがあります。
    • トラブルシューティング
      OSのDNSキャッシュをクリアする。
  • dns.lookup()とdns.resolve()の違いを理解する
    • dns.lookup()はOSのDNSリゾルバーを使用し、/etc/hostsファイルなどのOSレベルの設定の影響を受けます。
    • dns.resolve()はNode.jsに組み込まれているDNSクライアント(c-aresライブラリ)を使用し、OSのDNS設定とは独立して動作します。特定のDNSレコードタイプ(A、AAAAなど)を直接問い合わせます。
    • 問題が発生した場合、どちらの関数を使用しているかを確認し、もう一方を試してみることで、問題の切り分けができることがあります。


現在のデフォルトのDNS結果の順序を取得する

これは最も基本的な使用例です。

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

// 現在のデフォルトのDNS結果の順序を取得
const defaultOrder = dns.getDefaultResultOrder();

console.log(`現在のデフォルトのDNS結果の順序: ${defaultOrder}`);

// 例:
// 環境によっては 'verbatim' や 'ipv4first' が表示されます。
// Node.jsのバージョンやOSの設定によって異なります。

デフォルトのDNS結果の順序を変更する

dns.setDefaultResultOrder()を使って、DNS解決の順序をプログラムで変更できます。これはNode.jsアプリケーション全体に影響します。

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

console.log(`変更前のデフォルトのDNS結果の順序: ${dns.getDefaultResultOrder()}`);

// IPv4を優先するように設定
dns.setDefaultResultOrder('ipv4first');
console.log(`変更後のデフォルトのDNS結果の順序 (ipv4first): ${dns.getDefaultResultOrder()}`);

// 元に戻す(または 'verbatim' に設定)
dns.setDefaultResultOrder('verbatim');
console.log(`変更後のデフォルトのDNS結果の順序 (verbatim): ${dns.getDefaultResultOrder()}`);

// 存在しないオプションを設定しようとするとエラーになります
try {
    dns.setDefaultResultOrder('invalid_order');
} catch (error) {
    console.error(`エラー: ${error.message}`);
}

注意点
setDefaultResultOrder()は一度設定すると、そのNode.jsプロセスのライフサイクル中は有効です。特に理由がない限り、むやみに変更すべきではありません。アプリケーションの起動時に一度設定するなどの使い方が一般的です。

dns.lookup()とデフォルトの順序の関連性を見る

dns.lookup()は、dns.getDefaultResultOrder()で設定された順序の影響を直接受けます。以下の例では、同じホスト名に対して順序を変更しながら解決を試みます。

事前に準備

多くのウェブサイトはIPv4とIPv6の両方に対応しています。例えば、google.comなどは両方のアドレスを持っています。ただし、実行環境によってはIPv6ネットワークに接続されていない場合、IPv6アドレスは解決されないことがあります。

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

async function lookupAndLog(hostname, orderName) {
    console.log(`\n--- ホスト名: ${hostname} (${orderName} 設定) ---`);
    try {
        const results = await dns.promises.lookup(hostname, { all: true });
        results.forEach(result => {
            console.log(`  アドレス: ${result.address}, ファミリー: IPv${result.family}`);
        });
    } catch (error) {
        console.error(`  DNS解決エラー: ${error.message}`);
    }
}

async function runExample() {
    const hostname = 'google.com'; // IPv4とIPv6の両方を持つ可能性が高いホスト名

    // 1. デフォルトの順序でルックアップ
    const initialOrder = dns.getDefaultResultOrder();
    await lookupAndLog(hostname, `初期設定 (${initialOrder})`);

    // 2. IPv4を優先する設定でルックアップ
    dns.setDefaultResultOrder('ipv4first');
    await lookupAndLog(hostname, 'IPv4優先');

    // 3. 'verbatim' (DNSリゾルバーの順序) でルックアップ
    dns.setDefaultResultOrder('verbatim');
    await lookupAndLog(hostname, 'Verbatim');

    // 元の順序に戻す(もしあれば)
    dns.setDefaultResultOrder(initialOrder);
    console.log(`\n元のデフォルトのDNS結果の順序に戻しました: ${dns.getDefaultResultOrder()}`);
}

runExample();

実行結果の例(環境によって異なります)

--- ホスト名: google.com (初期設定 (verbatim) 設定) ---
  アドレス: 2404:6800:4004:812::200e, ファミリー: IPv6
  アドレス: 142.250.199.174, ファミリー: IPv4

--- ホスト名: google.com (IPv4優先 設定) ---
  アドレス: 142.250.199.174, ファミリー: IPv4
  アドレス: 2404:6800:4004:812::200e, ファミリー: IPv6

--- ホスト名: google.com (Verbatim 設定) ---
  アドレス: 2404:6800:4004:812::200e, ファミリー: IPv6
  アドレス: 142.250.199.174, ファミリー: IPv4

元のデフォルトのDNS結果の順序に戻しました: verbatim

この例からわかるように、dns.setDefaultResultOrder()を変更することで、dns.lookup()が返すIPアドレスの順序が変わることが確認できます。これは、アプリケーションがIPv4またはIPv6のどちらかのプロトコルを優先して通信を確立しようとする場合に重要です。

dns.getDefaultResultOrder()とは直接関係ありませんが、特定のIPバージョンのみを解決したい場合は、dns.lookup()のオプションを使う方がより制御しやすいです。この場合、デフォルトの順序設定は無視されます。

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

async function lookupSpecificFamily(hostname) {
    console.log(`\n--- ホスト名: ${hostname} (IPv4のみ) ---`);
    try {
        const results = await dns.promises.lookup(hostname, { family: 4, all: true });
        results.forEach(result => {
            console.log(`  アドレス: ${result.address}, ファミリー: IPv${result.family}`);
        });
    } catch (error) {
        console.error(`  DNS解決エラー: ${error.message}`);
    }

    console.log(`\n--- ホスト名: ${hostname} (IPv6のみ) ---`);
    try {
        const results = await dns.promises.lookup(hostname, { family: 6, all: true });
        results.forEach(result => {
            console.log(`  アドレス: ${result.address}, ファミリー: IPv${result.family}`);
        });
    } catch (error) {
        console.error(`  DNS解決エラー: ${error.message}`);
    }
}

lookupSpecificFamily('google.com');

実行結果の例

--- ホスト名: google.com (IPv4のみ) ---
  アドレス: 142.250.199.174, ファミリー: IPv4

--- ホスト名: google.com (IPv6のみ) ---
  アドレス: 2404:6800:4004:812::200e, ファミリー: IPv6

このように、dns.lookup()familyオプションを使用すると、getDefaultResultOrder()の設定とは関係なく、指定したIPファミリーのみを解決できます。



dns.lookup()の family オプションを使用する

これは、dns.getDefaultResultOrder()の挙動をオーバーライドし、特定のDNS解決リクエストに対して、特定のIPバージョン(IPv4またはIPv6)のみを解決させたい場合に最も直接的で推奨される方法です。

  • 欠点
    • 複数のdns.lookup()呼び出しがある場合、それぞれの呼び出しでオプションを指定する必要があります。
  • 利点
    • 個々のDNS解決リクエストに対して、柔軟にIPバージョンを制御できます。
    • グローバルなデフォルト設定に影響を与えません。
  • 説明
    dns.lookup()関数にoptionsオブジェクトを渡し、その中にfamilyプロパティを設定します。
    • family: 4 を指定すると、IPv4アドレスのみを返します。
    • family: 6 を指定すると、IPv6アドレスのみを返します。
    • family: 0 (または未指定) の場合、getDefaultResultOrder()で設定されたデフォルトの順序に従ってIPv4とIPv6の両方が返されます。

コード例

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

async function resolveSpecificFamily(hostname) {
    console.log(`--- ホスト名: ${hostname} ---`);

    // IPv4アドレスのみを解決
    try {
        const ipv4Result = await dns.promises.lookup(hostname, { family: 4 });
        console.log(`IPv4アドレス: ${ipv4Result.address}, ファミリー: IPv${ipv4Result.family}`);
    } catch (error) {
        console.error(`IPv4解決エラー: ${error.message}`);
    }

    // IPv6アドレスのみを解決
    try {
        const ipv6Result = await dns.promises.lookup(hostname, { family: 6 });
        console.log(`IPv6アドレス: ${ipv6Result.address}, ファミリー: IPv${ipv6Result.family}`);
    } catch (error) {
        console.error(`IPv6解決エラー: ${error.message}`);
    }
}

resolveSpecificFamily('example.com');

Node.jsの起動時オプション --dns-result-order を使用する

これは、アプリケーションの実行時にNode.jsプロセス全体のデフォルトのDNS解決順序を設定する代替方法です。

  • 欠点
    • 実行中のNode.jsプロセス全体に影響を与えます。
    • プログラム実行中に動的に変更することはできません。
    • dns.setDefaultResultOrder()がコード内で呼び出された場合、そちらの設定が優先されます。
  • 利点
    • アプリケーションのコードを変更することなく、DNS解決のデフォルト順序を変更できます。
    • 環境変数やデプロイ設定で容易に管理できます。
  • 説明
    Node.jsアプリケーションを起動する際に、コマンドライン引数として--dns-result-orderフラグを使用します。
    • node --dns-result-order=ipv4first app.js
    • node --dns-result-order=verbatim app.js

使用例(コマンドラインから)

# IPv4を優先してDNS解決を行うようにNode.jsプロセスを起動
node --dns-result-order=ipv4first your_application.js

# DNSリゾルバーが返した順序をそのまま使用するようにNode.jsプロセスを起動
node --dns-result-order=verbatim your_application.js

dns.resolve4() および dns.resolve6() を使用する

これらはdns.lookup()とは異なり、OSのホスト解決メカニズム(getaddrinfo)ではなく、Node.jsの内部DNSクライアント(c-aresライブラリ)を使用して直接DNSサーバーに問い合わせを行います。そのため、dns.getDefaultResultOrder()の設定には影響されません。

  • 欠点
    • hostsファイルなどのOSレベルの解決設定は無視されます。
    • IPアドレスの解決順序を細かく制御する必要がある場合、自分でリストを処理する必要があります。
  • 利点
    • 特定のIPバージョンを明示的に取得できます。
    • dns.lookup()の挙動に依存しないため、より予測可能です。
    • 通常、dns.resolve()系の関数は、より詳細なDNSレコードタイプ(MX, TXT, CNAMEなど)の解決にも使用されます。
  • 説明
    • dns.resolve4(hostname, callback): ホスト名に対するIPv4アドレス(Aレコード)のみを解決します。
    • dns.resolve6(hostname, callback): ホスト名に対するIPv6アドレス(AAAAレコード)のみを解決します。
    • これらの関数は、IPアドレスのリストを返します。

コード例

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

async function resolveDirectly(hostname) {
    console.log(`--- ホスト名: ${hostname} (直接解決) ---`);

    // IPv4アドレスのみを解決
    try {
        const ipv4Addresses = await dns.promises.resolve4(hostname);
        console.log(`IPv4アドレス (resolve4): ${ipv4Addresses}`);
    } catch (error) {
        console.error(`IPv4解決エラー (resolve4): ${error.message}`);
    }

    // IPv6アドレスのみを解決
    try {
        const ipv6Addresses = await dns.promises.resolve6(hostname);
        console.log(`IPv6アドレス (resolve6): ${ipv6Addresses}`);
    } catch (error) {
        console.error(`IPv6解決エラー (resolve6): ${error.message}`);
    }
}

resolveDirectly('example.com');

より高度なユースケースでは、Node.jsのデフォルトのDNS解決メカニズムを完全に置き換えたり、独自のDNSキャッシュやロードバランシングロジックを実装したりする場合があります。

  • 欠点
    • 実装が複雑になり、エラーハンドリングやパフォーマンスチューニングの責任が増大します。
    • 既存のDNSモジュールでは得られない特定の要件がある場合にのみ検討すべきです。
  • 利点
    • 完全にカスタマイズされたDNS解決動作を実現できます。
    • DNSキャッシュを自前で実装することで、パフォーマンスを向上させることができます。
  • 説明
    • http.request()net.connect()などのネットワークモジュールは、lookupオプションをサポートしており、カスタムのDNS解決関数を渡すことができます。
    • これにより、独自のキャッシュ、特定のDNSサーバーへの問い合わせ、または特定のIPアドレスの選択ロジックを組み込むことが可能になります。
const http = require('node:http');
const dns = require('node:dns');

// カスタムのDNSルックアップ関数
// 実際のアプリケーションでは、ここにキャッシュやカスタムロジックを追加します
async function myCustomLookup(hostname, options, callback) {
    console.log(`[カスタムルックアップ] ホスト名: ${hostname}, オプション:`, options);

    // デフォルトのdns.lookupを使用するが、必要に応じてロジックを追加
    try {
        const result = await dns.promises.lookup(hostname, { family: 4 }); // 例: IPv4のみを強制
        callback(null, result.address, result.family);
    } catch (err) {
        callback(err);
    }
}

// http.requestでカスタムルックアップを使用
const options = {
    hostname: 'example.com',
    port: 80,
    path: '/',
    method: 'GET',
    lookup: myCustomLookup // ここでカスタム関数を指定
};

const req = http.request(options, (res) => {
    console.log(`ステータスコード: ${res.statusCode}`);
    res.setEncoding('utf8');
    res.on('data', (chunk) => {
        // console.log(`ボディ: ${chunk}`);
    });
    res.on('end', () => {
        console.log('レスポンス終了');
    });
});

req.on('error', (e) => {
    console.error(`リクエストで問題が発生しました: ${e.message}`);
});

req.end();