Traceroute visualizer written in python
python
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tracer.py 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. # -*- coding: utf-8 -*-#
  2. # Copyright 2015 Weber Yann
  3. #
  4. # This file is part of pyMapTraceroute.
  5. #
  6. # pyMapTraceroute is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # pyMapTraceroute is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with pyMapTraceroute. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import os, re, GeoIP, time, pygame
  20. from subprocess import Popen, PIPE
  21. from pygame import gfxdraw
  22. import vtracemap
  23. import vtracert
  24. import geo6
  25. class Tracer:
  26. ## Tracer instanciation
  27. # @param vtr The Vtracert
  28. def __init__(self, vtr):
  29. #Check vtr argument
  30. if not isinstance(vtr, vtracert.Vtracert):
  31. raise TypeError('Vtracert waited')
  32. self.vtr = vtr
  33. #Init object properties
  34. self.initTrace()
  35. self.gi = GeoIP.open("/usr/share/GeoIP/GeoIPCity.dat",GeoIP.GEOIP_STANDARD)
  36. cproc = os.popen('curl --silent zered.org/getip.php')
  37. self.src = cproc.readline().strip()
  38. self.prev = None
  39. self.prevLonLat = None
  40. self.selected = [] #selected hop
  41. self.regetip = re.compile('^.*\(([^\)]*)\).*$');
  42. self.reghost = re.compile('^ *[0-9]* *([^\(]*)\(.*$');
  43. self.regHop = re.compile('^[^0-9]*([0-9]*).*$');
  44. pass
  45. ## Object trace initialisation
  46. def initTrace(self):
  47. self.ips = []
  48. self.hosts = []
  49. self.pts = []
  50. self.hops = []
  51. self.coords = []
  52. self.selected = []
  53. def progressStatus(self):
  54. self.vtr.drawStatus("Traceroute in progress : ...", self.vtr.font['status'],self.vtr.col['trace_progress'])
  55. pass
  56. ## Generator that yield ips from a running traceroute
  57. # @param The host to traceroute to
  58. # @yield Hop's IP
  59. def traceGen(self,dest_name):
  60. #Init tracer
  61. self.initTrace();
  62. #Store source ip as first hop
  63. self.storeIp(self.src)
  64. self.hosts.append('localhost')
  65. self.hops.append('0')
  66. #Update status bar
  67. self.progressStatus()
  68. self.vtr.flip()
  69. #Run traceroute process
  70. p = Popen(['traceroute', dest_name], stdout=PIPE)
  71. #First line give us destination ip
  72. line = p.stdout.readline().decode('utf-8')
  73. ml = self.regetip.match(line)
  74. if ml != None:
  75. dest_ip = ml.group(1)
  76. if ':' in dest_ip:
  77. coord = geo6.ip62coord(dest_ip)
  78. else:
  79. coord = self.ip2coord(dest_ip)
  80. if coord is not None:
  81. dlon, dlat = coord
  82. (x,y) = self.vtr.trmap.latlon2ortho(dlon, dlat)
  83. pygame.gfxdraw.circle(self.vtr.surf['trace'],x,y,3,self.vtr.col['trace_end'])
  84. else:
  85. dest_ip = None
  86. self.vtr.flip()
  87. yield self.src
  88. #Reading traceroute output
  89. while True:
  90. line = p.stdout.readline().decode('utf-8')
  91. if not line:
  92. break
  93. print(line)
  94. #Getting hop number
  95. ml = self.regHop.match(line)
  96. if ml != None:
  97. self.hops.append(ml.group(1))
  98. else:
  99. self.hops.append('?')
  100. #hop hostname
  101. ml = self.reghost.match(line)
  102. if ml != None:
  103. self.hosts.append(ml.group(1))
  104. else:
  105. self.hosts.append('?')
  106. #hop ip
  107. ml = self.regetip.match(line)
  108. if ml != None:
  109. ip = ml.group(1)
  110. else:
  111. ip = None
  112. self.storeIp(ip)
  113. strStatus = "Traceroute in progress : "
  114. strStatus += self.hops[-1]+' '+self.hosts[-1]
  115. if ip != None:
  116. strStatus += '('+ip+')'
  117. self.vtr.drawStatus(strStatus, self.vtr.font['status'],self.vtr.col['trace_progress'])
  118. self.drawRoute()
  119. yield self.ips[-1]
  120. if dest_ip != None:
  121. self.storeIp(dest_ip)
  122. self.hops.append('Dest')
  123. self.hosts.append(dest_name)
  124. yield dest_ip
  125. ## Store an hop's ip in object properties
  126. # @param ip The ip adress to store
  127. def storeIp(self,ip):
  128. nopts = False
  129. self.ips.append(ip)
  130. if ip != None:
  131. lonlat = self.ip2coord(ip)
  132. if lonlat != None:
  133. #print gir['latitude'], gir['longitude']
  134. self.pts.append(lonlat)
  135. else:
  136. nopts = True
  137. else:
  138. nopts = True
  139. if nopts:
  140. self.pts.append(None)
  141. pass
  142. ## Get geoip info for ip
  143. # @param ip to localisate
  144. def ip2coord(self,ip):
  145. gir = self.gi.record_by_addr(ip)
  146. if gir != None:
  147. return ( gir['longitude'], gir['latitude'])
  148. return None
  149. ## Populate data by running a traceroute
  150. # @param dest_name The host
  151. def trace(self,dest_name):
  152. #Traceroute generator
  153. tr = self.traceGen(dest_name)
  154. for ip in tr:
  155. if ip != None:
  156. pass #self.storeIp(ip)
  157. ## Run a traceroute and interactively update the screen
  158. # @param dest_name The host
  159. def liveTrace(self,dest_name):
  160. #Traceroute generator
  161. tr = self.traceGen(dest_name)
  162. self.initDraw()
  163. for ip in tr:
  164. if ip != None:
  165. #New hop
  166. pts = self.pts[-1]
  167. if pts != None:
  168. dcoord = self.drawNext(pts)
  169. self.coords.append(dcoord)
  170. else:
  171. self.coords.append(None)
  172. #Update the screen
  173. self.vtr.flip()
  174. pass
  175. ## Init a new trace drawing
  176. def initDraw(self):
  177. self.prev = None
  178. self.coords = []
  179. self.vtr.surf['trace'].fill((0,0,0,0))
  180. pass
  181. ## Draw the next hop in a route on the map
  182. # Hops are circle (filled when selected) and
  183. # the route is represented by line between circles
  184. # @param (lon,lat) Hop coordinates
  185. # @param selected A boolean
  186. # @return The coordinates of the hop on the screen as (x,y)
  187. def drawNext(self, pos, selected = False, last = False):
  188. lon,lat=pos
  189. if not selected :
  190. #If not selected draw a circle
  191. cdrawfun = pygame.gfxdraw.circle
  192. else:
  193. #If selected draw a filled circle
  194. cdrawfun = pygame.gfxdraw.filled_circle
  195. (x,y) = self.vtr.trmap.latlon2ortho(lon, lat)
  196. if self.prev == None:
  197. #Route begining
  198. cdrawfun(self.vtr.surf['trace'],x,y,3,self.vtr.col['trace_init'])
  199. else:
  200. #A new hop
  201. if last:
  202. cdrawfun(self.vtr.surf['trace'], x,y,3,self.vtr.col['trace_hop'])
  203. else:
  204. cdrawfun(self.vtr.surf['trace'], x,y,5,self.vtr.col['trace_hop'])
  205. (px,py) = self.prev
  206. #Trying to find a symetric closer from the new hop
  207. dw = self.vtr.trmap.scaledWidth
  208. spx1 = px - dw # x prev symetric 1
  209. spx2 = px + dw # x prev symetric 2
  210. (plon,_) = self.prevLonLat
  211. splon1 = plon - 360 # prev lon symetric 1
  212. splon2 = plon + 360 # prev lon symetric 2
  213. sd1 = abs(lon-splon1)
  214. sd2 = abs(lon-splon2)
  215. if sd1 < sd2:
  216. spx = spx1
  217. sx = x + dw
  218. sd = sd1
  219. else:
  220. spx = spx2
  221. sx = x - dw
  222. sd = sd2
  223. d = abs(lon-plon)
  224. #drawLine = Tracer.drawArcLine
  225. drawLine = pygame.gfxdraw.line
  226. if sd > d:
  227. drawLine(self.vtr.surf['trace'], px,py,x,y, self.vtr.col['trace_trace'])
  228. else:
  229. #symetric is closer
  230. drawLine(self.vtr.surf['trace'], spx,py,x,y, self.vtr.col['trace_trace'])
  231. drawLine(self.vtr.surf['trace'], px,py,sx,y, self.vtr.col['trace_trace'])
  232. self.prevLonLat = (lon,lat)
  233. self.prev = (x,y)
  234. return (x,y)
  235. ## Draw an existing route on the map
  236. # @param delay The time to wait between the draw of two hops (in float, seconds)
  237. def drawTrace(self, delay = 0.1):
  238. self.initDraw()
  239. cnt = 1
  240. for i,pt in enumerate(self.pts):
  241. if pt != None:
  242. dcoord = self.drawNext(pt, (i in self.selected), (i == len(self.pts)-1) )
  243. self.coords.append(dcoord)
  244. if delay > 0:
  245. self.drawRoute(cnt)
  246. self.vtr.flip()
  247. time.sleep(delay)
  248. else:
  249. self.coords.append(None)
  250. cnt += 1
  251. self.drawRoute()
  252. pass
  253. ## Draw the text representation of an existing route
  254. # One line per hop at the bottom left of the screen
  255. # @param stopAt Print the last 'stopAt' hops
  256. def drawRoute(self, stopAt = 32):
  257. strStatus=''
  258. self.vtr.surf['tracetxt'].fill((0,0,0,0))
  259. lh = self.vtr.font['trace_txt'].get_linesize()
  260. cy = self.vtr.surf['tracetxt'].get_height()-4
  261. cy -= lh
  262. if stopAt < 0 or stopAt > len(self.ips):
  263. rng = range(len(self.ips))
  264. else:
  265. rng = range(stopAt)
  266. rng=list(rng)
  267. rng.reverse()
  268. for i in rng:
  269. strStatus = self.hops[i]+' '+self.hosts[i]
  270. if self.ips[i] != None:
  271. strStatus += '('+self.ips[i]+')'
  272. if i in self.selected:
  273. col = self.vtr.col['trace_txtsel'] #selected color
  274. else:
  275. col = self.vtr.col['trace_txt'] #normal color
  276. statusSurf = self.vtr.font['trace_txt'].render(strStatus, 1, col)
  277. self.vtr.surf['tracetxt'].blit(statusSurf, (4,cy))
  278. cy -= lh
  279. pass
  280. ## Search if there is a hop near a screen coordinate
  281. # Will return mutliple host if the distance is equal
  282. # @param (x,y) The on screen coordinates
  283. # @param maxDist The maximum distance bellow wich a hop is selected
  284. # @return A list of hop index
  285. def searchHopByCoord(self, coord, maxDist = 10):
  286. x,y=coord
  287. maxDist = maxDist **2
  288. curRes = []
  289. curMin = maxDist+1
  290. for (i,pos) in enumerate(self.coords):
  291. if pos != None:
  292. (cx,cy) = pos
  293. d = ((cx-x)**2) + ((cy-y)**2)
  294. if d <= curMin:
  295. if d == curMin:
  296. curRes.append(i)
  297. else:
  298. curRes = [i]
  299. curMin = d
  300. return curRes
  301. ## Select hops given screen coordinate
  302. # @param pos On screen coordinate in a (x,y) tuple
  303. # @return A boolean set to True if hops were selected
  304. def select(self, pos):
  305. self.selected = self.searchHopByCoord(pos)
  306. return (len(self.selected)>0)
  307. ## Return a string representing selected hops ip
  308. # @return One ip on each line, preffixed by \t
  309. def selectedIp(self):
  310. res = ""
  311. for i in self.selected:
  312. res += '\t'+str(self.hosts[i])+'('+str(self.ips[i])+')\n'
  313. return res