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.

vtracert.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  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. control_summary = [
  20. "+----------------------+----------------------------------+",
  21. "| Controls summary |",
  22. "+----------------------+----------------------------------+",
  23. "+----------------------+----------------------------------+",
  24. "| ? | display this help |",
  25. "+----------------------+----------------------------------+",
  26. "| esc, ^q | exit the programm or this help |",
  27. "+----------------------+----------------------------------+",
  28. "+----------------------+----------------------------------+",
  29. "| [a-zA-Z0-9\.] | Host input |",
  30. "+----------------------+----------------------------------+",
  31. "| mid-mouse btn | paste clipboard into host input |",
  32. "+----------------------+----------------------------------+",
  33. "| enter | validate host input and |",
  34. "| | run traceroute |",
  35. "+----------------------+----------------------------------+",
  36. "| ^c | clear host input |",
  37. "+----------------------+----------------------------------+",
  38. "+----------------------+----------------------------------+",
  39. "| mouse scroll, +, - : zoom in/out |",
  40. "+----------------------+----------------------------------+",
  41. "| arrow keys : move on the map |",
  42. "+----------------------+----------------------------------+",
  43. "| left mouse btn press : select hops on the map |",
  44. "+----------------------+----------------------------------+"
  45. ]
  46. import random
  47. import time
  48. import os
  49. import pygame
  50. from pygame import gfxdraw
  51. import tracer
  52. import vtracemap
  53. class Vtracert:
  54. ## Instanciate a visual tracert
  55. # @param mapdatfile The filename containing the borders coordinates
  56. # @param width Window width
  57. # @param height Window height
  58. # @param cols Override default colors, its a dict of tuple (r,g,b) (r,g,b,a), valid keys are : bg, map, prompt, status, trace_init, trace_hop, trace_trace
  59. # @param fonts Override default font type and size its a dict of tuple (fontType, fontSize), valid keys are : prompt, status
  60. # @param promptText The prompt when reading the host to traceroute
  61. # @param defaultInput Default host to traceroute
  62. def __init__(self, mapdatfile, width = 1024, height = 768, cols = dict(), fonts = dict(), promptText = "Enter an ip or an hostname and press enter to traceroute : %s_", defaultInput = "gnu.org"):
  63. pygame.init() #Pygame init
  64. #Options
  65. self.mapfile = mapdatfile
  66. self.width = width
  67. self.height = height
  68. #Colors
  69. self.col = dict()
  70. self.col['alpha'] = pygame.Color(0,0,0,0)
  71. self.col['bg'] = pygame.Color(0,0,0)
  72. self.col['map'] = pygame.Color(15,8,63)
  73. self.col['prompt'] = pygame.Color(200,200,200)
  74. self.col['status'] = pygame.Color(200,200,200)
  75. self.col['cur_status'] = self.col['status']
  76. self.col['cur_statusline'] = self.col['status']
  77. self.col['trace_init'] = pygame.Color(0,0xf0,0)
  78. self.col['trace_end'] = pygame.Color(0xee,0x33,0xee)
  79. self.col['trace_hop'] = pygame.Color(0xf0,0xf0,0)
  80. self.col['trace_trace'] = pygame.Color(0xf0,0,0)
  81. self.col['trace_txt'] = pygame.Color(200, 200, 200, 125)
  82. self.col['trace_txtsel'] = self.col['trace_hop']
  83. self.col['trace_progress'] = pygame.Color(0,0xf0,0)
  84. self.col['help_txt'] = pygame.Color(200,200,200)
  85. self.col['help_title'] = pygame.Color(120,30,230)
  86. self.col['help_bg'] = pygame.Color(0,0,0)
  87. for n, c in cols:
  88. self.col[n] = pygame.Color(c)
  89. #Fonts
  90. self.font = dict()
  91. self.font['prompt'] = pygame.font.Font(None, 20)
  92. self.font['status'] = pygame.font.Font(None,20)
  93. self.font['cur_status'] = self.col['status']
  94. self.font['trace_txt'] = pygame.font.Font(None,20)
  95. self.font['help_title'] = pygame.font.Font(None,48)
  96. self.font['help_title'].set_underline(True);
  97. self.font['help_title'].set_bold(True);
  98. self.font['help'] = pygame.font.SysFont('monospace', 16)
  99. for n, (ft,fs) in fonts:
  100. self.font[n] = pygame.font.Font(ft,fs)
  101. #Prompt options
  102. self.promptText = promptText
  103. self.inputText = defaultInput
  104. self.inputText = '1.202.15.14'
  105. #Event filtering
  106. pygame.event.set_allowed(None)
  107. pygame.event.set_allowed([pygame.MOUSEBUTTONUP,pygame.KEYDOWN, pygame.KEYUP, pygame.QUIT])
  108. #Surface creation
  109. self.surf = dict()
  110. self.surf['screen'] = pygame.display.set_mode((width, height), pygame.DOUBLEBUF )
  111. self.surf['map'] = pygame.Surface((width, height),pygame.HWSURFACE)
  112. self.surf['trace'] = pygame.Surface((width, height),pygame.HWSURFACE | pygame.SRCALPHA)
  113. self.surf['tracetxt'] = pygame.Surface((width, height),pygame.HWSURFACE | pygame.SRCALPHA)
  114. self.surf['status'] = None
  115. for s in self.surf:
  116. if self.surf[s] != None:
  117. self.surf[s].fill(self.col['alpha'])
  118. self.help(0)
  119. self.drawStatus('Loading...');
  120. pygame.display.flip()
  121. #Clipboard handling module initialisation
  122. pygame.scrap.init()
  123. #Keyboard vars
  124. self.caps = False
  125. self.ctrl = False
  126. #Map info
  127. self.trmap = vtracemap.TraceMap(self)
  128. self.tracer = tracer.Tracer(self)
  129. ## Color accessor
  130. # @param cname Color name
  131. # @param v The value to set or False to get
  132. # @return The color named cname
  133. # @see Vtracert::opt()
  134. def Col(self, cname, v = False):
  135. return Vtracert.opt(cname, self.col, v)
  136. ## Surface accessor
  137. # @param sname surface name
  138. # @param v The value to set or False to get
  139. # @return The surface named sname
  140. # @see Vtracert::opt()
  141. def Surf(self, sname, v = False):
  142. return Vtracert.opt(sname, self.surf, v)
  143. ## Font accessor
  144. # @param fname font name
  145. # @param v The value to set or False to get
  146. # @return The font named fname
  147. # @see Vtracert::opt()
  148. def Font(self, fname, v = False):
  149. return Vtracert.opt(sname, self.font, v)
  150. ## A generic class to handle dict properties
  151. # @param cname The dict key
  152. # @param dopt The dict
  153. # @param v The value or False for get and not set
  154. # @return dopt[cname]
  155. # @see Vtracert::Col(), Vtracert::Surf(), Vtracert::Font()
  156. @classmethod
  157. def opt(c, cname, dopt, v):
  158. if not (not v and isinstance(v,bool)):
  159. dopt[cname] = v
  160. return dopt[cname]
  161. ## Return the unicode representation of the combiation of ctrl+a letter key
  162. @classmethod
  163. def ctrlUnicode(classo, c):
  164. return chr(ord(c.lower())-ord('a')+1)
  165. pass
  166. ##Display an help on the screen and wait for an KEYDOWN or QUIT event
  167. def help(self, ttl = None):
  168. scr = self.surf['screen']
  169. helpTxts = [
  170. "Vtraceroute help",
  171. "",
  172. "Vtraceroute is a traceroute program ( more info : man traceroute )",
  173. "that displays hops on a map using geoipLookup.",
  174. "",
  175. "Controls summary :",
  176. "",
  177. "? : display this help",
  178. "esc, ^q : exit the programm or this help",
  179. "",
  180. "[a-zA-Z0-9\.] : Host input",
  181. "mid-mouse btn : paste clipboard into host input",
  182. "enter : validate host input and",
  183. " run traceroute",
  184. "^c : clear host input",
  185. "",
  186. "",
  187. "mouse scroll, +, - : zoom in/out",
  188. "arrow keys : move on the map",
  189. "left mouse btn press : select hops on the map",
  190. "","",
  191. "All borders data extracted from data provided by http://thematicmapping.org and",
  192. "Licenced under Creatice Commons Attribution-Share Alike License 3.0"
  193. "",
  194. "Press any key or mouse button to exit this help"
  195. ]
  196. tFont = self.font['help_title']
  197. hFont = self.font['help']
  198. hCol = self.col['help_txt']
  199. tCol = self.col['help_title']
  200. ty = 50
  201. lspacing = 3
  202. hy = ty + tFont.get_linesize() + lspacing
  203. hx = 40
  204. #Fill screen
  205. scr.fill(self.col['help_bg'])
  206. #Print title
  207. titleTxt = helpTxts.pop(0)
  208. tsurf = tFont.render(titleTxt,1,tCol)
  209. tsc = tsurf.get_rect()
  210. tsc.centerx = self.width/2 #center the title
  211. tsc.y = ty
  212. scr.blit(tsurf,tsc)
  213. #Print help text
  214. for line in helpTxts:
  215. if len(line) > 0:
  216. tsurf = hFont.render(line,1,hCol)
  217. scr.blit(tsurf,(hx, hy))
  218. hy += hFont.get_linesize()
  219. hy += lspacing
  220. #pygame.display.flip();time.sleep(0.05);
  221. pygame.display.flip()
  222. if ttl == None:
  223. wait = True
  224. while wait:
  225. evts = pygame.event.get()
  226. for evt in evts:
  227. if evt.type in (pygame.QUIT, pygame.KEYDOWN, pygame.MOUSEBUTTONUP):
  228. wait = False
  229. break;
  230. else:
  231. time.sleep(ttl)
  232. pass
  233. """
  234. ## Return True
  235. # @c1 unicode1
  236. # @c2 unicode2
  237. def isCtrlUnicode(self, c, cu):
  238. return (self.ctrl and Vtracert.ctrlUnicode(c) == cu)
  239. """
  240. ## Blit a surface on the screen surface
  241. # @param sname The surface name
  242. # @param pos A tuple (x,y) for the blit position
  243. # @return self.surf['screen'].blit(self.surf[sname], pos)
  244. def blitScreen(self, sname, pos = (0,0) ):
  245. return self.surf['screen'].blit(self.surf[sname], pos)
  246. ## Draw the map again and call Vtracert::flip()
  247. # Call Vtracert::fillScreen()
  248. # @see Vtracert::fillScreen(), Vtracert::flip()
  249. def mapRedraw(self):
  250. self.fillScreen()
  251. self.trmap.draw()
  252. self.flip()
  253. ## Fill the screen with the 'bg' color
  254. def fillScreen(self):
  255. self.surf['screen'].fill(self.col['bg'])
  256. pass
  257. ## Blit all the surface is the right order but don't call pygame.display.flip()
  258. # The status bar horizontal ruler is a draw and not a blit
  259. # @see Vtracert::flip()
  260. def blit(self):
  261. self.blitScreen('map')
  262. self.blitScreen('status')
  263. self.drawStatusLine()
  264. self.blitScreen('trace')
  265. self.blitScreen('tracetxt')
  266. pass
  267. ## Blit all surface and flip display
  268. # @see Vtracert::blit()
  269. def flip(self, fill = True):
  270. if fill:
  271. self.fillScreen()
  272. self.blit()
  273. pygame.display.flip()
  274. pass
  275. ## Draw the prompt in the status surface
  276. # @see Vtracert::drawStatus()
  277. def drawPrompt(self):
  278. self.drawStatus(self.promptText%(self.inputText), self.font['prompt'], self.col['prompt'])
  279. ## Draw the status bar on the screen
  280. # @param statusText The text to display in the status bar
  281. # @param font The font used to render the text
  282. # @param col The text color
  283. # @param antialiasing A boolean telling if the text is rendered antialiased
  284. # @param line A boolean telling if the status bar line has to be drawn
  285. # @param lineCol The status bar line color (by default same as col)
  286. def drawStatus(self, statusText, font = None, col = None, antialiasing = True, line = True, lineCol = None):
  287. if font == None:
  288. font = self.font['status']
  289. self.font['cur_status'] = font
  290. if col == None:
  291. col = self.col['status']
  292. self.col['cur_status'] = col
  293. if lineCol == None:
  294. lineCol = col
  295. self.col['cur_statusline'] = lineCol
  296. self.surf['status'] = font.render(statusText, antialiasing, col)
  297. self.blitScreen('status')
  298. if line:
  299. self.drawStatusLine()
  300. pass
  301. ## Draw the status bar horizontal ruler
  302. # @see Vtracert::drawStatus()
  303. def drawStatusLine(self):
  304. ly = self.font['cur_status'].get_linesize() + 2
  305. pygame.gfxdraw.line(self.surf['screen'], 0, ly, self.width - 1, ly, self.col['cur_statusline'])
  306. pass
  307. ## Handle a KEYUP event
  308. # @param evt Event
  309. def keyupEvt(self, evt):
  310. if evt.key == 303 or evt.key == 304: #caps release
  311. self.caps = False
  312. elif evt.key == 305 or evt.key == 306: #ctrl release
  313. self.ctrl = False
  314. pass
  315. ## Handles a KEYDOWN event
  316. # @param evt Event
  317. def keydownEvt(self, evt):
  318. ccode = evt.key
  319. c2u = Vtracert.ctrlUnicode
  320. allowedChar = list(range(ord('a'),ord('z')+1))
  321. allowedChar += list(range(ord('A'),ord('Z')+1))
  322. allowedChar += list(range(ord('0'),ord('9')+1))
  323. allowedChar += [ord('.'),ord('\b'),ord('\r'),ord(':'),ord('\n')]
  324. allowedChar = [ chr(cc) for cc in allowedChar ]
  325. ctrlChar = [ c2u('q'), #^q
  326. c2u('n'), #^n
  327. c2u('c'), #^n
  328. '?',
  329. u'\x1b' #escape button
  330. ]
  331. #print evt
  332. #print self.ctrl
  333. cc = evt.unicode
  334. if ccode in [273,274,275,276]: #Arrow keys
  335. if ccode == 273:
  336. d = 'up'
  337. elif ccode == 274:
  338. d = 'down'
  339. elif ccode == 275:
  340. d = 'right'
  341. else:
  342. d = 'left'
  343. self.trmap.zoom( (None, None), d)
  344. self.tracer.drawTrace(0)
  345. self.flip()
  346. pygame.event.clear(pygame.MOUSEBUTTONUP)
  347. elif ccode == 303 or ccode == 304: #caps
  348. self.caps = True
  349. elif evt.key == 305 or evt.key == 306: #ctrl release
  350. self.ctrl = True
  351. elif self.ctrl or cc in ctrlChar: # ctrl + key handle
  352. cc = evt.unicode
  353. if cc == u'\x1b' or cc == c2u('q'): #quit (esc or ^q)
  354. pygame.event.post(pygame.event.Event(pygame.QUIT))
  355. elif cc == '?': #help ^?
  356. print("Help !!!!!!!!")
  357. self.help()
  358. self.flip()
  359. elif cc == c2u('c'):
  360. self.inputText = ''
  361. self.drawPrompt()
  362. elif cc == c2u('n'):
  363. for cname in self.col:
  364. c = self.col[cname]
  365. c.r = 255 - c.r
  366. c.g = 255 - c.g
  367. c.b = 255 - c.b
  368. self.col[cname] = c
  369. self.mapRedraw()
  370. elif evt.unicode in allowedChar:
  371. cc = evt.unicode
  372. if cc == '\b': #backspace
  373. if len(self.inputText) > 0:
  374. self.inputText = self.inputText[:-1]
  375. elif cc == '\r' or cc == '\n':
  376. print("OK, tracerouting %s ..."%self.inputText)
  377. dest = self.inputText
  378. self.inputText = ""
  379. self.surf['trace'].fill((0,0,0,0))
  380. self.tracer.liveTrace(dest)
  381. #self.tracer.trace(dest)
  382. self.flip()
  383. else:
  384. self.inputText += cc
  385. self.drawPrompt()
  386. pass
  387. ## Handles a MOUSEBUTTONUP event
  388. # @param evt Event
  389. def mouseboutonupEvt(self, evt):
  390. if evt.button == 4 or evt.button == 5: #scrollzoom
  391. if evt.button == 4 or evt.button == 1:
  392. zoom = True
  393. elif evt.button == 5 or evt.button == 3:
  394. zoom = False
  395. self.trmap.zoom(evt.pos,zoom)
  396. self.tracer.drawTrace(0)
  397. self.blit()
  398. pygame.event.clear(pygame.MOUSEBUTTONUP)
  399. #end scrollzoom
  400. elif evt.button == 1:
  401. #Trying to select hops
  402. if self.tracer.select(evt.pos):
  403. print('Selected : \n',self.tracer.selectedIp())
  404. self.tracer.drawTrace(0)
  405. elif evt.button == 2:
  406. clipTxt = pygame.scrap.get('TEXT')
  407. if clipTxt:
  408. inputTxt += clipTxt
  409. self.drawPrompt()
  410. else:
  411. print("\n\n")
  412. types = pygame.scrap.get_types()
  413. for t in types:
  414. print(t,pygame.scrap.get(t))
  415. pass
  416. ## Listen for pygame events
  417. def run(self):
  418. self.drawPrompt();
  419. self.mapRedraw();
  420. self.flip()
  421. #Display small onetime help text
  422. htext = self.font['status'].render("Type ? for help.",1, self.col['status'])
  423. self.surf['screen'].blit(htext, (5, self.font['status'].get_linesize()+2))
  424. pygame.display.flip()
  425. again = True
  426. while again:
  427. evt = pygame.event.wait()
  428. if evt != pygame.NOEVENT:
  429. if evt.type == pygame.QUIT:
  430. again = False
  431. break
  432. elif evt.type == pygame.KEYUP:
  433. self.keyupEvt(evt)
  434. elif evt.type == pygame.KEYDOWN:
  435. self.keydownEvt(evt)
  436. elif evt.type == pygame.MOUSEBUTTONUP:
  437. self.mouseboutonupEvt(evt)
  438. self.drawPrompt();
  439. self.flip()