概要

DNS (Domain Name System) とは、インターネットの重要な基盤技術の1つであり、ドメイン名 (ホスト名) とIPアドレスの対応関係を管理するシステムである。
DNSはインターネットの電話帳のような役割を果たしており、Webサイトにアクセスする時には不可欠な仕組みである。

  • 名前解決
    人間が覚えやすいドメイン名 (例: www.example.com) をコンピュータが理解できるIPアドレス (例: 192.0.2.1) に変換する。
  • 階層構造
    ドメイン名は階層的に構成されており、ルートドメイン、トップレベルドメイン、セカンドレベルドメイン等がある。
  • 分散システム
    世界中に多数のDNSサーバが存在して、お互いに連携して動作する。
  • キャッシュ機能
    1度解決した情報を一定期間保存して、応答速度を向上させる。
  • その他の情報
    IPアドレス以外にも、メールサーバの情報等、様々なデータを管理する。



DNSレコードの確認

DnsClient.NETライブラリとは

DnsClient.NETライブラリは、.NET Standardに準拠したクロスプラットフォームの高機能なDNS (Domain Name System) クライアントライブラリである。

このライブラリは以下の環境で動作する。

  • .NET Framework 4.6.1以降
  • .NET Core 2.0以降


DnsClient.NETライブラリの特徴および用途を以下に示す。

  • 主な機能
    • DNSクエリの実行 (A、AAAA、CNAME、MX、NS、PTR、SOA、SRV、TXTレコード等)
      例: DNSレコードの存在を確認する場合
    • 非同期DNSルックアップ
    • DNSキャッシュのサポート
    • DNSSEC (DNS Security Extensions) のサポート
    • IPv4およびIPv6のサポート


  • 使用目的
    • ドメイン名の解決
    • メールサーバーの検索 (MXレコード)
    • サービスディスカバリ (SRVレコード)
    • リバースDNSルックアップ
    • カスタムDNSサーバの使用


  • メリット
    • パフォーマンスの最適化
      システムのDNSリゾルバよりも高速な場合がある。
    • 細かな制御
      タイムアウト、リトライ、DNSサーバの選択などを詳細に設定できる。
    • クロスプラットフォーム
      Windows、Linux、MacOSで動作可能。
    • 豊富な機能
      標準的なDNS操作に加えて、高度な機能も提供している。


  • 一般的な使用シナリオ
    • マイクロサービスアーキテクチャでのサービスディスカバリ
    • ネットワーク診断ツール
    • メールシステムの実装
    • カスタムネットワークアプリケーション


DnsClient.NETライブラリのインストール

Riderからインストールする場合

Riderを起動して、対象のプロジェクトを開く。

[ツール]メニューバー - [NuGet] - [パッケージマネージャーの管理]を選択して、NuGetパッケージマネージャーを開く。
または、ソリューションエクスプローラでプロジェクトを右クリックして、[NuGetパッケージの管理]を選択して、NuGetパッケージマネージャーを開く。

次に、DnsClient.NETライブラリを検索する。
検索バーにて、DnsClient.NETと入力する。

DnsClient.NETライブラリのインストールする。
検索結果からDnsClient.NETライブラリを選択して、[インストール]ボタンを押下する。

インストールが完了した後、[閉じる]ボタンを押下してNuGetパッケージマネージャーを閉じる。

.NET CLIからインストールする場合

プロジェクトのディレクトリに移動して、DnsClient.NETライブラリをインストールする。

# 最新の安定版のDnsClient.NETライブラリをインストールする場合
dotnet add package DnsClient.NET

# 特定のバージョンを指定する場合
# 例: DnsClient.NET 1.7.0ライブラリをインストール
dotnet add package DnsClient.NET --version 1.7.0


ライブラリを追加した後、依存関係を復元する。

dotnet restore


DNSレコードタイプの取得 (PTRレコードおよびSRVレコード以外)

以下の例では、duckduckgo.comから複数のDNSレコードタイプを取得および表示している。

  1. LookupClientクラスのインスタンスを生成する。
  2. 取得するレコードタイプの配列を定義する。
  3. 各レコードタイプに対して、DNSクエリを送信する。
  4. 結果を取得して、レコードタイプに応じて表示する。


※注意
PTRレコードの逆引きはIPアドレスに対して行うものであることに注意する。
IPアドレスを逆にした形式 (例: 4.4.8.8.in-addr.arpa) を使用する。

SRVレコードは特定のサービスに対して使用されるため、適切なサービス名とプロトコルを指定する必要がある。

 using System;
 using System.Threading.Tasks;
 using DnsClient;
 
 class Program
 {
    public static async Task Main(string[] args)
    {
       var lookup = new LookupClient();
       var domain = "duckduckgo.com";
 
       var recordTypes = new[] { 
          QueryType.A, QueryType.AAAA, QueryType.CNAME, 
          QueryType.MX, QueryType.NS, QueryType.PTR, 
          QueryType.SOA, QueryType.SRV, QueryType.TXT 
       };
 
       foreach (var recordType in recordTypes) {
          Console.WriteLine($"--- {recordType} Records for {domain} ---");
          var result = await lookup.QueryAsync(domain, recordType);
 
          if (result.Answers.Count == 0) {
              Console.WriteLine($"No {recordType} records found.");
              continue;
          }
 
          foreach (var record in result.Answers) {
             switch (record)
             {
                case ARecord a:
                   Console.WriteLine($"A Record: {a.Address}");
                   break;
                case AaaaRecord aaaa:
                   Console.WriteLine($"AAAA Record: {aaaa.Address}");
                   break;
                case CNameRecord cname:
                   Console.WriteLine($"CNAME Record: {cname.CanonicalName}");
                   break;
                case MxRecord mx:
                   Console.WriteLine($"MX Record: {mx.Exchange} (Preference: {mx.Preference})");
                   break;
                case NsRecord ns:
                   Console.WriteLine($"NS Record: {ns.NSDName}");
                   break;
                case SoaRecord soa:
                   Console.WriteLine($"SOA Record: Primary NS: {soa.MName}, Responsible: {soa.RName}");
                   break;
                case TxtRecord txt:
                   Console.WriteLine($"TXT Record: {string.Join(", ", txt.Text)}");
                   break;
                default:
                   Console.WriteLine($"Unknown Record Type: {record}");
                   break;
             }
          }
       }
    }
 }


PTRレコードおよびSRVレコードの取得

PTRレコードはIPアドレスの逆引き用であり、SRVレコードは特定のサービスに対して使用される。

以下の例では、指定したIPアドレスのPTRレコードおよび指定したサービスのSRVレコードを取得して表示する。
もし、特定のIPアドレスやサービスに対して実行する場合は、GetPtrRecordメソッドおよびGetSrvRecordメソッドの引数を適宜変更すること。

  • PTRレコードの取得
    GetPtrRecordメソッドでは、指定されたIPアドレスに対してPTRレコードを取得する。
    QueryReverseAsyncメソッドを使用して、IPアドレスの逆引きを行う。
    以下の例では、"8.8.8.8" (Google Public DNS) を使用しているが、任意のIPアドレスに変更可能である。


  • SRVレコードの取得
    GetSrvRecordメソッドでは、指定されたサービス名に対してSRVレコードを取得する。
    SRVレコードのクエリ形式は、一般的に、_service._proto.nameの形式である。
    以下の例では、_sip._tcp.example.comを使用しているが、実際のサービスとドメインに応じて変更する必要がある。


※注意
PTRレコードの取得に使用するIPアドレスは、実際に逆引きが設定されているものを使用すること。
SRVレコードの取得に使用するサービス名は、実際に存在するサービスとドメインの組み合わせを使用すること。

エラーハンドリングを行っているが、ネットワーク状況やDNSサーバの設定によっては結果が得られない場合がある。

 using System;
 using System.Net;
 using System.Threading.Tasks;
 using DnsClient;
 
 class Program
 {
    public static async Task Main(string[] args)
    {
       // タイムアウトを5秒に設定する場合
       // ネットワークの状態が悪い場合やDNSサーバが応答しない場合でも、プログラムが無限に待機することなく適切に終了する
       // 設定したタイムアウト時間を超える場合、TimeoutExceptionがスローされる
       // この例外は捕捉され、ユーザにタイムアウトが発生したことを通知する
       var options = new LookupClientOptions
       {
          Timeout = TimeSpan.FromSeconds(5)
       };
       var lookup = new LookupClient(options);
 
       // デフォルト設定のタイムアウト時間を使用する場合
       // 通常、数秒程度のタイムアウトが設定されているが、正確な時間はライブラリのバージョンにより異なる可能性がある
       //var lookup = new LookupClient();
 
       // PTRレコードの取得
       await GetPtrRecord(lookup, "8.8.8.8");
 
       // SRVレコードの取得
       await GetSrvRecord(lookup, "_sip._tcp.example.com");
    }
 
    private static async Task GetPtrRecord(LookupClient lookup, string ipAddress)
    {
       Console.WriteLine($"--- PTR Record for {ipAddress} ---");
 
       try {
          var ip = IPAddress.Parse(ipAddress);
          var result = await lookup.QueryReverseAsync(ip);
 
          if (result.Answers.PtrRecords.Count == 0) {
             Console.WriteLine("No PTR records found.");
             return;
          }
 
          foreach (var ptrRecord in result.Answers.PtrRecords) {
             Console.WriteLine($"PTR Record: {ptrRecord.PtrDomainName}");
          }
       }
       catch (DnsResponseException ex) {
          Console.WriteLine($"DNS query failed: {ex.Message}");
       }
       catch (TimeoutException) {
          Console.WriteLine("The DNS query timed out.");
       }
       catch (Exception ex) {
          Console.WriteLine($"Error retrieving PTR record: {ex.Message}");
       }
    }
 
    private static async Task GetSrvRecord(LookupClient lookup, string service)
    {
       Console.WriteLine($"--- SRV Records for {service} ---");
 
       try {
          var result = await lookup.QueryAsync(service, QueryType.SRV);
 
          if (result.Answers.SrvRecords.Count == 0) {
             Console.WriteLine("No SRV records found.");
             return;
          }
 
          foreach (var srvRecord in result.Answers.SrvRecords) {
             Console.WriteLine($"SRV Record: Target: {srvRecord.Target}, " +
                               $"Port: {srvRecord.Port}, " +
                               $"Priority: {srvRecord.Priority}, " +
                               $"Weight: {srvRecord.Weight}");
          }
       }
       catch (DnsResponseException ex) {
          Console.WriteLine($"DNS query failed: {ex.Message}");
       }
       catch (TimeoutException) {
          Console.WriteLine("The DNS query timed out.");
       }
       catch (Exception ex) {
          Console.WriteLine($"Error retrieving SRV record: {ex.Message}");
       }
    }
 }


ドメインの確認

以下の例では、対象のDNSのリストが記述されたファイル (domains.txt) を上から順に読み込み、ドメインが存在しない場合は別ファイルに書き込んでいる。

LookupClientクラスのQueryメソッドを使用して、対象のドメインとレコードの種類 (AレコードおよびNSレコード) を指定する。
取得結果が無い場合は、存在しないと判断する。

MXレコードを対象としているが、QueryType (Enum) でレコードの種類 (AレコードおよびNSレコード) が定義されているため、
様々な種類のレコードを対象にすることができる。

# domains.txtファイル
# MXレコードの確認用のドメインリスト

google.com
microsoft.com
yahoo.com
apple.com
amazon.com
example.com
github.com
hoge.org
oracle.com
ibm.com


 using System;
 using System.IO;
 using DnsClient;
 
 namespace DNSRecordCheck;

 class Program
 {
    public static async void Main(string[] args)
    {
       // LookupClientクラスのインスタンスを生成
       var lookupClient = new LookupClient();
 
       using (var sreader = new StreamReader(@"C:\domains.txt")) {
          while (!sreader.EndOfStream) {
             // 調査対象のリストから1行ずつ読み込み
             var line = await sreader.ReadLineAsync();
 
             using (var swriter = File.AppendText(@"C:\ignores.txt")) {
                // 読み込んだDNSを問い合わせる
                // QueryType.MX : MXレコードを対象とする
                var result = lookupClient.Query(line, QueryType.MX);
 
                if (result.Answers.Count == 0) {
                   // 結果が0件の場合は存在しないと判断
                   // 別ファイル (ignores.txt) に書き込む
                   swriter.WriteLine(line);
                }
             }
          }
       }
    }
 }