/contrib/ntp/scripts/monitoring/ntptrap

https://bitbucket.org/freebsd/freebsd-head/ · Perl · 463 lines · 422 code · 33 blank · 8 comment · 47 complexity · 04903a6b00086d1d903b5aefaf626ff7 MD5 · raw file

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