SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

DNS問い合わせ (C#)(F#)

名前解決には、GetHostEntry()の利用が推奨されていますが、これは、「ホスト名」ー「IPアドレス」の相互解決しか対応していません。また、実装上、NAT内のネットワーク(ホストにリンク ローカルアドレスまたは Teredoアドレスしか割り当てられていない場合)では、IPv6アドレス(AAAA)を解決することができません。

注:「Windows Vista でのドメイン ネーム システム クライアントの動作」
DNS名クエリを実行する際の DNSサーバーへの影響が最小限に抑えられるように設計されています。ホストにリンク ローカル アドレスまたは Teredo アドレスしか割り当てられていない場合、DNSクライアントサービスはAレコードに関するクエリだけを送信します。

「MX」や「AAAA」など、自由にDNSへの問い合わせを発行するには、DNSAPI.dllのDnsQuery_Wを使用します。
DnsQueryは、DNSサーバから帰ったリソースレコードの内、質問フィールド以外を全て返しますので、必要部分のみタイプに応じた型(構造体)でデコードして使用します。(サンプルでは、A,AAAA,CNAME,MXのみ定義しています。)
取得したデータは、DnsRecordListFree()で開放が必要です。また、型の定義はwindns.hにあります。

C#サンプル

using System;
using System.Runtime.InteropServices;
using System.Net;

namespace ConsoleApplication3 {
    class Program {
        [DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Auto)]
        private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)]ref string name,
               QueryTypes type, QueryOptions options, int extra,
               ref IntPtr queryResultsSet, int reserved);

        [DllImport("dnsapi", CharSet = CharSet.Auto)]
        private static extern void DnsRecordListFree(IntPtr recordList, int freeType);

        private enum QueryOptions {
            DNS_QUERY_BYPASS_CACHE = 8,
        }

        private enum QueryTypes{
            DNS_TYPE_A = 1,
            DNS_TYPE_NS = 2,
            DNS_TYPE_CNAME = 5,
            DNS_TYPE_SOA = 6,
            DNS_TYPE_PTR = 12,
            DNS_TYPE_HINFO = 13,
            DNS_TYPE_MX = 15,
            DNS_TYPE_TXT = 16,
            DNS_TYPE_AAAA = 28,
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct RR{
            public IntPtr pNext;
            public string pName;
            public short wType;
            public short wDataLength;
            public int flags;
            public int dwTtl;
            public int dwReserved;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct MX_RR {
            public string pNameExchange;
            public short wPreference;
        }
        [StructLayout(LayoutKind.Sequential)]
        struct AAAA_RR {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public byte [] addr;
        }
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct CNAME_RR {
            public string pCName;
        }
        [StructLayout(LayoutKind.Sequential)]
        struct A_RR {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public byte[] addr;
        }

        static void Main(string[] args) {
            string name = "www.kame.net";
            IntPtr buf = IntPtr.Zero;
            if(0 == DnsQuery(ref name, QueryTypes.DNS_TYPE_AAAA, QueryOptions.DNS_QUERY_BYPASS_CACHE,
                              0, ref buf, 0)){
                IntPtr p = buf;
                while(p != IntPtr.Zero){
                    var rr = (RR) Marshal.PtrToStructure(p, typeof(RR));//1リソースレコード単位で処理する
                    if ((rr.flags & 0x000f) == 0x9) { // Answer レコードのみを対象にする
                        p = IntPtr.Add(p, Marshal.SizeOf(typeof(RR)));//タイプごとのデータへ移動
                        switch ((QueryTypes)rr.wType) {
                            case QueryTypes.DNS_TYPE_A:
                                var a = (A_RR)Marshal.PtrToStructure(p, typeof(A_RR));
                                Console.WriteLine(string.Format("A {0} {1}", rr.pName, new IPAddress(a.addr)));
                                break;
                            case QueryTypes.DNS_TYPE_AAAA:
                                var aaaa = (AAAA_RR)Marshal.PtrToStructure(p, typeof(AAAA_RR));
                                Console.WriteLine(string.Format("AAAA {0} {1}", rr.pName, new IPAddress(aaaa.addr)));
                                break;
                            case QueryTypes.DNS_TYPE_CNAME:
                                var cname = (CNAME_RR)Marshal.PtrToStructure(p, typeof(CNAME_RR));
                                Console.WriteLine(string.Format("CNAME {0}", cname.pCName));
                                break;
                            case QueryTypes.DNS_TYPE_MX:
                                var mx = (MX_RR)Marshal.PtrToStructure(p, typeof(MX_RR));
                                Console.WriteLine(string.Format("MX {0} {1}", mx.wPreference,mx.pNameExchange));
                                break;
                            default:
                                Console.WriteLine("unknown");
                                break;
                        }
                    }
                    p = rr.pNext;//次のリソースレコード
                }
                DnsRecordListFree(buf, 0);//メモリ開放
            }
            Console.WriteLine();
            Console.WriteLine("何かのキーを押してください。");
            Console.ReadKey();

        }
    }
}

F#サンプル

open System
open System.Runtime.InteropServices
open System.Net

type QueryOptions =
    | DNS_QUERY_BYPASS_CACHE = 8

type QueryTypes =
    | DNS_TYPE_A = 1
    | DNS_TYPE_NS = 2
    | DNS_TYPE_CNAME = 5
    | DNS_TYPE_SOA = 6
    | DNS_TYPE_PTR = 12
    | DNS_TYPE_HINFO = 13
    | DNS_TYPE_MX = 15
    | DNS_TYPE_TXT = 16
    | DNS_TYPE_AAAA = 28


[<DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Auto)>]
extern int DnsQuery(string name,QueryTypes wtype,QueryOptions options,int extra,IntPtr *queryResultsSet,int reserved);

[<DllImport("dnsapi", CharSet = CharSet.Auto)>]
extern void DnsRecordListFree(IntPtr recordList, int freeType);

[<StructLayout(LayoutKind.Sequential,CharSet=CharSet.Auto)>]
type IP_ADAPTER_INDEX_MAP = 
    struct
        val Index : int
         [<MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)>]
        val Name : string
    end


[<StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)>]
type RR = 
    struct
        val pNext:IntPtr
        val pName:string
        val wType:int16
        val wDataLength:int16
        val flags:int
        val dwTtl:int
        val dwReserved:int
    end

[<StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)>]
type MX_RR =
    struct
        val pNameExchange:string
        val wPreference:int16
    end

[<StructLayout(LayoutKind.Sequential)>]
type AAAA_RR = 
    struct
        [<MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)>]
        val addr:byte []
    end
     
[<StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)>]
type CNAME_RR =
    struct
        val pCName:string
    end

[<StructLayout(LayoutKind.Sequential)>]
type A_RR =
    struct
        [<MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)>]
        val addr:byte[]
    end


let name = "www.kame.net"
let mutable buf = IntPtr.Zero;
if 0 = DnsQuery(name,QueryTypes.DNS_TYPE_AAAA,QueryOptions.DNS_QUERY_BYPASS_CACHE, 0,&&buf, 0) then
    let mutable p = buf;
    while p<>IntPtr.Zero do
        let rr = Marshal.PtrToStructure(p,typeof<RR>) :?> RR//1リソースレコード単位で処理する
        if (rr.flags &&& 0x000f) = 0x9 then  // Answer レコードのみを対象にする
            p <- IntPtr.Add(p,Marshal.SizeOf(typeof<RR>))//タイプごとのデータへ移動
            match enum<QueryTypes>(int rr.wType) with
            |QueryTypes.DNS_TYPE_A -> 
                let a = Marshal.PtrToStructure(p,typeof<A_RR>):?>A_RR
                printfn "%s" ((new IPAddress(a.addr)).ToString())
            |QueryTypes.DNS_TYPE_AAAA -> 
                let aaaa = Marshal.PtrToStructure(p, typeof<AAAA_RR>):?>AAAA_RR
                printfn "AAAA %s %s" rr.pName ((new IPAddress(aaaa.addr)).ToString())
            |QueryTypes.DNS_TYPE_CNAME -> 
                let cname = Marshal.PtrToStructure(p, typeof<CNAME_RR>):?>CNAME_RR
                printfn "CNAME %s" cname.pCName
            |QueryTypes.DNS_TYPE_MX -> 
                let mx = Marshal.PtrToStructure(p, typeof<MX_RR>):?>MX_RR
                printfn "MX %d %s" mx.wPreference mx.pNameExchange
            |_ -> 
                printfn "unknown"

        p <- rr.pNext//次のリソースレコード
    DnsRecordListFree(buf, 0);//メモリ開放

printfn "何かのキーを押してください。"
Console.ReadKey() |> ignore

追記
このファンクションは、ping.exeのインポート関数を確認していて見つけました。
dumpbin ping.exe /IMPORTS
DNSAPI.dll
 7FF7200BD74 63 DnsResolverOp
 7FF7200313C 51 DnsQuery_W
 7FF72003064 24 DnsFree
 7FF72016670 27 DnsGetCacheDataTable
 7FF72003828 4A DnsQueryConfigAllocEx
 7FF720148D4 25 DnsFreeConfigStructure
 7FF7201AC70 20 DnsFlushResolverCache