xref: /freebsd/contrib/ntp/scripts/monitoring/ntptrap (revision ae77177087c655fc883075af4f425b37e032cd05)
1#!/local/bin/perl --*-perl-*-
2;#
3;# ntptrap,v 3.1 1993/07/06 01:09:15 jbj Exp
4;#
5;# a client for the xntp mode 6 trap mechanism
6;#
7;# Copyright (c) 1992
8;#  Rainer Pruy Friedrich-Alexander Universitaet Erlangen-Nuernberg
9;#
10;#
11;#############################################################
12$0 =~ s!^.*/([^/]+)$!$1!;		# strip to filename
13;# enforce STDOUT and STDERR to be line buffered
14$| = 1;
15select((select(STDERR),$|=1)[$[]);
16
17;#######################################
18;# load utility routines and definitions
19;#
20require('ntp.pl');			# implementation of the NTP protocol
21use Socket;
22
23#eval { require('sys/socket.ph'); require('netinet/in.ph') unless defined(&INADDR_ANY); } ||
24#do {
25  #die("$0: $@") unless $[ == index($@, "Can't locate ");
26  #warn "$0: $@";
27  #warn "$0: supplying some default definitions\n";
28  #eval 'sub INADDR_ANY { 0; } sub AF_INET {2;} sub SOCK_DGRAM {2;} 1;' || die "$0: $@";
29#};
30require('getopts.pl');			# option parsing
31require('ctime.pl');			# date/time formatting
32
33;######################################
34;# define some global constants
35;#
36$BASE_TIMEOUT=10;
37$FRAG_TIMEOUT=10;
38$MAX_TRY = 5;
39$REFRESH_TIME=60*15;		# 15 minutes (server uses 1 hour)
40$ntp'timeout = $FRAG_TIMEOUT; #';
41$ntp'timeout if 0;
42
43;######################################
44;# now process options
45;#
46sub usage
47{
48    die("usage: $0 [-n] [-p <port>] [-l <logfile>] [host] ...\n");
49}
50
51$opt_l = "/dev/null";	# where to write debug messages to
52$opt_p = 0;		# port to use locally - (0 does mean: will be choosen by kernel)
53
54&usage unless &Getopts('l:p:');
55&Getopts if 0;	# make -w happy
56
57@Hosts = ($#ARGV < $[) ? ("localhost") : @ARGV;
58
59;# setup for debug output
60$DEBUGFILE=$opt_l;
61$DEBUGFILE="&STDERR" if $DEBUGFILE eq '-';
62
63open(DEBUG,">>$DEBUGFILE") || die("Cannot open \"$DEBUGFILE\": $!\n");
64select((select(DEBUG),$|=1)[$[]);
65
66;# &log prints a single trap record (adding a (local) time stamp)
67sub log
68{
69    chop($date=&ctime(time));
70    print "$date ",@_,"\n";
71}
72
73sub debug
74{
75    print DEBUG @_,"\n";
76}
77;#
78$proto_udp = (getprotobyname('udp'))[$[+2] ||
79		(warn("$0: Could not get protocoll number for 'udp' using 17"), 17);
80
81$ntp_port = (getservbyname('ntp','udp'))[$[+2] ||
82	      (warn("$0: Could not get port number for service ntp/udp using 123"), 123);
83
84;#
85socket(S, &AF_INET, &SOCK_DGRAM, $proto_udp) || die("Cannot open socket: $!\n");
86
87;#
88bind(S, pack("S n a4 x8", &AF_INET, $opt_p, &INADDR_ANY)) ||
89    die("Cannot bind: $!\n");
90
91($my_port, $my_addr) = (unpack("S n a4 x8",getsockname(S)))[$[+1,$[+2];
92&log(sprintf("Listening at address %d.%d.%d.%d port %d",
93	     unpack("C4",$my_addr), $my_port));
94
95;# disregister with all servers in case of termination
96sub cleanup
97{
98    &log("Aborted by signal \"$_[$[]\"") if defined($_[$[]);
99
100    foreach (@Hosts)
101    {
102	if ( ! defined($Host{$_}) )
103	{
104		print "no info for host '$_'\n";
105		next;
106	}
107	&ntp'send(S,31,0,"",pack("Sna4x8",&AF_INET,$ntp_port,$Host{$_})); #';
108    }
109    close(S);
110    exit(2);
111}
112
113$SIG{'HUP'} = 'cleanup';
114$SIG{'INT'} = 'cleanup';
115$SIG{'QUIT'} = 'cleanup';
116$SIG{'TERM'} = 'cleanup';
117
1180 && $a && $b;
119sub timeouts			# sort timeout id array
120{
121    $TIMEOUTS{$a} <=> $TIMEOUTS{$b};
122}
123
124;# a Request element looks like: pack("a4SC",addr,associd,op)
125@Requests= ();
126
127;# compute requests for set trap control msgs to each host given
128{
129    local($name,$addr);
130
131    foreach (@Hosts)
132    {
133	if (/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/)
134	{
135	    ($name,$addr) =
136		(gethostbyaddr(pack("C4",$1,$2,$3,$4),&AF_INET))[$[,$[+4];
137	    unless (defined($name))
138	    {
139		$name = sprintf("[[%d.%d.%d.%d]]",$1,$2,$3,$4);
140		$addr = pack("C4",$1,$2,$3,$4);
141	    }
142	}
143	else
144	{
145	    ($name,$addr) = (gethostbyname($_))[$[,$[+4];
146	    unless (defined($name))
147	    {
148		warn "$0: unknown host \"$_\" - ignored\n";
149		next;
150	    }
151	}
152	next if defined($Host{$name});
153	$Host{$name} = $addr;
154	$Host{$_} = $addr;
155	push(@Requests,pack("a4SC",$addr,0,6));	# schedule a set trap request for $name
156    }
157}
158
159sub hostname
160{
161    local($addr) = @_;
162    return $HostName{$addr} if defined($HostName{$addr});
163    local($name) = gethostbyaddr($addr,&AF_INET);
164    &debug(sprintf("hostname(%d.%d.%d.%d) = \"%s\"",unpack("C4",$addr),$name))
165	if defined($name);
166    defined($name) && ($HostName{$addr} = $name) && (return $name);
167    &debug(sprintf("Failed to get name for %d.%d.%d.%d",unpack("C4",$addr)));
168    return sprintf("[%d.%d.%d.%d]",unpack("C4",$addr));
169}
170
171;# when no hosts were given on the commandline no requests have been scheduled
172&usage unless (@Requests);
173
174&debug(sprintf("%d request(s) scheduled",scalar(@Requests)));
175grep(&debug("    - ".$_),keys(%Host));
176
177;# allocate variables;
178$addr="";
179$assoc=0;
180$op = 0;
181$timeout = 0;
182$ret="";
183%TIMEOUTS = ();
184%TIMEOUT_PROCS = ();
185@TIMEOUTS = ();
186
187$len = 512;
188$buf = " " x $len;
189
190while (1)
191{
192    if (@Requests || @TIMEOUTS)		# if there is some work pending
193    {
194	if (@Requests)
195	{
196	    ($addr,$assoc,$op) = unpack("a4SC",($req = shift(@Requests)));
197	    &debug(sprintf("Request: %s: %s(%d)",&hostname($addr), &ntp'cntrlop_name($op), $assoc)); #';))
198	    $ret = &ntp'send(S,$op,$assoc,"", #'(
199                             pack("Sna4x8",&AF_INET,$ntp_port,$addr));
200	    &set_timeout("retry-".unpack("H*",$req),time+$BASE_TIMEOUT,
201			 sprintf("&retry(\"%s\");",unpack("H*",$req)));
202
203	    last unless (defined($ret)); # warn called by ntp'send();
204
205	    ;# if there are more requests just have a quick look for new messages
206	    ;# otherwise grant server time for a response
207	    $timeout = @Requests ? 0 : $BASE_TIMEOUT;
208	}
209	if ($timeout && @TIMEOUTS)
210	{
211	    ;# ensure not to miss a timeout
212	    if ($timeout + time > $TIMEOUTS{$TIMEOUTS[$[]})
213	    {
214		$timeout = $TIMEOUTS{$TIMEOUTS[$[]} - time;
215		$timeout = 0 if $timeout < 0;
216	    }
217	}
218    }
219    else
220    {
221	;# no work yet - wait for some messages dropping in
222	;# usually this will not hapen as the refresh semantic will
223	;# always have a pending timeout
224	undef($timeout);
225    }
226
227    vec($mask="",fileno(S),1) = 1;
228    $ret = select($mask,undef,undef,$timeout);
229
230    warn("$0: select: $!\n"),last if $ret < 0;	# give up on error return from select
231
232    if ($ret == 0)
233    {
234	;# timeout
235	if (@TIMEOUTS && time > $TIMEOUTS{$TIMEOUTS[$[]})
236	{
237	    ;# handle timeout
238	    $timeout_proc =
239		(delete $TIMEOUT_PROCS{$TIMEOUTS[$[]},
240		 delete $TIMEOUTS{shift(@TIMEOUTS)})[$[];
241	    eval $timeout_proc;
242	    die "timeout eval (\"$timeout_proc\"): $@\n" if $@;
243	}
244	;# else: there may be something to be sent
245    }
246    else
247    {
248	;# data avail
249	$from = recv(S,$buf,$len,0);
250	;# give up on error return from recv
251	warn("$0: recv: $!\n"), last unless (defined($from));
252
253	$from = (unpack("Sna4",$from))[$[+2]; # keep host addr only
254	;# could check for ntp_port - but who cares
255	&debug("-Packet from ",&hostname($from));
256
257	;# stuff packet into ntp mode 6 receive machinery
258	($ret,$data,$status,$associd,$op,$seq,$auth_keyid) =
259	    &ntp'handle_packet($buf,$from); # ';
260	&debug(sprintf("%s uses auth_keyid %d",&hostname($from),$auth_keyid)) if defined($auth_keyid);
261	next unless defined($ret);
262
263	if ($ret eq "")
264	{
265	    ;# handle packet
266	    ;# simple trap response messages have neither timeout nor retries
267	    &clear_timeout("retry-".unpack("H*",pack("a4SC",$from,$associd,$op))) unless $op == 7;
268	    delete $RETRY{pack("a4SC",$from,$associd,$op)} unless $op == 7;
269
270	    &process_response($from,$ret,$data,$status,$associd,$op,$seq,$auth_keyid);
271	}
272	else
273	{
274	    ;# some kind of error
275	    &log(sprintf("%50s: %s: %s",(gethostbyaddr($from,&AF_INET))[$[],$ret,$data));
276	    if ($ret ne "TIMEOUT" && $ret ne "ERROR")
277	    {
278		&clear_timeout("retry-".unpack("H*",pack("a4SC",$from,$associd,$op)));
279	    }
280	}
281    }
282
283}
284
285warn("$0: terminating\n");
286&cleanup;
287exit 0;
288
289;##################################################
290;# timeout support
291;#
292sub set_timeout
293{
294    local($id,$time,$proc) = @_;
295
296    $TIMEOUTS{$id} = $time;
297    $TIMEOUT_PROCS{$id} = $proc;
298    @TIMEOUTS = sort timeouts keys(%TIMEOUTS);
299    chop($date=&ctime($time));
300    &debug(sprintf("Schedule timeout \"%s\" for %s", $id, $date));
301}
302
303sub clear_timeout
304{
305    local($id) = @_;
306    delete $TIMEOUTS{$id};
307    delete $TIMEOUT_PROCS{$id};
308    @TIMEOUTS = sort timeouts keys(%TIMEOUTS);
309    &debug("Clear  timeout \"$id\"");
310}
311
3120 && &refresh;
313sub refresh
314{
315    local($addr) = @_[$[];
316    $addr = pack("H*",$addr);
317    &debug(sprintf("Refreshing trap for %s", &hostname($addr)));
318    push(@Requests,pack("a4SC",$addr,0,6));
319}
320
3210 && &retry;
322sub retry
323{
324    local($tag) = @_;
325    $tag = pack("H*",$tag);
326    $RETRY{$tag} = 0 if (!defined($RETRY{$tag}));
327
328    if (++$RETRY{$tag} > $MAX_TRY)
329    {
330	&debug(sprintf("Retry failed: %s assoc %5d op %d",
331		       &hostname(substr($tag,$[,4)),
332		       unpack("x4SC",$tag)));
333	return;
334    }
335    &debug(sprintf("Retrying: %s assoc %5d op %d",
336		       &hostname(substr($tag,$[,4)),
337		       unpack("x4SC",$tag)));
338    push(@Requests,$tag);
339}
340
341sub process_response
342{
343    local($from,$ret,$data,$status,$associd,$op,$seq,$auth_keyid) = @_;
344
345    $msg="";
346    if ($op == 7)		# trap response
347    {
348	$msg .= sprintf("%40s trap#%-5d",
349			&hostname($from),$seq);
350	&debug (sprintf("\nTrap %d associd %d:\n%s\n===============\n",$seq,$associd,$data));
351	if ($associd == 0)	# system event
352	{
353	    $msg .= "  SYSTEM   ";
354	    $evnt = &ntp'SystemEvent($status); #';
355	    $msg .= "$evnt ";
356	    ;# for special cases add additional info
357	    ($stratum) = ($data =~ /stratum=(\d+)/);
358	    ($refid) = ($data =~ /refid=([\w\.]+)/);
359	    $msg .= "stratum=$stratum refid=$refid";
360	    if ($refid =~ /\[?(\d+)\.(\d+)\.(\d+)\.(\d+)/)
361	    {
362		local($x) = (gethostbyaddr(pack("C4",$1,$2,$3,$4),&AF_INET));
363		$msg .= " " . $x if defined($x)
364	    }
365	    if ($evnt eq "event_sync_chg")
366	    {
367		$msg .= sprintf("%s %s ",
368				&ntp'LI($status), #',
369				&ntp'ClockSource($status) #'
370				);
371	    }
372	    elsif ($evnt eq "event_sync/strat_chg")
373	    {
374		($peer) = ($data =~ /peer=([0-9]+)/);
375		$msg .= " peer=$peer";
376	    }
377	    elsif ($evnt eq "event_clock_excptn")
378	    {
379		if (($device) = ($data =~ /device=\"([^\"]+)\"/))
380		{
381		    ($cstatus) = ($data =~ /refclockstatus=0?x?([\da-fA-F]+)/);
382		    $Cstatus = hex($cstatus);
383		    $msg .= sprintf("- %-32s",&ntp'clock_status($Cstatus)); #');
384		    ($timecode) = ($data =~ /timecode=\"([^\"]+)\"/);
385		    $msg .= " \"$device\" \"$timecode\"";
386		}
387		else
388		{
389		    push(@Requests,pack("a4SC",$from, $associd, 4));
390		}
391	    }
392	}
393	else			# peer event
394	{
395	    $msg .= sprintf("peer %5d ",$associd);
396	    ($srcadr) = ($data =~ /srcadr=\[?([\d\.]+)/);
397	    $msg .= sprintf("%-18s %40s ", "[$srcadr]",
398			    &hostname(pack("C4",split(/\./,$srcadr))));
399	    $evnt = &ntp'PeerEvent($status); #';
400	    $msg .= "$evnt ";
401	    ;# for special cases include additional info
402	    if ($evnt eq "event_clock_excptn")
403	    {
404		if (($device) = ($data =~ /device=\"([^\"]+)\"/))
405		{
406		    ;#&debug("----\n$data\n====\n");
407		    ($cstatus) = ($data =~ /refclockstatus=0?x?([\da-fA-F]+)/);
408		    $Cstatus = hex($cstatus);
409		    $msg .= sprintf("- %-32s",&ntp'clock_status($Cstatus)); #');
410		    ($timecode) = ($data =~ /timecode=\"([^\"]+)\"/);
411		    $msg .= " \"$device\" \"$timecode\"";
412		}
413		else
414		{
415		    ;# no clockvars included - post a cv request
416		    push(@Requests,pack("a4SC",$from, $associd, 4));
417		}
418	    }
419	    elsif ($evnt eq "event_stratum_chg")
420	    {
421		($stratum) = ($data =~ /stratum=(\d+)/);
422		$msg .= "new stratum $stratum";
423	    }
424	}
425    }
426    elsif ($op == 6)		# set trap resonse
427    {
428	&debug("Set trap ok from ",&hostname($from));
429	&set_timeout("refresh-".unpack("H*",$from),time+$REFRESH_TIME,
430		     sprintf("&refresh(\"%s\");",unpack("H*",$from)));
431	return;
432    }
433    elsif ($op == 4)		# read clock variables response
434    {
435	;# status of clock
436	$msg .= sprintf(" %40s ", &hostname($from));
437	if ($associd == 0)
438	{
439	    $msg .= "system clock status: ";
440	}
441	else
442	{
443	    $msg .= sprintf("peer %5d clock",$associd);
444	}
445	$msg .= sprintf("%-32s",&ntp'clock_status($status)); #');
446	($device) = ($data =~ /device=\"([^\"]+)\"/);
447	($timecode) = ($data =~ /timecode=\"([^\"]+)\"/);
448	$msg .= " \"$device\" \"$timecode\"";
449    }
450    elsif ($op == 31)		# unset trap response (UNOFFICIAL op)
451    {
452	;# clear timeout
453	&debug("Clear Trap ok from ",&hostname($from));
454	&clear_timeout("refresh-".unpack("H*",$from));
455	return;
456    }
457    else			# unexpected response
458    {
459	$msg .= "unexpected response to op $op assoc=$associd";
460	$msg .= sprintf(" status=%04x",$status);
461    }
462    &log($msg);
463}
464