Use pidfile in tinc-gui as well.
[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         piddir = '/var/run/'
100
101         def connect(self):
102                 f = open(self.pidfile)
103                 info = string.split(f.readline())
104                 f.close()
105                 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
106                 s.connect((info[2], int(info[4])))
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 ^' + info[1] + ' 17\r\n')
112                 self.sf.flush()
113                 resp = string.split(self.sf.readline())
114                 self.port = info[4]
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, pidfile = 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 pidfile is not None:
216                         self.pidfile = pidfile
217                 else:
218                         self.pidfile = VPN.piddir + 'tinc.'
219                         if netname:
220                                 self.pidfile += netname + '.'
221                         self.pidfile += 'pid'
222
223 # GUI starts here
224
225 argv0 = sys.argv[0]
226 del sys.argv[0]
227 net = None
228 pidfile = None
229
230 def usage(exitcode = 0):
231         print('Usage: ' + argv0 + ' [options]')
232         print('\nValid options are:')
233         print('  -n, --net=NETNAME       Connect to net NETNAME.')
234         print('      --pidfile=FILENAME  Read control cookie from FILENAME.')
235         print('      --help              Display this help and exit.')
236         print('\nReport bugs to tinc@tinc-vpn.org.')
237         sys.exit(exitcode)
238
239 while len(sys.argv):
240         if sys.argv[0] in ('-n', '--net'):
241                 del sys.argv[0]
242                 net = sys.argv[0]
243         elif sys.argv[0] in ('--pidfile'):
244                 del sys.argv[0]
245                 pidfile = sys.argv[0]
246         elif sys.argv[0] in ('--help'):
247                 usage(0)
248         else:
249                 print(argv0 + ': unrecognized option \'' + sys.argv[0] + '\'')
250                 usage(1)
251
252         del sys.argv[0]
253
254 vpn = VPN(net, pidfile)
255 vpn.connect()
256
257 class SuperListCtrl(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin):
258     def __init__(self, parent, style):
259         wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
260         ListCtrlAutoWidthMixin.__init__(self)
261         ColumnSorterMixin.__init__(self, 14)
262
263     def GetListCtrl(self):
264         return self
265
266
267 class SettingsPage(wx.Panel):
268         def OnDebugLevel(self, event):
269                 vpn.debug(self.debug.GetValue())
270
271         def __init__(self, parent, id):
272                 wx.Panel.__init__(self, parent, id)
273                 grid = wx.FlexGridSizer(cols = 2)
274                 grid.AddGrowableCol(0, 1)
275
276                 namelabel = wx.StaticText(self, -1, 'Name:')
277                 self.name = wx.TextCtrl(self, -1, vpn.name)
278                 grid.Add(namelabel)
279                 grid.Add(self.name)
280
281                 portlabel = wx.StaticText(self, -1, 'Port:')
282                 self.port = wx.TextCtrl(self, -1, vpn.port)
283                 grid.Add(portlabel)
284                 grid.Add(self.port)
285
286                 debuglabel = wx.StaticText(self, -1, 'Debug level:')
287                 self.debug = wx.SpinCtrl(self, min = 0, max = 5, initial = vpn.debug())
288                 self.debug.Bind(wx.EVT_SPINCTRL, self.OnDebugLevel)
289                 grid.Add(debuglabel)
290                 grid.Add(self.debug)
291
292                 modelabel = wx.StaticText(self, -1, 'Mode:')
293                 self.mode = wx.ComboBox(self, -1, style = wx.CB_READONLY, value = 'Router', choices = ['Router', 'Switch', 'Hub'])
294                 grid.Add(modelabel)
295                 grid.Add(self.mode)
296
297                 self.SetSizer(grid)
298
299 class ConnectionsPage(wx.Panel):
300         def __init__(self, parent, id):
301                 wx.Panel.__init__(self, parent, id)
302                 self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
303                 self.list.InsertColumn(0, 'Name')
304                 self.list.InsertColumn(1, 'Address')
305                 self.list.InsertColumn(2, 'Port')
306                 self.list.InsertColumn(3, 'Options')
307                 self.list.InsertColumn(4, 'Weight')
308
309                 hbox = wx.BoxSizer(wx.HORIZONTAL)
310                 hbox.Add(self.list, 1, wx.EXPAND)
311                 self.SetSizer(hbox)
312                 self.refresh()
313
314         class ContextMenu(wx.Menu):
315                 def __init__(self, item):
316                         wx.Menu.__init__(self)
317
318                         self.item = item
319
320                         disconnect = wx.MenuItem(self, -1, 'Disconnect')
321                         self.AppendItem(disconnect)
322                         self.Bind(wx.EVT_MENU, self.OnDisconnect, id=disconnect.GetId())
323
324                 def OnDisconnect(self, event):
325                         print('Disconnecting ' + self.item[0])
326                         vpn.disconnect(self.item[0])
327
328         def OnContext(self, event):
329                 print('Context menu!')
330                 i = event.GetIndex()
331                 print(i)
332                 self.PopupMenu(self.ContextMenu(self.list.itemDataMap[event.GetIndex()]), event.GetPosition())
333
334         def refresh(self):
335                 self.list.itemDataMap = {}
336                 i = 0
337
338                 for key, connection in vpn.connections.items():
339                         if self.list.GetItemCount() <= i:
340                                 self.list.InsertStringItem(i, connection.name)
341                         else:
342                                 self.list.SetStringItem(i, 0, connection.name)
343                         self.list.SetStringItem(i, 1, connection.address)
344                         self.list.SetStringItem(i, 2, connection.port)
345                         self.list.SetStringItem(i, 3, str(connection.options))
346                         self.list.SetStringItem(i, 4, str(connection.weight))
347                         self.list.itemDataMap[i] = (connection.name, connection.address, connection.port, connection.options, connection.weight)
348                         self.list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnContext)
349                         i += 1
350
351                 while self.list.GetItemCount() > i:
352                         self.list.DeleteItem(self.list.GetItemCount() - 1)
353
354
355 class NodesPage(wx.Panel):
356         def __init__(self, parent, id):
357                 wx.Panel.__init__(self, parent, id)
358                 self.list = SuperListCtrl(self, id)
359                 self.list.InsertColumn( 0, 'Name')
360                 self.list.InsertColumn( 1, 'Address')
361                 self.list.InsertColumn( 2, 'Port')
362                 self.list.InsertColumn( 3, 'Cipher')
363                 self.list.InsertColumn( 4, 'Digest')
364                 self.list.InsertColumn( 5, 'MACLength')
365                 self.list.InsertColumn( 6, 'Compression')
366                 self.list.InsertColumn( 7, 'Options')
367                 self.list.InsertColumn( 8, 'Status')
368                 self.list.InsertColumn( 9, 'Nexthop')
369                 self.list.InsertColumn(10, 'Via')
370                 self.list.InsertColumn(11, 'Distance')
371                 self.list.InsertColumn(12, 'PMTU')
372                 self.list.InsertColumn(13, 'Min MTU')
373                 self.list.InsertColumn(14, 'Max MTU')
374
375                 hbox = wx.BoxSizer(wx.HORIZONTAL)
376                 hbox.Add(self.list, 1, wx.EXPAND)
377                 self.SetSizer(hbox)
378                 self.refresh()
379
380         def refresh(self):
381                 self.list.itemDataMap = {}
382                 i = 0
383
384                 for key, node in vpn.nodes.items():
385                         if self.list.GetItemCount() <= i:
386                                 self.list.InsertStringItem(i, node.name)
387                         else:
388                                 self.list.SetStringItem(i,  0, node.name)
389                         self.list.SetStringItem(i,  1, node.address)
390                         self.list.SetStringItem(i,  2, node.port)
391                         self.list.SetStringItem(i,  3, str(node.cipher))
392                         self.list.SetStringItem(i,  4, str(node.digest))
393                         self.list.SetStringItem(i,  5, str(node.maclength))
394                         self.list.SetStringItem(i,  6, str(node.compression))
395                         self.list.SetStringItem(i,  7, str(node.options))
396                         self.list.SetStringItem(i,  8, str(node.status))
397                         self.list.SetStringItem(i,  9, node.nexthop)
398                         self.list.SetStringItem(i, 10, node.via)
399                         self.list.SetStringItem(i, 11, str(node.distance))
400                         self.list.SetStringItem(i, 12, str(node.pmtu))
401                         self.list.SetStringItem(i, 13, str(node.minmtu))
402                         self.list.SetStringItem(i, 14, str(node.maxmtu))
403                         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)
404                         self.list.SetItemData(i, i)
405                         i += 1
406
407                 while self.list.GetItemCount() > i:
408                         self.list.DeleteItem(self.list.GetItemCount() - 1)
409
410 class EdgesPage(wx.Panel):
411         def __init__(self, parent, id):
412                 wx.Panel.__init__(self, parent, id)
413                 self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
414                 self.list.InsertColumn(0, 'From')
415                 self.list.InsertColumn(1, 'To')
416                 self.list.InsertColumn(2, 'Address')
417                 self.list.InsertColumn(3, 'Port')
418                 self.list.InsertColumn(4, 'Options')
419                 self.list.InsertColumn(5, 'Weight')
420
421                 hbox = wx.BoxSizer(wx.HORIZONTAL)
422                 hbox.Add(self.list, 1, wx.EXPAND)
423                 self.SetSizer(hbox)
424                 self.refresh()
425
426         def refresh(self):
427                 self.list.itemDataMap = {}
428                 i = 0
429
430                 for key, edge in vpn.edges.items():
431                         if self.list.GetItemCount() <= i:
432                                 self.list.InsertStringItem(i, edge.fr)
433                         else:
434                                 self.list.SetStringItem(i, 0, edge.fr)
435                         self.list.SetStringItem(i, 1, edge.to)
436                         self.list.SetStringItem(i, 2, edge.address)
437                         self.list.SetStringItem(i, 3, edge.port)
438                         self.list.SetStringItem(i, 4, str(edge.options))
439                         self.list.SetStringItem(i, 5, str(edge.weight))
440                         self.list.itemDataMap[i] = (edge.fr, edge.to, edge.address, edge.port, edge.options, edge.weight)
441                         i += 1
442
443                 while self.list.GetItemCount() > i:
444                         self.list.DeleteItem(self.list.GetItemCount() - 1)
445
446 class SubnetsPage(wx.Panel):
447         def __init__(self, parent, id):
448                 wx.Panel.__init__(self, parent, id)
449                 self.list = SuperListCtrl(self, id)
450                 self.list.InsertColumn(0, 'Subnet', wx.LIST_FORMAT_RIGHT)
451                 self.list.InsertColumn(1, 'Weight', wx.LIST_FORMAT_RIGHT)
452                 self.list.InsertColumn(2, 'Owner')
453                 hbox = wx.BoxSizer(wx.HORIZONTAL)
454                 hbox.Add(self.list, 1, wx.EXPAND)
455                 self.SetSizer(hbox)
456                 self.refresh()
457
458         def refresh(self):
459                 self.list.itemDataMap = {}
460                 i = 0
461
462                 for key, subnet in vpn.subnets.items():
463                         if self.list.GetItemCount() <= i:
464                                 self.list.InsertStringItem(i, subnet.address + '/' + subnet.prefixlen)
465                         else:
466                                 self.list.SetStringItem(i, 0, subnet.address + '/' + subnet.prefixlen)
467                         self.list.SetStringItem(i, 1, subnet.weight)
468                         self.list.SetStringItem(i, 2, subnet.owner)
469                         self.list.itemDataMap[i] = (subnet.address + '/' + subnet.prefixlen, subnet.weight, subnet.owner)
470                         i = i + 1
471
472                 while self.list.GetItemCount() > i:
473                         self.list.DeleteItem(self.list.GetItemCount() - 1)
474
475 class StatusPage(wx.Panel):
476         def __init__(self, parent, id):
477                 wx.Panel.__init__(self, parent, id)
478
479 class GraphPage(wx.Window):
480         def __init__(self, parent, id):
481                 wx.Window.__init__(self, parent, id)
482
483 class NetPage(wx.Notebook):
484         def __init__(self, parent, id):
485                 wx.Notebook.__init__(self, parent)
486                 self.settings = SettingsPage(self, id)
487                 self.connections = ConnectionsPage(self, id)
488                 self.nodes = NodesPage(self, id)
489                 self.edges = EdgesPage(self, id)
490                 self.subnets = SubnetsPage(self, id)
491                 self.graph = GraphPage(self, id)
492                 self.status = StatusPage(self, id)
493
494                 self.AddPage(self.settings, 'Settings')
495                 #self.AddPage(self.status, 'Status')
496                 self.AddPage(self.connections, 'Connections')
497                 self.AddPage(self.nodes, 'Nodes')
498                 self.AddPage(self.edges, 'Edges')
499                 self.AddPage(self.subnets, 'Subnets')
500                 #self.AddPage(self.graph, 'Graph')
501                 
502
503 class MainWindow(wx.Frame):
504         def OnQuit(self, event):
505                 self.Close(True)
506
507         def OnTimer(self, event):
508                 vpn.refresh()
509                 self.np.nodes.refresh()
510                 self.np.subnets.refresh()
511                 self.np.edges.refresh()
512                 self.np.connections.refresh()
513
514         def __init__(self, parent, id, title):
515                 wx.Frame.__init__(self, parent, id, title)
516
517                 menubar = wx.MenuBar()
518                 file = wx.Menu()
519                 file.Append(1, '&Quit\tCtrl-X', 'Quit tinc GUI')
520                 menubar.Append(file, '&File')
521
522                 #nb = wx.Notebook(self, -1)
523                 #nb.SetPadding((0, 0))
524                 self.np = NetPage(self, -1)
525                 #nb.AddPage(np, 'VPN')
526                 
527                 self.timer = wx.Timer(self, -1)
528                 self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
529                 self.timer.Start(1000)
530                 self.Bind(wx.EVT_MENU, self.OnQuit, id=1)
531                 self.SetMenuBar(menubar)
532                 self.Show()
533
534 app = wx.App()
535 mw = MainWindow(None, -1, 'Tinc GUI')
536
537 #def OnTaskBarIcon(event):
538 #       mw.Raise()
539 #
540 #icon = wx.Icon("tincgui.ico", wx.BITMAP_TYPE_PNG)
541 #taskbaricon = wx.TaskBarIcon()
542 #taskbaricon.SetIcon(icon, 'Tinc GUI')
543 #wx.EVT_TASKBAR_RIGHT_UP(taskbaricon, OnTaskBarIcon)
544
545 app.MainLoop()
546 vpn.close()