#!/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.
"""
# add byte if not
dividable by 2
if len(packet)
& 1:
packet = packet + '\0'
# split into 16-bit word
and insert into a binary array
words = array.array('h',
packet)
sum = 0
# perform ones
complement arithmetic on 16-bit words
for word in words:
sum += (word &
0xffff)
hi = sum
>> 16
lo = sum
& 0xffff
sum = hi + lo
sum = sum + (sum
>> 16)
return (~sum)
& 0xffff # return ones complement
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?
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:
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
# 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
加载中,请稍候......