diff --git a/includes/functions.php b/includes/functions.php index 50a5252a39..e69325bf84 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -671,6 +671,11 @@ function isPingable($hostname, $address_family = 'ipv4', $attribs = []) $address_family ); + if ($status['dup'] > 0) { + Log::event('Duplicate ICMP response detected! This could indicate a network issue.', getidbyname($hostname), 'icmp', 4); + $status['exitcode'] = 0; // when duplicate is detected fping returns 1. The device is up, but there is another issue. Clue admins in with above event. + } + return [ 'result' => ($status['exitcode'] == 0 && $status['loss'] < 100), 'last_ping_timetaken' => $status['avg'], @@ -1445,26 +1450,29 @@ function fping($host, $count = 3, $interval = 1000, $timeout = 500, $address_fam { // Default to ipv4 $fping_name = $address_family == 'ipv6' ? 'fping6' : 'fping'; - $fping_path = Config::get($fping_name, $fping_name); - - // build the parameters - $params = '-e -q -c ' . max($count, 1); - $interval = max($interval, 20); - $params .= ' -p ' . $interval; - $params .= ' -t ' . max($timeout, $interval); + // build the command + $cmd = [ + Config::get($fping_name, $fping_name), + '-e', + '-q', + '-c', + max($count, 1), + '-p', + $interval, + '-t', + max($timeout, $interval), + $host + ]; - $cmd = "$fping_path $params $host"; - - d_echo("[FPING] $cmd\n"); - - $process = new Process($cmd); + $process = app()->make(Process::class, ['command' => $cmd]); + d_echo('[FPING] ' . $process->getCommandLine() . PHP_EOL); $process->run(); $output = $process->getErrorOutput(); - preg_match('#= (\d+)/(\d+)/(\d+)%, min/avg/max = ([\d.]+)/([\d.]+)/([\d.]+)$#', $output, $parsed); - list(, $xmt, $rcv, $loss, $min, $avg, $max) = $parsed; + preg_match('#= (\d+)/(\d+)/(\d+)%(, min/avg/max = ([\d.]+)/([\d.]+)/([\d.]+))?$#', $output, $parsed); + list(, $xmt, $rcv, $loss, , $min, $avg, $max) = array_pad($parsed, 8, 0); if ($loss < 0) { $xmt = 1; @@ -1473,12 +1481,13 @@ function fping($host, $count = 3, $interval = 1000, $timeout = 500, $address_fam } $response = [ - 'xmt' => set_numeric($xmt), - 'rcv' => set_numeric($rcv), - 'loss' => set_numeric($loss), - 'min' => set_numeric($min), - 'max' => set_numeric($max), - 'avg' => set_numeric($avg), + 'xmt' => (int)$xmt, + 'rcv' => (int)$rcv, + 'loss' => (int)$loss, + 'min' => (float)$min, + 'max' => (float)$max, + 'avg' => (float)$avg, + 'dup' => substr_count($output, 'duplicate'), 'exitcode' => $process->getExitCode(), ]; d_echo($response); diff --git a/tests/FpingTest.php b/tests/FpingTest.php new file mode 100644 index 0000000000..e50fdb244f --- /dev/null +++ b/tests/FpingTest.php @@ -0,0 +1,134 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 Tony Murray + * @author Tony Murray + */ + +namespace LibreNMS\Tests; + +use Symfony\Component\Process\Process; + +class FpingTest extends TestCase +{ + public function testUpPing() + { + $output = "192.168.1.3 : xmt/rcv/%loss = 3/3/0%, min/avg/max = 0.62/0.71/0.93\n"; + $this->mockFpingProcess($output, 0); + + $expected = [ + "xmt" => 3, + "rcv" => 3, + "loss" => 0, + "min" => 0.62, + "max" => 0.93, + "avg" => 0.71, + "dup" => 0, + "exitcode" => 0, + ]; + + $actual = fping('192.168.1.3'); + + $this->assertSame($expected, $actual); + } + + public function testPartialDownPing() + { + $output = "192.168.1.7 : xmt/rcv/%loss = 5/3/40%, min/avg/max = 0.13/0.23/0.32\n"; + $this->mockFpingProcess($output, 0); + + $expected = [ + "xmt" => 5, + "rcv" => 3, + "loss" => 40, + "min" => 0.13, + "max" => 0.32, + "avg" => 0.23, + "dup" => 0, + "exitcode" => 0, + ]; + + $actual = fping('192.168.1.7'); + + $this->assertSame($expected, $actual); + } + + public function testDownPing() + { + $output = "192.168.53.1 : xmt/rcv/%loss = 3/0/100%\n"; + $this->mockFpingProcess($output, 1); + + $expected = [ + "xmt" => 3, + "rcv" => 0, + "loss" => 100, + "min" => 0.0, + "max" => 0.0, + "avg" => 0.0, + "dup" => 0, + "exitcode" => 1, + ]; + + $actual = fping('192.168.53.1'); + + $this->assertSame($expected, $actual); + } + + public function testDuplicatePing() + { + $output = <<<'OUT' +192.168.1.2 : duplicate for [0], 84 bytes, 0.91 ms +192.168.1.2 : duplicate for [0], 84 bytes, 0.95 ms +192.168.1.2 : xmt/rcv/%loss = 3/3/0%, min/avg/max = 0.68/0.79/0.91 +OUT; + + $this->mockFpingProcess($output, 1); + + $expected = [ + "xmt" => 3, + "rcv" => 3, + "loss" => 0, + "min" => 0.68, + "max" => 0.91, + "avg" => 0.79, + "dup" => 2, + "exitcode" => 1, + ]; + + $actual = fping('192.168.1.2'); + + $this->assertSame($expected, $actual); + } + + private function mockFpingProcess($output, $exitCode) + { + $process = \Mockery::mock(Process::class); + $process->shouldReceive('getCommandLine', 'run'); + $process->shouldReceive('getErrorOutput')->andReturn($output); + $process->shouldReceive('getExitCode')->andReturn($exitCode); + + $this->app->bind(Process::class, function ($app, $params) use ($process) { + return $process; + }); + + return $process; + } +}