文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>ping written in python

ping written in python

时间:2005-09-10  来源:brightcrest

从研读ping.py开始学习python

#!/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

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 '
__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 + ''

   # 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 " --- %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

相关阅读 更多 +
排行榜 更多 +
辰域智控app

辰域智控app

系统工具 下载
网医联盟app

网医联盟app

运动健身 下载
汇丰汇选App

汇丰汇选App

金融理财 下载