Jervase

Ping

Apr 1st, 2012
1,828
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import os,sys,socket,struct,select,time,binascii,logging
  2. import ping_reporter
  3.  
  4. ping_count = 0
  5. ping_bandwidth = 0
  6. log = ping_reporter.setup_log('Ping')
  7. server_list = ['www.google.com','wikipedia.org','www.whitehouse.gov','127.0.0.1']
  8.  
  9. def select_server(log,max_timeout=2):
  10.     server = ''
  11.     log.notice('selecting server')
  12.     maxw = len(max(server_list, key=len))
  13.     min_delay = max_timeout * 1000 # seconds -> ms
  14.     for x in server_list:
  15.         delay = min_delay + 1
  16.         try: delay = single_ping(x,max_timeout)
  17.         finally:
  18.             if delay == None: log.notice('%-*s: timed out'%(maxw,x))
  19.             else:             log.notice('%-*s: %05.02fms'%(maxw,x,delay*1000))
  20.             if delay != None and delay < min_delay:
  21.                 min_delay = delay
  22.                 server = x
  23.     log.info('selected server: %s (%.02fms)'%(server,min_delay*1000))
  24.     return server
  25.  
  26. def carry_add(a, b):
  27.     c = a + b
  28.     return (c & 0xFFFF) + (c >> 16)
  29.  
  30. def checksum(msg):
  31.     s = 0
  32.     if len(msg)%2: # pad with NULL
  33.         msg = msg + '%c'%0
  34.     for i in range(0, len(msg)/2*2, 2):
  35.         w = ord(msg[i]) + (ord(msg[i+1]) << 8)
  36.         s = carry_add(s, w)
  37.     return ~s & 0xFFFF
  38.  
  39. def build_ping(ID, data):
  40.     log.trace('ping::build_ping: ID=%d, bytes=%d'%(ID,len(data)))
  41.     if ID == 0: raise Exception('Invalid BlockID (0): many servers will corrupt ID=0 ICMP messages')
  42.  
  43.     data = str(data) # string type, like the packed result
  44.  
  45.     # Header is type (8), code (8), checksum (16), id (16), sequence (16)
  46.     icmp_type      = 8 # ICMP_ECHO_REQUEST
  47.     icmp_code      = 0 # Can be anything, but reply MUST be 0
  48.     icmp_checksum  = 0 # 0 for initial checksum calculation
  49. #   icmp_id        = (ID >> 16) & 0xFFFF
  50. #   icmp_sequence  = (ID <<  0) & 0xFFFF
  51.     block_id       = ID # append id & seq for 4-byte identifier
  52.  
  53.     header = struct.pack("bbHI", icmp_type, icmp_code, icmp_checksum, block_id)
  54.     icmp_checksum = checksum(header+data)
  55.     header = struct.pack("bbHI", icmp_type, icmp_code, icmp_checksum, block_id)
  56.  
  57.     # Return built ICMP message
  58.     return header+data
  59.  
  60. def build_socket(RCVBUF=1024*1024):
  61. # By default, SO_RCVBUF is ~50k (kernel doubles to 114688), which only supports
  62. # ~1k blocks with <1ms timing. Raising this to 1m supports >16k blocks. Unfortunately,
  63. # raising it more does little because we can't read/process the events fast enough, so
  64. # the buffer pretty quickly fills, and then start dropping packets again.
  65.     log.trace('ping::build_socket')
  66.     icmp = socket.getprotobyname("icmp")
  67.     try:
  68.         icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
  69.     except socket.error, (errno, msg):
  70.         if errno == 1: # Operation not permitted
  71.             msg = msg + (" (ICMP messages can only be sent from processes running as root)")
  72.             raise socket.error(msg)
  73.         raise # raise the original error
  74.     socket.SO_SNDBUFFORCE = 32
  75.     socket.SO_RCVBUFFORCE = 33
  76.     icmp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUFFORCE, RCVBUF)
  77.     icmp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUFFORCE, RCVBUF)
  78.     return icmp_socket
  79.  
  80. def time_ping(d_socket, d_addr, ID=1):
  81.     log.trace('ping::time_ping: server=%s ID=%d'%(d_addr,ID))
  82.     data = struct.pack("d",time.time())
  83.     return data_ping(d_socket, d_addr, ID, data)
  84.  
  85. def data_ping(d_socket, d_addr, ID, data):
  86.     log.trace('ping::data_ping: server=%s ID=%d bytes=%d'%(d_addr,ID,len(data)))
  87.     send_ping(d_socket, socket.gethostbyname(d_addr), ID, data)
  88.  
  89. def send_ping(d_socket, d_addr, ID, data):
  90.     log.trace('ping::send_ping: server=%s ID=%d bytes=%d'%(d_addr,ID,len(data)))
  91.     global ping_count, ping_bandwidth
  92.     packet = build_ping(ID,data)
  93.     d_socket.sendto(packet, (d_addr, 1))
  94.     if 1:
  95.         ping_count = ping_count + 1
  96.         ping_bandwidth = ping_bandwidth + len(packet)
  97.  
  98. def parse_ip(packet):
  99.     log.trace('ping::parse_ip: bytes=%d'%(len(packet)))
  100.     if len(packet) < 20: return None
  101.     (verlen,ID,flags,frag,ttl,protocol,csum,src,dst) = struct.unpack('!B3xH4BHII',packet[:20])
  102.     ip = dict(  version= verlen >> 4,
  103.                 length=  4*(verlen & 0xF),
  104.                 ID=      ID,
  105.                 flags=   flags >> 5,
  106.                 fragment=((flags & 0x1F)+frag),
  107.                 ttl=     ttl,
  108.                 protocol=protocol,
  109.                 checksum=csum,
  110.                 src=     src,
  111.                 dst=     dst)
  112.     return ip
  113.  
  114. def parse_icmp(packet,validate):
  115.     log.trace('ping::parse_icmp: bytes=%d'%(len(packet)))
  116.     if len(packet) < 8: return None
  117.     (type, code, csum, block_id) = struct.unpack('bbHI', packet[:8])
  118.     log.debug('ping::parse_icmp: type=%d code=%d csum=%x ID=%d'%(type,code,csum,block_id))
  119.     icmp = dict(type=type,
  120.                 code=code,
  121.                 checksum=csum, # calculated big-endian
  122.                 block_id=block_id)
  123.  
  124.     if validate:
  125.         t_header = struct.pack('bbHI',type,code,0,block_id)
  126.         t_csum = checksum(t_header+packet[8:])
  127.         icmp['valid'] = (t_csum == csum)
  128.        
  129.     return icmp
  130.  
  131. def parse_ping(packet,validate=False):
  132.     log.trace('ping::parse_ping: bytes=%d validate=%s'%(len(packet),validate))
  133.     if len(packet) < 20+8+1: return None # require 1 block of data
  134.     ip = parse_ip(packet)
  135.     if not ip:                                return None
  136.     if ip['protocol'] != socket.IPPROTO_ICMP: return None # ICMP
  137.     if ip['version'] != socket.IPPROTO_IPIP:  return None # IPv4
  138.     if ip['length']+8+1 > len(packet):        return None # invalid ICMP header
  139.  
  140.     packet = packet[ip['length']:]
  141.     icmp = parse_icmp(packet,validate)
  142.     if not icmp:                              return None
  143.     if icmp['type'] != 0:                     return None # not an Echo Reply packet
  144.     if icmp['code'] != 0:                     return None # not a valid Echo Reply packet
  145.     if validate and icmp['valid'] != True:    return None # invalid ICMP checksum
  146.  
  147.     payload = packet[8:]
  148.     log.debug('ping::parse_ping: valid echo reply w/ ID=%d (%d bytes)'%(icmp['block_id'],len(payload)))
  149.     return dict(ip=ip,icmp=icmp,payload=payload)
  150.    
  151.  
  152. def recv_ping(d_socket, timeout, validate=False):
  153.     d_socket.settimeout(timeout)
  154.     try:
  155.         data,addr = d_socket.recvfrom(2048)
  156.     except socket.timeout:
  157.         return None
  158.     parsed = parse_ping(data,validate)
  159.     if not parsed: return None
  160.     parsed['ID']=parsed['icmp']['block_id']
  161.     parsed['address']=addr
  162.     parsed['raw']=data
  163.     log.debug('ping::recv_ping: ID=%d address=%s bytes=%d'%(parsed['ID'],addr,len(data)))
  164.     return parsed
  165.  
  166. def read_ping(d_socket, timeout):
  167.     start = time.time()
  168.     while time.time() - start < timeout:
  169.         msg = recv_ping(d_socket,timeout)
  170.         if msg: return msg
  171.     return None
  172.  
  173. def receive_ping(my_socket, ID, timeout):
  174.         timeLeft = timeout
  175.         while True:
  176.                 startedSelect = time.time()
  177.                 whatReady = select.select([my_socket], [], [], timeLeft)
  178.                 if whatReady[0] == []: # Timeout
  179.                         return
  180.        
  181.                 timeReceived = time.time()
  182.                 howLongInSelect = (timeReceived - startedSelect)
  183.                 recPacket, addr = my_socket.recvfrom(1024)
  184.                 icmpHeader = recPacket[20:28]
  185.                 type, code, checksum, packetID = struct.unpack("bbHI", icmpHeader)
  186.                 if packetID == ID:
  187.                         bytesInDouble = struct.calcsize("d")
  188.                         timeSent = struct.unpack("d", recPacket[28:28 + bytesInDouble])[0]
  189.                         return timeReceived - timeSent
  190.        
  191.                 timeLeft = timeLeft - howLongInSelect
  192.                 if timeLeft <= 0:
  193.                         return 0
  194.  
  195. def single_ping(dest_addr, timeout):
  196.     my_socket = build_socket()
  197.     my_ID = os.getpid() & 0xFFFF
  198.     time_ping(my_socket, dest_addr, my_ID)
  199.     delay = receive_ping(my_socket, my_ID, timeout)
  200.     my_socket.close()
  201.     return delay
  202.  
  203. def verbose_ping(dest_addr, timeout = 2, count = 4):
  204.     for i in xrange(count):
  205.         log.info("ping %s..." % dest_addr,)
  206.         try:
  207.             delay  =  single_ping(dest_addr, timeout)
  208.         except socket.gaierror, e:
  209.             log.error("failed. (socket error: '%s')" % e[1])
  210.             break
  211.  
  212.         if delay  ==  None:
  213.             log.info("failed. (timeout within %ssec.)" % timeout)
  214.         else:
  215.             delay  =  delay * 1000
  216.             log.info("get ping in %0.4fms" % delay)
  217.     print
  218.  
  219.  
  220. import os, pwd, grp
  221.  
  222. if __name__ == '__main__':
  223.     ping_reporter.start_log(log)
  224.     server = select_server(log,2)
  225.    
  226.     if 1:
  227.         verbose_ping(server)
  228.     else:
  229.         s = build_socket()
  230.         print 'sending 100 pings...'
  231.         for x in range(1,100):
  232.             data_ping(s,server,x,struct.pack('d',x))
  233.             print 'ping cycled...'
  234.             recv_ping(s,1)
  235.         print '100 pings sent'
RAW Paste Data