#!/usr/bin/python # -*- coding: utf_8 -*- u""" program : Pong V 3.01 file : pong.py author : Frédérick Lemasson < djassper [at] gmail [dot] com > licence : GPL v3.0 created : 2007-08-27 revised : 2007-11-26 purpose : Ping lots of IPs (V4) using a single command line and prints a report exemple : pong 172.16.1,2.1-5:172.20.1,2,3.1-25:192.168.1.1-10 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 3 of the License, or 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 : http://www.gnu.org/licenses http://www.gnu.org/licenses/gpl-3.0.html http://www.gnu.org/philosophy/ """ from __future__ import with_statement import os import time import datetime import sys from threading import Thread from optparse import OptionParser pong=sys.modules['__main__'] usage = u""" pong [options] IPCode IPCode is translated to IPs following the rules : Special characters you can use in octets (only one per octet) : "-" means range, Exemple : 1-5 means 1,2,3,4,5 "*" means range [0-255] "," means list, Exemple : 1,3,12 means 1,3,12 You can use ":" between IPCodes : IPCodes exemples : 172.16.1,2.1-5:172.20.1,2,3.1-25 192.168.1.* 10.1.1.100 Pong Copyright (C) 2007 Frédérick Lemasson - France This program comes with ABSOLUTELY NO WARRANTY. This is GPL free software, and you are welcome to redistribute it under certain conditions. If you Improve/Modify/Debug Pong, you ["must" AND "will be happy to"] send me an email. for details type "pong --licence". """ parser = OptionParser(usage) #Simple Options parser.add_option("-u", "--HideUp", dest="HideUp", action="store_true", default=False, help="Hide Hosts Up") parser.add_option("-d", "--HideDown", dest="HideDown", action="store_true", default=False, help="Hide Hosts Down") parser.add_option("-w", "--HideWeird", dest="HideWeird", action="store_true", default=False, help="Hide Hosts Weird") parser.add_option("-i", "--HideIP", dest="HideIP", action="store_true", default=False, help="Hide IPs from report") parser.add_option("-s", "--HideStatus", dest="HideStatus", action="store_true", default=False, help="Hide Status from report") parser.add_option("-m", "--HideMAC", dest="HideMAC", action="store_true", default=False, help="Hide MACs from report") parser.add_option("-t", "--HideTimes", dest="HideTimes", action="store_true", default=False, help="Hide Times from report") parser.add_option("-c", "--HideHeaders", dest="HideHeaders", action="store_true", default=False, help="Don't show the Columns Headers") parser.add_option("-r", "--HideReport", dest="HideReport", action="store_true", default=False, help="Don't show the report") parser.add_option("-b", "--Beep", dest="Beep", action="store_true", default=False, help="Beeps when a host is either Down or Weird") parser.add_option("-D", "--Date", dest="Date", action="store_true", default=False, help="Add Date Column to report") parser.add_option("-T", "--Time", dest="Time", action="store_true", default=False, help="Add Time Column to report") parser.add_option("--licence", dest="licence", action="store_true", default=False, help="Prints Pong Licence informations") #Parameter Options parser.add_option("-e", "--Exclude", dest="Exclude", metavar="IPCode", help="Exclude IPs from ping list") parser.add_option("-f", "--InputFile", dest="InputFile", help="Specify a file containing IPCodes to ping, one IPCode per line") parser.add_option("-R", "--Report", dest="Report", help="Build/Update a long term Report") parser.add_option("-L", "--Limit", type="int", dest="Limit", help="Sets the long term Report occurences limit") parser.add_option("-W", "--Wait", type="int", dest="Wait", default=1, help="Time to wait for a response, in seconds, default is 1s") (options, args) = parser.parse_args() if options.licence: print usage,pong.__doc__ sys.exit() if len(args) != 1 and not options.InputFile: parser.error("incorrect number of arguments") ### Functions ### class NDict(dict): """ Customized dict, don't raise KeyError exception, return None instead """ def __getitem__(self, key): if self.has_key(key): return dict.__getitem__(self, key) else: return "" class Pong(Thread): """ Sends pings in parallel using Thread module """ def __init__ (self,ip): Thread.__init__(self) self.ip = ip self.status = -1 self.times= "" def run(self): ping = os.popen("ping -q -c2 -W"+str(options.Wait)+" "+self.ip+" 2>&1","r") while 1: line = ping.readline() if not line: break if "," in line and self.status==-1: Result = int(line.split(",")[1].strip().split()[0]) if Result in [0,1,2]: self.status = Result if "=" in line and self.times=="": Result = line.split("=")[1].strip().split("/") self.times = str(Result[0]) + " " +str(Result[2]) def Beep(): print >> sys.stderr, "\a", def TimeFrom(StartTime): """ Returns Time elapsed between now and given time (returns in format : h m s ms)""" StopTime=time.time() Took=StopTime - StartTime Foo=time.gmtime(Took) return str(Foo[3])+"h "+str(Foo[4])+"m "+str(Foo[5])+"s "+str(Took).split(".")[1][:4]+"ms" def Range2List(TheRange): """ Turns octet range user input in python list""" TheRange=TheRange.split("-") return range(int(TheRange[0]),int(TheRange[1])+1) def List2List(TheList): """ Turns octet list user input in python list""" TheCleanList=[] for item in TheList.split(","): TheCleanList.append(int(item)) return TheCleanList def ClearFile(TheFile): with open(TheFile,"w") as f: f.write("") def ReadFile(TheFile): """ Returns complete text file""" with open(TheFile,"rb") as f: return f.read() def WriteFile(TheFile,Content): with open(TheFile,"w") as f: f.write(Content) def AppendFile(TheFile,Content): with open(TheFile,"a") as f: f.write(Content) def Read2Dic(TheFile): key=0 TheDic={} for line in file(TheFile): key += 1 TheDic[str(key)] = line return TheDic def Report2Dic(TheFile): TheDic=NDict() for line in file(TheFile): Data=line.split() TheDic[str(Data[0])] = str(" ".join(Data[1:])) return TheDic def Dic2Report(TheDic, TheReport): sortedkeys = TheDic.keys() sortedkeys.sort() #NatSort(sortedkeys) with open(TheReport,"a") as f: for k in sortedkeys: f.write(k+" "+TheDic[k]+"\n") def GetIPsMAC(): """ Populate global Dict MACs which contains IP as Key and MAC as Value""" global MACs arptable = ReadFile("/proc/net/arp") for line in arptable.split("\n"): if not line: break Item=line.split() MACs[Item[0]]=Item[3] def IPCode2List(TheIPCodes): """ Used in exclude mode to turn IPCodes into a IPs List""" IPCodes=TheIPCodes.split(":") TheList=[] for IPCode in IPCodes: if IPCode=="":continue Octets=IPCode.split(".") for Current in range(len(Octets)): if Octets[Current]=="*" : Octets[Current]="0-255" #Broadcast included but ping will zap it, could be handled though if Octets[Current].find("-")!=-1: #Octet Codes for a range Octets[Current]=Range2List(Octets[Current]) elif Octets[Current].find(",")!=-1: #Octet codes for a list Octets[Current]=List2List(Octets[Current]) else: #Octet codes for himself Octets[Current]=[int(Octets[Current])] for Octet0 in Octets[0]: for Octet1 in Octets[1]: for Octet2 in Octets[2]: for Octet3 in Octets[3]: TheList.append(str(Octet0)+"."+str(Octet1)+"."+str(Octet2)+"."+str(Octet3)) return TheList ### Variables ### StartTime=time.time() report = ("Down","Weird","Up") PingsUp=0 PingsDown=0 PingsWeird=0 MACs=NDict() Data=NDict() if options.Exclude: ExclusionList=IPCode2List(options.Exclude) else: ExclusionList=[] if options.InputFile: IPCodes=ReadFile(options.InputFile).replace("\n",":").split(":") else: #User input exemple : 172.16.50,52,60.1-25:172.20.150,154.1-20 IPCodes=args[0].split(":") if options.Report: Report=NDict() if os.path.exists(options.Report): Report=Report2Dic(options.Report) else: ClearFile(options.Report) ### Code ### if not options.HideHeaders: Headers="" Headers+="IP".ljust(16) if not options.HideIP else "" Headers+="MAC".ljust(19) if not options.HideMAC else "" Headers+="Status".ljust(8) if not options.HideStatus else "" Headers+="Times (ms)".ljust(15) if not options.HideTimes else "" Headers+="Date".ljust(11) if options.Date else "" Headers+="Time".ljust(7) if options.Time else "" print Headers for IPCode in IPCodes: if IPCode=="":continue Octets=IPCode.split(".") for Current in range(len(Octets)): if Octets[Current]=="*" : Octets[Current]="0-255" #Broadcast included but ping will zap it, could be handled though if Octets[Current].find("-")!=-1: #Octet Codes for a range Octets[Current]=Range2List(Octets[Current]) elif Octets[Current].find(",")!=-1: #Octet codes for a list Octets[Current]=List2List(Octets[Current]) else: #Octet codes for himself Octets[Current]=[int(Octets[Current])] for Octet0 in Octets[0]: for Octet1 in Octets[1]: for Octet2 in Octets[2]: pinglist = [] Data=NDict() #Build complete IPs and send Pings Threads for Octet3 in Octets[3]: IP = str(Octet0)+"."+str(Octet1)+"."+str(Octet2)+"."+str(Octet3) if IP in ExclusionList: continue current = Pong(IP) pinglist.append(current) current.start() #Join Pings Threads responses / define their status for pinged in pinglist: pinged.join() #Set Counters and what to print if pinged.status==0: PingsDown += 1 if options.Beep:Beep() if options.HideDown:continue elif pinged.status==1: PingsWeird += 1 if options.Beep:Beep() if options.HideWeird:continue elif pinged.status==2: PingsUp += 1 if options.HideUp:continue #Populate IP Datas on Status Data[pinged.ip]={} Data[pinged.ip]["Status"]=report[pinged.status] Data[pinged.ip]["Times"]=pinged.times Data[pinged.ip]["Date"],Data[pinged.ip]["Time"] = str(datetime.datetime.now()).split() #Pings Just returned, Populate MACs Dict with fresh arp table if not options.HideMAC:GetIPsMAC() IPs=Data.keys() #ReLoop in last Octet and print results for Octet3 in Octets[3]: IP = str(Octet0)+"."+str(Octet1)+"."+str(Octet2)+"."+str(Octet3) if IP in IPs: if not options.HideMAC:Data[IP]["Mac"]=MACs[IP] if MACs[IP]!="00:00:00:00:00:00" else "" Line="" Line+=IP.ljust(16) if not options.HideIP else "" Line+=Data[IP]["Mac"].ljust(19) if not options.HideMAC else "" Line+=Data[IP]["Status"].ljust(8) if not options.HideStatus else "" Line+=Data[IP]["Times"].ljust(15) if not options.HideTimes else "" Line+=Data[IP]["Date"].ljust(11) if options.Date else "" Line+=Data[IP]["Time"].ljust(7) if options.Time else "" print Line if options.Report: Infos=Line.split() Report[str(Infos[0])]+=" ".join(Infos[1:])+"/" #Report[str(Infos[0])]=Report[str(Infos[0])][0:-1] if options.Limit: #keeps the last options.Limits occurences occurences=Report[str(Infos[0])].split("/") if len(occurences)-1>options.Limit: Report[str(Infos[0])]="/".join(occurences[-options.Limit-1:]) if not options.HideReport: print "" print "Pong Report : "+str(PingsUp+PingsDown+PingsWeird).rjust(8) +" IPs Ponged in :",TimeFrom(StartTime) print " "+str(PingsUp).rjust(8)+" Hosts Up" print " "+str(PingsDown).rjust(8)+" Hosts Down" print " "+str(PingsWeird).rjust(8)+" Hosts Weird" print "" if options.Report: ClearFile(options.Report) Dic2Report(Report, options.Report)