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