Tiny NTP client.
[novacoin.git] / src / ntp.cpp
1 #include <sys/time.h>
2
3 #ifdef WIN32
4 #include <winsock2.h>
5 #else
6 #include <sys/socket.h>
7 #include <netinet/in.h>
8 #include <arpa/inet.h>
9 #include <netdb.h>
10 #endif
11 #ifndef WIN32
12 #include <unistd.h>
13 #endif
14
15 #include "netbase.h"
16
17 extern int GetRandInt(int nMax);
18
19 /*
20  * NTP uses two fixed point formats.  The first (l_fp) is the "long"
21  * format and is 64 bits long with the decimal between bits 31 and 32.
22  * This is used for time stamps in the NTP packet header (in network
23  * byte order) and for internal computations of offsets (in local host
24  * byte order). We use the same structure for both signed and unsigned
25  * values, which is a big hack but saves rewriting all the operators
26  * twice. Just to confuse this, we also sometimes just carry the
27  * fractional part in calculations, in both signed and unsigned forms.
28  * Anyway, an l_fp looks like:
29  *
30  *    0                   1                   2                   3
31  *    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
32  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33  *   |                         Integral Part                         |
34  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35  *   |                         Fractional Part                       |
36  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37  * REF http://www.eecis.udel.edu/~mills/database/rfc/rfc2030.txt
38  */
39
40
41 typedef struct {
42   union {
43     uint32_t Xl_ui;
44     int32_t Xl_i;
45   } Ul_i;
46   union {
47     uint32_t Xl_uf;
48     int32_t Xl_f;
49   } Ul_f;
50 } l_fp;
51
52
53 inline void Ntp2Unix(uint32_t &n, time_t &u)
54 {
55     // Ntp's time scale starts in 1900, Unix in 1970.
56
57     u = n - 0x83aa7e80; // 2208988800 1970 - 1900 in seconds
58 }
59
60 inline void HTONL_FP(l_fp *h, l_fp *n)
61 {
62     (n)->Ul_i.Xl_ui = htonl((h)->Ul_i.Xl_ui);
63     (n)->Ul_f.Xl_uf = htonl((h)->Ul_f.Xl_uf);
64 }
65
66 inline void ntohl_fp(l_fp *n, l_fp *h)
67 {
68     (h)->Ul_i.Xl_ui = ntohl((n)->Ul_i.Xl_ui);
69     (h)->Ul_f.Xl_uf = ntohl((n)->Ul_f.Xl_uf);
70 }
71
72 struct pkt {
73   uint8_t  li_vn_mode;     /* leap indicator, version and mode */
74   uint8_t  stratum;        /* peer stratum */
75   uint8_t  ppoll;          /* peer poll interval */
76   int8_t  precision;      /* peer clock precision */
77   uint32_t    rootdelay;      /* distance to primary clock */
78   uint32_t    rootdispersion; /* clock dispersion */
79   uint32_t refid;          /* reference clock ID */
80   l_fp    ref;        /* time peer clock was last updated */
81   l_fp    org;            /* originate time stamp */
82   l_fp    rec;            /* receive time stamp */
83   l_fp    xmt;            /* transmit time stamp */
84
85   uint32_t exten[1];       /* misused */
86   uint8_t  mac[5 * sizeof(uint32_t)]; /* mac */
87 };
88
89 int nServersCount = 65;
90
91 std::string NtpServers[65] = {
92     // Microsoft
93     "time.windows.com",
94
95     // Google
96     "time1.google.com",
97     "time2.google.com",
98
99     // Russian Federation
100     "ntp.ix.ru",
101     "0.ru.pool.ntp.org",
102     "1.ru.pool.ntp.org",
103     "2.ru.pool.ntp.org",
104     "3.ru.pool.ntp.org",
105     "ntp1.stratum2.ru",
106     "ntp2.stratum2.ru",
107     "ntp3.stratum2.ru",
108     "ntp4.stratum2.ru",
109     "ntp5.stratum2.ru",
110     "ntp6.stratum2.ru",
111     "ntp7.stratum2.ru",
112     "ntp1.stratum1.ru",
113     "ntp2.stratum1.ru",
114     "ntp3.stratum1.ru",
115     "ntp4.stratum1.ru",
116     "ntp1.vniiftri.ru",
117     "ntp2.vniiftri.ru",
118     "ntp3.vniiftri.ru",
119     "ntp4.vniiftri.ru",
120     "ntp21.vniiftri.ru",
121     "ntp1.niiftri.irkutsk.ru",
122     "ntp2.niiftri.irkutsk.ru",
123     "vniiftri.khv.ru",
124     "vniiftri2.khv.ru",
125
126     // United States
127     "nist1-pa.ustiming.org",
128     "time-a.nist.gov ",
129     "time-b.nist.gov ",
130     "time-c.nist.gov ",
131     "time-d.nist.gov ",
132     "nist1-macon.macon.ga.us",
133     "nist.netservicesgroup.com",
134     "nisttime.carsoncity.k12.mi.us",
135     "nist1-lnk.binary.net",
136     "wwv.nist.gov",
137     "time-a.timefreq.bldrdoc.gov",
138     "time-b.timefreq.bldrdoc.gov",
139     "time-c.timefreq.bldrdoc.gov",
140     "time.nist.gov",
141     "utcnist.colorado.edu",
142     "utcnist2.colorado.edu",
143     "ntp-nist.ldsbc.net",
144     "nist1-lv.ustiming.org",
145     "time-nw.nist.gov",
146     "nist-time-server.eoni.com",
147     "nist-time-server.eoni.com",
148     "ntp1.bu.edu",
149     "ntp2.bu.edu",
150     "ntp3.bu.edu",
151
152     // South Africa
153     "ntp1.meraka.csir.co.za",
154     "ntp.is.co.za",
155     "ntp2.is.co.za",
156     "igubu.saix.net",
157     "ntp1.neology.co.za",
158     "ntp2.neology.co.za",
159     "tick.meraka.csir.co.za",
160     "tock.meraka.csir.co.za",
161     "ntp.time.org.za",
162     "ntp1.meraka.csir.co.za",
163     "ntp2.meraka.csir.co.za",
164
165     // Italy
166     "ntp1.inrim.it",
167     "ntp2.inrim.it",
168
169     // ... To be continued
170 };
171
172 #ifdef WIN32
173 bool InitWithRandom(int &sockfd, int &servlen, struct sockaddr *pcliaddr)
174 #else
175 bool InitWithRandom(int &sockfd, socklen_t &servlen, struct sockaddr *pcliaddr)
176 #endif
177 {
178     int nAttempt = 0;
179
180     while(nAttempt < 100)
181     {
182         sockfd = -1;
183         nAttempt++;
184
185         int nServerNum = GetRandInt(nServersCount);
186
187         std::vector<CNetAddr> vIP;
188         bool fRet = LookupHost(NtpServers[nServerNum].c_str(), vIP, 10, true);
189         if (!fRet)
190             continue;
191
192         struct sockaddr_in servaddr;
193         servaddr.sin_family = AF_INET;
194         servaddr.sin_port = htons(123);
195
196         bool found = false;
197         for(unsigned int i = 0; i < vIP.size(); i++)
198         {
199             if ((found = vIP[i].GetInAddr(&servaddr.sin_addr)))
200             {
201                 break;
202             }
203         }
204
205         if (!found)
206             continue;
207
208         sockfd = socket(AF_INET, SOCK_DGRAM, 0);
209
210         if (sockfd == -1)
211             continue; // socket initialization error
212
213         if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) == -1 )
214         {
215             continue; // "connection" error
216         }
217
218         *pcliaddr = *((struct sockaddr *) &servaddr);
219         servlen = sizeof(servaddr);
220         return true;
221     }
222
223     return false;
224 }
225
226 #ifdef WIN32
227 bool InitWithHost(std::string &strHostName, int &sockfd, int &servlen, struct sockaddr *pcliaddr)
228 #else
229 bool InitWithHost(std::string &strHostName, int &sockfd, socklen_t &servlen, struct sockaddr *pcliaddr)
230 #endif
231 {
232     sockfd = -1;
233
234     std::vector<CNetAddr> vIP;
235     bool fRet = LookupHost(strHostName.c_str(), vIP, 10, true);
236     if (!fRet)
237         return false;
238
239     struct sockaddr_in servaddr;
240     servaddr.sin_family = AF_INET;
241     servaddr.sin_port = htons(123);
242
243     bool found = false;
244     for(unsigned int i = 0; i < vIP.size(); i++)
245     {
246         if ((found = vIP[i].GetInAddr(&servaddr.sin_addr)))
247         {
248             break;
249         }
250     }
251
252     if (!found)
253         return false;
254
255     sockfd = socket(AF_INET, SOCK_DGRAM, 0);
256
257     if (sockfd == -1)
258         return false; // socket initialization error
259
260     if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) == -1 )
261     {
262         return false; // "connection" error
263     }
264
265     *pcliaddr = *((struct sockaddr *) &servaddr);
266     servlen = sizeof(servaddr);
267
268     return true;
269 }
270
271
272 #ifdef WIN32
273 int64_t DoReq(int sockfd, int servlen, struct sockaddr cliaddr)
274 #else
275 int64_t DoReq(int sockfd, socklen_t servlen, struct sockaddr cliaddr)
276 #endif
277 {
278     struct pkt *msg = new pkt;
279     struct pkt *prt  = new pkt;
280
281     msg->li_vn_mode=227;
282     msg->stratum=0;
283     msg->ppoll=4;
284     msg->precision=0;
285     msg->rootdelay=0;
286     msg->rootdispersion=0;
287
288     msg->ref.Ul_i.Xl_i=0;
289     msg->ref.Ul_f.Xl_f=0;
290     msg->org.Ul_i.Xl_i=0;
291     msg->org.Ul_f.Xl_f=0;
292     msg->rec.Ul_i.Xl_i=0;
293     msg->rec.Ul_f.Xl_f=0;
294     msg->xmt.Ul_i.Xl_i=0;
295     msg->xmt.Ul_f.Xl_f=0;
296
297     int len=48;
298     sendto(sockfd, (char *) msg, len, 0, &cliaddr, servlen);
299     int n = recvfrom(sockfd, msg, len, 0, NULL, NULL);
300
301     ntohl_fp(&msg->rec, &prt->rec);
302     ntohl_fp(&msg->xmt, &prt->xmt);
303
304     time_t seconds_receive;
305     time_t seconds_transmit;
306
307     Ntp2Unix(prt->rec.Ul_i.Xl_ui, seconds_receive);
308     Ntp2Unix(prt->xmt.Ul_i.Xl_ui, seconds_transmit);
309
310     delete msg;
311     delete prt;
312
313     return (seconds_receive + seconds_transmit) / 2;
314 }
315
316 int64_t NtpGetTime()
317 {
318     int sockfd;
319     struct sockaddr cliaddr;
320
321 #ifdef WIN32
322     int servlen;
323 #else
324     socklen_t servlen;
325 #endif
326
327     if (!InitWithRandom(sockfd, servlen, &cliaddr))
328         return -1;
329
330     int64_t nTime = DoReq(sockfd, servlen, cliaddr);
331
332 #ifdef WIN32
333     closesocket(sockfd);
334 #else
335     close(sockfd);
336 #endif
337
338     return nTime;
339 }
340
341 int64_t NtpGetTime(std::string &strHostName)
342 {
343     int sockfd;
344     struct sockaddr cliaddr;
345
346 #ifdef WIN32
347     int servlen;
348 #else
349     socklen_t servlen;
350 #endif
351
352     if (!InitWithHost(strHostName, sockfd, servlen, &cliaddr))
353         return -1;
354
355     int64_t nTime = DoReq(sockfd, servlen, cliaddr);
356
357 #ifdef WIN32
358     closesocket(sockfd);
359 #else
360     close(sockfd);
361 #endif
362
363     return nTime;
364 }