nrpe.pm 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. package nrpe;
  2. use strict;
  3. use warnings;
  4. require Exporter;
  5. use Digest::CRC qw( crc32 );
  6. use IO::Socket;
  7. use IO::Socket::SSL;
  8. use Socket;
  9. use Test::More;
  10. our @ISA= qw( Exporter );
  11. # these CAN be exported.
  12. our @EXPORT_OK = qw( check_if_port_available check_if_ipv6_available supports_ssl
  13. switch_config_file launch_daemon restart_daemon kill_daemon ensure_daemon_running
  14. send_request send_and_wait_for_timeout is_response isnt_response
  15. STATE_OK STATE_WARNING STATE_CRITICAL STATE_UNKNOWN
  16. $nrpe $checknrpe );
  17. # these are exported by default.
  18. our @EXPORT = qw( check_if_port_available check_if_ipv6_available supports_ssl
  19. switch_config_file launch_daemon restart_daemon kill_daemon ensure_daemon_running
  20. send_request send_and_wait_for_timeout is_response isnt_response
  21. STATE_OK STATE_WARNING STATE_CRITICAL STATE_UNKNOWN
  22. $nrpe $checknrpe );
  23. defined($ARGV[0]) or die "Usage: $0 <top build dir>";
  24. my $top_builddir = $ARGV[0]; # shift @ARGV;
  25. our $nrpe = "$top_builddir/src/nrpe";
  26. our $checknrpe = "$top_builddir/src/check_nrpe -D";
  27. #our $checknrpe = "valgrind --leak-check=full --log-file=logs/valgrind-check-%p.log $top_builddir/src/check_nrpe -D";
  28. my $nrpe_pid = 0;
  29. use constant {
  30. STATE_UNKNOWN => 3 << 8,
  31. STATE_CRITICAL => 2 << 8,
  32. STATE_WARNING => 1 << 8,
  33. STATE_OK => 0 << 8,
  34. };
  35. $SIG{INT} = \&signal_handler;
  36. $SIG{TERM} = \&signal_handler;
  37. sub read_pid {
  38. open my $fh, '<', "run/nrpe.pid" or return 0;
  39. chomp( my $pid = <$fh> );
  40. return $pid
  41. }
  42. sub check_connection {
  43. if (socket(my $s, AF_INET, SOCK_STREAM, Socket::IPPROTO_TCP)) {
  44. my $a = connect($s, pack_sockaddr_in(40321, inet_aton("127.0.0.1")));
  45. close $s;
  46. return 1 if defined $a;
  47. }
  48. if (socket(my $s, AF_INET6, SOCK_STREAM, Socket::IPPROTO_TCP)) {
  49. my $a = connect($s, pack_sockaddr_in6(40321, Socket::inet_pton(AF_INET6, "::1")));
  50. close $s;
  51. return 1 if defined $a;
  52. }
  53. return 0;
  54. }
  55. sub check_if_ipv6_available {
  56. socket(my $s, AF_INET6, SOCK_STREAM, Socket::IPPROTO_TCP) || return 0;
  57. return 1;
  58. }
  59. sub check_if_port_available {
  60. BAIL_OUT('Something is already listening on our port 40321') if check_connection();
  61. }
  62. sub switch_config_file {
  63. my $filename = shift @_;
  64. unlink 'nrpe.cfg';
  65. symlink($filename, 'nrpe.cfg') || BAIL_OUT('Unable to update config symlink');
  66. }
  67. sub wait_for_daemon {
  68. my $counter = 0;
  69. while (!check_connection() && $counter < 15) {
  70. sleep(1);
  71. $counter++;
  72. }
  73. diag("Waiting $counter seconds for daemon") if $counter > 7;
  74. }
  75. sub launch_daemon {
  76. my @output = `$nrpe -d -C -c nrpe.cfg`;
  77. # my @output = `valgrind --leak-check=full --show-leak-kinds=all --log-file=logs/valgrind-%p.log $nrpe --daemon --dont-chdir --config nrpe.cfg`;
  78. my $pid = 0;
  79. my $counter = 0;
  80. while ( ($pid = read_pid()) == 0 && $counter < 10) {
  81. sleep(1);
  82. $counter++;
  83. }
  84. diag(@output);
  85. BAIL_OUT('Unable to get nrpe daemon pid') if $pid == 0;
  86. note("started daemon on $pid");
  87. $nrpe_pid = $pid;
  88. wait_for_daemon();
  89. return $pid
  90. }
  91. sub ensure_daemon_running {
  92. my $pid = read_pid() || BAIL_OUT('daemon is not running');
  93. kill 0, $pid || BAIL_OUT('daemon is not running');
  94. $nrpe_pid = $pid;
  95. }
  96. sub restart_daemon {
  97. if ($nrpe_pid > 0) {
  98. note("restarting daemon on $nrpe_pid");
  99. kill 'HUP', $nrpe_pid;
  100. sleep(1);
  101. wait_for_daemon();
  102. } else {
  103. diag('pid for nrpe daemon unknown');
  104. }
  105. return 0;
  106. }
  107. sub kill_daemon {
  108. if ($nrpe_pid > 0) {
  109. note("killing daemon on $nrpe_pid");
  110. kill 'TERM', $nrpe_pid;
  111. $nrpe_pid = 0;
  112. sleep(1);
  113. }
  114. return 0;
  115. }
  116. sub supports_ssl {
  117. my @output = `$nrpe -h`;
  118. return grep(m'^SSL/TLS Available', @output);
  119. }
  120. ################################################################################
  121. sub send_request {
  122. my (%arg) = (
  123. 'host' => 'localhost',
  124. 'port' => 5666,
  125. 'version' => 4,
  126. 'type' => 1,
  127. 'crc' => 1,
  128. 'command' => '_NRPE_CHECK',
  129. 'length' => 0,
  130. 'ssl' => 1,
  131. @_
  132. );
  133. my $client;
  134. my $buffer;
  135. if ($arg{'ssl'}) {
  136. $client = IO::Socket::SSL->new(
  137. PeerHost => $arg{'host'},
  138. PeerPort => $arg{'port'},
  139. SSL_verify_mode => SSL_VERIFY_NONE,
  140. ) or diag("error=$!, ssl_error=$SSL_ERROR") and return ();
  141. } else {
  142. $client = IO::Socket->new(
  143. Domain => AF_INET,
  144. Type => SOCK_STREAM,
  145. proto => 'tcp',
  146. PeerHost => $arg{'host'},
  147. PeerPort => $arg{'port'},
  148. ) or diag("error=$!") and return ();
  149. }
  150. if ($arg{'version'} == 2) {
  151. $buffer = pack('n!n!N!n! Z[1024] x![N]', $arg{'version'}, $arg{'type'}, 0, 0, $arg{'command'} );
  152. } else {
  153. $buffer = pack('n!n!N!n! n!N!/Z', $arg{'version'}, $arg{'type'}, 0, 0, 0, $arg{'command'} );
  154. }
  155. if ($arg{'crc'} == 1) {
  156. my $d = pack('N!', crc32($buffer));
  157. substr($buffer, 4, 4, $d);
  158. }
  159. if ($arg{'length'} > 0) {
  160. $buffer = $buffer . "\0" x $arg{'length'};
  161. } elsif ($arg{'length'} < 0) {
  162. $buffer = substr($buffer, 0, $arg{'length'});
  163. }
  164. # diag(length($buffer), " - ", unpack("H*", $buffer), "\n");
  165. print $client $buffer;
  166. my $response = <$client>;
  167. if ($arg{'version'} == 2 && defined $response) {
  168. if (length($response) != 1036) {
  169. $response .= <$client>;
  170. }
  171. }
  172. $client->close();
  173. return () if ! defined $response;
  174. if ($arg{'version'} == 2) {
  175. return unpack('n!n!N!n! Z[1024]', $response);
  176. }
  177. return unpack('n!n!N!n! x[n] N!/Z', $response);
  178. }
  179. sub send_and_wait_for_timeout {
  180. my ($buffer, $name) = @_;
  181. my (%arg) = (
  182. 'timeout' => 10,
  183. @_
  184. );
  185. SKIP: {
  186. my $client = IO::Socket::SSL->new(
  187. PeerHost => 'localhost',
  188. PeerPort => 40321,
  189. SSL_verify_mode => SSL_VERIFY_NONE,
  190. ) || skip 'failed create socket', 2;
  191. my $sel = IO::Select->new( $client );
  192. print $client $buffer;
  193. my $start = time();
  194. # SSL/TLS can have readable frames even though the server hasn't sent any data
  195. # We need to look for read letting us know the server closed the socket.
  196. $client->blocking(0);
  197. my $n;
  198. for (0..20) {
  199. $sel->can_read(15);
  200. $n = sysread($client, my $buf, 1);
  201. if (defined $n and $n <= 0) {
  202. last;
  203. }
  204. }
  205. my $end = time();
  206. $client->close();
  207. is($n, 0, "$name - disconnected");
  208. if ($arg{'timeout'} == 0) {
  209. # We're actually looking for an immediate abort
  210. cmp_ok($end - $start, '<=', 1, "$name - abort");
  211. } else {
  212. cmp_ok($end - $start, '>=', $arg{'timeout'}, "$name - timeout");
  213. }
  214. }
  215. }
  216. sub is_response {
  217. my $response = shift;
  218. my $name = shift;
  219. my (%arg) = (
  220. 'version' => 4,
  221. 'like' => qr/NRPE v.*/,
  222. @_
  223. );
  224. subtest "$name" => sub {
  225. plan tests => 5;
  226. is(@$response, 5, "$name count");
  227. my ($ver, $type, $crc, $result, $text) = @$response;
  228. is($ver, $arg{'version'}, "$name - is v$arg{'version'}");
  229. is($type, 2, "$name - is response");
  230. is($result, STATE_OK, "$name - result");
  231. like($text, $arg{'like'}, "$name - text");
  232. };
  233. }
  234. sub isnt_response {
  235. my $response = shift;
  236. my $name = shift;
  237. is(@$response, 0, "$name");
  238. }
  239. ################################################################################
  240. #END {
  241. # kill_daemon();
  242. #}
  243. sub signal_handler {
  244. kill_daemon();
  245. }
  246. 1;