implement Sntp; # # rfc1361 (simple network time protocol) # include "sys.m"; sys: Sys; include "draw.m"; include "ip.m"; ip: IP; IPaddr: import ip; include "dial.m"; dial: Dial; include "timers.m"; timers: Timers; Timer: import timers; include "arg.m"; Sntp: module { init: fn(nil: ref Draw->Context, nil: list of string); }; debug := 0; Retries: con 4; Delay: con 3*1000; # milliseconds SNTP: adt { li: int; vn: int; mode: int; stratum: int; # level of local clock poll: int; # log2(maximum interval in seconds between successive messages) precision: int; # log2(seconds precision of local clock) [eg, -6 for mains, -18 for microsec] rootdelay: int; # round trip delay in seconds to reference (16:16 fraction) dispersion: int; # maximum error relative to primary reference clockid: string; # reference clock identifier reftime: big; # local time at which clock last set/corrected orgtime: big; # local time at which client transmitted request rcvtime: big; # time at which request arrived at server xmttime: big; # time server transmitted reply auth: array of byte; # auth field (ignored by this implementation) new: fn(vn, mode: int): ref SNTP; pack: fn(s: self ref SNTP): array of byte; unpack: fn(a: array of byte): ref SNTP; }; SNTPlen: con 4+3*4+4*8; Version: con 1; # accepted by version 2 and version 3 servers Stratum: con 0; Poll: con 0; LI: con 0; Symmetric: con 2; ClientMode: con 3; ServerMode: con 4; Epoch: con big 86400*big (365*70 + 17); # seconds between 1 Jan 1900 and 1 Jan 1970 Microsec: con big 1000000; server := "$ntp"; stderr: ref Sys->FD; init(nil: ref Draw->Context, args: list of string) { sys = load Sys Sys->PATH; ip = load IP IP->PATH; timers = load Timers Timers->PATH; dial = load Dial Dial->PATH; ip->init(); arg := load Arg Arg->PATH; arg->init(args); arg->setusage("sntp [-d] [server]"); doset := 1; while((o := arg->opt()) != 0) case o { 'd' => debug++; 'i' => doset = 0; * => arg->usage(); } args = arg->argv(); if(len args > 1) arg->usage(); arg = nil; if(args != nil) server = hd args; sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil); stderr = sys->fildes(2); timers->init(100); conn := dial->dial(dial->netmkaddr(server, "udp", "ntp"), nil); if(conn == nil){ sys->fprint(stderr, "sntp: can't dial %s: %r\n", server); raise "fail:dial"; } replies := chan of ref SNTP; spawn reader(conn.dfd, replies); for(i:=0; iwrite(conn.dfd, b, len b) != len b){ sys->fprint(stderr, "sntp: UDP write failed: %r\n"); continue; } t := Timer.start(Delay); alt{ reply := <-replies => t.stop(); if(reply == nil) quit("read error"); if(debug){ sys->fprint(stderr, "LI = %d, version = %d, mode = %d\n", reply.li, reply.vn, reply.mode); if(reply.stratum == 1) sys->fprint(stderr, "stratum = 1 (%s), ", reply.clockid); else sys->fprint(stderr, "stratum = %d, ", reply.stratum); sys->fprint(stderr, "poll = %d, prec = %d\n", reply.poll, reply.precision); sys->fprint(stderr, "rootdelay = %d, dispersion = %d\n", reply.rootdelay, reply.dispersion); } if(reply.vn == 0 || reply.vn > 3) continue; # unsupported version, ignored if(reply.mode >= 6 || reply.mode == ClientMode) continue; now := ((reply.xmttime>>32)&16rFFFFFFFF) - Epoch; if(now <= big 1120000000) continue; if(reply.li == 3 || reply.stratum == 0) # unsynchronised sys->fprint(stderr, "sntp: time server not synchronised to reference time\n"); if(debug) sys->print("%bd\n", now); if(doset){ settime("#r/rtc", now); settime("/dev/time", now*Microsec); } quit(nil); <-t.timeout => continue; } } sys->fprint(sys->fildes(2), "sntp: no response from server %s\n", server); quit("timeout"); } reader(fd: ref Sys->FD, replies: chan of ref SNTP) { for(;;){ buf := array[512] of byte; nb := sys->read(fd, buf, len buf); if(nb <= 0) break; reply := SNTP.unpack(buf[0:nb]); if(reply == nil){ # ignore bad replies if(debug) sys->fprint(stderr, "sntp: invalid reply (len %d)\n", nb); continue; } replies <-= reply; } if(debug) sys->fprint(stderr, "sntp: UDP read failed: %r\n"); replies <-= nil; } quit(s: string) { pid := sys->pctl(0, nil); timers->shutdown(); fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); if(fd != nil) sys->fprint(fd, "killgrp"); if(s != nil) raise "fail:"+s; exit; } time(): int { n := rdn("#r/rtc"); if(n > big 300) # ie, possibly set return int n; n = rdn("/dev/time"); if(n <= big 0) return 0; return int(n/big Microsec); } rdn(f: string): big { fd := sys->open(f, Sys->OREAD); if(fd == nil) return big -1; b := array[128] of byte; n := sys->read(fd, b, len b); if(n <= 0) return big 0; return big string b[0:n]; } settime(f: string, t: big) { fd := sys->open(f, Sys->OWRITE); if(fd != nil) sys->fprint(fd, "%bd", t); } get8(a: array of byte, i: int): big { b := big ip->get4(a, i+4) & 16rFFFFFFFF; return (big ip->get4(a, i) << 32) | b; } put8(a: array of byte, o: int, v: big) { ip->put4(a, o, int (v>>32)); ip->put4(a, o+4, int v); } SNTP.unpack(a: array of byte): ref SNTP { if(len a < SNTPlen) return nil; s := ref SNTP; mode := int a[0]; s.li = mode>>6; s.vn = (mode>>3); s.mode = mode & 3; s.stratum = int a[1]; s.poll = int a[2]; if(s.poll & 16r80) s.poll |= ~0 << 8; s.precision = int a[3]; if(s.precision & 16r80) s.precision |= ~0 << 8; s.rootdelay = ip->get4(a, 4); s.dispersion = ip->get4(a, 8); if(s.stratum <= 1){ for(i := 12; i < 16; i++) if(a[i] == byte 0) break; s.clockid = string a[12:i]; }else s.clockid = sys->sprint("%d.%d.%d.%d", int a[12], int a[13], int a[14], int a[15]); s.reftime = get8(a, 16); s.orgtime = get8(a, 24); s.rcvtime = get8(a, 32); s.xmttime = get8(a, 40); if(len a > SNTPlen) s.auth = a[48:]; return s; } SNTP.pack(s: self ref SNTP): array of byte { a := array[SNTPlen + len s.auth] of byte; a[0] = byte ((s.li<<6) | (s.vn<<3) | s.mode); a[1] = byte s.stratum; a[2] = byte s.poll; a[3] = byte s.precision; ip->put4(a, 4, s.rootdelay); ip->put4(a, 8, s.dispersion); ip->put4(a, 12, 0); # clockid field if(s.clockid != nil){ if(s.stratum <= 1){ b := array of byte s.clockid; for(i := 0; i < len b && i < 4; i++) a[12+i] = b[i]; }else a[12:] = IPaddr.parse(s.clockid).t1.v4(); } put8(a, 16, s.reftime); put8(a, 24, s.orgtime); put8(a, 32, s.rcvtime); put8(a, 40, s.xmttime); if(s.auth != nil) a[48:] = s.auth; return a; } SNTP.new(vn, mode: int): ref SNTP { s := ref SNTP; s.vn = vn; s.mode = mode; s.li = 0; s.stratum = 0; s.poll = 0; s.precision = 0; s.clockid = nil; s.reftime = big 0; s.orgtime = big 0; s.rcvtime = big 0; s.xmttime = big 0; return s; }