• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* sntp.c - sntp client and server
2  *
3  * Copyright 2019 Rob Landley <rob@landley.net>
4  *
5  * See https://www.ietf.org/rfc/rfc4330.txt
6 
7   modes: oneshot display, oneshot set, persist, serve, multi
8 
9 USE_SNTP(NEWTOY(sntp, ">1M :m :Sp:t#<0=1>16asdDqr#<4>17=10[!as]", TOYFLAG_USR|TOYFLAG_BIN))
10 
11 config SNTP
12   bool "sntp"
13   default y
14   help
15     usage: sntp [-saSdDq] [-r SHIFT] [-mM[ADDRESS]] [-p PORT] [SERVER]
16 
17     Simple Network Time Protocol client. Query SERVER and display time.
18 
19     -p	Use PORT (default 123)
20     -s	Set system clock suddenly
21     -a	Adjust system clock gradually
22     -S	Serve time instead of querying (bind to SERVER address if specified)
23     -m	Wait for updates from multicast ADDRESS (RFC 4330 default 224.0.1.1)
24     -M	Multicast server on ADDRESS (deault 224.0.0.1)
25     -t	TTL (multicast only, default 1)
26     -d	Daemonize (run in background re-querying )
27     -D	Daemonize but stay in foreground: re-query time every 1000 seconds
28     -r	Retry shift (every 1<<SHIFT seconds)
29     -q	Quiet (don't display time)
30 */
31 
32 #define FOR_sntp
33 #include "toys.h"
34 #include <sys/timex.h>
35 
36 GLOBALS(
37   long r, t;
38   char *p, *m, *M;
39 )
40 
41 // Seconds from 1900 to 1970, including appropriate leap days
42 #define SEVENTIES 2208988800ULL
43 
44 // Get time and return ntptime (saving timespec in pointer if not null)
45 // NTP time is high 32 bits = seconds since 1970 (blame RFC 868), low 32 bits
46 // fraction of a second.
47 // diff is how far off we think our clock is from reality (in nanoseconds)
lunchtime(struct timespec * television,long long diff)48 static unsigned long long lunchtime(struct timespec *television, long long diff)
49 {
50   struct timespec tv;
51 
52   clock_gettime(CLOCK_REALTIME, &tv);
53   if (diff) nanomove(&tv, diff);
54 
55   if (television) *television = tv;
56 
57   // Unix time is 1970 but RFCs 868 and 958 said 1900 so add seconds 1900->1970
58   // If they'd done a 34/30 bit split the Y2036 problem would be centuries
59   // from now and still give us nanosecond accuracy, but no...
60   return ((tv.tv_sec+SEVENTIES)<<32)+(((long long)tv.tv_nsec)<<32)/1000000000;
61 }
62 
63 // convert ntptime back to struct timespec.
doublyso(unsigned long long now,struct timespec * tv)64 static void doublyso(unsigned long long now, struct timespec *tv)
65 {
66   // Y2036 fixup: if time wrapped, it's in the future
67   tv->tv_sec = (now>>32) + (1LL<<32)*!(now&(1LL<<63));
68   tv->tv_sec -= SEVENTIES; // Force signed math for Y2038 fixup
69   tv->tv_nsec = ((now&0xFFFFFFFF)*1000000000)>>32;
70 }
71 
sntp_main(void)72 void sntp_main(void)
73 {
74   struct timespec tv, tv2;
75   unsigned long long *pktime = (void *)toybuf, now, then, before = before;
76   long long diff = 0;
77   struct addrinfo *ai;
78   union socksaddr sa;
79   int fd, tries = 0;
80 
81   if (FLAG(M)) toys.optflags |= FLAG_S;
82   if (!(FLAG(S)||FLAG(m)) && !*toys.optargs)
83     error_exit("Need -SMm or SERVER address");
84 
85   // Lookup address and open server or client UDP socket
86   if (!TT.p || !*TT.p) TT.p = "123";
87   ai = xgetaddrinfo(*toys.optargs, TT.p, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
88     AI_PASSIVE*!*toys.optargs);
89 
90   if (FLAG(d) && daemon(0, 0)) perror_exit("daemonize");
91 
92   // Act as server if necessary
93   if (FLAG(S)||FLAG(m)) {
94     fd = xbindany(ai);
95     if (TT.m || TT.M) {
96       struct ip_mreq group;
97       int t = 0;
98 
99       // subscribe to multicast group
100       memset(&group, 0, sizeof(group));
101       group.imr_multiaddr.s_addr = inet_addr(TT.m ? TT.m : TT.M);
102       xsetsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
103       xsetsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &t, 4);
104       t = TT.t;
105       xsetsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &t, 4);
106     }
107   } else fd = xsocket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP);
108 
109   // -Sm = loop waiting for input
110   // -Dd = loop polling time and waiting until next poll period
111   // Otherwise poll up to 3 times to get 2 responses, then exit
112 
113   // loop sending/receiving packets
114   for (;;) {
115     now = millitime();
116 
117     // If we're in server or multicast client mode, don't poll
118     if (FLAG(m) || FLAG(S)) then = -1;
119 
120     // daemon and oneshot modes send a packet each time through outer loop
121     else {
122       then = now + 3000;
123       if (FLAG(d)||FLAG(D)||FLAG(M)) then = now + (1<<TT.r)*1000;
124 
125       // Send NTP query packet
126       memset(toybuf, 0, 48);
127       *toybuf = 0xe3; // li = 3 (unsynchronized), version = 4, mode = 3 (client)
128       toybuf[2] = 8;  // poll frequency 1<<8 = 256 seconds
129       pktime[5] = SWAP_BE64(before = lunchtime(&tv, diff));
130       xsendto(fd, toybuf, 48, ai->ai_addr);
131     }
132 
133     // Loop receiving packets until it's time to send the next one.
134     for (;;) {
135       int strike;
136 
137       // Wait to receive a packet
138 
139       if (then>0 && then<(now = millitime())) break;;
140       strike = xrecvwait(fd, toybuf, sizeof(toybuf), &sa, then-now);
141       if (strike<1) {
142         if (!(FLAG(S)||FLAG(m)||FLAG(D)||FLAG(d)) && ++tries == 3)
143           error_exit("no reply from %s", *toys.optargs);
144         break;
145       }
146       if (strike<48) continue;
147 
148       // Validate packet
149       if (!FLAG(S) || FLAG(m)) {
150         char buf[128];
151         int mode = 7&*toybuf;
152 
153         // Is source address what we expect?
154         xstrncpy(buf, ntop(ai->ai_addr), 128);
155         strike = strcmp(buf, ntop((void *)&sa));
156         // Does this reply's originate timestamp match the packet we sent?
157         if (!FLAG(S) && !FLAG(m) && before != SWAP_BE64(pktime[3])) continue;
158         // Ignore packets from wrong address or with wrong mode
159         if (strike && !FLAG(S)) continue;
160         if (!((FLAG(m) && mode==5) || (FLAG(S) && mode==3) ||
161             (!FLAG(m) && !FLAG(S) && mode==4))) continue;
162       }
163 
164       // If received a -S request packet, send server packet
165       if (strike) {
166         char *buf = toybuf+48;
167 
168         *buf = 0x24;  // LI 0 VN 4 mode 4.
169         buf[1] = 3;   // stratum 3
170         buf[2] = 10;  // recommended retry every 1<<10=1024 seconds
171         buf[3] = 250; // precision -6, minimum allowed
172         strcpy(buf+12, "LOCL");
173         pktime[6+3] = pktime[5]; // send back reference time they sent us
174         // everything else is current time
175         pktime[6+2] = pktime[6+4] = pktime[6+5] = SWAP_BE64(lunchtime(0, 0));
176         xsendto(fd, buf, 48, (void *)&sa);
177 
178       // Got a time packet from a recognized server
179       } else {
180         int unset = !diff;
181 
182         // First packet: figure out how far off our clock is from what server
183         // said and try again. Don't set clock, just record offset to use
184         // generating second request. (We know this time is in the past
185         // because transmission took time, but it's a start. And if time is
186         // miraculously exact, don't loop.)
187 
188         lunchtime(&tv2, diff);
189         diff = nanodiff(&tv, &tv2);
190         if (unset && diff) break;
191 
192         // Second packet: determine midpoint of packet transit time according
193         // to local clock, assuming each direction took same time so midpoint
194         // is time server reported. The first tv was the adjusted time
195         // we sent the packet at, tv2 is what server replied, so now diff
196         // is round trip time.
197 
198         // What time did the server say and how far off are we?
199         nanomove(&tv, diff/2);
200         doublyso(SWAP_BE64(pktime[5]), &tv2);
201         diff = nanodiff(&tv, &tv2);
202 
203         if (FLAG(s)) {
204           // Do read/adjust/set to lose as little time as possible.
205           clock_gettime(CLOCK_REALTIME, &tv2);
206           nanomove(&tv2, diff);
207           if (clock_settime(CLOCK_REALTIME, &tv2))
208             perror_exit("clock_settime");
209         } else if (FLAG(a)) {
210           struct timex tx;
211 
212           // call adjtimex() to move the clock gradually
213           nanomove(&tv2, diff);
214           memset(&tx, 0, sizeof(struct timex));
215           tx.offset = tv2.tv_sec*1000000+tv2.tv_nsec/1000;
216           tx.modes = ADJ_OFFSET_SINGLESHOT;
217           if (adjtimex(&tx) == -1) perror_exit("adjtimex");
218         }
219 
220         // Display the time and offset
221         if (!FLAG(q)) {
222           format_iso_time(toybuf, sizeof(toybuf)-1, &tv2);
223           printf("%s offset %c%lld.%09lld secs\n", toybuf, (diff<0) ? '-' : '+',
224             llabs(diff/1000000000), llabs(diff%1000000000));
225         }
226 
227         // If we're not in daemon mode, we're done. (Can't get here for -S.)
228         if (!FLAG(d) && !FLAG(D)) return;
229       }
230     }
231   }
232 }
233