小默

【转】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) < [23]:
        _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

posted on 2010-10-25 22:16 小默 阅读(582) 评论(0)  编辑 收藏 引用 所属分类: Network


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

统计

留言簿(13)

随笔分类(287)

随笔档案(289)

漏洞

搜索

积分与排名

最新评论

阅读排行榜