# -*- coding: utf-8 -*-# # Copyright 2015 Weber Yann # # This file is part of pyMapTraceroute. # # pyMapTraceroute 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 # (at your option) any later version. # # pyMapTraceroute 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 pyMapTraceroute. If not, see . # import os, re, GeoIP, time, pygame from subprocess import Popen, PIPE from pygame import gfxdraw import vtracemap import vtracert import geo6 class Tracer: ## Tracer instanciation # @param vtr The Vtracert def __init__(self, vtr): #Check vtr argument if not isinstance(vtr, vtracert.Vtracert): raise TypeError('Vtracert waited') self.vtr = vtr #Init object properties self.initTrace() self.gi = GeoIP.open("/usr/share/GeoIP/GeoIPCity.dat",GeoIP.GEOIP_STANDARD) cproc = os.popen('curl --silent zered.org/getip.php') self.src = cproc.readline().strip() self.prev = None self.prevLonLat = None self.selected = [] #selected hop self.regetip = re.compile('^.*\(([^\)]*)\).*$'); self.reghost = re.compile('^ *[0-9]* *([^\(]*)\(.*$'); self.regHop = re.compile('^[^0-9]*([0-9]*).*$'); pass ## Object trace initialisation def initTrace(self): self.ips = [] self.hosts = [] self.pts = [] self.hops = [] self.coords = [] self.selected = [] def progressStatus(self): self.vtr.drawStatus("Traceroute in progress : ...", self.vtr.font['status'],self.vtr.col['trace_progress']) pass ## Generator that yield ips from a running traceroute # @param The host to traceroute to # @yield Hop's IP def traceGen(self,dest_name): #Init tracer self.initTrace(); #Store source ip as first hop self.storeIp(self.src) self.hosts.append('localhost') self.hops.append('0') #Update status bar self.progressStatus() self.vtr.flip() #Run traceroute process p = Popen(['traceroute', dest_name], stdout=PIPE) #First line give us destination ip line = p.stdout.readline().decode('utf-8') ml = self.regetip.match(line) if ml != None: dest_ip = ml.group(1) if ':' in dest_ip: coord = geo6.ip62coord(dest_ip) else: coord = self.ip2coord(dest_ip) if coord is not None: dlon, dlat = coord (x,y) = self.vtr.trmap.latlon2ortho(dlon, dlat) pygame.gfxdraw.circle(self.vtr.surf['trace'],x,y,3,self.vtr.col['trace_end']) else: dest_ip = None self.vtr.flip() yield self.src #Reading traceroute output while True: line = p.stdout.readline().decode('utf-8') if not line: break print(line) #Getting hop number ml = self.regHop.match(line) if ml != None: self.hops.append(ml.group(1)) else: self.hops.append('?') #hop hostname ml = self.reghost.match(line) if ml != None: self.hosts.append(ml.group(1)) else: self.hosts.append('?') #hop ip ml = self.regetip.match(line) if ml != None: ip = ml.group(1) else: ip = None self.storeIp(ip) strStatus = "Traceroute in progress : " strStatus += self.hops[-1]+' '+self.hosts[-1] if ip != None: strStatus += '('+ip+')' self.vtr.drawStatus(strStatus, self.vtr.font['status'],self.vtr.col['trace_progress']) self.drawRoute() yield self.ips[-1] if dest_ip != None: self.storeIp(dest_ip) self.hops.append('Dest') self.hosts.append(dest_name) yield dest_ip ## Store an hop's ip in object properties # @param ip The ip adress to store def storeIp(self,ip): nopts = False self.ips.append(ip) if ip != None: lonlat = self.ip2coord(ip) if lonlat != None: #print gir['latitude'], gir['longitude'] self.pts.append(lonlat) else: nopts = True else: nopts = True if nopts: self.pts.append(None) pass ## Get geoip info for ip # @param ip to localisate def ip2coord(self,ip): gir = self.gi.record_by_addr(ip) if gir != None: return ( gir['longitude'], gir['latitude']) return None ## Populate data by running a traceroute # @param dest_name The host def trace(self,dest_name): #Traceroute generator tr = self.traceGen(dest_name) for ip in tr: if ip != None: pass #self.storeIp(ip) ## Run a traceroute and interactively update the screen # @param dest_name The host def liveTrace(self,dest_name): #Traceroute generator tr = self.traceGen(dest_name) self.initDraw() for ip in tr: if ip != None: #New hop pts = self.pts[-1] if pts != None: dcoord = self.drawNext(pts) self.coords.append(dcoord) else: self.coords.append(None) #Update the screen self.vtr.flip() pass ## Init a new trace drawing def initDraw(self): self.prev = None self.coords = [] self.vtr.surf['trace'].fill((0,0,0,0)) pass ## Draw the next hop in a route on the map # Hops are circle (filled when selected) and # the route is represented by line between circles # @param (lon,lat) Hop coordinates # @param selected A boolean # @return The coordinates of the hop on the screen as (x,y) def drawNext(self, pos, selected = False, last = False): lon,lat=pos if not selected : #If not selected draw a circle cdrawfun = pygame.gfxdraw.circle else: #If selected draw a filled circle cdrawfun = pygame.gfxdraw.filled_circle (x,y) = self.vtr.trmap.latlon2ortho(lon, lat) if self.prev == None: #Route begining cdrawfun(self.vtr.surf['trace'],x,y,3,self.vtr.col['trace_init']) else: #A new hop if last: cdrawfun(self.vtr.surf['trace'], x,y,3,self.vtr.col['trace_hop']) else: cdrawfun(self.vtr.surf['trace'], x,y,5,self.vtr.col['trace_hop']) (px,py) = self.prev #Trying to find a symetric closer from the new hop dw = self.vtr.trmap.scaledWidth spx1 = px - dw # x prev symetric 1 spx2 = px + dw # x prev symetric 2 (plon,_) = self.prevLonLat splon1 = plon - 360 # prev lon symetric 1 splon2 = plon + 360 # prev lon symetric 2 sd1 = abs(lon-splon1) sd2 = abs(lon-splon2) if sd1 < sd2: spx = spx1 sx = x + dw sd = sd1 else: spx = spx2 sx = x - dw sd = sd2 d = abs(lon-plon) #drawLine = Tracer.drawArcLine drawLine = pygame.gfxdraw.line if sd > d: drawLine(self.vtr.surf['trace'], px,py,x,y, self.vtr.col['trace_trace']) else: #symetric is closer drawLine(self.vtr.surf['trace'], spx,py,x,y, self.vtr.col['trace_trace']) drawLine(self.vtr.surf['trace'], px,py,sx,y, self.vtr.col['trace_trace']) self.prevLonLat = (lon,lat) self.prev = (x,y) return (x,y) ## Draw an existing route on the map # @param delay The time to wait between the draw of two hops (in float, seconds) def drawTrace(self, delay = 0.1): self.initDraw() cnt = 1 for i,pt in enumerate(self.pts): if pt != None: dcoord = self.drawNext(pt, (i in self.selected), (i == len(self.pts)-1) ) self.coords.append(dcoord) if delay > 0: self.drawRoute(cnt) self.vtr.flip() time.sleep(delay) else: self.coords.append(None) cnt += 1 self.drawRoute() pass ## Draw the text representation of an existing route # One line per hop at the bottom left of the screen # @param stopAt Print the last 'stopAt' hops def drawRoute(self, stopAt = 32): strStatus='' self.vtr.surf['tracetxt'].fill((0,0,0,0)) lh = self.vtr.font['trace_txt'].get_linesize() cy = self.vtr.surf['tracetxt'].get_height()-4 cy -= lh if stopAt < 0 or stopAt > len(self.ips): rng = range(len(self.ips)) else: rng = range(stopAt) rng=list(rng) rng.reverse() for i in rng: strStatus = self.hops[i]+' '+self.hosts[i] if self.ips[i] != None: strStatus += '('+self.ips[i]+')' if i in self.selected: col = self.vtr.col['trace_txtsel'] #selected color else: col = self.vtr.col['trace_txt'] #normal color statusSurf = self.vtr.font['trace_txt'].render(strStatus, 1, col) self.vtr.surf['tracetxt'].blit(statusSurf, (4,cy)) cy -= lh pass ## Search if there is a hop near a screen coordinate # Will return mutliple host if the distance is equal # @param (x,y) The on screen coordinates # @param maxDist The maximum distance bellow wich a hop is selected # @return A list of hop index def searchHopByCoord(self, coord, maxDist = 10): x,y=coord maxDist = maxDist **2 curRes = [] curMin = maxDist+1 for (i,pos) in enumerate(self.coords): if pos != None: (cx,cy) = pos d = ((cx-x)**2) + ((cy-y)**2) if d <= curMin: if d == curMin: curRes.append(i) else: curRes = [i] curMin = d return curRes ## Select hops given screen coordinate # @param pos On screen coordinate in a (x,y) tuple # @return A boolean set to True if hops were selected def select(self, pos): self.selected = self.searchHopByCoord(pos) return (len(self.selected)>0) ## Return a string representing selected hops ip # @return One ip on each line, preffixed by \t def selectedIp(self): res = "" for i in self.selected: res += '\t'+str(self.hosts[i])+'('+str(self.ips[i])+')\n' return res