【转】ping.py
差点勾成Jiong的分类了,因为最近真的很囧
#
!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
""" ping.py
ping.py uses the ICMP protocol's mandatory ECHO_REQUEST
datagram to elicit an ICMP ECHO_RESPONSE from a
host or gateway.
Copyright (C) 2004 - Lars Strand <lars strand at gnist org>;
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Must be running as root, or write a suid-wrapper. Since newer *nix
variants, the kernel ignores the set[ug]id flags on #! scripts for
security reasons
RFC792, echo/reply message:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data
+-+-+-+-+-
TODO:
- do not create socket inside 'while' (but if not: ipv6 won't work)
- add support for broadcast/multicast
- add support for own payload string
CHANGELOG:
DONE -->; bugfix from Filip Van Raemdonck mechanix debian org
DONE -->; add more support for modules (raise instead of sys.exit)
DONE -->; locale func names
DONE -->; package def
DONE -->; some code cleanup
"""
import sys
import os
import struct
import array
import time
import select
import binascii
import math
import getopt
import string
import socket
# total size of data (payload)
ICMP_DATA_STR = 56
# initial values of header variables
ICMP_TYPE = 8
ICMP_TYPE_IP6 = 128
ICMP_CODE = 0
ICMP_CHECKSUM = 0
ICMP_ID = 0
ICMP_SEQ_NR = 0
# Package definitions.
__program__ = ' ping '
__version__ = ' 0.5a '
__date__ = ' 2004/15/12 '
__author__ = ' Lars Strand <lars at unik no>; '
__licence__ = ' GPL '
__copyright__ = ' Copyright (C) 2004 Lars Strand '
def _construct(id, size, ipv6):
""" Constructs a ICMP echo packet of variable size
"""
# size must be big enough to contain time sent
if size < int(struct.calcsize( " d " )):
_error( " packetsize to small, must be at least %d " % int(struct.calcsize( " d " )))
# construct header
if ipv6:
header = struct.pack( ' BbHHh ' , ICMP_TYPE_IP6, ICMP_CODE, ICMP_CHECKSUM, \
ICMP_ID, ICMP_SEQ_NR + id)
else :
header = struct.pack( ' bbHHh ' , ICMP_TYPE, ICMP_CODE, ICMP_CHECKSUM, \
ICMP_ID, ICMP_SEQ_NR + id)
# if size big enough, embed this payload
load = " -- IF YOU ARE READING THIS YOU ARE A NERD! -- "
# space for time
size -= struct.calcsize( " d " )
# construct payload based on size, may be omitted :)
rest = ""
if size > len(load):
rest = load
size -= len(load)
# pad the rest of payload
rest += size * " X "
# pack
data = struct.pack( " d " , time.time()) + rest
packet = header + data # ping packet without checksum
checksum = _in_cksum(packet) # make checksum
# construct header with correct checksum
if ipv6:
header = struct.pack( ' BbHHh ' , ICMP_TYPE_IP6, ICMP_CODE, checksum, \
ICMP_ID, ICMP_SEQ_NR + id)
else :
header = struct.pack( ' bbHHh ' , ICMP_TYPE, ICMP_CODE, checksum, ICMP_ID, \
ICMP_SEQ_NR + id)
# ping packet *with* checksum
packet = header + data
# a perfectly formatted ICMP echo packet
return packet
def _in_cksum(packet):
""" THE RFC792 states: 'The 16 bit one's complement of
the one's complement sum of all 16 bit words in the header.'
Generates a checksum of a (ICMP) packet. Based on in_chksum found
in ping.c on FreeBSD.
"""
print " packet = " , packet
# add byte if not dividable by 2
if len(packet) & 1 :
packet = packet + ' \0 '
print " packet = [after add byte] " , packet
# split into 16-bit word and insert into a binary array
words = array.array( ' h ' , packet) # there's a big/little-endian issue
# when analysis with wireshark
sum = 0
print " words in packet array: " , words
# perform ones complement arithmetic on 16-bit words
for word in words:
print " process word: " , hex(word)
sum += (word & 0xffff )
print " sum: " , hex(sum)
hi = sum >> 16
print " hi: " , hex(hi)
lo = sum & 0xffff
print " lo: " , hex(lo)
sum = hi + lo # one's complement
print " sum(=hi+lo): " , hex(sum)
sum = sum + (sum >> 16 ) # TODO: another one's complement, why we need this?
# sum is at most 16 bit, so here do nothing.???
print " sum(=sum+(sum>>16)): " , hex(sum)
print ( ~ sum) & 0xffff # TODO: the implement of one number is just ~ ?
return ( ~ sum) & 0xffff # return ones complement
# We know whether the host we will ping is alive before we call this function?
def pingNode(alive = 0, timeout = 1.0 , ipv6 = 0, number = sys.maxint, node = None, \
flood = 0, size = ICMP_DATA_STR):
""" Pings a node based on input given to the function.
"""
# if no node, exit
if not node:
_error( "" )
# if not a valid host, exit
if ipv6:
if socket.has_ipv6:
try :
info, port = socket.getaddrinfo(node, None)
host = info[ 4 ][0]
# do not print ipv6 twice if ipv6 address given as node
if host == node:
noPrintIPv6adr = 1
except :
_error( " cannot resolve %s: Unknow host " % node)
else :
_error( " No support for IPv6 on this plattform " )
else : # IPv4
try :
host = socket.gethostbyname(node)
except :
_error( " cannot resolve %s: Unknow host " % node)
# trying to ping a network?
# TODO: if a ip is end with ".0", so it must be a network?
# and why we cannot ping a network?
# ICMP cannot broadcast?
if not ipv6:
if int(string.split(host, " . " )[ - 1 ]) == 0:
_error( " no support for network ping " )
# do some sanity check
if number == 0:
_error( " invalid count of packets to transmit: '%s' " % str(a))
if alive: # TODO: what does alive mean?
number = 1
# Send the ping(s)
start = 1 ; mint = 999 ; maxt = 0.0 ; avg = 0.0
lost = 0; tsum = 0.0 ; tsumsq = 0.0
# tell the user what we do
if not alive:
if ipv6:
# do not print the ipv6 twice if ip adress given as node
# (it can be to long in term window)
if noPrintIPv6adr == 1 :
# add 40 (header) + 8 (icmp header) + payload
print " PING %s : %d data bytes (40+8+%d) " % (str(node), \
40 + 8 + size, size)
else :
# add 40 (header) + 8 (icmp header) + payload
print " PING %s (%s): %d data bytes (40+8+%d) " % (str(node), \
str(host), 40 + 8 + size, size)
else :
# add 20 (header) + 8 (icmp header) + payload
print " PING %s (%s): %d data bytes (20+8+%d) " % (str(node), str(host), \
20 + 8 + size, size)
# trap ctrl-d and ctrl-c
try :
# send the number of ping packets as given
while start <= number:
lost += 1 # in case user hit ctrl-c
# TODO: what does this mean?
# create the IPv6/IPv4 socket
if ipv6:
# can not create a raw socket if not root or setuid to root
try :
pingSocket = socket.socket(socket.AF_INET6, socket.SOCK_RAW, \
socket.getprotobyname( " ipv6-icmp " ))
except socket.error, e:
print " socket error: %s " % e
_error( " You must be root (uses raw sockets) " % os.path.basename(sys.argv[0]))
# IPv4
else :
# can not create a raw socket if not root or setuid to root
try :
pingSocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, \
socket.getprotobyname( " icmp " ))
except socket.error, e:
print " socket error: %s " % e
_error( " You must be root (%s uses raw sockets) " % os.path.basename(sys.argv[0]))
packet = _construct(start, size, ipv6) # make a ping packet
# send the ping
try :
pingSocket.sendto(packet,(node, 1 ))
except socket.error, e:
_error( " socket error: %s " % e)
# reset values
pong = "" ; iwtd = []
# wait until there is data in the socket
while 1 :
# input, output, exceptional conditions
iwtd, owtd, ewtd = select.select([pingSocket], [], [], timeout)
break # no data and timout occurred
# data on socket - this means we have an answer
if iwtd: # ok, data on socket
endtime = time.time() # time packet received
# read data (we only need the header)
pong, address = pingSocket.recvfrom(size + 48 )
lost -= 1 # in case user hit ctrl-c
# examine packet
# fetch TTL from IP header
if ipv6:
# since IPv6 header and any extension header are never passed
# to a raw socket, we can *not* get hoplimit field..
# I hoped that a socket option would help, but it's not
# supported:
# pingSocket.setsockopt(IPPROTO_IPV6, IPV6_RECVHOPLIMIT, 1)
# so we can't fetch hoplimit..
# fetch hoplimit
# rawPongHop = struct.unpack("c", pong[7])[0]
# fetch pong header
pongHeader = pong[0: 8 ]
pongType, pongCode, pongChksum, pongID, pongSeqnr = \
struct.unpack( " bbHHh " , pongHeader)
# fetch starttime from pong
starttime = struct.unpack( " d " , pong[ 8 : 16 ])[0]
# IPv4
else :
# time to live
rawPongHop = struct.unpack( " s " , pong[ 8 ])[0]
# convert TTL from 8 bit to 16 bit integer
pongHop = int(binascii.hexlify(str(rawPongHop)), 16 )
# fetch pong header
pongHeader = pong[ 20 : 28 ]
pongType, pongCode, pongChksum, pongID, pongSeqnr = \
struct.unpack( " bbHHh " , pongHeader)
# fetch starttime from pong
starttime = struct.unpack( " d " , pong[ 28 : 36 ])[0]
# valid ping packet received?
if not pongSeqnr == start:
pong = None
# NO data on socket - timeout waiting for answer
if not pong:
if alive:
print " no reply from %s (%s) " % (str(node), str(host))
else :
print " ping timeout: %s (icmp_seq=%d) " % (host, start)
# do not wait if just sending one packet
if number != 1 and start < number:
time.sleep(flood ^ 1 )
start += 1
continue # lost a packet - try again
triptime = endtime - starttime # compute RRT
tsum += triptime # triptime for all packets (stddev)
tsumsq += triptime * triptime # triptime^2 for all packets (stddev)
# compute statistic
maxt = max ((triptime, maxt))
mint = min ((triptime, mint))
if alive:
print str(node) + " ( " + str(host) + " ) is alive "
else :
if ipv6:
# size + 8 = payload + header
print " %d bytes from %s: icmp_seq=%d time=%.5f ms " % \
(size + 8 , host, pongSeqnr, triptime * 1000 )
else :
print " %d bytes from %s: icmp_seq=%d ttl=%s time=%.5f ms " % \
(size + 8 , host, pongSeqnr, pongHop, triptime * 1000 )
# do not wait if just sending one packet
if number != 1 and start < number:
# if flood = 1; do not sleep - just ping
time.sleep(flood ^ 1 ) # wait before send new packet
# the last thing to do is update the counter - else the value
# (can) get wrong when computing summary at the end (if user
# hit ctrl-c when pinging)
start += 1
# end ping send/recv while
# if user ctrl-d or ctrl-c
except (EOFError, KeyboardInterrupt):
# if user disrupts ping, it is most likly done before
# the counter get updates - if do not update it here, the
# summary get all wrong.
start += 1
pass
# compute and print som stats
# stddev computation based on ping.c from FreeBSD
if start != 0 or lost > 0: # do not print stats if 0 packet sent
start -= 1 # since while is '<='
avg = tsum / start # avg round trip
vari = tsumsq / start - avg * avg
# %-packet lost
if start == lost:
plost = 100
else :
plost = (lost / start) * 100
if not alive:
print " \n--- %s ping statistics --- " % node
print " %d packets transmitted, %d packets received, %d%% packet loss " % \
(start, start - lost, plost)
# don't display summary if 100% packet-loss
if plost != 100 :
print " round-trip min/avg/max/stddev = %.3f/%.3f/%.3f/%.3f ms " % \
(mint * 1000 , (tsum / start) * 1000 , maxt * 1000 , math.sqrt(vari) * 1000 )
pingSocket.close()
def _error(err):
""" Exit if running standalone, else raise an exception
"""
if __name__ == ' __main__ ' :
print " %s: %s " % (os.path.basename(sys.argv[0]), str(err))
print " Try `%s --help' for more information. " % os.path.basename(sys.argv[0])
sys.exit( 1 )
else :
raise Exception, str(err)
def _usage():
""" Print usage if run as a standalone program
"""
print """ usage: %s [OPTIONS] HOST
Send ICMP ECHO_REQUEST packets to network hosts.
Mandatory arguments to long options are mandatory for short options too.
-c, --count=N Stop after sending (and receiving) 'N' ECHO_RESPONSE
packets.
-s, --size=S Specify the number of data bytes to be sent. The default
is 56, which translates into 64 ICMP data bytes when
combined with the 8 bytes of ICMP header data.
-f, --flood Flood ping. Outputs packets as fast as they come back. Use
with caution!
-6, --ipv6 Ping using IPv6.
-t, --timeout=s Specify a timeout, in seconds, before a ping packet is
considered 'lost'.
-h, --help Display this help and exit
Report bugs to lars [at] gnist org """ % os.path.basename(sys.argv[0])
if __name__ == ' __main__ ' :
""" Main loop
"""
# version control
version = string.split(string.split(sys.version)[0][: 3 ], " . " )
if map(int, version) < [ 2 , 3 ]:
_error( " You need Python ver 2.3 or higher to run! " )
try :
# opts = arguments recognized,
# args = arguments NOT recognized (leftovers)
opts, args = getopt.getopt(sys.argv[ 1 : - 1 ], " hat:6c:fs: " , \
[ " help " , " alive " , " timeout= " , " ipv6 " , \
" count= " , " flood " , " packetsize= " ])
except getopt.GetoptError:
# print help information and exit:
_error( " illegal option(s) -- " + str(sys.argv[ 1 :]))
# test whether any host given
if len(sys.argv) >= 2 :
node = sys.argv[ - 1 :][0] # host to be pinged
if node[0] == ' - ' or node == ' -h ' or node == ' --help ' :
_usage()
else :
_error( " No arguments given " )
if args:
_error( " illegal option -- %s " % str(args))
# default variables
alive = 0; timeout = 1.0 ; ipv6 = 0; count = sys.maxint;
flood = 0; size = ICMP_DATA_STR
# run through arguments and set variables
for o, a in opts:
if o == " -h " or o == " --help " : # display help and exit
_usage()
sys.exit(0)
if o == " -t " or o == " --timeout " : # timeout before "lost"
try :
timeout = float(a)
except :
_error( " invalid timout: '%s' " % str(a))
if o == " -6 " or o == " --ipv6 " : # ping ipv6
ipv6 = 1
if o == " -c " or o == " --count " : # how many pings?
try :
count = int(a)
except :
_error( " invalid count of packets to transmit: '%s' " % str(a))
if o == " -f " or o == " --flood " : # no delay between ping send
flood = 1
if o == " -s " or o == " --packetsize " : # set the ping payload size
try :
size = int(a)
except :
_error( " invalid packet size: '%s' " % str(a))
# just send one packet and say "it's alive"
if o == " -a " or o == " --alive " :
alive = 1
# here we send
pingNode(alive = alive, timeout = timeout, ipv6 = ipv6, number = count, \
node = node, flood = flood, size = size)
# if we made it this far, do a clean exit
sys.exit(0)
# ## end
# -*- coding: iso-8859-1 -*-
""" ping.py
ping.py uses the ICMP protocol's mandatory ECHO_REQUEST
datagram to elicit an ICMP ECHO_RESPONSE from a
host or gateway.
Copyright (C) 2004 - Lars Strand <lars strand at gnist org>;
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Must be running as root, or write a suid-wrapper. Since newer *nix
variants, the kernel ignores the set[ug]id flags on #! scripts for
security reasons
RFC792, echo/reply message:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data
+-+-+-+-+-
TODO:
- do not create socket inside 'while' (but if not: ipv6 won't work)
- add support for broadcast/multicast
- add support for own payload string
CHANGELOG:
DONE -->; bugfix from Filip Van Raemdonck mechanix debian org
DONE -->; add more support for modules (raise instead of sys.exit)
DONE -->; locale func names
DONE -->; package def
DONE -->; some code cleanup
"""
import sys
import os
import struct
import array
import time
import select
import binascii
import math
import getopt
import string
import socket
# total size of data (payload)
ICMP_DATA_STR = 56
# initial values of header variables
ICMP_TYPE = 8
ICMP_TYPE_IP6 = 128
ICMP_CODE = 0
ICMP_CHECKSUM = 0
ICMP_ID = 0
ICMP_SEQ_NR = 0
# Package definitions.
__program__ = ' ping '
__version__ = ' 0.5a '
__date__ = ' 2004/15/12 '
__author__ = ' Lars Strand <lars at unik no>; '
__licence__ = ' GPL '
__copyright__ = ' Copyright (C) 2004 Lars Strand '
def _construct(id, size, ipv6):
""" Constructs a ICMP echo packet of variable size
"""
# size must be big enough to contain time sent
if size < int(struct.calcsize( " d " )):
_error( " packetsize to small, must be at least %d " % int(struct.calcsize( " d " )))
# construct header
if ipv6:
header = struct.pack( ' BbHHh ' , ICMP_TYPE_IP6, ICMP_CODE, ICMP_CHECKSUM, \
ICMP_ID, ICMP_SEQ_NR + id)
else :
header = struct.pack( ' bbHHh ' , ICMP_TYPE, ICMP_CODE, ICMP_CHECKSUM, \
ICMP_ID, ICMP_SEQ_NR + id)
# if size big enough, embed this payload
load = " -- IF YOU ARE READING THIS YOU ARE A NERD! -- "
# space for time
size -= struct.calcsize( " d " )
# construct payload based on size, may be omitted :)
rest = ""
if size > len(load):
rest = load
size -= len(load)
# pad the rest of payload
rest += size * " X "
# pack
data = struct.pack( " d " , time.time()) + rest
packet = header + data # ping packet without checksum
checksum = _in_cksum(packet) # make checksum
# construct header with correct checksum
if ipv6:
header = struct.pack( ' BbHHh ' , ICMP_TYPE_IP6, ICMP_CODE, checksum, \
ICMP_ID, ICMP_SEQ_NR + id)
else :
header = struct.pack( ' bbHHh ' , ICMP_TYPE, ICMP_CODE, checksum, ICMP_ID, \
ICMP_SEQ_NR + id)
# ping packet *with* checksum
packet = header + data
# a perfectly formatted ICMP echo packet
return packet
def _in_cksum(packet):
""" THE RFC792 states: 'The 16 bit one's complement of
the one's complement sum of all 16 bit words in the header.'
Generates a checksum of a (ICMP) packet. Based on in_chksum found
in ping.c on FreeBSD.
"""
print " packet = " , packet
# add byte if not dividable by 2
if len(packet) & 1 :
packet = packet + ' \0 '
print " packet = [after add byte] " , packet
# split into 16-bit word and insert into a binary array
words = array.array( ' h ' , packet) # there's a big/little-endian issue
# when analysis with wireshark
sum = 0
print " words in packet array: " , words
# perform ones complement arithmetic on 16-bit words
for word in words:
print " process word: " , hex(word)
sum += (word & 0xffff )
print " sum: " , hex(sum)
hi = sum >> 16
print " hi: " , hex(hi)
lo = sum & 0xffff
print " lo: " , hex(lo)
sum = hi + lo # one's complement
print " sum(=hi+lo): " , hex(sum)
sum = sum + (sum >> 16 ) # TODO: another one's complement, why we need this?
# sum is at most 16 bit, so here do nothing.???
print " sum(=sum+(sum>>16)): " , hex(sum)
print ( ~ sum) & 0xffff # TODO: the implement of one number is just ~ ?
return ( ~ sum) & 0xffff # return ones complement
# We know whether the host we will ping is alive before we call this function?
def pingNode(alive = 0, timeout = 1.0 , ipv6 = 0, number = sys.maxint, node = None, \
flood = 0, size = ICMP_DATA_STR):
""" Pings a node based on input given to the function.
"""
# if no node, exit
if not node:
_error( "" )
# if not a valid host, exit
if ipv6:
if socket.has_ipv6:
try :
info, port = socket.getaddrinfo(node, None)
host = info[ 4 ][0]
# do not print ipv6 twice if ipv6 address given as node
if host == node:
noPrintIPv6adr = 1
except :
_error( " cannot resolve %s: Unknow host " % node)
else :
_error( " No support for IPv6 on this plattform " )
else : # IPv4
try :
host = socket.gethostbyname(node)
except :
_error( " cannot resolve %s: Unknow host " % node)
# trying to ping a network?
# TODO: if a ip is end with ".0", so it must be a network?
# and why we cannot ping a network?
# ICMP cannot broadcast?
if not ipv6:
if int(string.split(host, " . " )[ - 1 ]) == 0:
_error( " no support for network ping " )
# do some sanity check
if number == 0:
_error( " invalid count of packets to transmit: '%s' " % str(a))
if alive: # TODO: what does alive mean?
number = 1
# Send the ping(s)
start = 1 ; mint = 999 ; maxt = 0.0 ; avg = 0.0
lost = 0; tsum = 0.0 ; tsumsq = 0.0
# tell the user what we do
if not alive:
if ipv6:
# do not print the ipv6 twice if ip adress given as node
# (it can be to long in term window)
if noPrintIPv6adr == 1 :
# add 40 (header) + 8 (icmp header) + payload
print " PING %s : %d data bytes (40+8+%d) " % (str(node), \
40 + 8 + size, size)
else :
# add 40 (header) + 8 (icmp header) + payload
print " PING %s (%s): %d data bytes (40+8+%d) " % (str(node), \
str(host), 40 + 8 + size, size)
else :
# add 20 (header) + 8 (icmp header) + payload
print " PING %s (%s): %d data bytes (20+8+%d) " % (str(node), str(host), \
20 + 8 + size, size)
# trap ctrl-d and ctrl-c
try :
# send the number of ping packets as given
while start <= number:
lost += 1 # in case user hit ctrl-c
# TODO: what does this mean?
# create the IPv6/IPv4 socket
if ipv6:
# can not create a raw socket if not root or setuid to root
try :
pingSocket = socket.socket(socket.AF_INET6, socket.SOCK_RAW, \
socket.getprotobyname( " ipv6-icmp " ))
except socket.error, e:
print " socket error: %s " % e
_error( " You must be root (uses raw sockets) " % os.path.basename(sys.argv[0]))
# IPv4
else :
# can not create a raw socket if not root or setuid to root
try :
pingSocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, \
socket.getprotobyname( " icmp " ))
except socket.error, e:
print " socket error: %s " % e
_error( " You must be root (%s uses raw sockets) " % os.path.basename(sys.argv[0]))
packet = _construct(start, size, ipv6) # make a ping packet
# send the ping
try :
pingSocket.sendto(packet,(node, 1 ))
except socket.error, e:
_error( " socket error: %s " % e)
# reset values
pong = "" ; iwtd = []
# wait until there is data in the socket
while 1 :
# input, output, exceptional conditions
iwtd, owtd, ewtd = select.select([pingSocket], [], [], timeout)
break # no data and timout occurred
# data on socket - this means we have an answer
if iwtd: # ok, data on socket
endtime = time.time() # time packet received
# read data (we only need the header)
pong, address = pingSocket.recvfrom(size + 48 )
lost -= 1 # in case user hit ctrl-c
# examine packet
# fetch TTL from IP header
if ipv6:
# since IPv6 header and any extension header are never passed
# to a raw socket, we can *not* get hoplimit field..
# I hoped that a socket option would help, but it's not
# supported:
# pingSocket.setsockopt(IPPROTO_IPV6, IPV6_RECVHOPLIMIT, 1)
# so we can't fetch hoplimit..
# fetch hoplimit
# rawPongHop = struct.unpack("c", pong[7])[0]
# fetch pong header
pongHeader = pong[0: 8 ]
pongType, pongCode, pongChksum, pongID, pongSeqnr = \
struct.unpack( " bbHHh " , pongHeader)
# fetch starttime from pong
starttime = struct.unpack( " d " , pong[ 8 : 16 ])[0]
# IPv4
else :
# time to live
rawPongHop = struct.unpack( " s " , pong[ 8 ])[0]
# convert TTL from 8 bit to 16 bit integer
pongHop = int(binascii.hexlify(str(rawPongHop)), 16 )
# fetch pong header
pongHeader = pong[ 20 : 28 ]
pongType, pongCode, pongChksum, pongID, pongSeqnr = \
struct.unpack( " bbHHh " , pongHeader)
# fetch starttime from pong
starttime = struct.unpack( " d " , pong[ 28 : 36 ])[0]
# valid ping packet received?
if not pongSeqnr == start:
pong = None
# NO data on socket - timeout waiting for answer
if not pong:
if alive:
print " no reply from %s (%s) " % (str(node), str(host))
else :
print " ping timeout: %s (icmp_seq=%d) " % (host, start)
# do not wait if just sending one packet
if number != 1 and start < number:
time.sleep(flood ^ 1 )
start += 1
continue # lost a packet - try again
triptime = endtime - starttime # compute RRT
tsum += triptime # triptime for all packets (stddev)
tsumsq += triptime * triptime # triptime^2 for all packets (stddev)
# compute statistic
maxt = max ((triptime, maxt))
mint = min ((triptime, mint))
if alive:
print str(node) + " ( " + str(host) + " ) is alive "
else :
if ipv6:
# size + 8 = payload + header
print " %d bytes from %s: icmp_seq=%d time=%.5f ms " % \
(size + 8 , host, pongSeqnr, triptime * 1000 )
else :
print " %d bytes from %s: icmp_seq=%d ttl=%s time=%.5f ms " % \
(size + 8 , host, pongSeqnr, pongHop, triptime * 1000 )
# do not wait if just sending one packet
if number != 1 and start < number:
# if flood = 1; do not sleep - just ping
time.sleep(flood ^ 1 ) # wait before send new packet
# the last thing to do is update the counter - else the value
# (can) get wrong when computing summary at the end (if user
# hit ctrl-c when pinging)
start += 1
# end ping send/recv while
# if user ctrl-d or ctrl-c
except (EOFError, KeyboardInterrupt):
# if user disrupts ping, it is most likly done before
# the counter get updates - if do not update it here, the
# summary get all wrong.
start += 1
pass
# compute and print som stats
# stddev computation based on ping.c from FreeBSD
if start != 0 or lost > 0: # do not print stats if 0 packet sent
start -= 1 # since while is '<='
avg = tsum / start # avg round trip
vari = tsumsq / start - avg * avg
# %-packet lost
if start == lost:
plost = 100
else :
plost = (lost / start) * 100
if not alive:
print " \n--- %s ping statistics --- " % node
print " %d packets transmitted, %d packets received, %d%% packet loss " % \
(start, start - lost, plost)
# don't display summary if 100% packet-loss
if plost != 100 :
print " round-trip min/avg/max/stddev = %.3f/%.3f/%.3f/%.3f ms " % \
(mint * 1000 , (tsum / start) * 1000 , maxt * 1000 , math.sqrt(vari) * 1000 )
pingSocket.close()
def _error(err):
""" Exit if running standalone, else raise an exception
"""
if __name__ == ' __main__ ' :
print " %s: %s " % (os.path.basename(sys.argv[0]), str(err))
print " Try `%s --help' for more information. " % os.path.basename(sys.argv[0])
sys.exit( 1 )
else :
raise Exception, str(err)
def _usage():
""" Print usage if run as a standalone program
"""
print """ usage: %s [OPTIONS] HOST
Send ICMP ECHO_REQUEST packets to network hosts.
Mandatory arguments to long options are mandatory for short options too.
-c, --count=N Stop after sending (and receiving) 'N' ECHO_RESPONSE
packets.
-s, --size=S Specify the number of data bytes to be sent. The default
is 56, which translates into 64 ICMP data bytes when
combined with the 8 bytes of ICMP header data.
-f, --flood Flood ping. Outputs packets as fast as they come back. Use
with caution!
-6, --ipv6 Ping using IPv6.
-t, --timeout=s Specify a timeout, in seconds, before a ping packet is
considered 'lost'.
-h, --help Display this help and exit
Report bugs to lars [at] gnist org """ % os.path.basename(sys.argv[0])
if __name__ == ' __main__ ' :
""" Main loop
"""
# version control
version = string.split(string.split(sys.version)[0][: 3 ], " . " )
if map(int, version) < [ 2 , 3 ]:
_error( " You need Python ver 2.3 or higher to run! " )
try :
# opts = arguments recognized,
# args = arguments NOT recognized (leftovers)
opts, args = getopt.getopt(sys.argv[ 1 : - 1 ], " hat:6c:fs: " , \
[ " help " , " alive " , " timeout= " , " ipv6 " , \
" count= " , " flood " , " packetsize= " ])
except getopt.GetoptError:
# print help information and exit:
_error( " illegal option(s) -- " + str(sys.argv[ 1 :]))
# test whether any host given
if len(sys.argv) >= 2 :
node = sys.argv[ - 1 :][0] # host to be pinged
if node[0] == ' - ' or node == ' -h ' or node == ' --help ' :
_usage()
else :
_error( " No arguments given " )
if args:
_error( " illegal option -- %s " % str(args))
# default variables
alive = 0; timeout = 1.0 ; ipv6 = 0; count = sys.maxint;
flood = 0; size = ICMP_DATA_STR
# run through arguments and set variables
for o, a in opts:
if o == " -h " or o == " --help " : # display help and exit
_usage()
sys.exit(0)
if o == " -t " or o == " --timeout " : # timeout before "lost"
try :
timeout = float(a)
except :
_error( " invalid timout: '%s' " % str(a))
if o == " -6 " or o == " --ipv6 " : # ping ipv6
ipv6 = 1
if o == " -c " or o == " --count " : # how many pings?
try :
count = int(a)
except :
_error( " invalid count of packets to transmit: '%s' " % str(a))
if o == " -f " or o == " --flood " : # no delay between ping send
flood = 1
if o == " -s " or o == " --packetsize " : # set the ping payload size
try :
size = int(a)
except :
_error( " invalid packet size: '%s' " % str(a))
# just send one packet and say "it's alive"
if o == " -a " or o == " --alive " :
alive = 1
# here we send
pingNode(alive = alive, timeout = timeout, ipv6 = ipv6, number = count, \
node = node, flood = flood, size = size)
# if we made it this far, do a clean exit
sys.exit(0)
# ## end