web-dev-qa-db-ja.com

System.Net.DNSでDNS名のMXレコードを取得する方法は?

特定のドメインのすべてのMXレコードを返す組み込みメソッドが.NETライブラリにありますか? CNAMEを取得する方法はわかりますが、MXレコードは取得しません。

43
Segfault

.netコア/ xplatのサポートがなかったため、自分のライブラリをロールしました... https://github.com/MichaCo/DnsClient.NET

それはかなりうまく機能し、必要に応じてDigのようなログメッセージを提供します。

使いやすい

var lookup = new LookupClient();
var result = await lookup.QueryAsync("google.com", QueryType.ANY);

任意のポート、複数のサーバーなどで実行されているカスタムサーバーで動作します。

DnsClient Website も参照してください

17
MichaC

2018/5/23の更新

.NET標準をサポートしている新しいライブラリについては、 MichaCの回答 をご覧ください。

元の答え:

Alexander Reinertの ARSoft.Tools.Net ライブラリは、かなりうまく機能しているようです。

NuGetから入手できます。

PM> Install-Package ARSoft.Tools.Net

名前空間をインポートします。

using ARSoft.Tools.Net.Dns;

次に、同期ルックアップの作成は次のように簡単です。

var resolver = new DnsStubResolver();
var records = resolver.Resolve<MxRecord>("gmail.com", RecordType.Mx);
foreach (var record in records) {
    Console.WriteLine(record.ExchangeDomainName?.ToString());
}

出力は次のとおりです。

gmail-smtp-in.l.google.com.
alt1.gmail-smtp-in.l.google.com.
alt2.gmail-smtp-in.l.google.com.
alt3.gmail-smtp-in.l.google.com.
alt4.gmail-smtp-in.l.google.com.

ボンネットの下では、ライブラリがリゾルバに送信するために必要なUDP(またはTCP)パケットを構築するように見えます。ライブラリには、照会するDNSサーバーを検出するロジック(DnsClient.Defaultで呼び出される)もあります。

完全なドキュメントは here にあります。

41
Michael Kropat

私は一日中、DNSリクエストを送信/受信する方法を見つけるのに費やし、これを思いつきました。その完全な汎用ハンドラー。 DNSサーバーを設定して、「d」を渡すだけです。 my.website.com?d=itmanx.com

<%@ WebHandler Language="C#" Class="Handler" %>

using System;
using System.Web;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;

public class Handler : IHttpHandler
{
    string dns = "dc1";  //change to your dns
    string qtype = "15"; //A=1  MX=15
    string domain = "";
    int[] resp;

    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";

        try
        {
            if (context.Request["t"] != null) qtype = context.Request["t"];
            if (context.Request["d"] != null) domain = context.Request["d"];

            if (string.IsNullOrEmpty(domain)) throw new Exception("Add ?d=<domain name> to url or post data");

            Do(context);
        }
        catch (Exception ex)
        {
            string msg = ex.Message;
            if (msg == "1") msg = "Malformed packet";
            else if (msg == "5") msg = "Refused";
            else if (msg == "131") msg = "No such name";

            context.Response.Write("Error: " + msg);
        }
    }

    public void Do(HttpContext context)
    {
        UdpClient udpc = new UdpClient(dns, 53);

        // SEND REQUEST--------------------
        List<byte> list = new List<byte>();
        list.AddRange(new byte[] { 88, 89, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0 });

        string[] tmp = domain.Split('.');
        foreach (string s in tmp)
        {
            list.Add(Convert.ToByte(s.Length));
            char[] chars = s.ToCharArray();
            foreach (char c in chars)
                list.Add(Convert.ToByte(Convert.ToInt32(c)));
        }
        list.AddRange(new byte[] { 0, 0, Convert.ToByte(qtype), 0, 1 });

        byte[] req = new byte[list.Count];
        for (int i = 0; i < list.Count; i++) { req[i] = list[i]; }

        udpc.Send(req, req.Length);


        // RECEIVE RESPONSE--------------
        IPEndPoint ep = null;
        byte[] recv = udpc.Receive(ref ep);
        udpc.Close();

        resp = new int[recv.Length];
        for (int i = 0; i < resp.Length; i++)
            resp[i] = Convert.ToInt32(recv[i]);

        int status = resp[3];
        if (status != 128) throw new Exception(string.Format("{0}", status));
        int answers = resp[7];
        if (answers == 0) throw new Exception("No results");

        int pos = domain.Length + 18;
        if (qtype == "15") // MX record
        {
            while (answers > 0)
            {
                int preference = resp[pos + 13];
                pos += 14; //offset
                string str = GetMXRecord(pos, out pos);
                context.Response.Write(string.Format("{0}: {1}\n", preference, str));
                answers--;
            }
        }
        else if (qtype == "1") // A record
        {
            while (answers > 0)
            {
                pos += 11; //offset
                string str = GetARecord(ref pos);
                context.Response.Write(string.Format("{0}\n", str));
                answers--;
            }
        }
    }

    //------------------------------------------------------
    private string GetARecord(ref int start)
    {
        StringBuilder sb = new StringBuilder();

        int len = resp[start];
        for (int i = start; i < start + len; i++)
        {
            if (sb.Length > 0) sb.Append(".");
            sb.Append(resp[i + 1]);
        }
        start += len + 1;
        return sb.ToString();
    }
    private string GetMXRecord(int start, out int pos)
    {
        StringBuilder sb = new StringBuilder();
        int len = resp[start];
        while (len > 0)
        {
            if (len != 192)
            {
                if (sb.Length > 0) sb.Append(".");
                for (int i = start; i < start + len; i++)
                    sb.Append(Convert.ToChar(resp[i + 1]));
                start += len + 1;
                len = resp[start];
            }
            if (len == 192)
            {
                int newpos = resp[start + 1];
                if (sb.Length > 0) sb.Append(".");
                sb.Append(GetMXRecord(newpos, out newpos));
                start++;
                break;
            }
        }
        pos = start + 1;
        return sb.ToString();
    }

    //------------------------------------------------------
    public bool IsReusable { get { return false; } }
}
9
Christian

受け入れられた答えは、.NETフレームワーク<4.5では機能しないため、ARSOFT.Toolsを使用できない場合は、 https://dndns.codeplex.com からDNDNを使用できることをお勧めします。

以下は、特定のドメインのMXレコードを返し、例を変更するコンソールアプリケーションです。

using System;
using System.Net.Sockets;
using DnDns.Enums;
using DnDns.Query;
using DnDns.Records;

namespace DnDnsExamples
{
class Program
{
    static void Main(string[] args)
    {
        DnsQueryRequest request3 = new DnsQueryRequest();
        DnsQueryResponse response3 = request3.Resolve("gmail.com", NsType.MX, NsClass.INET, ProtocolType.Tcp);
        OutputResults(response3);
        Console.ReadLine();
    }

    private static void OutputResults(DnsQueryResponse response)
    {
        foreach (IDnsRecord record in response.Answers)
        {
            Console.WriteLine(record.Answer);
            Console.WriteLine("  |--- RDATA Field Length: " + record.DnsHeader.DataLength);
            Console.WriteLine("  |--- Name: " + record.DnsHeader.Name);
            Console.WriteLine("  |--- NS Class: " + record.DnsHeader.NsClass);
            Console.WriteLine("  |--- NS Type: " + record.DnsHeader.NsType);
            Console.WriteLine("  |--- TTL: " + record.DnsHeader.TimeToLive);
            Console.WriteLine();
        }            
    }
}
}

私のアプローチはnslookup.exe MXレコードを取得します。

解決策は、DNS全体を書き換えたり、System DLL->を使用するほど洗練されていませんが、わずかな行数で機能します。

物事を正しくするために、このコードは>うまく機能します<効率的でも高速でもリソースを再配分せず、改善の余地がたくさんあります(複数のホスト名、非同期、より便利な戻り値、優先度の追加):

static List<string> GetMxRecords(string Host){
    ProcessStartInfo nslookup_config = new ProcessStartInfo("nslookup.exe");
    nslookup_config.RedirectStandardInput = true;
    nslookup_config.RedirectStandardOutput = true;
    nslookup_config.RedirectStandardError = true;
    nslookup_config.UseShellExecute = false;
    var nslookup  = Process.Start(nslookup_config);
    nslookup.StandardInput.WriteLine("set q=mx");
    nslookup.StandardInput.WriteLine(Host);
    nslookup.StandardInput.WriteLine("exit");
    List<string> lines = new List<string>();
    while (!nslookup.StandardOutput.EndOfStream)
    {
        string l = nslookup.StandardOutput.ReadLine();
        if (l.Contains("internet address ="))
        {
            while (l.Contains("\t\t"))
            {
                l = l.Replace("\t\t", "\t");
            }
            lines.Add(l.Replace("\tinternet address = ","="));
        }
    }
    nslookup.Close();
}

nslookupは翻訳をサポートしていないため、国際的に機能するはずです(ドイツ語のマシンで作業しており、英語の出力を取得しています)。

結果は次のような文字列です。

alt4.gmail-smtp-in.l.google.com=74.125.28.27
alt2.gmail-smtp-in.l.google.com=74.125.200.27
alt1.gmail-smtp-in.l.google.com=209.85.233.26
gmail-smtp-in.l.google.com=66.102.1.27
alt3.gmail-smtp-in.l.google.com=108.177.97.27
1
Clemens Jung

以下は、MXレコードのみを検索するために使用するクラスです。

    using System;
    using System.Text;
    using System.Net;
    using System.Net.Sockets;
    using System.Collections.Specialized;

    namespace Mx.Dns
    {
        public class Query
        {
            //Build a DNS query buffer according to RFC 1035 4.1.1 e 4.1.2
            private readonly int id;
        private readonly int flags;
        private readonly int QDcount;
        private readonly int ANcount;
        private readonly int NScount;
        private readonly int ARcount;
        private readonly string Qname;
        private readonly int Qtype;
        private readonly  int Qclass;
        public byte[] buf;

        public Query(int ID, string query, int qtype)
        {
            //init vectors with given + default values
            id = ID;
            flags = 256;
            QDcount = 1;
            ANcount = 0;
            NScount = 0;
            ARcount = 0;
            Qname = query;
            Qtype = qtype;
            Qclass = 1; //Internet = IN = 1

            //build a buffer with formatted query data

            //header information (16 bit padding
            buf = new byte[12 + Qname.Length + 2 + 4];
            buf[0] = (byte)(id / 256);
            buf[1] = (byte)(id - (buf[0] * 256));
            buf[2] = (byte)(flags / 256);
            buf[3] = (byte)(flags - (buf[2] * 256));
            buf[4] = (byte)(QDcount / 256);
            buf[5] = (byte)(QDcount - (buf[4] * 256));
            buf[6] = (byte)(ANcount / 256);
            buf[7] = (byte)(ANcount - (buf[6] * 256));
            buf[8] = (byte)(NScount / 256);
            buf[9] = (byte)(NScount - (buf[8] * 256));
            buf[10] = (byte)(ARcount / 256);
            buf[11] = (byte)(ARcount - (buf[10] * 256));
            //QNAME (RFC 1035 4.1.2)
            //no padding
            string[] s = Qname.Split('.');
            int index = 12;
            foreach (string str in s) {
                buf[index] = (byte)str.Length;
                index++;
                byte[] buf1 = Encoding.ASCII.GetBytes(str);
                buf1.CopyTo(buf, index);
                index += buf1.Length;
            }
            //add root domain label (chr(0))
            buf[index] = 0;

            //add Qtype and Qclass (16 bit values)
            index = buf.Length - 4;
            buf[index] = (byte)(Qtype / 256);
            buf[index + 1] = (byte)(Qtype - (buf[index] * 256));
            buf[index + 2] = (byte)(Qclass / 256);
            buf[index + 3] = (byte)(Qclass - (buf[index + 2] * 256));
        }
    }
    public class C_DNSquery
    {
        public StringCollection result = new StringCollection();
        public int Error = 0;
        public string ErrorTxt = "undefined text";
        public bool Done = false;
        public UdpClient udpClient;
        private string DNS;
        private string Query;
        private int Qtype;
        public bool IS_BLACKLIST_QUERY = false;
        public C_DNSquery(string IPorDNSname, string query, int type)
        {
            DNS = IPorDNSname;
            Query = query;
            Qtype = type;
        }
        public void doTheJob()
        {
            //check if provided DNS contains an IP address or a name
            IPAddress ipDNS;
            IPHostEntry he;
            try {
                //try to parse an IPaddress
                ipDNS = IPAddress.Parse(DNS);
            } catch (FormatException ) {
//              Console.WriteLine(e);
                //format error, probably is a FQname, try to resolve it
                try {
                    //try to resolve the hostname
                    he = Dns.GetHostEntry(DNS);
                } catch {
                    //Error, invalid server name or address
                    Error = 98;
                    ErrorTxt = "Invalid server name:" + DNS;
                    Done = true;
                    return;
                }
                //OK, get the first server address
                ipDNS = he.AddressList[0];
            }

            //Query the DNS server
            //our current thread ID is used to match the reply with this process

            Query myQuery = new Query(System.Threading.Thread.CurrentThread.ManagedThreadId, Query, Qtype);
            //data buffer for query return value
            Byte[] recBuf;

            //use UDP protocol to connect
            udpClient = new UdpClient();
            do {
                try {
                    //connect to given nameserver, port 53 (DNS)
                    udpClient.Connect(DNS, 53);
                    //send query
                    udpClient.Send(myQuery.buf, myQuery.buf.Length);
                    //IPEndPoint object allow us to read datagrams..
                    //..selecting only packet coming from our nameserver and port
                    IPEndPoint RemoteIpEndPoint = new IPEndPoint(ipDNS, 53);
                    //Blocks until a message returns on this socket from a remote Host.
                    recBuf = udpClient.Receive(ref RemoteIpEndPoint);
                    udpClient.Close();
                } catch (Exception e) {
                    //connection error, probably a wrong server address
                    udpClient.Close();
                    Error = 99;
                    ErrorTxt = e.Message + "(server:" + DNS + ")";
                    Done = true;
                    return;
                }
                //repeat until we get the reply with our threadID
            } while (System.Threading.Thread.CurrentThread.ManagedThreadId != ((recBuf[0] * 256) + recBuf[1]));

            //Check the DNS reply
            //check if bit QR (Query response) is set
            if (recBuf[2] < 128) {
                //response byte not set (probably a malformed packet)
                Error = 2;
                ErrorTxt = "Query response bit not set";
                Done = true;
                return;
            }
            //check if RCODE field is 0
            if ((recBuf[3] & 15) > 0) {
                //DNS server error, invalid reply
                switch (recBuf[3] & 15) {
                    case 1:
                        Error = 31;
                        ErrorTxt = "Format error. The nameserver was unable to interpret the query";
                        break;
                    case 2:
                        Error = 32;
                        ErrorTxt = "Server failure. The nameserver was unable to process the query.";
                        break;
                    case 3:
                        Error = 33;
                        ErrorTxt = "Name error. Check provided domain name!!";
                        break;
                    case 4:
                        Error = 34;
                        ErrorTxt = "Not implemented. The name server does not support the requested query";
                        break;
                    case 5:
                        Error = 35;
                        ErrorTxt = "Refused. The name server refuses to reply for policy reasons";
                        break;
                    default:
                        Error = 36;
                        ErrorTxt = "Unknown. The name server error code was: " + Convert.ToString((recBuf[3] & 15));
                        break;
                }
                Done = true;
                return;
            }
            //OK, now we should have valid header fields
            int QDcnt, ANcnt, NScnt, ARcnt;
            int index;
            QDcnt = (recBuf[4] * 256) + recBuf[5];
            ANcnt = (recBuf[6] * 256) + recBuf[7];
            NScnt = (recBuf[8] * 256) + recBuf[9];
            ARcnt = (recBuf[10] * 256) + recBuf[11];
            index = 12;
            //sometimes there are no erros but blank reply... ANcnt == 0...
            if (ANcnt == 0) { // if blackhole list query, means no spammer !!//if ((ANcnt == 0) & (IS_BLACKLIST_QUERY == false))
                //error blank reply, return an empty array
                Error = 4;
                ErrorTxt = "Empty string array";
                Done = true;
                return;
            }

            //Decode received information
            string s1;
            // START TEST
            s1 = Encoding.ASCII.GetString(recBuf, 0, recBuf.Length);
            // END TEST

            if (QDcnt > 0) {
                //we are not really interested to this string, just parse and skip
                s1 = "";
                index = parseString(recBuf, index, out s1);
                index += 4; //skip root domain, Qtype and QClass values... unuseful in this contest
            }
            if (IS_BLACKLIST_QUERY) {
                // get the answers, normally one !
                // int the four last bytes there is the ip address
                Error = 0;
                int Last_Position = recBuf.Length - 1;
                result.Add(recBuf[Last_Position - 3].ToString() + "." + recBuf[Last_Position - 2].ToString() + "." + recBuf[Last_Position - 1].ToString() + "." + recBuf[Last_Position].ToString());
                Done = true;
                return;
            }
            int count = 0;
            //get all answers
            while (count < ANcnt) {
                s1 = "";
                index = parseString(recBuf, index, out s1);
                //Qtype
                int QType = (recBuf[index] * 256) + recBuf[index + 1];
                index += 2;
                s1 += "," + QType.ToString();
                //QClass
                int QClass = (recBuf[index] * 256) + recBuf[index + 1];
                index += 2;
                s1 += "," + QClass.ToString();
                //TTL (Time to live)
                int TTL = (recBuf[index] * 16777216) + (recBuf[index + 1] * 65536) + (recBuf[index + 2] * 256) + recBuf[index + 3];
                index += 4;
                s1 += "," + TTL.ToString();
                int blocklen = (recBuf[index] * 256) + recBuf[index + 1];
                index += 2;
                if (QType == 15) {
                    int MXprio = (recBuf[index] * 256) + recBuf[index + 1];
                    index += 2;
                    s1 += "," + MXprio.ToString();
                }
                string s2;
                index = parseString(recBuf, index, out s2);
                s1 += "," + s2;
                result.Add(s1);
                count++;
            }
            Error = 0;
            Done = true;
        }
        private int parseString(byte[] buf, int i, out string s)
        {
            int len;
            s = "";
            bool end = false;
            while (!end) {
                if (buf[i] == 192) {
                    //next byte is a pointer to the string, get it..
                    i++;
                    s += getString(buf, buf[i]);
                    i++;
                    end = true;
                } else {
                    //next byte is the string length
                    len = buf[i];
                    i++;
                    //get the string
                    s += Encoding.ASCII.GetString(buf, i, len);
                    i += len;
                    //check for the null terminator
                    if (buf[i] != 0) {
                        //not null, add a point to the name
                        s += ".";
                    } else {
                        //null char..the string is complete, exit
                        end = true;
                        i++;
                    }
                }
            }
            return i;
        }
        private string getString(byte[] buf, int i)
        {
            string s = "";
            int len;
            bool end = false;
            while (!end) {
                len = buf[i];
                i++;
                s += Encoding.ASCII.GetString(buf, i, len);
                i += len;
                if (buf[i] == 192) {
                    i++;
                    s += "." + getString(buf, buf[i]);
                    return s;
                }
                if (buf[i] != 0) {
                    s += ".";
                } else {
                    end = true;
                }
            }
            return s;
        }
    }
}

使用方法は次のとおりです。

/// <summary>
        /// Get the MX from the domain address.
        /// </summary>
        public static string getMXrecord(string domain)
        {
            domain = domain.Substring(domain.IndexOf('@') + 1);
            string LocalDNS = GetDnsAdress().ToString();
            Console.WriteLine("domain: " + domain);

            // resolv the authoritative domain (type=2)
            C_DNSquery DnsQry = new C_DNSquery(LocalDNS, domain, 2);
            Thread t1 = new Thread(new ThreadStart(DnsQry.doTheJob));
            t1.Start();
            int timeout = 20;
            while ((timeout > 0) & (!DnsQry.Done)) {
                Thread.Sleep(100);
                timeout--;
            }
            if (timeout == 0) {
                if (DnsQry.udpClient != null) {
                    DnsQry.udpClient.Close();
                }
                t1.Abort();
                DnsQry.Error = 100;
            }

            string[] ns1;
            string MyNs = "";
            if (DnsQry.Error == 0) {
                ns1 = DnsQry.result[0].Split(',');
                MyNs = ns1[4];
                t1.Abort();
            } else {
                t1.Abort();
                MyNs = LocalDNS;
            }

            // Resolve MX (type = 15)
            DnsQry = new C_DNSquery(MyNs, domain, 15);
            Thread t2 = new Thread(new ThreadStart(DnsQry.doTheJob));
            t2.Start();
            timeout = 20;
            string TTL = "";
            string MXName = "";
            Int32 preference = 9910000;
            while ((timeout > 0) & (!DnsQry.Done)) {
                Thread.Sleep(100);
                timeout--;
            }
            if (timeout == 0) {
                if (DnsQry.udpClient != null) {
                    DnsQry.udpClient.Close();
                }
                t2.Abort();
                DnsQry.Error = 100;
            }
            if (DnsQry.Error == 0) {

                if (DnsQry.result.Count == 1) {
                    string[] ns2 = DnsQry.result[0].Split(',');
                    MXName = ns2[5];
                    TTL = ns2[3];
                    preference = Int32.Parse(ns2[4]);
                    Console.WriteLine("domaine: {0} MX: {1} time: {2} pref: {3} ttl: {4}", domain.Substring(domain.IndexOf('@') + 1), MXName, 
                        DateTime.Now, preference, TTL);


                } else {
                    for (int indns = 0; indns <= DnsQry.result.Count - 1; indns++) {
                        string[] ns2 = DnsQry.result[indns].Split(',');
                        if (Int32.Parse(ns2[4]) < preference) {
                            MXName = ns2[5];
                            TTL = ns2[3];
                            preference = Int32.Parse(ns2[4]);
Console.WriteLine("domain: {0} MX: {1} time: {2} pref: {3} ttl: {4}", domain.Substring(domain.IndexOf('@') + 1), MXName, 
                                DateTime.Now, preference, TTL);

                            }
                    }
                }
            }
            return MXName;
        }
1
Joseph Philbert