toybox/toys/net/sntp.c
<<
>>
Prefs
   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
   9USE_SNTP(NEWTOY(sntp, "M:m:Sp:asdDqr#<4>17=10[!as]", TOYFLAG_USR|TOYFLAG_BIN))
  10
  11config 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
  34GLOBALS(
  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)
  46static 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.
  62static 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
  70void 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 = xbindany(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