VB - Simple Network Time (NTP) Protocol Client


SUBMITTED BY: TheSwarm

DATE: Oct. 20, 2015, 9 p.m.

FORMAT: Text only

SIZE: 23.8 kB

HITS: 1959

  1. Imports System
  2. Imports System.Net
  3. Imports System.Net.Sockets
  4. Imports System.Runtime.InteropServices
  5. Imports System.Text
  6. Namespace InternetTime
  7. 'Leap indicator field values
  8. Public Enum _LeapIndicator
  9. NoWarning '0 - No warning
  10. LastMinute61 '1 - Last minute has 61 seconds
  11. LastMinute59 '2 - Last minute has 59 seconds
  12. Alarm '3 - Alarm condition (clock not synchronized)
  13. End Enum
  14. 'Mode field values
  15. Public Enum _Mode
  16. SymmetricActive '1 - Symmetric active
  17. SymmetricPassive '2 - Symmetric pasive
  18. Client '3 - Client
  19. Server '4 - Server
  20. Broadcast '5 - Broadcast
  21. Unknown '0, 6, 7 - Reserved
  22. End Enum
  23. 'Stratum field values
  24. Public Enum _Stratum
  25. Unspecified '0 - unspecified or unavailable
  26. PrimaryReference '1 - primary reference (e.g. radio-clock)
  27. SecondaryReference '2-15 - secondary reference (via NTP or SNTP)
  28. Reserved '16-255 - reserved
  29. End Enum
  30. '/// <summary>
  31. '/// SNTPClient is a VB.NET# class designed to connect to time servers on the Internet and
  32. '/// fetch the current date and time. Optionally, it may update the time of the local system.
  33. '/// The implementation of the protocol is based on the RFC 2030.
  34. '///
  35. '/// Public class members:
  36. '///
  37. '/// LeapIndicator - Warns of an impending leap second to be inserted/deleted in the last
  38. '/// minute of the current day. (See the _LeapIndicator enum)
  39. '///
  40. '/// VersionNumber - Version number of the protocol (3 or 4).
  41. '///
  42. '/// Mode - Returns mode. (See the _Mode enum)
  43. '///
  44. '/// Stratum - Stratum of the clock. (See the _Stratum enum)
  45. '///
  46. '/// PollInterval - Maximum interval between successive messages
  47. '///
  48. '/// Precision - Precision of the clock
  49. '///
  50. '/// RootDelay - Round trip time to the primary reference source.
  51. '///
  52. '/// RootDispersion - Nominal error relative to the primary reference source.
  53. '///
  54. '/// ReferenceID - Reference identifier (either a 4 character string or an IP address).
  55. '///
  56. '/// ReferenceTimestamp - The time at which the clock was last set or corrected.
  57. '///
  58. '/// OriginateTimestamp - The time at which the request departed the client for the server.
  59. '///
  60. '/// ReceiveTimestamp - The time at which the request arrived at the server.
  61. '///
  62. '/// Transmit Timestamp - The time at which the reply departed the server for client.
  63. '///
  64. '/// RoundTripDelay - The time between the departure of request and arrival of reply.
  65. '///
  66. '/// LocalClockOffset - The offset of the local clock relative to the primary reference
  67. '/// source.
  68. '///
  69. '/// Initialize - Sets up data structure and prepares for connection.
  70. '///
  71. '/// Connect - Connects to the time server and populates the data structure.
  72. '/// It can also update the system time.
  73. '///
  74. '/// IsResponseValid - Returns true if received data is valid and if comes from
  75. '/// a NTP-compliant time server.
  76. '///
  77. '/// ToString - Returns a string representation of the object.
  78. '///
  79. '/// -----------------------------------------------------------------------------
  80. '/// Structure of the standard NTP header (as described in RFC 2030)
  81. '/// 1 2 3
  82. '/// 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
  83. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  84. '/// |LI | VN |Mode | Stratum | Poll | Precision |
  85. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  86. '/// | Root Delay |
  87. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  88. '/// | Root Dispersion |
  89. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  90. '/// | Reference Identifier |
  91. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  92. '/// | |
  93. '/// | Reference Timestamp (64) |
  94. '/// | |
  95. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  96. '/// | |
  97. '/// | Originate Timestamp (64) |
  98. '/// | |
  99. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  100. '/// | |
  101. '/// | Receive Timestamp (64) |
  102. '/// | |
  103. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  104. '/// | |
  105. '/// | Transmit Timestamp (64) |
  106. '/// | |
  107. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  108. '/// | Key Identifier (optional) (32) |
  109. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  110. '/// | |
  111. '/// | |
  112. '/// | Message Digest (optional) (128) |
  113. '/// | |
  114. '/// | |
  115. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  116. '///
  117. '/// -----------------------------------------------------------------------------
  118. '///
  119. '/// SNTP Timestamp Format (as described in RFC 2030)
  120. '/// 1 2 3
  121. '/// 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
  122. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  123. '/// | Seconds |
  124. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  125. '/// | Seconds Fraction (0-padded) |
  126. '/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  127. '///
  128. '/// </summary>
  129. Public Class SNTPClient
  130. '// NTP Data Structure Length
  131. Private Const SNTPDataLength As Byte = 47
  132. '// NTP Data Structure (as described in RFC 2030)
  133. Dim SNTPData(SNTPDataLength) As Byte
  134. '// Offset constants for timestamps in the data structure
  135. Private Const offReferenceID As Byte = 12
  136. Private Const offReferenceTimestamp As Byte = 16
  137. Private Const offOriginateTimestamp As Byte = 24
  138. Private Const offReceiveTimestamp As Byte = 32
  139. Private Const offTransmitTimestamp As Byte = 40
  140. 'Leap Indicator
  141. Public ReadOnly Property LeapIndicator() As _LeapIndicator
  142. Get
  143. 'Isolate the two most significant bits
  144. Dim bVal As Byte = (SNTPData(0) >> 6)
  145. Select Case bVal
  146. Case 0 : Return _LeapIndicator.NoWarning
  147. Case 1 : Return _LeapIndicator.LastMinute61
  148. Case 2 : Return _LeapIndicator.LastMinute59
  149. Case 3 : Return _LeapIndicator.Alarm
  150. Case Else : Return _LeapIndicator.Alarm
  151. End Select
  152. End Get
  153. End Property
  154. ' Version Number
  155. Public ReadOnly Property VersionNumber() As Byte
  156. Get
  157. 'Isolate bits 3 - 5
  158. Dim bVal As Byte = (SNTPData(0) And &H38) >> 3
  159. Return bVal
  160. End Get
  161. End Property
  162. Public ReadOnly Property Mode()
  163. Get
  164. 'Isolate bits 0 - 3
  165. Dim bVal As Byte = (SNTPData(0) And &H7)
  166. Select Case bVal
  167. Case 0, 6, 7
  168. Return _Mode.Unknown
  169. Case 1
  170. Return _Mode.SymmetricActive
  171. Case 2
  172. Return _Mode.SymmetricPassive
  173. Case 3
  174. Return _Mode.Client
  175. Case 4
  176. Return _Mode.Server
  177. Case 5
  178. Return _Mode.Broadcast
  179. End Select
  180. End Get
  181. End Property
  182. 'Stratum
  183. Public ReadOnly Property Stratum() As _Stratum
  184. Get
  185. Dim bVal As Byte = SNTPData(1)
  186. If (bVal = 0) Then
  187. Return _Stratum.Unspecified
  188. ElseIf (bVal = 1) Then
  189. Return _Stratum.PrimaryReference
  190. ElseIf (bVal <= 15) Then
  191. Return _Stratum.SecondaryReference
  192. Else
  193. Return _Stratum.Reserved
  194. End If
  195. End Get
  196. End Property
  197. 'Poll Interval
  198. Public ReadOnly Property PollInterval() As Int32
  199. Get
  200. '// Thanks to Jim Hollenhorst <hollenho@attbi.com>
  201. Return Math.Pow(2, SNTPData(2))
  202. 'Return Math.Round(Math.Pow(2, SNTPData(2)))
  203. End Get
  204. End Property
  205. 'Precision (in milliseconds)
  206. Public ReadOnly Property Precision() As Double
  207. Get
  208. '// Thanks to Jim Hollenhorst <hollenho@attbi.com>
  209. Return Math.Pow(2, SNTPData(3))
  210. 'Return (1000 * Math.Pow(2, SNTPData(3) - 256))
  211. End Get
  212. End Property
  213. 'Root Delay (in milliseconds)
  214. Public ReadOnly Property RootDelay() As Double
  215. Get
  216. Dim temp As Int64 = 0
  217. temp = 256 * (256 * (256 * SNTPData(4) + SNTPData(5)) + SNTPData(6)) + SNTPData(7)
  218. Return 1000 * ((temp) / &H10000)
  219. End Get
  220. End Property
  221. 'Root Dispersion (in milliseconds)
  222. Public ReadOnly Property RootDispersion() As Double
  223. Get
  224. Dim temp As Int64 = 0
  225. temp = 256 * (256 * (256 * SNTPData(8) + SNTPData(9)) + SNTPData(10)) + SNTPData(11)
  226. Return 1000 * ((temp) / &H10000)
  227. End Get
  228. End Property
  229. 'Reference Identifier
  230. Public ReadOnly Property ReferenceID() As String
  231. Get
  232. Dim val As String = ""
  233. Select Case Stratum
  234. Case _Stratum.PrimaryReference Or Stratum.Unspecified
  235. If SNTPData(offReferenceID + 0) <> 0 Then val += Chr(SNTPData(offReferenceID + 0))
  236. If SNTPData(offReferenceID + 1) <> 0 Then val += Chr(SNTPData(offReferenceID + 1))
  237. If SNTPData(offReferenceID + 2) <> 0 Then val += Chr(SNTPData(offReferenceID + 2))
  238. If SNTPData(offReferenceID + 3) <> 0 Then val += Chr(SNTPData(offReferenceID + 3))
  239. Case _Stratum.SecondaryReference
  240. Select Case VersionNumber
  241. Case 3 '// Version 3, Reference ID is an IPv4 address
  242. Dim Address As String = SNTPData(offReferenceID + 0).ToString() + "." + SNTPData(offReferenceID + 1).ToString() + "." + SNTPData(offReferenceID + 2).ToString() + "." + SNTPData(offReferenceID + 3).ToString()
  243. Try
  244. Dim Host As IPHostEntry = Dns.GetHostByAddress(Address)
  245. val = Host.HostName + " (" + Address + ")"
  246. Catch e As Exception
  247. val = "N/A"
  248. End Try
  249. Case 4 '// Version 4, Reference ID is the timestamp of last update
  250. Dim time As DateTime = ComputeDate(GetMilliSeconds(offReferenceID))
  251. '// Take care of the time zone
  252. Dim offspan As TimeSpan = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now)
  253. val = (time.Add(offspan)).ToString()
  254. Case Else
  255. val = "N/A"
  256. End Select
  257. End Select
  258. Return val
  259. End Get
  260. End Property
  261. '// Reference Timestamp
  262. Public ReadOnly Property ReferenceTimestamp() As DateTime
  263. Get
  264. Dim time As DateTime = ComputeDate(GetMilliSeconds(offReferenceTimestamp))
  265. '// Take care of the time zone
  266. Dim offspan As TimeSpan = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now)
  267. Return time.Add(offspan)
  268. End Get
  269. End Property
  270. '// Originate Timestamp
  271. Public ReadOnly Property OriginateTimestamp() As DateTime
  272. Get
  273. Return ComputeDate(GetMilliSeconds(offOriginateTimestamp))
  274. End Get
  275. End Property
  276. '// Receive Timestamp
  277. Public ReadOnly Property ReceiveTimestamp() As DateTime
  278. Get
  279. Dim time As DateTime = ComputeDate(GetMilliSeconds(offReceiveTimestamp))
  280. 'Take care of the time zone
  281. Dim offspan As TimeSpan = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now)
  282. Return time.Add(offspan)
  283. End Get
  284. End Property
  285. '// Transmit Timestamp
  286. Public Property TransmitTimestamp() As DateTime
  287. Get
  288. Dim time As DateTime = ComputeDate(GetMilliSeconds(offTransmitTimestamp))
  289. 'Take care of the time zone
  290. Dim offspan As TimeSpan = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now)
  291. Return time.Add(offspan)
  292. End Get
  293. Set(ByVal Value As DateTime)
  294. SetDate(offTransmitTimestamp, Value)
  295. End Set
  296. End Property
  297. '// Destination Timestamp
  298. Public DestinationTimestamp As DateTime
  299. '// Round trip delay (in milliseconds)
  300. Public ReadOnly Property RoundTripDelay() As Int64
  301. Get
  302. '// Thanks to DNH <dnharris@csrlink.net>
  303. Dim span As TimeSpan = DestinationTimestamp.Subtract(OriginateTimestamp).Subtract(ReceiveTimestamp.Subtract(TransmitTimestamp))
  304. Return span.TotalMilliseconds
  305. End Get
  306. End Property
  307. '// Local clock offset (in milliseconds)
  308. Public ReadOnly Property LocalClockOffset() As Int64
  309. Get
  310. '// Thanks to DNH <dnharris@csrlink.net>
  311. Dim span As TimeSpan = ReceiveTimestamp.Subtract(OriginateTimestamp).Add((TransmitTimestamp.Subtract(DestinationTimestamp)))
  312. Return span.TotalMilliseconds / 2
  313. End Get
  314. End Property
  315. '// Compute date, given the number of milliseconds since January 1, 1900
  316. Private Function ComputeDate(ByVal milliseconds As Decimal) As DateTime
  317. Dim span As TimeSpan = TimeSpan.FromMilliseconds(milliseconds)
  318. Dim time As DateTime = New DateTime(1900, 1, 1)
  319. time = time.Add(span)
  320. Return time
  321. End Function
  322. '// Compute the number of milliseconds, given the offset of a 8-byte array
  323. Private Function GetMilliSeconds(ByVal offset As Byte) As Decimal
  324. Dim intPart As Decimal = 0, fractPart As Decimal = 0
  325. Dim i As Int32
  326. For i = 0 To 3
  327. intPart = Int(256 * intPart + SNTPData(offset + i))
  328. Next
  329. For i = 4 To 7
  330. fractPart = Int(256 * fractPart + SNTPData(offset + i))
  331. Next
  332. Dim milliseconds As Decimal = Int(intPart * 1000 + (fractPart * 1000) / &H100000000L)
  333. Return milliseconds
  334. End Function
  335. '// Compute the 8-byte array, given the date
  336. Private Sub SetDate(ByVal offset As Byte, ByVal dateval As DateTime)
  337. Dim intPart As Decimal = 0, fractPart As Decimal = 0
  338. Dim StartOfCentury As DateTime = New DateTime(1900, 1, 1, 0, 0, 0)
  339. Dim milliseconds As Decimal = Int(dateval.Subtract(StartOfCentury).TotalMilliseconds)
  340. intPart = Int(milliseconds / 1000)
  341. fractPart = Int(((milliseconds Mod 1000) * &H100000000L) / 1000)
  342. Dim temp As Decimal = intPart
  343. Dim i As Decimal
  344. For i = 3 To 0 Step -1
  345. SNTPData(offset + i) = Int(temp Mod 256)
  346. temp = Int(temp / 256)
  347. Next
  348. temp = Int(fractPart)
  349. For i = 7 To 4 Step -1
  350. SNTPData(offset + i) = Int(temp Mod 256)
  351. temp = Int(temp / 256)
  352. Next
  353. End Sub
  354. '// Initialize the NTPClient data
  355. Private Sub Initialize()
  356. 'Set version number to 4 and Mode to 3 (client)
  357. SNTPData(0) = &H1B
  358. 'Initialize all other fields with 0
  359. Dim i As Int32
  360. For i = 1 To 47
  361. SNTPData(i) = 0
  362. Next
  363. 'Initialize the transmit timestamp
  364. TransmitTimestamp = DateTime.Now
  365. End Sub
  366. Public Sub New(ByVal host As String)
  367. TimeServer = host
  368. End Sub
  369. '// Connect to the time server and update system time
  370. Public Function Connect(ByVal UpdateSystemTime As Boolean) As Boolean
  371. Try
  372. 'Resolve server address
  373. Dim hostadd As IPHostEntry = Dns.Resolve(TimeServer)
  374. Dim EPhost As IPEndPoint = New IPEndPoint(hostadd.AddressList(0), 123)
  375. 'Connect the time server
  376. Dim TimeSocket As UdpClient = New UdpClient
  377. TimeSocket.Connect(EPhost)
  378. 'Initialize data structure
  379. Initialize()
  380. TimeSocket.Send(SNTPData, SNTPData.Length)
  381. SNTPData = TimeSocket.Receive(EPhost)
  382. If IsResponseValid() = False Then
  383. Throw New Exception("Invalid response from " + TimeServer)
  384. End If
  385. DestinationTimestamp = DateTime.Now
  386. Catch e As SocketException
  387. Throw New Exception(e.Message)
  388. End Try
  389. '// Update system time
  390. If (UpdateSystemTime) Then
  391. SetTime()
  392. End If
  393. End Function
  394. '// Check if the response from server is valid
  395. Public Function IsResponseValid() As Boolean
  396. If (SNTPData.Length < SNTPDataLength Or Mode <> _Mode.Server) Then
  397. Return False
  398. Else
  399. Return True
  400. End If
  401. End Function
  402. '// Converts the object to string
  403. Public Overrides Function ToString() As String
  404. Dim str As String
  405. Dim sb As New StringBuilder("")
  406. sb.Append("Leap Indicator: ")
  407. Select Case LeapIndicator
  408. Case _LeapIndicator.NoWarning
  409. sb.Append("No warning")
  410. Case _LeapIndicator.LastMinute61
  411. sb.Append("Last minute has 61 seconds")
  412. Case _LeapIndicator.LastMinute59
  413. sb.Append("Last minute has 59 seconds")
  414. Case _LeapIndicator.Alarm
  415. sb.Append("Alarm Condition (clock not synchronized)")
  416. End Select
  417. sb.Append(vbCrLf & "Version number: " + VersionNumber.ToString())
  418. sb.Append(vbCrLf & "Mode: ")
  419. Select Case Mode
  420. Case _Mode.Unknown
  421. sb.Append("Unknown")
  422. Case _Mode.SymmetricActive
  423. sb.Append("Symmetric Active")
  424. Case _Mode.SymmetricPassive
  425. sb.Append("Symmetric Pasive")
  426. Case _Mode.Client
  427. sb.Append("Client")
  428. Case _Mode.Server
  429. sb.Append("Server")
  430. Case _Mode.Broadcast
  431. sb.Append("Broadcast")
  432. End Select
  433. sb.Append(vbCrLf & "Stratum: ")
  434. Select Case Stratum
  435. Case _Stratum.Unspecified
  436. Case _Stratum.Reserved
  437. sb.Append("Unspecified")
  438. Case _Stratum.PrimaryReference
  439. sb.Append("Primary Reference")
  440. Case _Stratum.SecondaryReference
  441. sb.Append("Secondary Reference")
  442. End Select
  443. sb.Append(vbCrLf & "Local time: " + TransmitTimestamp.ToString())
  444. sb.Append(vbCrLf & "Precision: " + Precision.ToString() + " ms")
  445. sb.Append(vbCrLf & "Poll Interval: " + PollInterval.ToString() + " s")
  446. sb.Append(vbCrLf & "Reference ID: " + ReferenceID.ToString())
  447. sb.Append(vbCrLf & "Root Delay: " + RootDelay.ToString() + " ms")
  448. sb.Append(vbCrLf & "Root Dispersion: " + RootDispersion.ToString() + " ms")
  449. sb.Append(vbCrLf & "Round Trip Delay: " + RoundTripDelay.ToString() + " ms")
  450. sb.Append(vbCrLf & "Local Clock Offset: " + LocalClockOffset.ToString() + " ms")
  451. sb.Append(vbCrLf)
  452. Return sb.ToString
  453. End Function
  454. '// SYSTEMTIME structure used by SetSystemTime
  455. <StructLayout(LayoutKind.Sequential)> Private Structure SYSTEMTIME
  456. Public year As Int16
  457. Public month As Int16
  458. Public dayOfWeek As Int16
  459. Public day As Int16
  460. Public hour As Int16
  461. Public minute As Int16
  462. Public second As Int16
  463. Public milliseconds As Int16
  464. End Structure
  465. <DllImport("KERNEL32.DLL", EntryPoint:="SetLocalTime", SetLastError:=True, CharSet:=CharSet.Unicode, ExactSpelling:=False, CallingConvention:=CallingConvention.StdCall)> Private Shared Function SetLocalTime(ByRef time As SYSTEMTIME) As Int32
  466. End Function
  467. '// Set system time according to transmit timestamp
  468. Private Sub SetTime()
  469. Dim x As Boolean
  470. Dim st As SYSTEMTIME
  471. Dim trts As DateTime = DateTime.Now.AddMilliseconds(LocalClockOffset)
  472. st.year = trts.Year
  473. st.month = trts.Month
  474. st.dayOfWeek = trts.DayOfWeek
  475. st.day = trts.Day
  476. st.hour = trts.Hour
  477. st.minute = trts.Minute
  478. st.second = trts.Second
  479. st.milliseconds = trts.Millisecond
  480. SetLocalTime(st)
  481. End Sub
  482. '// The URL of the time server we're connecting to
  483. Private TimeServer As String
  484. End Class
  485. End Namespace

comments powered by Disqus