使用 SNTP 进行日期验证
检查您的应用程序的到期日期是否已过,而不考虑系统日期。
概述
DateValidator是一个.NET组件,它将给定日期与从时间服务器检索到的日期进行比较。我最近需要制作一个有时限的应用程序版本。在这种特定情况下,应用程序的到期日期不需要灵活,因此被硬编码到应用程序中。仅检查系统日期是不够的,因为它可以很容易地更改,所以显而易见的方法是从另一个来源获取“真实”日期。本文是我提出的一个快速解决方案。
这不是一个用于保护您的有时限应用程序或NTP/SNTP的全面解决方案,但它可以作为一个良好的起点。
SNTP
世界各地有许多时间服务器,并且有几种不同的协议在使用。此组件使用了简单网络时间协议(SNTP)的一小部分。SNTP通过在UDP套接字123端口上发送/接收48(或更多)字节的数据包工作。在本组件中(由于所需的精度较低),我们只关心其中5个字节(0和40-43)。客户端连接到服务器并发送一个请求包,然后接收一个包含数据的响应包。实际的日期/时间不在数据中!相反,一个包含自1900年1月1日00:00:00 UTC以来的秒数的 Timestamp 嵌入在字节40-43中(小数秒在字节44-47中)。通过这个,我们可以计算出当前的UTC并与我们的进行比较,以查看它是否有效。
为了实现完整的时间同步,还需要使用许多其他字节/时间戳来获得高精度并补偿发送/接收中的延迟。这对于本组件来说不是必需的,因为我们只需要到天的精度;因此这里不作介绍,但如果您需要,它很容易实现。
组件
注意:在验证过程中抛出的任何异常都会被捕获并传递给事件参数,而不是被重新抛出。您可以检查参数以确定要作为验证失败处理的异常。
该组件非常简单。它有一个方法(+3个重载)ValidateAsync
和一个事件Validated
。该方法接受一个DateTime
作为应用程序的到期日期,以及可选的TimeServer
和TimeOut
值。此方法启动一个BackgroundWorker
,实际工作在那里完成(在一个单独的线程上)。完成后,将引发事件,并附带一个ValidatedEventArgs
实例,其中包含我们所需的所有信息。
这是执行验证的代码(详细信息如下)
ValidatedEventArgs validationArgs =
new ValidatedEventArgs(_TimeServer, _MaxDate);
UdpClient client = null;
try
{
client = new UdpClient();
IPEndPoint ipEndPoint = _TimeServer.GetIPEndPoint();
client.Client.SendTimeout = _TimeOut * 1000;
client.Client.ReceiveTimeout = _TimeOut * 1000;
client.Connect(ipEndPoint);
client.Send(Request, byteCount);
byte[] received = client.Receive(ref ipEndPoint);
if (received.Length >= byteCount &&
((received[0] & modeMask) == modeServer))
{
ulong transmitTimeStamp = 0;
for (int i = 40; i <= 43; i++)
transmitTimeStamp = (transmitTimeStamp << 8) | received[i];
DateTime result = rootDateTime;
result += TimeSpan.FromSeconds(transmitTimeStamp);
validationArgs.TimeServerDate = result.Date;
if (validationArgs.TimeServerDate <= _MaxDate)
{
validationArgs.ValidationResult = ValidationResult.OK;
}
else
{
validationArgs.ValidationResult = ValidationResult.Fail;
validationArgs.ErrorText = "This software has expired.";
}
}
else
{
validationArgs.ValidationResult = ValidationResult.Invalid;
validationArgs.ErrorText = "Invalid result from TimeServer.";
}
}
catch (Exception ex)
{
validationArgs.ValidationResult = ValidationResult.Exception;
validationArgs.ErrorText = ex.Message;
validationArgs.Exception = ex;
}
finally
{
// clean up and set the result to our args.
if(client!=null)
client.Close();
e.Result = validationArgs;
}
首先,我们创建、初始化和连接UDP套接字。然后,我们发送请求包。这是一个48字节的数组。字节[0]的位0、1和2指示模式。我们是一个客户端,由值3(011)表示。字节[0]的位3、4和5指示我们使用的SNTP版本。这是使用版本4,所以我们将'4'(100)左移三位,并将其与模式进行OR运算,得到字节[0](00100011)。我们也可以直接将字节[0]设置为35,但我们是按照协议的逻辑正确进行的,这样在协议发生变化时,代码会更容易重构。
private const int byteCount = 48;
private const byte sntpVersion = 4;
private const int versionBitOffset = 3;
private const byte modeMask = 7;
private const byte modeClient = 3;
private const byte modeServer = 4;
private static byte[] Request
{
get
{
byte[] result = new byte[byteCount];
result[0] = modeClient | (sntpVersion << versionBitOffset);
return result;
}
}
接下来,我们等待从服务器接收响应。一旦我们收到字节数组,我们就检查长度和模式位,以确保数据有效并且来自服务器。然后,我们遍历时间戳的整数部分(字节40-43)以获取秒数,并将其转换为DateTime
(UTC)。如果过程中有任何错误或异常,它们会相应地放置在ValidatedEventArgs
的一个实例中。最后,我们关闭套接字。
此时,将调用BackgroundWorker
的RunWorkerCompleted
方法,在那里我们检索事件参数并引发事件;现在,我们回到了原始线程。
backgroundWorker = null;
ValidatedEventArgs result = e.Result as ValidatedEventArgs;
OnValidated(result);
其他类
TimeServer
这是一个简单的类,用于保存服务器的主机名和端口。为了方便起见,我在其中放置了几个静态TimeServer
(有些分组在数组中)。
ValidatedEventArgs
一个简单的类,派生自System.EventArgs
,用于保存有关验证的数据。
有用参考
历史
- 2009年7月8日:初始版本。