GuerrillaNtp - Simple NTP client for .NET

GuerrillaNtp is a RFC4330 SNTP/NTP client written in C# that can be embedded in .NET applications to provide them with accurate network time even when the system clock is unsynchronized.

Download

Get GuerrillaNtp from NuGet:

Tool
<PackageReference Include="GuerrillaNtp" Version="3.1.0" />

Or clone sources from GitHub or Bitbucket. The only dependency is either .NET 5 or .NET Standard 2.0. Sources and binaries are distributed under Apache License 2.0.

Basic use

To get NTP-synchronized time from pool.ntp.org:

NtpClient client = NtpClient.Default;
NtpClock clock = client.Query();
DateTimeOffset local = clock.Now;
DateTimeOffset utc = clock.UtcNow;

Reference: NtpClient, NtpClock

The call to NtpClient.Query() is doing all the expensive network communication. Everything else is cheap. Call NtpClient.Query() once when your application launches and then cache the returned NtpClock until your application exits.

If you prefer DateTime over DateTimeOffset, replace the last two lines with:

DateTime local = clock.Now.LocalDateTime;
DateTime utc = clock.UtcNow.UtcDateTime;

Custom NTP server

If you prefer to run your own NTP server, for example in enterprise context, you can query it instead of pool.ntp.org:

NtpClient client = new NtpClient("ntp.example.com");
NtpClock clock = client.Query();

Async API

Call to NtpClient.Query() blocks until response from the server arrives or until the default 1-second timeout expires. You can use QueryAsync() to avoid blocking:

NtpClock clock = await client.QueryAsync();

Fallback

Querying the server might take too long, especially when the query fails for some reason and your code performs retries with the recommended exponential backoff. Fortunately, there is a convenient way to fall back to system clock:

var time = (client.Last ?? NtpClock.LocalFallback).UtcNow;

This code will work latency-free from the moment your application starts, although the time will not be initially synchronized. Once the first call to Query() or QueryAsync() completes, Last will be populated and the above code begins returning accurate network time.

Exponential backoff

When NTP server cannot be reached, it is recommended to perform exponential backoff:

NtpClock QueryWithBackoff(NtpClient client)
{
    var delay = TimeSpan.FromSeconds(1);
    while (true)
    {
        try
        {
            return client.Query();
        }
        catch
        {
            Thread.Sleep(delay);
            delay = 2 * delay;
        }
    }
}

While this loop runs in separate thread, all other code should use the fallback technique described in previous section.

Polling

Applications that run for days should poll NTP server regularly to resync, perhaps once per day:

while (true)
{
    QueryWithBackoff(client); // see example above
    Thread.Sleep(TimeSpan.FromDays(1));
}

Correction offset

While convenient, it is not always practical to use NtpClock.UtcNow or NtpClock.Now. You can instead get raw correction offset and compute network time yourself:

TimeSpan correction = NtpClient.Default.Query().CorrectionOffset;
var local = DateTime.Now + correction;
var utc = DateTime.UtcNow + correction;

Next steps

Alternatives

GuerrillaNtp is based on Netduino NTP client that I have found on NuGet. I've ported it to desktop .NET Framework and later to .NET Standard and NET 5+ and added numerous patches. Some of the patches are based on SNTP client from Valer Bocan that has numerous mutations circulating around the Internet. Platform.NET contains fairly competent NTP client, which I have discovered after creating GuerrillaNtp.