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, ">1M :m :Sp:t#<0=1>16asdDqr#<4>17=10[!as]", TOYFLAG_USR|TOYFLAG_BIN))
  10
  11config 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 (default 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
  36GLOBALS(
  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)
  48static 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.
  64static 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
  72void sntp_main(void)
  73{
  74  struct timespec tv, tv2;
  75  unsigned long long *pktime = (void *)toybuf, now, then, before QUIET;
  76  long long diff = 0;
  77  struct addrinfo *ai;
  78  union socksaddr sa;
  79  int fd, tries = 0;
  80
  81  if (FLAG(d)) xvdaemon();
  82
  83  if (FLAG(M)) toys.optflags |= FLAG_S;
  84  if (!(FLAG(S)||FLAG(m)) && !*toys.optargs)
  85    error_exit("Need -SMm or SERVER address");
  86
  87  // Lookup address and open server or client UDP socket
  88  if (!TT.p || !*TT.p) TT.p = "123";
  89  ai = xgetaddrinfo(*toys.optargs, TT.p, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
  90    AI_PASSIVE*!*toys.optargs);
  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