Handle hostnames with upper-case letters
[webmin.git] / status / ping-monitor.pl
1 # ping-monitor.pl
2 # Ping some host
3 # Contains code ripped from Net::Ping by Russell Mosemann
4
5 use Socket;
6
7 sub get_ping_status
8 {
9 local $wait = defined($_[0]->{'wait'}) ? $_[0]->{'wait'} : 5;
10 local $ip = &to_ipaddress($_[0]->{'host'}) ||
11             &to_ip6address($_[0]->{'host'});
12 return { 'up' => 0 } if (!$ip);
13 local $ipv6 = &to_ip6address($_[0]->{'host'}) &&
14               !&to_ipaddress($_[0]->{'host'});
15 if ($config{'pinger'} || $ipv6) {
16         # Call a ping command if configured, or if using IPv6 since the built-
17         # in code doesn't support it yet
18         local $cmd;
19         local $auto_pinger = $config{'pinger'} eq "linux" || !$config{'pinger'};
20         if ($auto_pinger && $gconfig{'os_type'} =~ /-linux$/) {
21                 # Use linux command
22                 $cmd = ($ipv6 ? "ping6" : "ping")." -c 1 -w $wait";
23                 }
24         elsif ($auto_pinger && $gconfig{'os_type'} eq 'freebsd') {
25                 # Use FreeBSD command
26                 $cmd = ($ipv6 ? "ping6" : "ping")." -c 1 -W ".($wait * 1000);
27                 }
28         elsif ($auto_pinger) {
29                 # Don't know command for this OS
30                 return { 'up' => - 1 };
31                 }
32         else {
33                 $cmd = $config{'pinger'};
34                 }
35         local $rv = system("$cmd ".quotemeta($_[0]->{'host'}).
36                            " >/dev/null 2>&1 </dev/null");
37         return { 'up' => $rv ? 0 : 1 };
38         }
39 else {
40         # Use builtin code
41         local $rv = &ping_icmp(inet_aton($ip), $wait);
42         return { 'up' => $rv ? 1 : 0 };
43         }
44 }
45
46 sub show_ping_dialog
47 {
48 print &ui_table_row($text{'ping_host'},
49         &ui_textbox("host", $_[0]->{'host'}, 50), 3);
50
51 print &ui_table_row($text{'ping_wait'},
52         &ui_textbox("wait", defined($_[0]->{'wait'}) ? $_[0]->{'wait'} : 5, 6).
53         " ".$text{'oldfile_secs'});
54 }
55
56 sub parse_ping_dialog
57 {
58 #$config{'ping_cmd'} || &error($text{'ping_econfig'});
59 &to_ipaddress($in{'host'}) || &to_ip6address($in{'host'}) ||
60         &error($text{'ping_ehost'});
61 $in{'wait'} =~ /^(\d*\.)?\d+$/ || &error($text{'ping_ewait'});
62 $_[0]->{'host'} = $in{'host'};
63 $_[0]->{'wait'} = $in{'wait'};
64 }
65
66 sub ping_icmp
67 {
68     my ($ip,                # Packed IP number of the host
69         $timeout            # Seconds after which ping times out
70         ) = @_;
71
72     my $ICMP_ECHOREPLY = 0; # ICMP packet types
73     my $ICMP_ECHO = 8;
74     my $icmp_struct = "C2 S3 A";  # Structure of a minimal ICMP packet
75     my $subcode = 0;        # No ICMP subcode for ECHO and ECHOREPLY
76     my $flags = 0;          # No special flags when opening a socket
77     my $port = 0;           # No port with ICMP
78
79     my ($saddr,             # sockaddr_in with port and ip
80         $checksum,          # Checksum of ICMP packet
81         $msg,               # ICMP packet to send
82         $len_msg,           # Length of $msg
83         $rbits,             # Read bits, filehandles for reading
84         $nfound,            # Number of ready filehandles found
85         $finish_time,       # Time ping should be finished
86         $done,              # set to 1 when we are done
87         $ret,               # Return value
88         $recv_msg,          # Received message including IP header
89         $from_saddr,        # sockaddr_in of sender
90         $from_port,         # Port packet was sent from
91         $from_ip,           # Packed IP of sender
92         $from_type,         # ICMP type
93         $from_subcode,      # ICMP subcode
94         $from_chk,          # ICMP packet checksum
95         $from_pid,          # ICMP packet id
96         $from_seq,          # ICMP packet sequence
97         $from_msg,           # ICMP message
98         $data,
99         $cnt,
100         $data_size
101         );
102
103     # Construct packet data string
104     $data_size = 0;
105     for ($cnt = 0; $cnt < $data_size; $cnt++)
106     {
107         $data .= chr($cnt % 256);
108     }
109
110     my $proto_num = (getprotobyname('icmp'))[2];
111     socket(PSOCK, PF_INET, SOCK_RAW, $proto_num);
112
113     $ping_seq = ($ping_seq + 1) % 65536; # Increment sequence
114     $checksum = 0;                          # No checksum for starters
115     $msg = pack($icmp_struct . $data_size, $ICMP_ECHO, $subcode,
116                 $checksum, $$, $ping_seq, $data);
117     $checksum = checksum($msg);
118     $msg = pack($icmp_struct . $data_size, $ICMP_ECHO, $subcode,
119                 $checksum, $$, $ping_seq, $data);
120     $len_msg = length($msg);
121     $saddr = pack_sockaddr_in($port, $ip);
122     send(PSOCK, $msg, $flags, $saddr); # Send the message
123
124     $rbits = "";
125     vec($rbits, fileno(PSOCK), 1) = 1;
126     $ret = 0;
127     $done = 0;
128     $finish_time = time() + $timeout;       # Must be done by this time
129     while (!$done && $timeout > 0)          # Keep trying if we have time
130     {
131         $nfound = select($rbits, undef, undef, $timeout); # Wait for packet
132         $timeout = $finish_time - time();   # Get remaining time
133         if (!defined($nfound))              # Hmm, a strange error
134         {
135             # Probably an interrupted system call, so try again
136             $ret = undef;
137             #$done = 1;
138         }
139         elsif ($nfound)                     # Got a packet from somewhere
140         {
141             $recv_msg = "";
142             $from_saddr = recv(PSOCK, $recv_msg, 1500, $flags);
143             if ($from_saddr) {
144                     ($from_port, $from_ip) = unpack_sockaddr_in($from_saddr);
145                     ($from_type, $from_subcode, $from_chk,
146                      $from_pid, $from_seq, $from_msg) =
147                         unpack($icmp_struct . $data_size,
148                                substr($recv_msg, length($recv_msg) - $len_msg,
149                                       $len_msg));
150                     if (($from_type == $ICMP_ECHOREPLY) &&
151                         ($from_ip eq $ip) &&
152                         ($from_pid == $$) && # Does the packet check out?
153                         ($from_seq == $ping_seq))
154                     {
155                         $ret = 1;                   # It's a winner
156                         $done = 1;
157                     }
158              } else {
159                     # Packet not actually received
160                     $ret = undef;
161              }
162         }
163         else                                # Oops, timed out
164         {
165             $done = 1;
166         }
167     }
168     close(PSOCK);
169     return($ret)
170 }
171
172 # Description:  Do a checksum on the message.  Basically sum all of
173 # the short words and fold the high order bits into the low order bits.
174
175 sub checksum
176 {
177     my ($msg            # The message to checksum
178         ) = @_;
179     my ($len_msg,       # Length of the message
180         $num_short,     # The number of short words in the message
181         $short,         # One short word
182         $chk            # The checksum
183         );
184
185     $len_msg = length($msg);
186     $num_short = $len_msg / 2;
187     $chk = 0;
188     foreach $short (unpack("S$num_short", $msg))
189     {
190         $chk += $short;
191     }                                           # Add the odd byte in
192     $chk += unpack("C", substr($msg, $len_msg - 1, 1)) if $len_msg % 2;
193     $chk = ($chk >> 16) + ($chk & 0xffff);      # Fold high into low
194     return(~(($chk >> 16) + $chk) & 0xffff);    # Again and complement
195 }
196
197