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