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.
26 from wx.lib.mixins.listctrl import ColumnSorterMixin
27 from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
29 if platform.system == 'Windows':
32 # Classes to interface with a running tinc daemon
40 REQ_DUMP_CONNECTIONS = 6
53 def parse(self, args):
55 self.address = args[2]
57 args.insert(3, 'port')
60 self.cipher = int(args[6])
61 self.digest = int(args[8])
62 self.maclength = int(args[10])
63 self.compression = int(args[12])
64 self.options = int(args[14], 0x10)
65 self.status = int(args[16], 0x10)
66 self.nexthop = args[18]
68 self.distance = int(args[22])
69 self.pmtu = int(args[24])
70 self.minmtu = int(args[26])
71 self.maxmtu = int(args[28][:-1])
76 def parse(self, args):
79 self.address = args[4]
81 self.options = int(args[8], 16)
82 self.weight = int(args[10])
85 def parse(self, args):
86 if args[0].find('#') >= 0:
87 (address, self.weight) = args[0].split('#', 1)
92 if address.find('/') >= 0:
93 (self.address, self.prefixlen) = address.split('/', 1)
95 self.address = address
101 def parse(self, args):
103 self.address = args[2]
104 if args[3] != 'port':
105 args.insert(3, 'port')
108 self.options = int(args[6], 0x10)
109 self.socket = int(args[8])
110 self.status = int(args[10], 0x10)
114 confdir = '/etc/tinc'
118 f = open(self.pidfile)
119 info = string.split(f.readline())
121 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
122 s.connect((info[2], int(info[4])))
123 self.sf = s.makefile()
125 hello = string.split(self.sf.readline())
127 self.sf.write('0 ^' + info[1] + ' 17\r\n')
129 resp = string.split(self.sf.readline())
134 self.connections = {}
138 self.sf.write('18 3\r\n18 4\r\n18 5\r\n18 6\r\n')
141 for node in self.nodes.values():
143 for edge in self.edges.values():
145 for subnet in self.subnets.values():
146 subnet.visited = False
147 for connections in self.connections.values():
148 connections.visited = False
151 resp = string.split(self.sf.readline())
159 node = self.nodes.get(resp[2]) or Node()
162 self.nodes[resp[2]] = node
166 edge = self.nodes.get((resp[2], resp[4])) or Edge()
169 self.edges[(resp[2], resp[4])] = edge
173 subnet = self.subnets.get((resp[2], resp[4])) or Subnet()
174 subnet.parse(resp[2:])
175 subnet.visited = True
176 self.subnets[(resp[2], resp[4])] = subnet
177 self.nodes[subnet.owner].subnets[resp[2]] = subnet
181 connection = self.connections.get((resp[2], resp[4])) or Connection()
182 connection.parse(resp[2:])
183 connection.visited = True
184 self.connections[(resp[2], resp[4])] = connection
188 for key, subnet in self.subnets.items():
189 if not subnet.visited:
190 del self.subnets[key]
192 for key, edge in self.edges.items():
196 for key, node in self.nodes.items():
200 for key, subnet in node.subnets.items():
201 if not subnet.visited:
202 del node.subnets[key]
204 for key, connection in self.connections.items():
205 if not connection.visited:
206 del self.connections[key]
211 def disconnect(self, name):
212 self.sf.write('18 12 ' + name + '\r\n')
214 resp = string.split(self.sf.readline())
216 def debug(self, level = -1):
217 self.sf.write('18 9 ' + str(level) + '\r\n')
219 resp = string.split(self.sf.readline())
222 def __init__(self, netname = None, pidfile = None):
223 if platform.system == 'Windows':
225 reg = _winreg.ConnectRegistry(None, HKEY_LOCAL_MACHINE)
226 key = _winreg.OpenKey(reg, "SOFTWARE\\tinc")
227 VPN.confdir = _winreg.QueryValue(key, None)
232 self.netname = netname
233 self.confbase = os.path.join(VPN.confdir, netname)
235 self.confbase = VPN.confdir
237 self.tincconf = os.path.join(self.confbase, 'tinc.conf')
240 self.pidfile = pidfile
242 if platform.system == 'Windows':
243 self.pidfile = os.path.join(self.confbase, 'pid')
246 self.pidfile = os.path.join(VPN.piddir, 'tinc.' + netname + '.pid')
248 self.pidfile = os.path.join(VPN.piddir, 'tinc.pid')
257 def usage(exitcode = 0):
258 print('Usage: ' + argv0 + ' [options]')
259 print('\nValid options are:')
260 print(' -n, --net=NETNAME Connect to net NETNAME.')
261 print(' --pidfile=FILENAME Read control cookie from FILENAME.')
262 print(' --help Display this help and exit.')
263 print('\nReport bugs to tinc@tinc-vpn.org.')
267 if sys.argv[0] in ('-n', '--net'):
269 netname = sys.argv[0]
270 elif sys.argv[0] in ('--pidfile'):
272 pidfile = sys.argv[0]
273 elif sys.argv[0] in ('--help'):
276 print(argv0 + ': unrecognized option \'' + sys.argv[0] + '\'')
282 netname = os.getenv("NETNAME")
287 vpn = VPN(netname, pidfile)
290 class SuperListCtrl(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin):
291 def __init__(self, parent, style):
292 wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
293 ListCtrlAutoWidthMixin.__init__(self)
294 ColumnSorterMixin.__init__(self, 14)
296 def GetListCtrl(self):
300 class SettingsPage(wx.Panel):
301 def OnDebugLevel(self, event):
302 vpn.debug(self.debug.GetValue())
304 def __init__(self, parent, id):
305 wx.Panel.__init__(self, parent, id)
306 grid = wx.FlexGridSizer(cols = 2)
307 grid.AddGrowableCol(0, 1)
309 namelabel = wx.StaticText(self, -1, 'Name:')
310 self.name = wx.TextCtrl(self, -1, vpn.name)
314 portlabel = wx.StaticText(self, -1, 'Port:')
315 self.port = wx.TextCtrl(self, -1, vpn.port)
319 debuglabel = wx.StaticText(self, -1, 'Debug level:')
320 self.debug = wx.SpinCtrl(self, min = 0, max = 5, initial = vpn.debug())
321 self.debug.Bind(wx.EVT_SPINCTRL, self.OnDebugLevel)
325 modelabel = wx.StaticText(self, -1, 'Mode:')
326 self.mode = wx.ComboBox(self, -1, style = wx.CB_READONLY, value = 'Router', choices = ['Router', 'Switch', 'Hub'])
332 class ConnectionsPage(wx.Panel):
333 def __init__(self, parent, id):
334 wx.Panel.__init__(self, parent, id)
335 self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
336 self.list.InsertColumn(0, 'Name')
337 self.list.InsertColumn(1, 'Address')
338 self.list.InsertColumn(2, 'Port')
339 self.list.InsertColumn(3, 'Options')
340 self.list.InsertColumn(4, 'Weight')
342 hbox = wx.BoxSizer(wx.HORIZONTAL)
343 hbox.Add(self.list, 1, wx.EXPAND)
347 class ContextMenu(wx.Menu):
348 def __init__(self, item):
349 wx.Menu.__init__(self)
353 disconnect = wx.MenuItem(self, -1, 'Disconnect')
354 self.AppendItem(disconnect)
355 self.Bind(wx.EVT_MENU, self.OnDisconnect, id=disconnect.GetId())
357 def OnDisconnect(self, event):
358 vpn.disconnect(self.item[0])
360 def OnContext(self, event):
362 self.PopupMenu(self.ContextMenu(self.list.itemDataMap[event.GetIndex()]), event.GetPosition())
365 self.list.itemDataMap = {}
368 for key, connection in vpn.connections.items():
369 if self.list.GetItemCount() <= i:
370 self.list.InsertStringItem(i, connection.name)
372 self.list.SetStringItem(i, 0, connection.name)
373 self.list.SetStringItem(i, 1, connection.address)
374 self.list.SetStringItem(i, 2, connection.port)
375 self.list.SetStringItem(i, 3, str(connection.options))
376 self.list.SetStringItem(i, 4, str(connection.weight))
377 self.list.itemDataMap[i] = (connection.name, connection.address, connection.port, connection.options, connection.weight)
378 self.list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnContext)
381 while self.list.GetItemCount() > i:
382 self.list.DeleteItem(self.list.GetItemCount() - 1)
385 class NodesPage(wx.Panel):
386 def __init__(self, parent, id):
387 wx.Panel.__init__(self, parent, id)
388 self.list = SuperListCtrl(self, id)
389 self.list.InsertColumn( 0, 'Name')
390 self.list.InsertColumn( 1, 'Address')
391 self.list.InsertColumn( 2, 'Port')
392 self.list.InsertColumn( 3, 'Cipher')
393 self.list.InsertColumn( 4, 'Digest')
394 self.list.InsertColumn( 5, 'MACLength')
395 self.list.InsertColumn( 6, 'Compression')
396 self.list.InsertColumn( 7, 'Options')
397 self.list.InsertColumn( 8, 'Status')
398 self.list.InsertColumn( 9, 'Nexthop')
399 self.list.InsertColumn(10, 'Via')
400 self.list.InsertColumn(11, 'Distance')
401 self.list.InsertColumn(12, 'PMTU')
402 self.list.InsertColumn(13, 'Min MTU')
403 self.list.InsertColumn(14, 'Max MTU')
405 hbox = wx.BoxSizer(wx.HORIZONTAL)
406 hbox.Add(self.list, 1, wx.EXPAND)
411 self.list.itemDataMap = {}
414 for key, node in vpn.nodes.items():
415 if self.list.GetItemCount() <= i:
416 self.list.InsertStringItem(i, node.name)
418 self.list.SetStringItem(i, 0, node.name)
419 self.list.SetStringItem(i, 1, node.address)
420 self.list.SetStringItem(i, 2, node.port)
421 self.list.SetStringItem(i, 3, str(node.cipher))
422 self.list.SetStringItem(i, 4, str(node.digest))
423 self.list.SetStringItem(i, 5, str(node.maclength))
424 self.list.SetStringItem(i, 6, str(node.compression))
425 self.list.SetStringItem(i, 7, str(node.options))
426 self.list.SetStringItem(i, 8, str(node.status))
427 self.list.SetStringItem(i, 9, node.nexthop)
428 self.list.SetStringItem(i, 10, node.via)
429 self.list.SetStringItem(i, 11, str(node.distance))
430 self.list.SetStringItem(i, 12, str(node.pmtu))
431 self.list.SetStringItem(i, 13, str(node.minmtu))
432 self.list.SetStringItem(i, 14, str(node.maxmtu))
433 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)
434 self.list.SetItemData(i, i)
437 while self.list.GetItemCount() > i:
438 self.list.DeleteItem(self.list.GetItemCount() - 1)
440 class EdgesPage(wx.Panel):
441 def __init__(self, parent, id):
442 wx.Panel.__init__(self, parent, id)
443 self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
444 self.list.InsertColumn(0, 'From')
445 self.list.InsertColumn(1, 'To')
446 self.list.InsertColumn(2, 'Address')
447 self.list.InsertColumn(3, 'Port')
448 self.list.InsertColumn(4, 'Options')
449 self.list.InsertColumn(5, 'Weight')
451 hbox = wx.BoxSizer(wx.HORIZONTAL)
452 hbox.Add(self.list, 1, wx.EXPAND)
457 self.list.itemDataMap = {}
460 for key, edge in vpn.edges.items():
461 if self.list.GetItemCount() <= i:
462 self.list.InsertStringItem(i, edge.fr)
464 self.list.SetStringItem(i, 0, edge.fr)
465 self.list.SetStringItem(i, 1, edge.to)
466 self.list.SetStringItem(i, 2, edge.address)
467 self.list.SetStringItem(i, 3, edge.port)
468 self.list.SetStringItem(i, 4, str(edge.options))
469 self.list.SetStringItem(i, 5, str(edge.weight))
470 self.list.itemDataMap[i] = (edge.fr, edge.to, edge.address, edge.port, edge.options, edge.weight)
473 while self.list.GetItemCount() > i:
474 self.list.DeleteItem(self.list.GetItemCount() - 1)
476 class SubnetsPage(wx.Panel):
477 def __init__(self, parent, id):
478 wx.Panel.__init__(self, parent, id)
479 self.list = SuperListCtrl(self, id)
480 self.list.InsertColumn(0, 'Subnet', wx.LIST_FORMAT_RIGHT)
481 self.list.InsertColumn(1, 'Weight', wx.LIST_FORMAT_RIGHT)
482 self.list.InsertColumn(2, 'Owner')
483 hbox = wx.BoxSizer(wx.HORIZONTAL)
484 hbox.Add(self.list, 1, wx.EXPAND)
489 self.list.itemDataMap = {}
492 for key, subnet in vpn.subnets.items():
493 if self.list.GetItemCount() <= i:
494 self.list.InsertStringItem(i, subnet.address + '/' + subnet.prefixlen)
496 self.list.SetStringItem(i, 0, subnet.address + '/' + subnet.prefixlen)
497 self.list.SetStringItem(i, 1, subnet.weight)
498 self.list.SetStringItem(i, 2, subnet.owner)
499 self.list.itemDataMap[i] = (subnet.address + '/' + subnet.prefixlen, subnet.weight, subnet.owner)
502 while self.list.GetItemCount() > i:
503 self.list.DeleteItem(self.list.GetItemCount() - 1)
505 class StatusPage(wx.Panel):
506 def __init__(self, parent, id):
507 wx.Panel.__init__(self, parent, id)
509 class GraphPage(wx.Window):
510 def __init__(self, parent, id):
511 wx.Window.__init__(self, parent, id)
513 class NetPage(wx.Notebook):
514 def __init__(self, parent, id):
515 wx.Notebook.__init__(self, parent)
516 self.settings = SettingsPage(self, id)
517 self.connections = ConnectionsPage(self, id)
518 self.nodes = NodesPage(self, id)
519 self.edges = EdgesPage(self, id)
520 self.subnets = SubnetsPage(self, id)
521 self.graph = GraphPage(self, id)
522 self.status = StatusPage(self, id)
524 self.AddPage(self.settings, 'Settings')
525 #self.AddPage(self.status, 'Status')
526 self.AddPage(self.connections, 'Connections')
527 self.AddPage(self.nodes, 'Nodes')
528 self.AddPage(self.edges, 'Edges')
529 self.AddPage(self.subnets, 'Subnets')
530 #self.AddPage(self.graph, 'Graph')
533 class MainWindow(wx.Frame):
534 def OnQuit(self, event):
537 def OnTimer(self, event):
539 self.np.nodes.refresh()
540 self.np.subnets.refresh()
541 self.np.edges.refresh()
542 self.np.connections.refresh()
544 def __init__(self, parent, id, title):
545 wx.Frame.__init__(self, parent, id, title)
547 menubar = wx.MenuBar()
549 file.Append(1, '&Quit\tCtrl-X', 'Quit tinc GUI')
550 menubar.Append(file, '&File')
552 #nb = wx.Notebook(self, -1)
553 #nb.SetPadding((0, 0))
554 self.np = NetPage(self, -1)
555 #nb.AddPage(np, 'VPN')
557 self.timer = wx.Timer(self, -1)
558 self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
559 self.timer.Start(1000)
560 self.Bind(wx.EVT_MENU, self.OnQuit, id=1)
561 self.SetMenuBar(menubar)
565 mw = MainWindow(None, -1, 'Tinc GUI')
567 #def OnTaskBarIcon(event):
570 #icon = wx.Icon("tincgui.ico", wx.BITMAP_TYPE_PNG)
571 #taskbaricon = wx.TaskBarIcon()
572 #taskbaricon.SetIcon(icon, 'Tinc GUI')
573 #wx.EVT_TASKBAR_RIGHT_UP(taskbaricon, OnTaskBarIcon)