check_online_status/check_online_status

247 lines
6.7 KiB
Perl

#!/usr/bin/perl
# INTRO:
# Script for checking if an interface can reach hosts via icmp ping.
#
#
# USAGE:
# <script> --src-dev=<ethX> --target=<8.8.8.8> [--target=<2001:4860:4860::8888> ...] [--src-ip=<203.0.113.1|2001:db8::1>] [--family=<AF_INET|AF_INET6>] [--allow-rfc1918] [--allow-rfc4193] [--enable-warning]
# * --src-dev source network interface for sending out icmp requests
# * --target set icmp test targets, use again for multiple test targets
# * --src-ip source ip address of source network interface (OPTIONAL)
# * --family set type of network address AF_INET = IPv4, AF_INET6 = IPv6 (OPTIONAL)
# * --allow-rfc1918 allow private/local/reserved IPv4 addresses (OPTIONAL)
# * --allow-rfc4193 allow private/local/reserved IPv6 addresses (OPTIONAL)
# * --enable-warning enable WARNING state and return exit code 2 if applicable (OPTIONAL)
#
#
# TIPS AND TRICKS:
# 1. always the first valid address of an interface gets select if --src-ip is not set
# 2. default --family is "AF_INET" (IPv4), to test the IPv6 connection set it to "AF_INET6"
# 2. use multiple targets, as long as one target is reachable the check returns non-CRITICAL
# 3. for allowing private or local network addresses, set --allow-rfc1918 and/or --allow-rfc4193
# 4. for returning a WARNING state if at least one host if reachable set --enable-warning
# 5. setting "0.0.0.0" or "::/0" as --src-ip is the same as setting none (useful for automation)
#
#
# INSTALL:
# ANY system
# * required perl modules:
# * Net::Interface
# * Net::Oping
# * Net::Subnet
#
# Debian >= 9.0
# * required perl packages via apt:
# * libnet-interface-perl libnet-subnet-perl libnet-oping-perl liboping0
#
#
# INFORMATION:
# Version: 0.4
# Created by: fjs.xicon.de <XiCoN-FJS->
#
#
# CHANGELOG:
# v0.4 (2020-11-23)
# * fixed bug when script died if interace doesn't have an ip, now it "UNKNOWN"s
#
# v0.3 (2020-11-22):
# * switched from Net::Ping to Net::Oping
# * enabled full IPv6 support
# * added RFC4193++ matcher
# * added option to allow RFC1918 and RFC4193 addresses
# * added option for WARNING state if not all hosts are reachable
# * fixed bug where vlan-interfaces like eth2.4 where stripped down to eth2
# * extented the manual
#
# v0.2 (2020-11-03):
# * switched to Net::Interface from "manual" regex-ing system commands
# * integrated minimal IPv6 support
# * added RFC1918 matcher
#
use strict;
use warnings;
use Getopt::Long;
use Net::Interface qw(inet_ntop :inet :lower);
use Net::Oping;
use Net::Subnet;
my @test_targets = ();
my $interface_dev = "";
my $interface_ip = "";
my $family = "";
my $_family = "";
my $allow_rfc1918 = 0;
my $allow_rfc4193 = 0;
my $enable_warning = 0;
GetOptions ("target=s" => \@test_targets,
"src-dev=s" => \$interface_dev,
"src-ip=s" => \$interface_ip,
"family=s" => \$family,
"allow-rfc1918" => \$allow_rfc1918,
"allow-rfc4193" => \$allow_rfc4193,
"enable-warning" => \$enable_warning
)
or die("\nUsage: ".$0." --src-dev=<ethX> --target=<8.8.8.8> [--target=<2001:4860:4860::8888> ...] [--src-ip=<203.0.113.1|2001:db8::1>] [--family=<AF_INET|AF_INET6>] [--allow-rfc1918] [--allow-rfc4193] [--enable-warning]\n\n");
### check if the required arguments are somewhat valid
if($interface_dev eq "" or (!defined ($test_targets[0]) or $test_targets[0] eq ""))
{
print "\nUsage: ".$0." --src-dev=<ethX> --target=<8.8.8.8> [--target=<2001:4860:4860::8888> ...] [--src-ip=<203.0.113.1|2001:db8::1>] [--family=<AF_INET|AF_INET6>] [--allow-rfc1918] [--allow-rfc4193] [--enable-warning]\n\n";
exit(0);
}
### set AF_INET* vars
if($family ne "AF_INET6")
{
$family = "AF_INET";
$_family = Net::Interface::AF_INET();
}
else
{
$family = "AF_INET6";
$_family = Net::Interface::AF_INET6();
}
### get the RFC address ranges
my $is_rfc1918 = get_rfc1918_matcher($allow_rfc1918);
my $is_rfc4193 = get_rfc4193_matcher($allow_rfc4193);
### get/set source address
if($interface_ip eq '' || $interface_ip eq '0.0.0.0' || $interface_ip eq '::/0')
{
$interface_ip = get_ip_from_interface($interface_dev, $interface_ip, $family, $_family, $is_rfc1918, $is_rfc4193);
}
### exit UNKNOWN if no source address could be used
if($interface_ip eq '') { print "UNKNOWN: couldn't determine an IP on interface '".$interface_dev."!\n"; exit(3); }
### do the icmp tests
my $online = check_pings($interface_ip, $interface_dev, $family, @test_targets);
### validate the results
if($online == 0)
{
my $targets = join(", ",@test_targets);
print "CRITICAL: uplink (".$interface_ip." @ ".$interface_dev.") can't reach test hosts: ".$targets."!\n";
exit(2);
}
elsif($online < (scalar @test_targets) and $enable_warning == 1)
{
print "WARNING: uplink (".$interface_ip." @ ".$interface_dev.") is online. (".$online." of ".scalar @test_targets." test hosts reachable)\n";
exit(1);
}
else
{
print "OK: uplink (".$interface_ip." @ ".$interface_dev.") is online. (".$online." of ".scalar @test_targets." test hosts reachable)\n";
exit(0);
}
###
### FUNCTIONS
###
sub get_ip_from_interface
{
my ($interface_dev, $interface_ip, $family, $_family, $is_rfc1918, $is_rfc4193) = @_;
my $interface;
my @infs = Net::Interface->interfaces();
foreach my $inf (@infs)
{
if($inf->{'name'} eq $interface_dev)
{
$interface = $inf;
}
}
my @addresses = $interface->address($_family) if defined $interface;
foreach my $ip (@addresses)
{
if($family eq "AF_INET")
{
$ip = inet_ntoa($ip);
if(!$is_rfc1918->($ip))
{
return $ip;
}
}
if($family eq "AF_INET6")
{
$ip = inet_ntop($ip);
if(!$is_rfc4193->($ip))
{
return $ip;
}
}
}
return "";
}
sub check_pings
{
my ($src,$dev,$family, @test_targets) = @_;
my $pings_ok = 0;
foreach my $target ( @test_targets )
{
my $ping_result = ping_test($target,$src,$dev);
if(defined($ping_result->{$target}) && $ping_result->{$target} =~/^\d+(?:\.\d+)?/) # match float value
{
$pings_ok++;
}
}
return $pings_ok;
}
sub ping_test
{
my ($dst,$src,$dev) = @_;
my $oping = Net::Oping->new();
$oping->timeout("2");
$oping->bind($src);
$oping->device($dev);
$oping->host_add($dst);
return $oping->ping();
}
sub get_rfc1918_matcher
{
my ($match) = @_;
my @subnets = ();
my @real_subnets = ('10.0.0.0/8','172.16.0.0/12','192.168.0.0/16');
if(!$match) { @subnets = @real_subnets; }
my $is_rfc = subnet_matcher @subnets;
return $is_rfc;
}
sub get_rfc4193_matcher
{
my ($match) = @_;
my @subnets = ();
my @real_subnets = ('fc00::/7','fe80::/10','2001:0002::/48','2001:0010::/28','2001:db8::/32','ff00::/8');
if(!$match) { @subnets = @real_subnets; }
my $is_rfc = subnet_matcher @subnets;
return $is_rfc;
}