using System; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; namespace Muchinfo.MTPClient.Infrastructure.Helpers { // Leap indicator field values public enum _LeapIndicator { NoWarning, // 0 - No warning LastMinute61, // 1 - Last minute has 61 seconds LastMinute59, // 2 - Last minute has 59 seconds Alarm // 3 - Alarm condition (clock not synchronized) } //Mode field values public enum _Mode { SymmetricActive, // 1 - Symmetric active SymmetricPassive, // 2 - Symmetric pasive Client, // 3 - Client Server, // 4 - Server Broadcast, // 5 - Broadcast Unknown // 0, 6, 7 - Reserved } // Stratum field values public enum _Stratum { Unspecified, // 0 - unspecified or unavailable PrimaryReference, // 1 - primary reference (e.g. radio-clock) SecondaryReference, // 2-15 - secondary reference (via NTP or SNTP) Reserved // 16-255 - reserved } [StructLayout(LayoutKind.Sequential)] public struct SystemTime { public ushort wYear; public ushort wMonth; public ushort wDayOfWeek; public ushort wDay; public ushort wHour; public ushort wMinute; public ushort wSecond; public ushort wMiliseconds; } /// /// SNTPTimeClient 的摘要说明。 /// /// Public class members: /// /// LeapIndicator - Warns of an impending leap second to be inserted/deleted in the last /// minute of the current day. (See the _LeapIndicator enum) /// /// VersionNumber - Version number of the protocol (3 or 4). /// /// Mode - Returns mode. (See the _Mode enum) /// /// Stratum - Stratum of the clock. (See the _Stratum enum) /// /// PollInterval - Maximum interval between successive messages. /// /// Precision - Precision of the clock. /// /// RootDelay - Round trip time to the primary reference source. /// /// RootDispersion - Nominal error relative to the primary reference source. /// /// ReferenceID - Reference identifier (either a 4 character string or an IP address). /// /// ReferenceTimestamp - The time at which the clock was last set or corrected. /// /// OriginateTimestamp - The time at which the request departed the client for the server. /// /// ReceiveTimestamp - The time at which the request arrived at the server. /// /// Transmit Timestamp - The time at which the reply departed the server for client. /// /// RoundTripDelay - The time between the departure of request and arrival of reply. /// /// LocalClockOffset - The offset of the local clock relative to the primary reference /// source. /// /// Initialize - Sets up data structure and prepares for connection. /// /// Connect - Connects to the time server and populates the data structure. /// /// IsResponseValid - Returns true if received data is valid and if comes from /// a NTP-compliant time server. /// /// ToString - Returns a string representation of the object. /// /// ----------------------------------------------------------------------------- /// Structure of the standard NTP header (as described in RFC 2030) /// 1 2 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// |LI | VN |Mode | Stratum | Poll | Precision | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Root Delay | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Root Dispersion | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Reference Identifier | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | /// | Reference Timestamp (64) | /// | | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | /// | Originate Timestamp (64) | /// | | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | /// | Receive Timestamp (64) | /// | | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | /// | Transmit Timestamp (64) | /// | | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Key Identifier (optional) (32) | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | /// | | /// | Message Digest (optional) (128) | /// | | /// | | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// ----------------------------------------------------------------------------- /// /// NTP Timestamp Format (as described in RFC 2030) /// 1 2 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Seconds | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Seconds Fraction (0-padded) | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// public class SNTPTimeClient { // NTP Data Structure Length private const byte NTPDataLength = 48; // NTP Data Structure (as described in RFC 2030) byte[] NTPData = new byte[NTPDataLength]; // Offset constants for timestamps in the data structure private const byte offReferenceID = 12; private const byte offReferenceTimestamp = 16; private const byte offOriginateTimestamp = 24; private const byte offReceiveTimestamp = 32; private const byte offTransmitTimestamp = 40; [DllImport("Kernel32.dll")] public static extern bool SetSystemTime(ref SystemTime sysTime); [DllImport("Kernel32.dll")] public static extern bool SetLocalTime(ref SystemTime sysTime); [DllImport("Kernel32.dll")] public static extern void GetSystemTime(ref SystemTime sysTime); [DllImport("Kernel32.dll")] public static extern void GetLocalTime(ref SystemTime sysTime); // Leap Indicator public _LeapIndicator LeapIndicator { get { // Isolate the two most significant bits byte val = (byte)(NTPData[0] >> 6); switch (val) { case 0: return _LeapIndicator.NoWarning; case 1: return _LeapIndicator.LastMinute61; case 2: return _LeapIndicator.LastMinute59; case 3: default: return _LeapIndicator.Alarm; } } } // Version Number public byte VersionNumber { get { // Isolate bits 3 - 5 byte val = (byte)((NTPData[0] & 0x38) >> 3); return val; } } // Mode public _Mode Mode { get { // Isolate bits 0 - 3 byte val = (byte)(NTPData[0] & 0x7); switch (val) { case 0: case 6: case 7: default: return _Mode.Unknown; case 1: return _Mode.SymmetricActive; case 2: return _Mode.SymmetricPassive; case 3: return _Mode.Client; case 4: return _Mode.Server; case 5: return _Mode.Broadcast; } } } // Stratum public _Stratum Stratum { get { byte val = (byte)NTPData[1]; if (val == 0) return _Stratum.Unspecified; else if (val == 1) return _Stratum.PrimaryReference; else if (val <= 15) return _Stratum.SecondaryReference; else return _Stratum.Reserved; } } // Poll Interval public uint PollInterval { get { return (uint)Math.Round(Math.Pow(2, NTPData[2])); } } // Precision (in milliseconds) public double Precision { get { return (1000 * Math.Pow(2, NTPData[3])); } } // Root Delay (in milliseconds) public double RootDelay { get { int temp = 0; temp = 256 * (256 * (256 * NTPData[4] + NTPData[5]) + NTPData[6]) + NTPData[7]; return 1000 * (((double)temp) / 0x10000); } } // Root Dispersion (in milliseconds) public double RootDispersion { get { int temp = 0; temp = 256 * (256 * (256 * NTPData[8] + NTPData[9]) + NTPData[10]) + NTPData[11]; return 1000 * (((double)temp) / 0x10000); } } // Reference Identifier public string ReferenceID { get { string val = ""; switch (Stratum) { case _Stratum.Unspecified: case _Stratum.PrimaryReference: val += Convert.ToChar(NTPData[offReferenceID + 0]); val += Convert.ToChar(NTPData[offReferenceID + 1]); val += Convert.ToChar(NTPData[offReferenceID + 2]); val += Convert.ToChar(NTPData[offReferenceID + 3]); break; case _Stratum.SecondaryReference: //// switch(VersionNumber) //// { //// case 3: // Version 3, Reference ID is an IPv4 address //// string Address = NTPData[offReferenceID + 0].ToString() + "." + //// NTPData[offReferenceID + 1].ToString() + "." + //// NTPData[offReferenceID + 2].ToString() + "." + //// NTPData[offReferenceID + 3].ToString(); //// try //// { //// IPAddress RefAddr = new IPAddress(Address); //// IPHostEntry Host = DNS.GetHostByAddr(RefAddr); //// val = Host.Hostname + " (" + Address + ")"; //// } //// catch(Exception) //// { //// val = "N/A"; //// } //// //// break; //// case 4: // Version 4, Reference ID is the timestamp of last update //// DateTime time = ComputeDate(GetMilliSeconds(offReferenceID)); //// // Take care of the time zone //// long offset = TimeZone.CurrentTimeZone.GetUTCOffset(DateTime.Now); //// TimeSpan offspan = TimeSpan.FromTicks(offset); //// val = (time + offspan).ToString(); //// break; //// default: //// val = "N/A"; //// } break; } return val; } } // Reference Timestamp public DateTime ReferenceTimestamp { get { DateTime time = ComputeDate(GetMilliSeconds(offReferenceTimestamp)); // Take care of the time zone long offset = Convert.ToInt64(TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now)); TimeSpan offspan = TimeSpan.FromTicks(offset); return time + offspan; } } // Originate Timestamp public DateTime OriginateTimestamp { get { return ComputeDate(GetMilliSeconds(offOriginateTimestamp)); } } // Receive Timestamp public DateTime ReceiveTimestamp { get { DateTime time = ComputeDate(GetMilliSeconds(offReceiveTimestamp)); // Take care of the time zone long offset = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).Ticks; TimeSpan offspan = TimeSpan.FromTicks(offset); return time + offspan; } } // Transmit Timestamp public DateTime TransmitTimestamp { get { DateTime time = ComputeDate(GetMilliSeconds(offTransmitTimestamp)); // Take care of the time zone long offset = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).Ticks; TimeSpan offspan = TimeSpan.FromTicks(offset); return time + offspan; } set { SetDate(offTransmitTimestamp, value); } } // Reception Timestamp public DateTime ReceptionTimestamp; // Round trip delay (in milliseconds) public int RoundTripDelay { get { TimeSpan span = (ReceiveTimestamp - OriginateTimestamp) + (ReceptionTimestamp - TransmitTimestamp); return (int)span.TotalMilliseconds; } } // Local clock offset (in milliseconds) public int LocalClockOffset { get { TimeSpan span = (ReceiveTimestamp - OriginateTimestamp) - (ReceptionTimestamp - TransmitTimestamp); return (int)(span.TotalMilliseconds / 2); } } // Compute date, given the number of milliseconds since January 1, 1900 private DateTime ComputeDate(ulong milliseconds) { TimeSpan span = TimeSpan.FromMilliseconds((double)milliseconds); DateTime time = new DateTime(1900, 1, 1); time += span; return time; } // Compute the number of milliseconds, given the offset of a 8-byte array private ulong GetMilliSeconds(byte offset) { ulong intpart = 0, fractpart = 0; for (int i = 0; i <= 3; i++) { intpart = 256 * intpart + NTPData[offset + i]; } for (int i = 4; i <= 7; i++) { fractpart = 256 * fractpart + NTPData[offset + i]; } ulong milliseconds = intpart * 1000 + (fractpart * 1000) / 0x100000000L; return milliseconds; } // Compute the 8-byte array, given the date private void SetDate(byte offset, DateTime date) { ulong intpart = 0, fractpart = 0; DateTime StartOfCentury = new DateTime(1900, 1, 1, 0, 0, 0); // January 1, 1900 12:00 AM ulong milliseconds = (ulong)(date - StartOfCentury).TotalMilliseconds; intpart = milliseconds / 1000; fractpart = ((milliseconds % 1000) * 0x100000000L) / 1000; ulong temp = intpart; for (int i = 3; i >= 0; i--) { NTPData[offset + i] = (byte)(temp % 256); temp = temp / 256; } temp = fractpart; for (int i = 7; i >= 4; i--) { NTPData[offset + i] = (byte)(temp % 256); temp = temp / 256; } } // Initialize the NTPClient data private void Initialize() { // Set version number to 4 and Mode to 3 (client) NTPData[0] = 0x1B; string s2 = Convert.ToString(NTPData[0], 2); //NTPData[0].ToString("2"); // Initialize all other fields with 0 for (int i = 1; i < 48; i++) { NTPData[i] = 0; } // Initialize the transmit timestamp TransmitTimestamp = DateTime.Now; } // Connect to the time server public void Connect() { try { IPAddress hostadd = IPAddress.Parse(TimeServer); IPEndPoint EPhost = new IPEndPoint(hostadd, Convert.ToInt32(TimePort)); UdpClient TimeSocket = new UdpClient(); TimeSocket.Client.ReceiveTimeout = 3000; TimeSocket.Connect(EPhost); Initialize(); TimeSocket.Send(NTPData, NTPData.Length); NTPData = TimeSocket.Receive(ref EPhost); if (!IsResponseValid()) { throw new Exception("Invalid response from " + TimeServer); } ReceptionTimestamp = DateTime.Now; } catch (SocketException e) { throw new Exception(e.Message); } } // Check if the response from server is valid public bool IsResponseValid() { if (NTPData.Length < NTPDataLength || Mode != _Mode.Server) { return false; } else { return true; } } // Converts the object to string public override string ToString() { string str; str = "Leap Indicator: "; switch (LeapIndicator) { case _LeapIndicator.NoWarning: str += "No warning"; break; case _LeapIndicator.LastMinute61: str += "Last minute has 61 seconds"; break; case _LeapIndicator.LastMinute59: str += "Last minute has 59 seconds"; break; case _LeapIndicator.Alarm: str += "Alarm Condition (clock not synchronized)"; break; } str += "\r\nVersion number: " + VersionNumber.ToString() + "\r\n"; str += "Mode: "; switch (Mode) { case _Mode.Unknown: str += "Unknown"; break; case _Mode.SymmetricActive: str += "Symmetric Active"; break; case _Mode.SymmetricPassive: str += "Symmetric Pasive"; break; case _Mode.Client: str += "Client"; break; case _Mode.Server: str += "Server"; break; case _Mode.Broadcast: str += "Broadcast"; break; } str += "\r\nStratum: "; switch (Stratum) { case _Stratum.Unspecified: case _Stratum.Reserved: str += "Unspecified"; break; case _Stratum.PrimaryReference: str += "Primary Reference"; break; case _Stratum.SecondaryReference: str += "Secondary Reference"; break; } str += "\r\nLocal time: " + TransmitTimestamp.ToString(); str += "\r\nPrecision: " + Precision.ToString() + " ms"; str += "\r\nPoll Interval: " + PollInterval.ToString() + " s"; str += "\r\nReference ID: " + ReferenceID.ToString(); str += "\r\nRoot Dispersion: " + RootDispersion.ToString() + " ms"; str += "\r\nRound Trip Delay: " + RoundTripDelay.ToString() + " ms"; str += "\r\nLocal Clock Offset: " + LocalClockOffset.ToString() + " ms"; str += "\r\n"; return str; } // The URL of the time server we're connecting to private string TimeServer; private string TimePort; public SNTPTimeClient(string host, string port) { TimeServer = host; TimePort = port; } } /// /// SNTPTimeClient使用方法 /// public class TestSample { /// /// Tests this instance. /// public void Test() { /* * 1. NTP基于UDP报文进行传输,使用的UDP端口号为123. * 2. 指定的IP可以是公网IP * 3. 也可以是内网局域网--须开户Windows Time服务 */ var client = new SNTPTimeClient("127.0.0.1", "123"); client.Connect(); var remoteDate = client.ReceiveTimestamp; var st = new SystemTime() { wDay = (ushort)remoteDate.Day, wDayOfWeek = (ushort)remoteDate.DayOfWeek, wHour = (ushort)remoteDate.Hour, wMiliseconds = (ushort)remoteDate.Millisecond, wMinute = (ushort)remoteDate.Minute, wMonth = (ushort)remoteDate.Month, wSecond = (ushort)remoteDate.Second, wYear = (ushort)remoteDate.Year }; SNTPTimeClient.SetLocalTime(ref st); } } }