# ping-monitor.pl
# Ping some host
# Contains code ripped from Net::Ping by Russell Mosemann

use Socket;

sub get_ping_status
{
local $wait = defined($_[0]->{'wait'}) ? $_[0]->{'wait'} : 5;
local $ip = inet_aton($_[0]->{'host'});
return { 'up' => 0 } if (!$ip);
local $rv = &ping_icmp($ip, $wait);
return { 'up' => $rv ? 1 : 0 };
}

sub show_ping_dialog
{
print "<tr> <td><b>$text{'ping_host'}</b></td>\n";
print "<td colspan=3><input name=host size=45 value='$_[0]->{'host'}'></td> </tr>\n";

print "<tr> <td><b>$text{'ping_wait'}</b></td>\n";
printf "<td><input name=wait size=6 value='%s'> %s</td> </tr>\n",
	defined($_[0]->{'wait'}) ? $_[0]->{'wait'} : 5,
	$text{'oldfile_secs'};
}

sub parse_ping_dialog
{
#$config{'ping_cmd'} || &error($text{'ping_econfig'});
&check_ipaddress($in{'host'}) || gethostbyname($in{'host'}) ||
	&error($text{'ping_ehost'});
$in{'wait'} =~ /^(\d*\.)?\d+$/ || &error($text{'ping_ewait'});
$_[0]->{'host'} = $in{'host'};
$_[0]->{'wait'} = $in{'wait'};
}

sub ping_icmp
{
    my ($ip,                # Packed IP number of the host
        $timeout            # Seconds after which ping times out
        ) = @_;

    my $ICMP_ECHOREPLY = 0; # ICMP packet types
    my $ICMP_ECHO = 8;
    my $icmp_struct = "C2 S3 A";  # Structure of a minimal ICMP packet
    my $subcode = 0;        # No ICMP subcode for ECHO and ECHOREPLY
    my $flags = 0;          # No special flags when opening a socket
    my $port = 0;           # No port with ICMP

    my ($saddr,             # sockaddr_in with port and ip
        $checksum,          # Checksum of ICMP packet
        $msg,               # ICMP packet to send
        $len_msg,           # Length of $msg
        $rbits,             # Read bits, filehandles for reading
        $nfound,            # Number of ready filehandles found
        $finish_time,       # Time ping should be finished
        $done,              # set to 1 when we are done
        $ret,               # Return value
        $recv_msg,          # Received message including IP header
        $from_saddr,        # sockaddr_in of sender
        $from_port,         # Port packet was sent from
        $from_ip,           # Packed IP of sender
        $from_type,         # ICMP type
        $from_subcode,      # ICMP subcode
        $from_chk,          # ICMP packet checksum
        $from_pid,          # ICMP packet id
        $from_seq,          # ICMP packet sequence
        $from_msg,           # ICMP message
	$data,
	$cnt,
	$data_size
        );

    # Construct packet data string
    $data_size = 0;
    for ($cnt = 0; $cnt < $data_size; $cnt++)
    {
        $data .= chr($cnt % 256);
    }

    my $proto_num = (getprotobyname('icmp'))[2];
    socket(PSOCK, PF_INET, SOCK_RAW, $proto_num);

    $ping_seq = ($ping_seq + 1) % 65536; # Increment sequence
    $checksum = 0;                          # No checksum for starters
    $msg = pack($icmp_struct . $data_size, $ICMP_ECHO, $subcode,
                $checksum, $$, $ping_seq, $data);
    $checksum = checksum($msg);
    $msg = pack($icmp_struct . $data_size, $ICMP_ECHO, $subcode,
                $checksum, $$, $ping_seq, $data);
    $len_msg = length($msg);
    $saddr = pack_sockaddr_in($port, $ip);
    send(PSOCK, $msg, $flags, $saddr); # Send the message

    $rbits = "";
    vec($rbits, fileno(PSOCK), 1) = 1;
    $ret = 0;
    $done = 0;
    $finish_time = time() + $timeout;       # Must be done by this time
    while (!$done && $timeout > 0)          # Keep trying if we have time
    {
        $nfound = select($rbits, undef, undef, $timeout); # Wait for packet
        $timeout = $finish_time - time();   # Get remaining time
        if (!defined($nfound))              # Hmm, a strange error
        {
	    # Probably an interrupted system call, so try again
            $ret = undef;
            #$done = 1;
        }
        elsif ($nfound)                     # Got a packet from somewhere
        {
            $recv_msg = "";
            $from_saddr = recv(PSOCK, $recv_msg, 1500, $flags);
            ($from_port, $from_ip) = unpack_sockaddr_in($from_saddr);
            ($from_type, $from_subcode, $from_chk,
             $from_pid, $from_seq, $from_msg) =
                unpack($icmp_struct . $data_size,
                       substr($recv_msg, length($recv_msg) - $len_msg,
                              $len_msg));
            if (($from_type == $ICMP_ECHOREPLY) &&
                ($from_ip eq $ip) &&
                ($from_pid == $$) && # Does the packet check out?
                ($from_seq == $ping_seq))
            {
                $ret = 1;                   # It's a winner
                $done = 1;
            }
        }
        else                                # Oops, timed out
        {
            $done = 1;
        }
    }
    close(PSOCK);
    return($ret)
}

# Description:  Do a checksum on the message.  Basically sum all of
# the short words and fold the high order bits into the low order bits.

sub checksum
{
    my ($msg            # The message to checksum
        ) = @_;
    my ($len_msg,       # Length of the message
        $num_short,     # The number of short words in the message
        $short,         # One short word
        $chk            # The checksum
        );

    $len_msg = length($msg);
    $num_short = $len_msg / 2;
    $chk = 0;
    foreach $short (unpack("S$num_short", $msg))
    {
        $chk += $short;
    }                                           # Add the odd byte in
    $chk += unpack("C", substr($msg, $len_msg - 1, 1)) if $len_msg % 2;
    $chk = ($chk >> 16) + ($chk & 0xffff);      # Fold high into low
    return(~(($chk >> 16) + $chk) & 0xffff);    # Again and complement
}


