Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C#: how to set DNS resolve timeout?

Tags:

c#

.net

I want to check if a host can be resolved,but I don't want to wait a long time; so I want to set a timeout.

I have tried

 public static bool ResolveTest(string hostNameOrAddress, TimeSpan time_out)
 {
     var result = Dns.BeginGetHostEntry(hostNameOrAddress, null, null);
     var success = result.AsyncWaitHandle.WaitOne(time_out);
     if (!success) {
         //throw new Exception("Failed to resolve the domain.");
         return false;
     }
     return true;
 }

but this is not working correctly, because when it's a wrong host, it also can return true. So how to set a timeout for DnsResolve?

like image 424
elsonwx Avatar asked Aug 17 '16 02:08

elsonwx


2 Answers

Original Answer

look at the updated answer

There is no way of actually cancelling the Dns.BeginGetHostEntry() but you could try to implement something that can be cancelled after a set time. This is slightly hackish but can do as you have asked.

However DNS lookups are (typically) really fast and in tests typically resolve within 10ms.

Here is a sample. Basically making a Task based approach that allows you to execute a Task which returns a bool whether the result was completed.

public static Task<bool> Resolve(string hostNameOrAddress, int millisecond_time_out)
{
    return Task.Run(async () =>
    {
        bool completed = false;
        var asCallBack = new AsyncCallback(ar =>
        {
            ResolveState context = (ResolveState)ar.AsyncState;
            if (context.Result == ResolveType.Pending)
            {
                try
                {
                    var ipList = Dns.EndGetHostEntry(ar);
                    if (ipList == null || ipList.AddressList == null || ipList.AddressList.Length == 0)
                        context.Result = ResolveType.InvalidHost;
                    else
                        context.Result = ResolveType.Completed;
                }
                catch
                {
                    context.Result = ResolveType.InvalidHost;
                }
            }
            completed = true;
        });
        ResolveState ioContext = new ResolveState(hostNameOrAddress);
        var result = Dns.BeginGetHostEntry(ioContext.HostName, asCallBack, ioContext);
        int miliCount = 0;
        while (!completed)
        {
            miliCount++;
            if (miliCount >= millisecond_time_out)
            {
                result.AsyncWaitHandle.Close();
                result = null;
                ioContext.Result = ResolveType.Timeout;
                break;
            }
            await Task.Delay(1);
        }
        Console.WriteLine($"The result of {ioContext.HostName} is {ioContext.Result}");
        return ioContext.Result == ResolveType.Completed;
    });
}

public class ResolveState
{
    public ResolveState(string hostName)
    {
        if (string.IsNullOrWhiteSpace(hostName))
            throw new ArgumentNullException(nameof(hostName));
        _hostName = hostName;
    }

    readonly string _hostName;

    public ResolveType Result { get; set; } = ResolveType.Pending;

    public string HostName => _hostName;

}

public enum ResolveType
{
    Pending,
    Completed,
    InvalidHost,
    Timeout
}

The method can then be called as

public static async void RunTest()
{
    await Resolve("asdfasdfasdfasdfasdfa.ca", 60);
    await Resolve("google.com", 60);
}

When the Resolve is executed it creates an internal AsyncCallBack delegate. This delegate is then passed to the Dns.BeginGetHostEntry() method. In addition we pass in a state object of ResolveState. This state object is resposible for managing the state between the contexts. Therefore when the AsyncCallBack gets executed we can set the Result of the ResolveState. Next the completed flag is set to indicate the callback delegate completed.

Finally (and this is the part I dont like) is we then have a loop that executes every 1ms and if the timeout expires sets the Result of our state object to Timeout.

Again this is slightly hackish but does result in sort of the desired effect. Now this is also done async so you can use the async & await features.

Updated Answer

After the first answer I didnt like the result and reviewed your initial question. You were on the right track except we need to check the complete flag as you have done. However success will return false if result didn't finish after the WaitOne call. If it finished then success will be true (even without resolving the IP address) you then need to check the result using the Dns.EndGetHostEntry.

Revised Code:

public static bool ResolveTest(string hostNameOrAddress, int millisecond_time_out)
{
    ResolveState ioContext = new ResolveState(hostNameOrAddress);
    var result = Dns.BeginGetHostEntry(ioContext.HostName, null, null);
    var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(millisecond_time_out), true);
    if (!success)
    {
        ioContext.Result = ResolveType.Timeout;
    }
    else
    {
        try
        {
            var ipList = Dns.EndGetHostEntry(result);
            if (ipList == null || ipList.AddressList == null || ipList.AddressList.Length == 0)
                ioContext.Result = ResolveType.InvalidHost;
            else
                ioContext.Result = ResolveType.Completed;
        }
        catch
        {
            ioContext.Result = ResolveType.InvalidHost;
        }
    }
    Console.WriteLine($"The result of ResolveTest for {ioContext.HostName} is {ioContext.Result}");
    return ioContext.Result == ResolveType.Completed;
}

Here's the full gist https://gist.github.com/Nico-VanHaaster/35342c1386de752674d0c37ceaa65e00.

like image 183
Nico Avatar answered Oct 04 '22 07:10

Nico


Current accepted answer is quite old and complex, nowadays you can easily use System.Threading.Tasks.Task.Run() for this, this way:

private Task<IPAddress> ResolveAsync(string hostName) {
    return System.Threading.Tasks.Task.Run(() => {
        return System.Net.Dns.GetHostEntry(hostName).AddressList[0];
    });
}

private string ResolveWithTimeout(string hostNameOrIpAddress) {
    var timeout = TimeSpan.FromSeconds(3.0);
    var task = ResolveAsync(hostNameOrIpAddress);
    if (!task.Wait(timeout)) {
        throw new TimeoutException();
    }
    return task.Result.ToString();
}
like image 43
knocte Avatar answered Oct 04 '22 06:10

knocte