3 # tinc-gui -- GUI for controlling a running tincd
4 # Copyright (C) 2009-2012 Guus Sliepen <guus@tinc-vpn.org>
6 # This program 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 2 of the License, or
9 # (at your option) any later version.
11 # This program 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.
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 from wx.lib.mixins.listctrl import ColumnSorterMixin
28 from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
30 if platform.system == 'Windows':
33 # Classes to interface with a running tinc daemon
41 REQ_DUMP_CONNECTIONS = 6
54 def parse(self, args):
56 self.address = args[1]
58 self.cipher = int(args[4])
59 self.digest = int(args[5])
60 self.maclength = int(args[6])
61 self.compression = int(args[7])
62 self.options = int(args[8], 0x10)
63 self.status = int(args[9], 0x10)
64 self.nexthop = args[10]
66 self.distance = int(args[12])
67 self.pmtu = int(args[13])
68 self.minmtu = int(args[14])
69 self.maxmtu = int(args[15])
70 self.last_state_change = float(args[16])
75 def parse(self, args):
78 self.address = args[2]
80 self.options = int(args[5], 16)
81 self.weight = int(args[6])
84 def parse(self, args):
85 if args[0].find('#') >= 0:
86 (address, self.weight) = args[0].split('#', 1)
91 if address.find('/') >= 0:
92 (self.address, self.prefixlen) = address.split('/', 1)
94 self.address = address
100 def parse(self, args):
102 self.address = args[1]
104 self.options = int(args[4], 0x10)
105 self.socket = int(args[5])
106 self.status = int(args[6], 0x10)
110 confdir = '/etc/tinc'
114 f = open(self.pidfile)
115 info = string.split(f.readline())
117 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
118 s.connect((info[2], int(info[4])))
119 self.sf = s.makefile()
121 hello = string.split(self.sf.readline())
123 self.sf.write('0 ^' + info[1] + ' 17\r\n')
125 resp = string.split(self.sf.readline())
130 self.connections = {}
134 self.sf.write('18 3\r\n18 4\r\n18 5\r\n18 6\r\n')
137 for node in self.nodes.values():
139 for edge in self.edges.values():
141 for subnet in self.subnets.values():
142 subnet.visited = False
143 for connections in self.connections.values():
144 connections.visited = False
147 resp = string.split(self.sf.readline())
155 node = self.nodes.get(resp[2]) or Node()
158 self.nodes[resp[2]] = node
162 edge = self.nodes.get((resp[2], resp[3])) or Edge()
165 self.edges[(resp[2], resp[3])] = edge
169 subnet = self.subnets.get((resp[2], resp[3])) or Subnet()
170 subnet.parse(resp[2:])
171 subnet.visited = True
172 self.subnets[(resp[2], resp[3])] = subnet
173 self.nodes[subnet.owner].subnets[resp[2]] = subnet
177 connection = self.connections.get((resp[2], resp[3], resp[5])) or Connection()
178 connection.parse(resp[2:])
179 connection.visited = True
180 self.connections[(resp[2], resp[3], resp[5])] = connection
184 for key, subnet in self.subnets.items():
185 if not subnet.visited:
186 del self.subnets[key]
188 for key, edge in self.edges.items():
192 for key, node in self.nodes.items():
196 for key, subnet in node.subnets.items():
197 if not subnet.visited:
198 del node.subnets[key]
200 for key, connection in self.connections.items():
201 if not connection.visited:
202 del self.connections[key]
207 def disconnect(self, name):
208 self.sf.write('18 12 ' + name + '\r\n')
210 resp = string.split(self.sf.readline())
212 def debug(self, level = -1):
213 self.sf.write('18 9 ' + str(level) + '\r\n')
215 resp = string.split(self.sf.readline())
218 def __init__(self, netname = None, pidfile = None):
219 if platform.system == 'Windows':
221 reg = _winreg.ConnectRegistry(None, HKEY_LOCAL_MACHINE)
222 key = _winreg.OpenKey(reg, "SOFTWARE\\tinc")
223 VPN.confdir = _winreg.QueryValue(key, None)
228 self.netname = netname
229 self.confbase = os.path.join(VPN.confdir, netname)
231 self.confbase = VPN.confdir
233 self.tincconf = os.path.join(self.confbase, 'tinc.conf')
236 self.pidfile = pidfile
238 if platform.system == 'Windows':
239 self.pidfile = os.path.join(self.confbase, 'pid')
242 self.pidfile = os.path.join(VPN.piddir, 'tinc.' + netname + '.pid')
244 self.pidfile = os.path.join(VPN.piddir, 'tinc.pid')
253 def usage(exitcode = 0):
254 print('Usage: ' + argv0 + ' [options]')
255 print('\nValid options are:')
256 print(' -n, --net=NETNAME Connect to net NETNAME.')
257 print(' --pidfile=FILENAME Read control cookie from FILENAME.')
258 print(' --help Display this help and exit.')
259 print('\nReport bugs to tinc@tinc-vpn.org.')
263 if sys.argv[0] in ('-n', '--net'):
265 netname = sys.argv[0]
266 elif sys.argv[0] in ('--pidfile'):
268 pidfile = sys.argv[0]
269 elif sys.argv[0] in ('--help'):
272 print(argv0 + ': unrecognized option \'' + sys.argv[0] + '\'')
278 netname = os.getenv("NETNAME")
283 vpn = VPN(netname, pidfile)
286 class SuperListCtrl(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin):
287 def __init__(self, parent, style):
288 wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
289 ListCtrlAutoWidthMixin.__init__(self)
290 ColumnSorterMixin.__init__(self, 14)
292 def GetListCtrl(self):
296 class SettingsPage(wx.Panel):
297 def OnDebugLevel(self, event):
298 vpn.debug(self.debug.GetValue())
300 def __init__(self, parent, id):
301 wx.Panel.__init__(self, parent, id)
302 grid = wx.FlexGridSizer(cols = 2)
303 grid.AddGrowableCol(0, 1)
305 namelabel = wx.StaticText(self, -1, 'Name:')
306 self.name = wx.TextCtrl(self, -1, vpn.name)
310 portlabel = wx.StaticText(self, -1, 'Port:')
311 self.port = wx.TextCtrl(self, -1, vpn.port)
315 debuglabel = wx.StaticText(self, -1, 'Debug level:')
316 self.debug = wx.SpinCtrl(self, min = 0, max = 5, initial = vpn.debug())
317 self.debug.Bind(wx.EVT_SPINCTRL, self.OnDebugLevel)
321 modelabel = wx.StaticText(self, -1, 'Mode:')
322 self.mode = wx.ComboBox(self, -1, style = wx.CB_READONLY, value = 'Router', choices = ['Router', 'Switch', 'Hub'])
328 class ConnectionsPage(wx.Panel):
329 def __init__(self, parent, id):
330 wx.Panel.__init__(self, parent, id)
331 self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
332 self.list.InsertColumn(0, 'Name')
333 self.list.InsertColumn(1, 'Address')
334 self.list.InsertColumn(2, 'Port')
335 self.list.InsertColumn(3, 'Options')
336 self.list.InsertColumn(4, 'Weight')
338 hbox = wx.BoxSizer(wx.HORIZONTAL)
339 hbox.Add(self.list, 1, wx.EXPAND)
343 class ContextMenu(wx.Menu):
344 def __init__(self, item):
345 wx.Menu.__init__(self)
349 disconnect = wx.MenuItem(self, -1, 'Disconnect')
350 self.AppendItem(disconnect)
351 self.Bind(wx.EVT_MENU, self.OnDisconnect, id=disconnect.GetId())
353 def OnDisconnect(self, event):
354 vpn.disconnect(self.item[0])
356 def OnContext(self, event):
358 self.PopupMenu(self.ContextMenu(self.list.itemDataMap[event.GetIndex()]), event.GetPosition())
361 self.list.itemDataMap = {}
364 for key, connection in vpn.connections.items():
365 if self.list.GetItemCount() <= i:
366 self.list.InsertStringItem(i, connection.name)
368 self.list.SetStringItem(i, 0, connection.name)
369 self.list.SetStringItem(i, 1, connection.address)
370 self.list.SetStringItem(i, 2, connection.port)
371 self.list.SetStringItem(i, 3, str(connection.options))
372 self.list.SetStringItem(i, 4, str(connection.weight))
373 self.list.itemDataMap[i] = (connection.name, connection.address, connection.port, connection.options, connection.weight)
374 self.list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnContext)
377 while self.list.GetItemCount() > i:
378 self.list.DeleteItem(self.list.GetItemCount() - 1)
381 class NodesPage(wx.Panel):
382 def __init__(self, parent, id):
383 wx.Panel.__init__(self, parent, id)
384 self.list = SuperListCtrl(self, id)
385 self.list.InsertColumn( 0, 'Name')
386 self.list.InsertColumn( 1, 'Address')
387 self.list.InsertColumn( 2, 'Port')
388 self.list.InsertColumn( 3, 'Cipher')
389 self.list.InsertColumn( 4, 'Digest')
390 self.list.InsertColumn( 5, 'MACLength')
391 self.list.InsertColumn( 6, 'Compression')
392 self.list.InsertColumn( 7, 'Options')
393 self.list.InsertColumn( 8, 'Status')
394 self.list.InsertColumn( 9, 'Nexthop')
395 self.list.InsertColumn(10, 'Via')
396 self.list.InsertColumn(11, 'Distance')
397 self.list.InsertColumn(12, 'PMTU')
398 self.list.InsertColumn(13, 'Min MTU')
399 self.list.InsertColumn(14, 'Max MTU')
400 self.list.InsertColumn(15, 'Since')
402 hbox = wx.BoxSizer(wx.HORIZONTAL)
403 hbox.Add(self.list, 1, wx.EXPAND)
408 self.list.itemDataMap = {}
411 for key, node in vpn.nodes.items():
412 if self.list.GetItemCount() <= i:
413 self.list.InsertStringItem(i, node.name)
415 self.list.SetStringItem(i, 0, node.name)
416 self.list.SetStringItem(i, 1, node.address)
417 self.list.SetStringItem(i, 2, node.port)
418 self.list.SetStringItem(i, 3, str(node.cipher))
419 self.list.SetStringItem(i, 4, str(node.digest))
420 self.list.SetStringItem(i, 5, str(node.maclength))
421 self.list.SetStringItem(i, 6, str(node.compression))
422 self.list.SetStringItem(i, 7, format(node.options, "x"))
423 self.list.SetStringItem(i, 8, format(node.status, "04x"))
424 self.list.SetStringItem(i, 9, node.nexthop)
425 self.list.SetStringItem(i, 10, node.via)
426 self.list.SetStringItem(i, 11, str(node.distance))
427 self.list.SetStringItem(i, 12, str(node.pmtu))
428 self.list.SetStringItem(i, 13, str(node.minmtu))
429 self.list.SetStringItem(i, 14, str(node.maxmtu))
430 if node.last_state_change:
431 since = time.strftime("%Y-%m-%d %H:%M", time.localtime(node.last_state_change))
434 self.list.SetStringItem(i, 15, since)
435 self.list.itemDataMap[i] = (node.name, node.address, node.port, node.cipher, node.digest, node.maclength, node.compression, node.options, node.status, node.nexthop, node.via, node.distance, node.pmtu, node.minmtu, node.maxmtu)
436 self.list.SetItemData(i, i)
439 while self.list.GetItemCount() > i:
440 self.list.DeleteItem(self.list.GetItemCount() - 1)
442 class EdgesPage(wx.Panel):
443 def __init__(self, parent, id):
444 wx.Panel.__init__(self, parent, id)
445 self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
446 self.list.InsertColumn(0, 'From')
447 self.list.InsertColumn(1, 'To')
448 self.list.InsertColumn(2, 'Address')
449 self.list.InsertColumn(3, 'Port')
450 self.list.InsertColumn(4, 'Options')
451 self.list.InsertColumn(5, 'Weight')
453 hbox = wx.BoxSizer(wx.HORIZONTAL)
454 hbox.Add(self.list, 1, wx.EXPAND)
459 self.list.itemDataMap = {}
462 for key, edge in vpn.edges.items():
463 if self.list.GetItemCount() <= i:
464 self.list.InsertStringItem(i, edge.fr)
466 self.list.SetStringItem(i, 0, edge.fr)
467 self.list.SetStringItem(i, 1, edge.to)
468 self.list.SetStringItem(i, 2, edge.address)
469 self.list.SetStringItem(i, 3, edge.port)
470 self.list.SetStringItem(i, 4, format(edge.options, "x"))
471 self.list.SetStringItem(i, 5, str(edge.weight))
472 self.list.itemDataMap[i] = (edge.fr, edge.to, edge.address, edge.port, edge.options, edge.weight)
475 while self.list.GetItemCount() > i:
476 self.list.DeleteItem(self.list.GetItemCount() - 1)
478 class SubnetsPage(wx.Panel):
479 def __init__(self, parent, id):
480 wx.Panel.__init__(self, parent, id)
481 self.list = SuperListCtrl(self, id)
482 self.list.InsertColumn(0, 'Subnet', wx.LIST_FORMAT_RIGHT)
483 self.list.InsertColumn(1, 'Weight', wx.LIST_FORMAT_RIGHT)
484 self.list.InsertColumn(2, 'Owner')
485 hbox = wx.BoxSizer(wx.HORIZONTAL)
486 hbox.Add(self.list, 1, wx.EXPAND)
491 self.list.itemDataMap = {}
494 for key, subnet in vpn.subnets.items():
495 if self.list.GetItemCount() <= i:
496 self.list.InsertStringItem(i, subnet.address + '/' + subnet.prefixlen)
498 self.list.SetStringItem(i, 0, subnet.address + '/' + subnet.prefixlen)
499 self.list.SetStringItem(i, 1, subnet.weight)
500 self.list.SetStringItem(i, 2, subnet.owner)
501 self.list.itemDataMap[i] = (subnet.address + '/' + subnet.prefixlen, subnet.weight, subnet.owner)
504 while self.list.GetItemCount() > i:
505 self.list.DeleteItem(self.list.GetItemCount() - 1)
507 class StatusPage(wx.Panel):
508 def __init__(self, parent, id):
509 wx.Panel.__init__(self, parent, id)
511 class GraphPage(wx.Window):
512 def __init__(self, parent, id):
513 wx.Window.__init__(self, parent, id)
515 class NetPage(wx.Notebook):
516 def __init__(self, parent, id):
517 wx.Notebook.__init__(self, parent)
518 self.settings = SettingsPage(self, id)
519 self.connections = ConnectionsPage(self, id)
520 self.nodes = NodesPage(self, id)
521 self.edges = EdgesPage(self, id)
522 self.subnets = SubnetsPage(self, id)
523 self.graph = GraphPage(self, id)
524 self.status = StatusPage(self, id)
526 self.AddPage(self.settings, 'Settings')
527 #self.AddPage(self.status, 'Status')
528 self.AddPage(self.connections, 'Connections')
529 self.AddPage(self.nodes, 'Nodes')
530 self.AddPage(self.edges, 'Edges')
531 self.AddPage(self.subnets, 'Subnets')
532 #self.AddPage(self.graph, 'Graph')
535 class MainWindow(wx.Frame):
536 def OnQuit(self, event):
539 def OnTimer(self, event):
541 self.np.nodes.refresh()
542 self.np.subnets.refresh()
543 self.np.edges.refresh()
544 self.np.connections.refresh()
546 def __init__(self, parent, id, title):
547 wx.Frame.__init__(self, parent, id, title)
549 menubar = wx.MenuBar()
551 file.Append(1, '&Quit\tCtrl-X', 'Quit tinc GUI')
552 menubar.Append(file, '&File')
554 #nb = wx.Notebook(self, -1)
555 #nb.SetPadding((0, 0))
556 self.np = NetPage(self, -1)
557 #nb.AddPage(np, 'VPN')
559 self.timer = wx.Timer(self, -1)
560 self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
561 self.timer.Start(1000)
562 self.Bind(wx.EVT_MENU, self.OnQuit, id=1)
563 self.SetMenuBar(menubar)
567 mw = MainWindow(None, -1, 'Tinc GUI')
569 #def OnTaskBarIcon(event):
572 #icon = wx.Icon("tincgui.ico", wx.BITMAP_TYPE_PNG)
573 #taskbaricon = wx.TaskBarIcon()
574 #taskbaricon.SetIcon(icon, 'Tinc GUI')
575 #wx.EVT_TASKBAR_RIGHT_UP(taskbaricon, OnTaskBarIcon)