xref: /inferno-os/appl/cmd/ip/sntp.b (revision 22a0db99a682f2e9f189978dde51ba5da9d1ec2c)
1implement Sntp;
2
3#
4# rfc1361 (simple network time protocol)
5#
6
7include "sys.m";
8	sys: Sys;
9
10include "draw.m";
11
12include "ip.m";
13	ip: IP;
14	IPaddr: import ip;
15
16include "dial.m";
17	dial: Dial;
18
19include "timers.m";
20	timers: Timers;
21	Timer: import timers;
22
23include "arg.m";
24
25Sntp: module
26{
27	init:	fn(nil: ref Draw->Context, nil: list of string);
28};
29
30debug := 0;
31
32Retries: con 4;
33Delay: con 3*1000;	# milliseconds
34
35SNTP: adt {
36	li:	int;
37	vn:	int;
38	mode:	int;
39	stratum:	int;	# level of local clock
40	poll:	int;	# log2(maximum interval in seconds between successive messages)
41	precision:	int;	# log2(seconds precision of local clock) [eg, -6 for mains, -18 for microsec]
42	rootdelay:	int;	# round trip delay in seconds to reference (16:16 fraction)
43	dispersion:	int;	# maximum error relative to primary reference
44	clockid:	string;	# reference clock identifier
45	reftime:	big;	# local time at which clock last set/corrected
46	orgtime:	big;	# local time at which client transmitted request
47	rcvtime:	big;	# time at which request arrived at server
48	xmttime:	big;	# time server transmitted reply
49	auth:	array of byte;	# auth field (ignored by this implementation)
50
51	new:	fn(vn, mode: int): ref SNTP;
52	pack:	fn(s: self ref SNTP): array of byte;
53	unpack:	fn(a: array of byte): ref SNTP;
54};
55SNTPlen: con 4+3*4+4*8;
56
57Version: con 1;	# accepted by version 2 and version 3 servers
58Stratum: con 0;
59Poll: con 0;
60LI: con 0;
61Symmetric: con 2;
62ClientMode: con 3;
63ServerMode: con 4;
64Epoch: con big 86400*big (365*70 + 17);	# seconds between 1 Jan 1900 and 1 Jan 1970
65
66Microsec: con big 1000000;
67
68server := "$ntp";
69stderr: ref Sys->FD;
70
71init(nil: ref Draw->Context, args: list of string)
72{
73	sys = load Sys Sys->PATH;
74	ip = load IP IP->PATH;
75	timers = load Timers Timers->PATH;
76	dial = load Dial Dial->PATH;
77
78	ip->init();
79	arg := load Arg Arg->PATH;
80	arg->init(args);
81	arg->setusage("sntp [-d] [server]");
82
83	doset := 1;
84	while((o := arg->opt()) != 0)
85		case o {
86		'd' => debug++;
87		'i' => doset = 0;
88		* =>	arg->usage();
89		}
90	args = arg->argv();
91	if(len args > 1)
92		arg->usage();
93	arg = nil;
94
95	if(args != nil)
96		server = hd args;
97
98	sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
99	stderr = sys->fildes(2);
100	timers->init(100);
101
102	conn := dial->dial(dial->netmkaddr(server, "udp", "ntp"), nil);
103	if(conn == nil){
104		sys->fprint(stderr, "sntp: can't dial %s: %r\n", server);
105		raise "fail:dial";
106	}
107
108	replies := chan of ref SNTP;
109	spawn reader(conn.dfd, replies);
110
111	for(i:=0; i<Retries; i++){
112		request := SNTP.new(Version, ClientMode);
113		request.poll = 6;
114		request.orgtime = (big time() + Epoch)<<32;
115		b := request.pack();
116		if(sys->write(conn.dfd, b, len b) != len b){
117			sys->fprint(stderr, "sntp: UDP write failed: %r\n");
118			continue;
119		}
120		t := Timer.start(Delay);
121		alt{
122		reply := <-replies =>
123			t.stop();
124			if(reply == nil)
125				quit("read error");
126			if(debug){
127				sys->fprint(stderr, "LI = %d, version = %d, mode = %d\n", reply.li, reply.vn, reply.mode);
128				if(reply.stratum == 1)
129					sys->fprint(stderr, "stratum = 1 (%s), ", reply.clockid);
130				else
131					sys->fprint(stderr, "stratum = %d, ", reply.stratum);
132				sys->fprint(stderr, "poll = %d, prec = %d\n", reply.poll, reply.precision);
133				sys->fprint(stderr, "rootdelay = %d, dispersion = %d\n", reply.rootdelay, reply.dispersion);
134			}
135			if(reply.vn == 0 || reply.vn > 3)
136				continue;	# unsupported version, ignored
137			if(reply.mode >= 6 || reply.mode == ClientMode)
138				continue;
139			now := ((reply.xmttime>>32)&16rFFFFFFFF) - Epoch;
140			if(now <= big 1120000000)
141				continue;
142			if(reply.li == 3 || reply.stratum == 0)	# unsynchronised
143				sys->fprint(stderr, "sntp: time server not synchronised to reference time\n");
144			if(debug)
145				sys->print("%bd\n", now);
146			if(doset){
147				settime("#r/rtc", now);
148				settime("/dev/time", now*Microsec);
149			}
150			quit(nil);
151		<-t.timeout =>
152			continue;
153		}
154	}
155	sys->fprint(sys->fildes(2), "sntp: no response from server %s\n", server);
156	quit("timeout");
157}
158
159reader(fd: ref Sys->FD, replies: chan of ref SNTP)
160{
161	for(;;){
162		buf := array[512] of byte;
163		nb := sys->read(fd, buf, len buf);
164		if(nb <= 0)
165			break;
166		reply := SNTP.unpack(buf[0:nb]);
167		if(reply == nil){
168			# ignore bad replies
169			if(debug)
170				sys->fprint(stderr, "sntp: invalid reply (len %d)\n", nb);
171			continue;
172		}
173		replies <-= reply;
174	}
175	if(debug)
176		sys->fprint(stderr, "sntp: UDP read failed: %r\n");
177	replies <-= nil;
178}
179
180quit(s: string)
181{
182	pid := sys->pctl(0, nil);
183	timers->shutdown();
184	fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
185	if(fd != nil)
186		sys->fprint(fd, "killgrp");
187	if(s != nil)
188		raise "fail:"+s;
189	exit;
190}
191
192time(): int
193{
194	n := rdn("#r/rtc");
195	if(n > big 300)	# ie, possibly set
196		return int n;
197	n = rdn("/dev/time");
198	if(n <= big 0)
199		return 0;
200	return int(n/big Microsec);
201}
202
203rdn(f: string): big
204{
205	fd := sys->open(f, Sys->OREAD);
206	if(fd == nil)
207		return big -1;
208	b := array[128] of byte;
209	n := sys->read(fd, b, len b);
210	if(n <= 0)
211		return big 0;
212	return big string b[0:n];
213}
214
215settime(f: string, t: big)
216{
217	fd := sys->open(f, Sys->OWRITE);
218	if(fd != nil)
219		sys->fprint(fd, "%bd", t);
220}
221
222get8(a: array of byte, i: int): big
223{
224	b := big ip->get4(a, i+4) & 16rFFFFFFFF;
225	return (big ip->get4(a, i) << 32) | b;
226}
227
228put8(a: array of byte, o: int, v: big)
229{
230	ip->put4(a, o, int (v>>32));
231	ip->put4(a, o+4, int v);
232}
233
234SNTP.unpack(a: array of byte): ref SNTP
235{
236	if(len a < SNTPlen)
237		return nil;
238	s := ref SNTP;
239	mode := int a[0];
240	s.li = mode>>6;
241	s.vn = (mode>>3);
242	s.mode = mode & 3;
243	s.stratum = int a[1];
244	s.poll = int a[2];
245	if(s.poll & 16r80)
246		s.poll |= ~0 << 8;
247	s.precision = int a[3];
248	if(s.precision & 16r80)
249		s.precision |= ~0 << 8;
250	s.rootdelay = ip->get4(a, 4);
251	s.dispersion = ip->get4(a, 8);
252	if(s.stratum <= 1){
253		for(i := 12; i < 16; i++)
254			if(a[i] == byte 0)
255				break;
256		s.clockid = string a[12:i];
257	}else
258		s.clockid = sys->sprint("%d.%d.%d.%d", int a[12], int a[13], int a[14], int a[15]);
259	s.reftime = get8(a, 16);
260	s.orgtime = get8(a, 24);
261	s.rcvtime = get8(a, 32);
262	s.xmttime = get8(a, 40);
263	if(len a > SNTPlen)
264		s.auth = a[48:];
265	return s;
266}
267
268SNTP.pack(s: self ref SNTP): array of byte
269{
270	a := array[SNTPlen + len s.auth] of byte;
271	a[0] = byte ((s.li<<6) | (s.vn<<3) | s.mode);
272	a[1] = byte s.stratum;
273	a[2] = byte s.poll;
274	a[3] = byte s.precision;
275	ip->put4(a, 4, s.rootdelay);
276	ip->put4(a, 8, s.dispersion);
277	ip->put4(a, 12, 0);	# clockid field
278	if(s.clockid != nil){
279		if(s.stratum <= 1){
280			b := array of byte s.clockid;
281			for(i := 0; i < len b && i < 4; i++)
282				a[12+i] = b[i];
283		}else
284			a[12:] = IPaddr.parse(s.clockid).t1.v4();
285	}
286	put8(a, 16, s.reftime);
287	put8(a, 24, s.orgtime);
288	put8(a, 32, s.rcvtime);
289	put8(a, 40, s.xmttime);
290	if(s.auth != nil)
291		a[48:] = s.auth;
292	return a;
293}
294
295SNTP.new(vn, mode: int): ref SNTP
296{
297	s := ref SNTP;
298	s.vn = vn;
299	s.mode = mode;
300	s.li = 0;
301	s.stratum = 0;
302	s.poll = 0;
303	s.precision = 0;
304	s.clockid = nil;
305	s.reftime = big 0;
306	s.orgtime = big 0;
307	s.rcvtime = big 0;
308	s.xmttime = big 0;
309	return s;
310}
311