1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
|
# copyright (C) 1997-2005 Jean-Luc Fontaine (mailto:jfontain@free.fr)
# this program is free software: please read the COPYRIGHT file enclosed in this package or use the Help Copyright menu
# $Id: ping.tcl,v 2.41 2005/01/02 00:45:07 jfontain Exp $
package provide ping [lindex {$Revision: 2.41 $} 1]
if {$::tcl_platform(platform) eq "windows"} {
package require Tnm ;# for host to IP address conversion since the library below is not ported to Windows
} else {
package require network 1
}
package require hashes
package require stooop 4.1
namespace import stooop::*
namespace eval ping {
variable directory [pwd] ;# save this module directory
array set data {
updates 0
0,label host 0,type dictionary 0,message {host or gateway name} 0,anchor left
1,label address 1,type dictionary 1,message {IP address} 1,anchor left
2,label replied 2,type dictionary 2,message {reply address} 2,anchor left
3,label period 3,type integer 3,message {polling period in seconds}
4,label count 4,type integer 4,message {number of packets transmitted for each period}
5,label size 5,type integer 5,message {packet size in bytes}
6,label timeout 6,type integer 6,message {maximum time spent waiting for a response for each packet, in seconds}
7,label delay 7,type integer 7,message {delay between transmitted packets in seconds}
8,label transmitted 8,type integer 8,message {number of packets transmitted for the last period}
9,label received 9,type integer 9,message {number of packets received for the last period}
10,label loss 10,type integer 10,message {percentage of packets lost for the last period}
11,label minimum 11,type integer 11,message {minimum round trip time for the last period, in milliseconds}
12,label averaged 12,type integer 12,message {averaged round trip time for the last period, in milliseconds}
13,label maximum 13,type integer 13,message {maximum round trip time for the last period, in milliseconds}
views {
{visibleColumns {0 1 2 3 4 5 6 7} sort {0 increasing}}
{visibleColumns {0 8 9 10 11 12 13} sort {0 increasing}}
}
persistent 1 64Bits 1
switches {-f 1 -r 1}
}
set file [open ping.htm]
set data(helpText) [read $file] ;# initialize HTML help data from file
close $file
unset file
proc initialize {optionsName} {
upvar 1 $optionsName options
variable directory
variable data
if {[catch {set name $options(-f)}]} { ;# no hosts file specified
set name [file join $directory hosts] ;# use default
}
set file [open $name]
set period 2147483647
set rows {}
while {[gets $file line] >= 0} {
set line [string trim $line]
if {$line eq ""} continue ;# skip empty lines
if {[string match #* $line]} continue ;# comments
if {[llength $line] != 6} { ;# and invalid lines
error "invalid line in hosts file:\n$line"
}
set row [hash64::string [lindex $line 0]] ;# derive row number from host name or IP address
foreach "data($row,0) data($row,3) data($row,6) data($row,4) data($row,5) data($row,7)" $line {}
if {$data($row,3) <= 0} {
error "host $data($row,0) period ($data($row,3)) must be greater than 0."
exit 1
}
if {$data($row,6) <= 0} {
error "host $data($row,0) timeout ($data($row,6)) must be greater than 0."
exit 1
}
if {$data($row,4) <= 0} {
error "host $data($row,0) packet count ($data($row,4)) must be greater than 0."
exit 1
}
if {$data($row,5) < 56} {
error "host $data($row,0) packet size ($data($row,5)) must be greater than or equal 56."
exit 1
}
if {$data($row,7) < 0} {
error "host $data($row,0) delay ($data($row,7)) must be greater than or equal 0."
exit 1
}
pushMessage "looking up IP address for $data($row,0)" ;# looking time may be long
set data($row,1) {}
if {$::tcl_platform(platform) eq "windows"} {
catch {set data($row,1) [::Tnm::netdb hosts address $data($row,0)]}
} else {
catch {set data($row,1) [::network::addressfromhost $data($row,0)]}
}
popMessage
array set data [list $row,2 {} $row,8 ? $row,9 ? $row,10 ? $row,11 ? $row,12 ? $row,13 ?]
if {$data($row,3) < $period} {
set period $data($row,3) ;# keep track of the minimum period for all hosts
}
lappend rows $row
}
close $file
set data(pollTimes) -$period
set requestsHost 127.0.0.1 ;# local host by default
catch {set requestsHost $options(-r)} ;# may be overridden in command line
::session::initialize $requestsHost ::ping::received
foreach row $rows { ;# initialize polling
if {$data($row,1) eq ""} continue ;# do not attempt to ping unresolved addresses
launch $row
}
}
proc launch {row} {
variable stamp
variable data
set stamp [clock clicks -milliseconds]
new session $data($row,1) $data($row,4) $data($row,6) $data($row,5) $data($row,7) "::ping::process $row"
}
proc process {row session replyAddress times states} {
variable data
variable stamp
delete $session ;# first clean up
set transmitted 0
set received 0
set total 0
set minimum 2147483647
set maximum 0
foreach time $times status $states {
if {$status == 2} continue ;# general error occured: no packets were transmitted
incr transmitted
if {$status == 1} continue ;# timeout occurred
incr received
incr total $time
if {$time < $minimum} {
set minimum $time
}
if {$time > $maximum} {
set maximum $time
}
}
set data($row,2) $replyAddress
set data($row,8) $transmitted
set data($row,9) $received
if {$transmitted > 0} {
set data($row,10) [expr {(100 * ($transmitted - $received)) / $transmitted}]
} else { ;# may happen when network is unreachable, for example
set data($row,10) ?
}
if {$received > 0} {
set data($row,11) $minimum
set data($row,12) [expr {$total / $received}]
set data($row,13) $maximum
} else {
array set data [list $row,11 ? $row,12 ? $row,13 ?]
}
# now setup next poll for this row
set left [expr {($data($row,3) * 1000) - ([clock clicks -milliseconds] - $stamp)}] ;# in milliseconds
if {$left < 500} { ;# with a half second precision
launch $row
} else {
after $left "::ping::launch $row"
}
}
proc received {} {
variable event
if {![info exists event]} { ;# optimize by waiting a short time before updating so that multiple updates can be grouped
set event [after 1000 ::ping::update]
}
}
proc update {} {
variable event
variable data
unset event
incr data(updates)
}
}
class session {
proc session {this address count timeout size delay command} {
set ($this,address) [split $address .]
set ($this,count) $count
set ($this,timeout) $timeout
set ($this,size) $size
set ($this,delay) [expr {1000 * $delay}] ;# in milliseconds
set ($this,command) $command
send $this
}
proc ~session {this} {}
proc initialize {requestsHost receiveCommand} { ;# command to be invoked once reception is completed
if {[catch {set ::session::(socket) [socket $requestsHost nmicmp]} message]} {
error \
"could not connect to the nmicmpd server for the following reason:
$message
Please read the INSTALL file in the ping module sub-directory."
}
fconfigure $::session::(socket) -blocking 0 -translation binary
set ::session::(command) $receiveCommand
fileevent $::session::(socket) readable ::session::receive
}
proc send {this} {
set ($this,stamp) [clock clicks -milliseconds]
puts -nonewline $::session::(socket)\
[binary format c4Ic4ccccSS {0 1 0 0} $this $($this,address) 255 $($this,timeout) 0 0 $($this,size) 0]
flush $::session::(socket)
}
proc receive {} {
set completed 0
while {[binary scan [read $::session::(socket) 16] xxcxIc4I status session bytes time] == 4} { ;# process all replies
if {$status == 0} { ;# valid response
if {![info exists ($session,replyAddress)]} { ;# only store valid reply address once
catch {unset address}
foreach byte $bytes {
if {[info exists address]} {
append address .
}
append address [expr {($byte + 256) % 256}] ;# convert to unsigned
}
set ($session,replyAddress) $address
}
lappend ($session,times) $time
} else { ;# no reply
lappend ($session,times) {}
}
lappend ($session,states) $status
if {[incr ($session,count) -1] == 0} { ;# done
if {![info exists ($session,replyAddress)]} { ;# if no reply address, use an empty string
set ($session,replyAddress) {}
}
uplevel #0 $($session,command) $session\
[list $($session,replyAddress)] [list $($session,times)] [list $($session,states)]
set completed 1 ;# one request was completed
} else {
# calculate time left before next probe
set left [expr {$($session,delay) - ([clock clicks -milliseconds] - $($session,stamp))}] ;# in milliseconds
if {$left < 500} { ;# with a half second precision
send $session
} else {
after $left "::session::send $session"
}
}
}
if {$completed} { ;# at least one request was completed
uplevel #0 $::session::(command)
}
}
}
|