Ensure tinc-gui running in 64 bits mode can find tinc's 32 bit registry key.
[tinc] / gui / tinc-gui
1 #!/usr/bin/python
2
3 # tinc-gui -- GUI for controlling a running tincd
4 # Copyright (C) 2009-2012 Guus Sliepen <guus@tinc-vpn.org>
5 #
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.
10 #
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.
15 #
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.
19
20 import string
21 import socket
22 import wx
23 import sys
24 import os
25 import platform
26 import time
27 from wx.lib.mixins.listctrl import ColumnSorterMixin
28 from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
29
30 if platform.system() == 'Windows':
31         import _winreg
32
33 # Classes to interface with a running tinc daemon
34
35 REQ_STOP = 0
36 REQ_RELOAD = 1
37 REQ_RESTART = 2
38 REQ_DUMP_NODES = 3
39 REQ_DUMP_EDGES = 4
40 REQ_DUMP_SUBNETS = 5
41 REQ_DUMP_CONNECTIONS = 6
42 REQ_DUMP_GRAPH = 7
43 REQ_PURGE = 8
44 REQ_SET_DEBUG = 9
45 REQ_RETRY = 10
46 REQ_CONNECT = 11
47 REQ_DISCONNECT = 12
48
49 ID = 0
50 ACK = 4
51 CONTROL = 18
52
53 class Node:
54         def parse(self, args):
55                 self.name = args[0]
56                 self.address = args[1]
57                 self.port = args[3]
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]
65                 self.via = args[11]
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])
71
72                 self.subnets = {}
73
74 class Edge:
75         def parse(self, args):
76                 self.fr = args[0]
77                 self.to = args[1]
78                 self.address = args[2]
79                 self.port = args[4]
80                 self.options = int(args[5], 16)
81                 self.weight = int(args[6])
82
83 class Subnet:
84         def parse(self, args):
85                 if args[0].find('#') >= 0:
86                         (address, self.weight) = args[0].split('#', 1)
87                 else:
88                         self.weight = 10
89                         address = args[0]
90
91                 if address.find('/') >= 0:
92                         (self.address, self.prefixlen) = address.split('/', 1)
93                 else:
94                         self.address = address
95                         self.prefixlen = '48'
96
97                 self.owner = args[1]    
98
99 class Connection:
100         def parse(self, args):
101                 self.name = args[0]
102                 self.address = args[1]
103                 self.port = args[3]
104                 self.options = int(args[4], 0x10)
105                 self.socket = int(args[5])
106                 self.status = int(args[6], 0x10)
107                 self.weight = 123
108
109 class VPN:
110         confdir = '/etc/tinc'
111         piddir = '/var/run/'
112
113         def connect(self):
114                 # read the pidfile
115                 f = open(self.pidfile)
116                 info = string.split(f.readline())
117                 f.close()
118
119                 # check if there is a UNIX socket as well
120                 if self.pidfile.endswith(".pid"):
121                         unixfile = self.pidfile.replace(".pid", ".socket");
122                 else:
123                         unixfile = self.pidfile + ".socket";
124
125                 if os.path.exists(unixfile):
126                         # use it if it exists
127                         print(unixfile + " exists!");
128                         s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
129                         s.connect(unixfile)
130                 else:
131                         # otherwise connect via TCP
132                         print(unixfile + " does not exist.");
133                         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
134                         s.connect((info[2], int(info[4])))
135
136                 self.sf = s.makefile()
137                 s.close()
138                 hello = string.split(self.sf.readline())
139                 self.name = hello[1]
140                 self.sf.write('0 ^' + info[1] + ' 17\r\n')
141                 self.sf.flush()
142                 resp = string.split(self.sf.readline())
143                 self.port = info[4]
144                 self.nodes = {}
145                 self.edges = {}
146                 self.subnets = {}
147                 self.connections = {}
148                 self.refresh()
149
150         def refresh(self):
151                 self.sf.write('18 3\r\n18 4\r\n18 5\r\n18 6\r\n')
152                 self.sf.flush()
153
154                 for node in self.nodes.values():
155                         node.visited = False
156                 for edge in self.edges.values():
157                         edge.visited = False
158                 for subnet in self.subnets.values():
159                         subnet.visited = False
160                 for connections in self.connections.values():
161                         connections.visited = False
162
163                 while True:
164                         resp = string.split(self.sf.readline())
165                         if len(resp) < 2:
166                                 break
167                         if resp[0] != '18':
168                                 break
169                         if resp[1] == '3':
170                                 if len(resp) < 19:
171                                         continue
172                                 node = self.nodes.get(resp[2]) or Node()
173                                 node.parse(resp[2:])
174                                 node.visited = True
175                                 self.nodes[resp[2]] = node
176                         elif resp[1] == '4':
177                                 if len(resp) < 9:
178                                         continue
179                                 edge = self.nodes.get((resp[2], resp[3])) or Edge()
180                                 edge.parse(resp[2:])
181                                 edge.visited = True
182                                 self.edges[(resp[2], resp[3])] = edge
183                         elif resp[1] == '5':
184                                 if len(resp) < 4:
185                                         continue
186                                 subnet = self.subnets.get((resp[2], resp[3])) or Subnet()
187                                 subnet.parse(resp[2:])
188                                 subnet.visited = True
189                                 self.subnets[(resp[2], resp[3])] = subnet
190                                 self.nodes[subnet.owner].subnets[resp[2]] = subnet
191                         elif resp[1] == '6':
192                                 if len(resp) < 9:
193                                         break
194                                 connection = self.connections.get((resp[2], resp[3], resp[5])) or Connection()
195                                 connection.parse(resp[2:])
196                                 connection.visited = True
197                                 self.connections[(resp[2], resp[3], resp[5])] = connection
198                         else:
199                                 break
200
201                 for key, subnet in self.subnets.items():
202                         if not subnet.visited:
203                                 del self.subnets[key]
204
205                 for key, edge in self.edges.items():
206                         if not edge.visited:
207                                 del self.edges[key]
208
209                 for key, node in self.nodes.items():
210                         if not node.visited:
211                                 del self.nodes[key]
212                         else:
213                                 for key, subnet in node.subnets.items():
214                                         if not subnet.visited:
215                                                 del node.subnets[key]
216
217                 for key, connection in self.connections.items():
218                         if not connection.visited:
219                                 del self.connections[key]
220
221         def close(self):
222                 self.sf.close()
223
224         def disconnect(self, name):
225                 self.sf.write('18 12 ' + name + '\r\n')
226                 self.sf.flush()
227                 resp = string.split(self.sf.readline())
228
229         def debug(self, level = -1):
230                 self.sf.write('18 9 ' + str(level) + '\r\n')
231                 self.sf.flush()
232                 resp = string.split(self.sf.readline())
233                 return int(resp[2])
234
235         def __init__(self, netname = None, pidfile = None):
236                 if platform.system() == 'Windows':
237                         sam = _winreg.KEY_READ
238                         if platform.machine().endswith('64'):
239                                 sam = sam | _winreg.KEY_WOW64_64KEY
240                         try:
241                                 reg = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
242                                 try:
243                                         key = _winreg.OpenKey(reg, "SOFTWARE\\tinc", 0, sam)
244                                 except WindowsError:
245                                         key = _winreg.OpenKey(reg, "SOFTWARE\\Wow6432Node\\tinc", 0, sam)
246                                 VPN.confdir = _winreg.QueryValue(key, None)
247                         except WindowsError:
248                                 pass
249
250                 if netname:
251                         self.netname = netname
252                         self.confbase = os.path.join(VPN.confdir, netname)
253                 else:
254                         self.confbase = VPN.confdir
255
256                 self.tincconf = os.path.join(self.confbase, 'tinc.conf')
257
258                 if pidfile != None:
259                         self.pidfile = pidfile
260                 else:
261                         if platform.system() == 'Windows':
262                                 self.pidfile = os.path.join(self.confbase, 'pid')
263                         else:
264                                 if netname:
265                                         self.pidfile = os.path.join(VPN.piddir, 'tinc.' + netname + '.pid')
266                                 else:
267                                         self.pidfile = os.path.join(VPN.piddir, 'tinc.pid')
268
269 # GUI starts here
270
271 argv0 = sys.argv[0]
272 del sys.argv[0]
273 netname = None
274 pidfile = None
275
276 def usage(exitcode = 0):
277         print('Usage: ' + argv0 + ' [options]')
278         print('\nValid options are:')
279         print('  -n, --net=NETNAME       Connect to net NETNAME.')
280         print('      --pidfile=FILENAME  Read control cookie from FILENAME.')
281         print('      --help              Display this help and exit.')
282         print('\nReport bugs to tinc@tinc-vpn.org.')
283         sys.exit(exitcode)
284
285 while sys.argv:
286         if sys.argv[0] in ('-n', '--net'):
287                 del sys.argv[0]
288                 netname = sys.argv[0]
289         elif sys.argv[0] in ('--pidfile'):
290                 del sys.argv[0]
291                 pidfile = sys.argv[0]
292         elif sys.argv[0] in ('--help'):
293                 usage(0)
294         else:
295                 print(argv0 + ': unrecognized option \'' + sys.argv[0] + '\'')
296                 usage(1)
297
298         del sys.argv[0]
299
300 if netname == None:
301         netname = os.getenv("NETNAME")
302
303 if netname == ".":
304         netname = None
305
306 vpn = VPN(netname, pidfile)
307 vpn.connect()
308
309 class SuperListCtrl(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin):
310     def __init__(self, parent, style):
311         wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
312         ListCtrlAutoWidthMixin.__init__(self)
313         ColumnSorterMixin.__init__(self, 16)
314
315     def GetListCtrl(self):
316         return self
317
318
319 class SettingsPage(wx.Panel):
320         def OnDebugLevel(self, event):
321                 vpn.debug(self.debug.GetValue())
322
323         def __init__(self, parent, id):
324                 wx.Panel.__init__(self, parent, id)
325                 grid = wx.FlexGridSizer(cols = 2)
326                 grid.AddGrowableCol(1, 1)
327
328                 namelabel = wx.StaticText(self, -1, 'Name:')
329                 self.name = wx.TextCtrl(self, -1, vpn.name)
330                 grid.Add(namelabel)
331                 grid.Add(self.name, 1, wx.EXPAND)
332
333                 portlabel = wx.StaticText(self, -1, 'Port:')
334                 self.port = wx.TextCtrl(self, -1, vpn.port)
335                 grid.Add(portlabel)
336                 grid.Add(self.port)
337
338                 debuglabel = wx.StaticText(self, -1, 'Debug level:')
339                 self.debug = wx.SpinCtrl(self, min = 0, max = 5, initial = vpn.debug())
340                 self.debug.Bind(wx.EVT_SPINCTRL, self.OnDebugLevel)
341                 grid.Add(debuglabel)
342                 grid.Add(self.debug)
343
344                 modelabel = wx.StaticText(self, -1, 'Mode:')
345                 self.mode = wx.ComboBox(self, -1, style = wx.CB_READONLY, value = 'Router', choices = ['Router', 'Switch', 'Hub'])
346                 grid.Add(modelabel)
347                 grid.Add(self.mode)
348
349                 self.SetSizer(grid)
350
351 class ConnectionsPage(wx.Panel):
352         def __init__(self, parent, id):
353                 wx.Panel.__init__(self, parent, id)
354                 self.list = SuperListCtrl(self, id)
355                 self.list.InsertColumn(0, 'Name')
356                 self.list.InsertColumn(1, 'Address')
357                 self.list.InsertColumn(2, 'Port')
358                 self.list.InsertColumn(3, 'Options')
359                 self.list.InsertColumn(4, 'Weight')
360
361                 hbox = wx.BoxSizer(wx.HORIZONTAL)
362                 hbox.Add(self.list, 1, wx.EXPAND)
363                 self.SetSizer(hbox)
364                 self.refresh()
365
366         class ContextMenu(wx.Menu):
367                 def __init__(self, item):
368                         wx.Menu.__init__(self)
369
370                         self.item = item
371
372                         disconnect = wx.MenuItem(self, -1, 'Disconnect')
373                         self.AppendItem(disconnect)
374                         self.Bind(wx.EVT_MENU, self.OnDisconnect, id=disconnect.GetId())
375
376                 def OnDisconnect(self, event):
377                         vpn.disconnect(self.item[0])
378
379         def OnContext(self, event):
380                 i = event.GetIndex()
381                 self.PopupMenu(self.ContextMenu(self.list.itemDataMap[event.GetIndex()]), event.GetPosition())
382
383         def refresh(self):
384                 sortstate = self.list.GetSortState()
385                 self.list.itemDataMap = {}
386                 i = 0
387
388                 for key, connection in vpn.connections.items():
389                         if self.list.GetItemCount() <= i:
390                                 self.list.InsertStringItem(i, connection.name)
391                         else:
392                                 self.list.SetStringItem(i, 0, connection.name)
393                         self.list.SetStringItem(i, 1, connection.address)
394                         self.list.SetStringItem(i, 2, connection.port)
395                         self.list.SetStringItem(i, 3, str(connection.options))
396                         self.list.SetStringItem(i, 4, str(connection.weight))
397                         self.list.itemDataMap[i] = (connection.name, connection.address, connection.port, connection.options, connection.weight)
398                         self.list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnContext)
399                         self.list.SetItemData(i, i)
400                         i += 1
401
402                 while self.list.GetItemCount() > i:
403                         self.list.DeleteItem(self.list.GetItemCount() - 1)
404
405                 self.list.SortListItems(sortstate[0], sortstate[1])
406
407 class NodesPage(wx.Panel):
408         def __init__(self, parent, id):
409                 wx.Panel.__init__(self, parent, id)
410                 self.list = SuperListCtrl(self, id)
411                 self.list.InsertColumn( 0, 'Name')
412                 self.list.InsertColumn( 1, 'Address')
413                 self.list.InsertColumn( 2, 'Port')
414                 self.list.InsertColumn( 3, 'Cipher')
415                 self.list.InsertColumn( 4, 'Digest')
416                 self.list.InsertColumn( 5, 'MACLength')
417                 self.list.InsertColumn( 6, 'Compression')
418                 self.list.InsertColumn( 7, 'Options')
419                 self.list.InsertColumn( 8, 'Status')
420                 self.list.InsertColumn( 9, 'Nexthop')
421                 self.list.InsertColumn(10, 'Via')
422                 self.list.InsertColumn(11, 'Distance')
423                 self.list.InsertColumn(12, 'PMTU')
424                 self.list.InsertColumn(13, 'Min MTU')
425                 self.list.InsertColumn(14, 'Max MTU')
426                 self.list.InsertColumn(15, 'Since')
427
428                 hbox = wx.BoxSizer(wx.HORIZONTAL)
429                 hbox.Add(self.list, 1, wx.EXPAND)
430                 self.SetSizer(hbox)
431                 self.refresh()
432
433         def refresh(self):
434                 sortstate = self.list.GetSortState()
435                 self.list.itemDataMap = {}
436                 i = 0
437
438                 for key, node in vpn.nodes.items():
439                         if self.list.GetItemCount() <= i:
440                                 self.list.InsertStringItem(i, node.name)
441                         else:
442                                 self.list.SetStringItem(i,  0, node.name)
443                         self.list.SetStringItem(i,  1, node.address)
444                         self.list.SetStringItem(i,  2, node.port)
445                         self.list.SetStringItem(i,  3, str(node.cipher))
446                         self.list.SetStringItem(i,  4, str(node.digest))
447                         self.list.SetStringItem(i,  5, str(node.maclength))
448                         self.list.SetStringItem(i,  6, str(node.compression))
449                         self.list.SetStringItem(i,  7, format(node.options, "x"))
450                         self.list.SetStringItem(i,  8, format(node.status, "04x"))
451                         self.list.SetStringItem(i,  9, node.nexthop)
452                         self.list.SetStringItem(i, 10, node.via)
453                         self.list.SetStringItem(i, 11, str(node.distance))
454                         self.list.SetStringItem(i, 12, str(node.pmtu))
455                         self.list.SetStringItem(i, 13, str(node.minmtu))
456                         self.list.SetStringItem(i, 14, str(node.maxmtu))
457                         if node.last_state_change:
458                                 since = time.strftime("%Y-%m-%d %H:%M", time.localtime(node.last_state_change))
459                         else:
460                                 since = "never"
461                         self.list.SetStringItem(i, 15, since)
462                         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, since)
463                         self.list.SetItemData(i, i)
464                         i += 1
465
466                 while self.list.GetItemCount() > i:
467                         self.list.DeleteItem(self.list.GetItemCount() - 1)
468
469                 self.list.SortListItems(sortstate[0], sortstate[1])
470
471 class EdgesPage(wx.Panel):
472         def __init__(self, parent, id):
473                 wx.Panel.__init__(self, parent, id)
474                 self.list = SuperListCtrl(self, id)
475                 self.list.InsertColumn(0, 'From')
476                 self.list.InsertColumn(1, 'To')
477                 self.list.InsertColumn(2, 'Address')
478                 self.list.InsertColumn(3, 'Port')
479                 self.list.InsertColumn(4, 'Options')
480                 self.list.InsertColumn(5, 'Weight')
481
482                 hbox = wx.BoxSizer(wx.HORIZONTAL)
483                 hbox.Add(self.list, 1, wx.EXPAND)
484                 self.SetSizer(hbox)
485                 self.refresh()
486
487         def refresh(self):
488                 sortstate = self.list.GetSortState()
489                 self.list.itemDataMap = {}
490                 i = 0
491
492                 for key, edge in vpn.edges.items():
493                         if self.list.GetItemCount() <= i:
494                                 self.list.InsertStringItem(i, edge.fr)
495                         else:
496                                 self.list.SetStringItem(i, 0, edge.fr)
497                         self.list.SetStringItem(i, 1, edge.to)
498                         self.list.SetStringItem(i, 2, edge.address)
499                         self.list.SetStringItem(i, 3, edge.port)
500                         self.list.SetStringItem(i, 4, format(edge.options, "x"))
501                         self.list.SetStringItem(i, 5, str(edge.weight))
502                         self.list.itemDataMap[i] = (edge.fr, edge.to, edge.address, edge.port, edge.options, edge.weight)
503                         self.list.SetItemData(i, i)
504                         i += 1
505
506                 while self.list.GetItemCount() > i:
507                         self.list.DeleteItem(self.list.GetItemCount() - 1)
508
509                 self.list.SortListItems(sortstate[0], sortstate[1])
510
511 class SubnetsPage(wx.Panel):
512         def __init__(self, parent, id):
513                 wx.Panel.__init__(self, parent, id)
514                 self.list = SuperListCtrl(self, id)
515                 self.list.InsertColumn(0, 'Subnet', wx.LIST_FORMAT_RIGHT)
516                 self.list.InsertColumn(1, 'Weight', wx.LIST_FORMAT_RIGHT)
517                 self.list.InsertColumn(2, 'Owner')
518                 hbox = wx.BoxSizer(wx.HORIZONTAL)
519                 hbox.Add(self.list, 1, wx.EXPAND)
520                 self.SetSizer(hbox)
521                 self.refresh()
522
523         def refresh(self):
524                 sortstate = self.list.GetSortState()
525                 self.list.itemDataMap = {}
526                 i = 0
527
528                 for key, subnet in vpn.subnets.items():
529                         if self.list.GetItemCount() <= i:
530                                 self.list.InsertStringItem(i, subnet.address + '/' + subnet.prefixlen)
531                         else:
532                                 self.list.SetStringItem(i, 0, subnet.address + '/' + subnet.prefixlen)
533                         self.list.SetStringItem(i, 1, subnet.weight)
534                         self.list.SetStringItem(i, 2, subnet.owner)
535                         self.list.itemDataMap[i] = (subnet.address + '/' + subnet.prefixlen, subnet.weight, subnet.owner)
536                         self.list.SetItemData(i, i)
537                         i += 1
538
539                 while self.list.GetItemCount() > i:
540                         self.list.DeleteItem(self.list.GetItemCount() - 1)
541
542                 self.list.SortListItems(sortstate[0], sortstate[1])
543
544 class StatusPage(wx.Panel):
545         def __init__(self, parent, id):
546                 wx.Panel.__init__(self, parent, id)
547
548 class GraphPage(wx.Window):
549         def __init__(self, parent, id):
550                 wx.Window.__init__(self, parent, id)
551
552 class NetPage(wx.Notebook):
553         def __init__(self, parent, id):
554                 wx.Notebook.__init__(self, parent)
555                 self.settings = SettingsPage(self, id)
556                 self.connections = ConnectionsPage(self, id)
557                 self.nodes = NodesPage(self, id)
558                 self.edges = EdgesPage(self, id)
559                 self.subnets = SubnetsPage(self, id)
560                 self.graph = GraphPage(self, id)
561                 self.status = StatusPage(self, id)
562
563                 self.AddPage(self.settings, 'Settings')
564                 #self.AddPage(self.status, 'Status')
565                 self.AddPage(self.connections, 'Connections')
566                 self.AddPage(self.nodes, 'Nodes')
567                 self.AddPage(self.edges, 'Edges')
568                 self.AddPage(self.subnets, 'Subnets')
569                 #self.AddPage(self.graph, 'Graph')
570                 
571
572 class MainWindow(wx.Frame):
573         def OnQuit(self, event):
574                 app.ExitMainLoop()
575
576         def OnTimer(self, event):
577                 vpn.refresh()
578                 self.np.nodes.refresh()
579                 self.np.subnets.refresh()
580                 self.np.edges.refresh()
581                 self.np.connections.refresh()
582
583         def __init__(self, parent, id, title):
584                 wx.Frame.__init__(self, parent, id, title)
585
586                 menubar = wx.MenuBar()
587                 file = wx.Menu()
588                 file.Append(1, '&Quit\tCtrl-X', 'Quit tinc GUI')
589                 menubar.Append(file, '&File')
590
591                 #nb = wx.Notebook(self, -1)
592                 #nb.SetPadding((0, 0))
593                 self.np = NetPage(self, -1)
594                 #nb.AddPage(np, 'VPN')
595                 
596                 self.timer = wx.Timer(self, -1)
597                 self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
598                 self.timer.Start(1000)
599                 self.Bind(wx.EVT_MENU, self.OnQuit, id=1)
600                 self.SetMenuBar(menubar)
601                 self.Show()
602
603 app = wx.App()
604 mw = MainWindow(None, -1, 'Tinc GUI')
605
606 #def OnTaskBarIcon(event):
607 #       mw.Raise()
608 #
609 #icon = wx.Icon("tincgui.ico", wx.BITMAP_TYPE_PNG)
610 #taskbaricon = wx.TaskBarIcon()
611 #taskbaricon.SetIcon(icon, 'Tinc GUI')
612 #wx.EVT_TASKBAR_RIGHT_UP(taskbaricon, OnTaskBarIcon)
613
614 app.MainLoop()
615 vpn.close()