Merge Tinc.py into tinc-gui to simplify make install.
[tinc] / gui / tinc-gui
1 #!/usr/bin/python
2
3 import string
4 import socket
5 import wx
6 import sys
7 from wx.lib.mixins.listctrl import ColumnSorterMixin
8 from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
9
10 # Classes to interface with a running tinc daemon
11
12 REQ_STOP = 0
13 REQ_RELOAD = 1
14 REQ_RESTART = 2
15 REQ_DUMP_NODES = 3
16 REQ_DUMP_EDGES = 4
17 REQ_DUMP_SUBNETS = 5
18 REQ_DUMP_CONNECTIONS = 6
19 REQ_DUMP_GRAPH = 7
20 REQ_PURGE = 8
21 REQ_SET_DEBUG = 9
22 REQ_RETRY = 10
23 REQ_CONNECT = 11
24 REQ_DISCONNECT = 12
25
26 ID = 0
27 ACK = 4
28 CONTROL = 18
29
30 class Node:
31         def __init__(self):
32                 print('New node')
33
34         def __exit__(self):
35                 print('Deleting node ' + self.name)
36
37         def parse(self, args):
38                 self.name = args[0]
39                 self.address = args[2]
40                 if args[3] != 'port':
41                         args.insert(3, 'port')
42                         args.insert(4, '')
43                 self.port = args[4]
44                 self.cipher = int(args[6])
45                 self.digest = int(args[8])
46                 self.maclength = int(args[10])
47                 self.compression = int(args[12])
48                 self.options = int(args[14], 0x10)
49                 self.status = int(args[16], 0x10)
50                 self.nexthop = args[18]
51                 self.via = args[20]
52                 self.distance = int(args[22])
53                 self.pmtu = int(args[24])
54                 self.minmtu = int(args[26])
55                 self.maxmtu = int(args[28][:-1])
56
57                 self.subnets = {}
58
59 class Edge:
60         def parse(self, args):
61                 self.fr = args[0]
62                 self.to = args[2]
63                 self.address = args[4]
64                 self.port = args[6]
65                 self.options = int(args[8], 16)
66                 self.weight = int(args[10])
67
68 class Subnet:
69         def parse(self, args):
70                 if args[0].find('#') >= 0:
71                         (address, self.weight) = args[0].split('#', 1)
72                 else:
73                         self.weight = 10
74                         address = args[0]
75
76                 if address.find('/') >= 0:
77                         (self.address, self.prefixlen) = address.split('/', 1)
78                 else:
79                         self.address = address
80                         self.prefixlen = '48'
81
82                 self.owner = args[2]    
83
84 class Connection:
85         def parse(self, args):
86                 self.name = args[0]
87                 self.address = args[2]
88                 if args[3] != 'port':
89                         args.insert(3, 'port')
90                         args.insert(4, '')
91                 self.port = args[4]
92                 self.options = int(args[6], 0x10)
93                 self.socket = int(args[8])
94                 self.status = int(args[10], 0x10)
95                 self.weight = 123
96
97 class VPN:
98         confdir = '/etc/tinc'
99         cookiedir = '/var/run/'
100
101         def connect(self):
102                 f = open(self.cookiefile)
103                 cookie = string.split(f.readline())
104                 f.close()
105                 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
106                 s.connect(('127.0.0.1', int(cookie[1])))
107                 self.sf = s.makefile()
108                 s.close()
109                 hello = string.split(self.sf.readline())
110                 self.name = hello[1]
111                 self.sf.write('0 ^' + cookie[0] + ' 17\r\n')
112                 self.sf.flush()
113                 resp = string.split(self.sf.readline())
114                 self.port = cookie[1]
115                 self.nodes = {}
116                 self.edges = {}
117                 self.subnets = {}
118                 self.connections = {}
119                 self.refresh()
120
121         def refresh(self):
122                 self.sf.write('18 3\r\n18 4\r\n18 5\r\n18 6\r\n')
123                 self.sf.flush()
124
125                 for node in self.nodes.values():
126                         node.visited = False
127                 for edge in self.edges.values():
128                         edge.visited = False
129                 for subnet in self.subnets.values():
130                         subnet.visited = False
131                 for connections in self.connections.values():
132                         connections.visited = False
133
134                 while True:
135                         resp = string.split(self.sf.readline())
136                         if len(resp) < 2:
137                                 break
138                         if resp[0] != '18':
139                                 break
140                         if resp[1] == '3':
141                                 if len(resp) < 3:
142                                         continue
143                                 node = self.nodes.get(resp[2]) or Node()
144                                 node.parse(resp[2:])
145                                 node.visited = True
146                                 self.nodes[resp[2]] = node
147                         elif resp[1] == '4':
148                                 if len(resp) < 5:
149                                         continue
150                                 edge = self.nodes.get((resp[2], resp[4])) or Edge()
151                                 edge.parse(resp[2:])
152                                 edge.visited = True
153                                 self.edges[(resp[2], resp[4])] = edge
154                         elif resp[1] == '5':
155                                 if len(resp) < 5:
156                                         continue
157                                 subnet = self.subnets.get((resp[2], resp[4])) or Subnet()
158                                 subnet.parse(resp[2:])
159                                 subnet.visited = True
160                                 self.subnets[(resp[2], resp[4])] = subnet
161                                 self.nodes[subnet.owner].subnets[resp[2]] = subnet
162                         elif resp[1] == '6':
163                                 if len(resp) < 5:
164                                         break
165                                 connection = self.connections.get((resp[2], resp[4])) or Connection()
166                                 connection.parse(resp[2:])
167                                 connection.visited = True
168                                 self.connections[(resp[2], resp[4])] = connection
169                         else:
170                                 break
171
172                 for key, subnet in self.subnets.items():
173                         if not subnet.visited:
174                                 del self.subnets[key]
175
176                 for key, edge in self.edges.items():
177                         if not edge.visited:
178                                 del self.edges[key]
179
180                 for key, node in self.nodes.items():
181                         if not node.visited:
182                                 del self.nodes[key]
183                         else:
184                                 for key, subnet in node.subnets.items():
185                                         if not subnet.visited:
186                                                 del node.subnets[key]
187
188                 for key, connection in self.connections.items():
189                         if not connection.visited:
190                                 del self.connections[key]
191
192         def close(self):
193                 self.sf.close()
194
195         def disconnect(self, name):
196                 self.sf.write('18 12 ' + name + '\r\n')
197                 self.sf.flush()
198                 resp = string.split(self.sf.readline())
199
200         def debug(self, level = -1):
201                 self.sf.write('18 9 ' + str(level) + '\r\n')
202                 self.sf.flush()
203                 resp = string.split(self.sf.readline())
204                 return int(resp[2])
205
206         def __init__(self, netname = None, controlcookie = None):
207                 self.tincconf = VPN.confdir + '/'
208
209                 if netname:
210                         self.netname = netname
211                         self.tincconf += netname + '/'
212
213                 self.tincconf += 'tinc.conf'
214
215                 if controlcookie is not None:
216                         self.cookiefile = controlcookie
217                 else:
218                         self.cookiefile = VPN.cookiedir + 'tinc.'
219                         if netname:
220                                 self.cookiefile += netname + '.'
221                         self.cookiefile += 'cookie'
222
223 # GUI starts here
224
225 del sys.argv[0]
226 net = None
227 controlcookie = None
228
229 while len(sys.argv) >= 2:
230         if sys.argv[0] in ('-n', '--net'):
231                 net = sys.argv[1]
232         elif sys.argv[0] in ('--controlcookie'):
233                 controlcookie = sys.argv[1]
234         else:
235                 print('Unknown option ' + sys.argv[0])
236                 sys.exit(1)
237
238         del sys.argv[0]
239         del sys.argv[0]
240
241 vpn = VPN(net, controlcookie)
242 vpn.connect()
243
244 class SuperListCtrl(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin):
245     def __init__(self, parent, style):
246         wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
247         ListCtrlAutoWidthMixin.__init__(self)
248         ColumnSorterMixin.__init__(self, 14)
249
250     def GetListCtrl(self):
251         return self
252
253
254 class SettingsPage(wx.Panel):
255         def OnDebugLevel(self, event):
256                 vpn.debug(self.debug.GetValue())
257
258         def __init__(self, parent, id):
259                 wx.Panel.__init__(self, parent, id)
260                 grid = wx.FlexGridSizer(cols = 2)
261                 grid.AddGrowableCol(0, 1)
262
263                 namelabel = wx.StaticText(self, -1, 'Name:')
264                 self.name = wx.TextCtrl(self, -1, vpn.name)
265                 grid.Add(namelabel)
266                 grid.Add(self.name)
267
268                 portlabel = wx.StaticText(self, -1, 'Port:')
269                 self.port = wx.TextCtrl(self, -1, vpn.port)
270                 grid.Add(portlabel)
271                 grid.Add(self.port)
272
273                 debuglabel = wx.StaticText(self, -1, 'Debug level:')
274                 self.debug = wx.SpinCtrl(self, min = 0, max = 5, initial = vpn.debug())
275                 self.debug.Bind(wx.EVT_SPINCTRL, self.OnDebugLevel)
276                 grid.Add(debuglabel)
277                 grid.Add(self.debug)
278
279                 modelabel = wx.StaticText(self, -1, 'Mode:')
280                 self.mode = wx.ComboBox(self, -1, style = wx.CB_READONLY, value = 'Router', choices = ['Router', 'Switch', 'Hub'])
281                 grid.Add(modelabel)
282                 grid.Add(self.mode)
283
284                 self.SetSizer(grid)
285
286 class ConnectionsPage(wx.Panel):
287         def __init__(self, parent, id):
288                 wx.Panel.__init__(self, parent, id)
289                 self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
290                 self.list.InsertColumn(0, 'Name')
291                 self.list.InsertColumn(1, 'Address')
292                 self.list.InsertColumn(2, 'Port')
293                 self.list.InsertColumn(3, 'Options')
294                 self.list.InsertColumn(4, 'Weight')
295
296                 hbox = wx.BoxSizer(wx.HORIZONTAL)
297                 hbox.Add(self.list, 1, wx.EXPAND)
298                 self.SetSizer(hbox)
299                 self.refresh()
300
301         class ContextMenu(wx.Menu):
302                 def __init__(self, item):
303                         wx.Menu.__init__(self)
304
305                         self.item = item
306
307                         disconnect = wx.MenuItem(self, -1, 'Disconnect')
308                         self.AppendItem(disconnect)
309                         self.Bind(wx.EVT_MENU, self.OnDisconnect, id=disconnect.GetId())
310
311                 def OnDisconnect(self, event):
312                         print('Disconnecting ' + self.item[0])
313                         vpn.disconnect(self.item[0])
314
315         def OnContext(self, event):
316                 print('Context menu!')
317                 i = event.GetIndex()
318                 print(i)
319                 self.PopupMenu(self.ContextMenu(self.list.itemDataMap[event.GetIndex()]), event.GetPosition())
320
321         def refresh(self):
322                 self.list.itemDataMap = {}
323                 i = 0
324
325                 for key, connection in vpn.connections.items():
326                         if self.list.GetItemCount() <= i:
327                                 self.list.InsertStringItem(i, connection.name)
328                         else:
329                                 self.list.SetStringItem(i, 0, connection.name)
330                         self.list.SetStringItem(i, 1, connection.address)
331                         self.list.SetStringItem(i, 2, connection.port)
332                         self.list.SetStringItem(i, 3, str(connection.options))
333                         self.list.SetStringItem(i, 4, str(connection.weight))
334                         self.list.itemDataMap[i] = (connection.name, connection.address, connection.port, connection.options, connection.weight)
335                         self.list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnContext)
336                         i += 1
337
338                 while self.list.GetItemCount() > i:
339                         self.list.DeleteItem(self.list.GetItemCount() - 1)
340
341
342 class NodesPage(wx.Panel):
343         def __init__(self, parent, id):
344                 wx.Panel.__init__(self, parent, id)
345                 self.list = SuperListCtrl(self, id)
346                 self.list.InsertColumn( 0, 'Name')
347                 self.list.InsertColumn( 1, 'Address')
348                 self.list.InsertColumn( 2, 'Port')
349                 self.list.InsertColumn( 3, 'Cipher')
350                 self.list.InsertColumn( 4, 'Digest')
351                 self.list.InsertColumn( 5, 'MACLength')
352                 self.list.InsertColumn( 6, 'Compression')
353                 self.list.InsertColumn( 7, 'Options')
354                 self.list.InsertColumn( 8, 'Status')
355                 self.list.InsertColumn( 9, 'Nexthop')
356                 self.list.InsertColumn(10, 'Via')
357                 self.list.InsertColumn(11, 'Distance')
358                 self.list.InsertColumn(12, 'PMTU')
359                 self.list.InsertColumn(13, 'Min MTU')
360                 self.list.InsertColumn(14, 'Max MTU')
361
362                 hbox = wx.BoxSizer(wx.HORIZONTAL)
363                 hbox.Add(self.list, 1, wx.EXPAND)
364                 self.SetSizer(hbox)
365                 self.refresh()
366
367         def refresh(self):
368                 self.list.itemDataMap = {}
369                 i = 0
370
371                 for key, node in vpn.nodes.items():
372                         if self.list.GetItemCount() <= i:
373                                 self.list.InsertStringItem(i, node.name)
374                         else:
375                                 self.list.SetStringItem(i,  0, node.name)
376                         self.list.SetStringItem(i,  1, node.address)
377                         self.list.SetStringItem(i,  2, node.port)
378                         self.list.SetStringItem(i,  3, str(node.cipher))
379                         self.list.SetStringItem(i,  4, str(node.digest))
380                         self.list.SetStringItem(i,  5, str(node.maclength))
381                         self.list.SetStringItem(i,  6, str(node.compression))
382                         self.list.SetStringItem(i,  7, str(node.options))
383                         self.list.SetStringItem(i,  8, str(node.status))
384                         self.list.SetStringItem(i,  9, node.nexthop)
385                         self.list.SetStringItem(i, 10, node.via)
386                         self.list.SetStringItem(i, 11, str(node.distance))
387                         self.list.SetStringItem(i, 12, str(node.pmtu))
388                         self.list.SetStringItem(i, 13, str(node.minmtu))
389                         self.list.SetStringItem(i, 14, str(node.maxmtu))
390                         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)
391                         self.list.SetItemData(i, i)
392                         i += 1
393
394                 while self.list.GetItemCount() > i:
395                         self.list.DeleteItem(self.list.GetItemCount() - 1)
396
397 class EdgesPage(wx.Panel):
398         def __init__(self, parent, id):
399                 wx.Panel.__init__(self, parent, id)
400                 self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
401                 self.list.InsertColumn(0, 'From')
402                 self.list.InsertColumn(1, 'To')
403                 self.list.InsertColumn(2, 'Address')
404                 self.list.InsertColumn(3, 'Port')
405                 self.list.InsertColumn(4, 'Options')
406                 self.list.InsertColumn(5, 'Weight')
407
408                 hbox = wx.BoxSizer(wx.HORIZONTAL)
409                 hbox.Add(self.list, 1, wx.EXPAND)
410                 self.SetSizer(hbox)
411                 self.refresh()
412
413         def refresh(self):
414                 self.list.itemDataMap = {}
415                 i = 0
416
417                 for key, edge in vpn.edges.items():
418                         if self.list.GetItemCount() <= i:
419                                 self.list.InsertStringItem(i, edge.fr)
420                         else:
421                                 self.list.SetStringItem(i, 0, edge.fr)
422                         self.list.SetStringItem(i, 1, edge.to)
423                         self.list.SetStringItem(i, 2, edge.address)
424                         self.list.SetStringItem(i, 3, edge.port)
425                         self.list.SetStringItem(i, 4, str(edge.options))
426                         self.list.SetStringItem(i, 5, str(edge.weight))
427                         self.list.itemDataMap[i] = (edge.fr, edge.to, edge.address, edge.port, edge.options, edge.weight)
428                         i += 1
429
430                 while self.list.GetItemCount() > i:
431                         self.list.DeleteItem(self.list.GetItemCount() - 1)
432
433 class SubnetsPage(wx.Panel):
434         def __init__(self, parent, id):
435                 wx.Panel.__init__(self, parent, id)
436                 self.list = SuperListCtrl(self, id)
437                 self.list.InsertColumn(0, 'Subnet', wx.LIST_FORMAT_RIGHT)
438                 self.list.InsertColumn(1, 'Weight', wx.LIST_FORMAT_RIGHT)
439                 self.list.InsertColumn(2, 'Owner')
440                 hbox = wx.BoxSizer(wx.HORIZONTAL)
441                 hbox.Add(self.list, 1, wx.EXPAND)
442                 self.SetSizer(hbox)
443                 self.refresh()
444
445         def refresh(self):
446                 self.list.itemDataMap = {}
447                 i = 0
448
449                 for key, subnet in vpn.subnets.items():
450                         if self.list.GetItemCount() <= i:
451                                 self.list.InsertStringItem(i, subnet.address + '/' + subnet.prefixlen)
452                         else:
453                                 self.list.SetStringItem(i, 0, subnet.address + '/' + subnet.prefixlen)
454                         self.list.SetStringItem(i, 1, subnet.weight)
455                         self.list.SetStringItem(i, 2, subnet.owner)
456                         self.list.itemDataMap[i] = (subnet.address + '/' + subnet.prefixlen, subnet.weight, subnet.owner)
457                         i = i + 1
458
459                 while self.list.GetItemCount() > i:
460                         self.list.DeleteItem(self.list.GetItemCount() - 1)
461
462 class StatusPage(wx.Panel):
463         def __init__(self, parent, id):
464                 wx.Panel.__init__(self, parent, id)
465
466 class GraphPage(wx.Window):
467         def __init__(self, parent, id):
468                 wx.Window.__init__(self, parent, id)
469
470 class NetPage(wx.Notebook):
471         def __init__(self, parent, id):
472                 wx.Notebook.__init__(self, parent)
473                 self.settings = SettingsPage(self, id)
474                 self.connections = ConnectionsPage(self, id)
475                 self.nodes = NodesPage(self, id)
476                 self.edges = EdgesPage(self, id)
477                 self.subnets = SubnetsPage(self, id)
478                 self.graph = GraphPage(self, id)
479                 self.status = StatusPage(self, id)
480
481                 self.AddPage(self.settings, 'Settings')
482                 #self.AddPage(self.status, 'Status')
483                 self.AddPage(self.connections, 'Connections')
484                 self.AddPage(self.nodes, 'Nodes')
485                 self.AddPage(self.edges, 'Edges')
486                 self.AddPage(self.subnets, 'Subnets')
487                 #self.AddPage(self.graph, 'Graph')
488                 
489
490 class MainWindow(wx.Frame):
491         def OnQuit(self, event):
492                 self.Close(True)
493
494         def OnTimer(self, event):
495                 vpn.refresh()
496                 self.np.nodes.refresh()
497                 self.np.subnets.refresh()
498                 self.np.edges.refresh()
499                 self.np.connections.refresh()
500
501         def __init__(self, parent, id, title):
502                 wx.Frame.__init__(self, parent, id, title)
503
504                 menubar = wx.MenuBar()
505                 file = wx.Menu()
506                 file.Append(1, '&Quit\tCtrl-X', 'Quit tinc GUI')
507                 menubar.Append(file, '&File')
508
509                 #nb = wx.Notebook(self, -1)
510                 #nb.SetPadding((0, 0))
511                 self.np = NetPage(self, -1)
512                 #nb.AddPage(np, 'VPN')
513                 
514                 self.timer = wx.Timer(self, -1)
515                 self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
516                 self.timer.Start(1000)
517                 self.Bind(wx.EVT_MENU, self.OnQuit, id=1)
518                 self.SetMenuBar(menubar)
519                 self.Show()
520
521 app = wx.App()
522 mw = MainWindow(None, -1, 'Tinc GUI')
523
524 #def OnTaskBarIcon(event):
525 #       mw.Raise()
526 #
527 #icon = wx.Icon("tincgui.ico", wx.BITMAP_TYPE_PNG)
528 #taskbaricon = wx.TaskBarIcon()
529 #taskbaricon.SetIcon(icon, 'Tinc GUI')
530 #wx.EVT_TASKBAR_RIGHT_UP(taskbaricon, OnTaskBarIcon)
531
532 app.MainLoop()
533 vpn.close()