X-Git-Url: https://www.tinc-vpn.org/git/browse?p=tinc;a=blobdiff_plain;f=gui%2Ftinc-gui;h=2b4f903441435abd898f3eb51e3dcb0b3ff90c4b;hp=e5c9f76647549dea371359ef09ecec1a844ef3c8;hb=refs%2Fheads%2F1.1-brainpool;hpb=ce8775000ab38229a78ecf3dc26bab008ca0f332 diff --git a/gui/tinc-gui b/gui/tinc-gui index e5c9f766..2b4f9034 100755 --- a/gui/tinc-gui +++ b/gui/tinc-gui @@ -1,35 +1,310 @@ #!/usr/bin/python +# tinc-gui -- GUI for controlling a running tincd +# Copyright (C) 2009-2012 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import string +import socket import wx import sys -import Tinc +import os +import platform +import time from wx.lib.mixins.listctrl import ColumnSorterMixin from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin -del sys.argv[0] -net = None -controlcookie = None +if platform.system == 'Windows': + import _winreg + +# Classes to interface with a running tinc daemon + +REQ_STOP = 0 +REQ_RELOAD = 1 +REQ_RESTART = 2 +REQ_DUMP_NODES = 3 +REQ_DUMP_EDGES = 4 +REQ_DUMP_SUBNETS = 5 +REQ_DUMP_CONNECTIONS = 6 +REQ_DUMP_GRAPH = 7 +REQ_PURGE = 8 +REQ_SET_DEBUG = 9 +REQ_RETRY = 10 +REQ_CONNECT = 11 +REQ_DISCONNECT = 12 + +ID = 0 +ACK = 4 +CONTROL = 18 + +class Node: + def parse(self, args): + self.name = args[0] + self.address = args[1] + self.port = args[3] + self.cipher = int(args[4]) + self.digest = int(args[5]) + self.maclength = int(args[6]) + self.compression = int(args[7]) + self.options = int(args[8], 0x10) + self.status = int(args[9], 0x10) + self.nexthop = args[10] + self.via = args[11] + self.distance = int(args[12]) + self.pmtu = int(args[13]) + self.minmtu = int(args[14]) + self.maxmtu = int(args[15]) + self.last_state_change = float(args[16]) + + self.subnets = {} + +class Edge: + def parse(self, args): + self.fr = args[0] + self.to = args[1] + self.address = args[2] + self.port = args[4] + self.options = int(args[5], 16) + self.weight = int(args[6]) + +class Subnet: + def parse(self, args): + if args[0].find('#') >= 0: + (address, self.weight) = args[0].split('#', 1) + else: + self.weight = 10 + address = args[0] + + if address.find('/') >= 0: + (self.address, self.prefixlen) = address.split('/', 1) + else: + self.address = address + self.prefixlen = '48' + + self.owner = args[1] + +class Connection: + def parse(self, args): + self.name = args[0] + self.address = args[1] + self.port = args[3] + self.options = int(args[4], 0x10) + self.socket = int(args[5]) + self.status = int(args[6], 0x10) + self.weight = 123 + +class VPN: + confdir = '/etc/tinc' + piddir = '/var/run/' + + def connect(self): + # read the pidfile + f = open(self.pidfile) + info = string.split(f.readline()) + f.close() + + # check if there is a UNIX socket as well + if self.pidfile.endswith(".pid"): + unixfile = self.pidfile.replace(".pid", ".socket"); + else: + unixfile = self.pidfile + ".socket"; + + if os.path.exists(unixfile): + # use it if it exists + print(unixfile + " exists!"); + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(unixfile) + else: + # otherwise connect via TCP + print(unixfile + " does not exist."); + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((info[2], int(info[4]))) + + self.sf = s.makefile() + s.close() + hello = string.split(self.sf.readline()) + self.name = hello[1] + self.sf.write('0 ^' + info[1] + ' 17\r\n') + self.sf.flush() + resp = string.split(self.sf.readline()) + self.port = info[4] + self.nodes = {} + self.edges = {} + self.subnets = {} + self.connections = {} + self.refresh() + + def refresh(self): + self.sf.write('18 3\r\n18 4\r\n18 5\r\n18 6\r\n') + self.sf.flush() + + for node in self.nodes.values(): + node.visited = False + for edge in self.edges.values(): + edge.visited = False + for subnet in self.subnets.values(): + subnet.visited = False + for connections in self.connections.values(): + connections.visited = False + + while True: + resp = string.split(self.sf.readline()) + if len(resp) < 2: + break + if resp[0] != '18': + break + if resp[1] == '3': + if len(resp) < 19: + continue + node = self.nodes.get(resp[2]) or Node() + node.parse(resp[2:]) + node.visited = True + self.nodes[resp[2]] = node + elif resp[1] == '4': + if len(resp) < 9: + continue + edge = self.nodes.get((resp[2], resp[3])) or Edge() + edge.parse(resp[2:]) + edge.visited = True + self.edges[(resp[2], resp[3])] = edge + elif resp[1] == '5': + if len(resp) < 4: + continue + subnet = self.subnets.get((resp[2], resp[3])) or Subnet() + subnet.parse(resp[2:]) + subnet.visited = True + self.subnets[(resp[2], resp[3])] = subnet + self.nodes[subnet.owner].subnets[resp[2]] = subnet + elif resp[1] == '6': + if len(resp) < 9: + break + connection = self.connections.get((resp[2], resp[3], resp[5])) or Connection() + connection.parse(resp[2:]) + connection.visited = True + self.connections[(resp[2], resp[3], resp[5])] = connection + else: + break + + for key, subnet in self.subnets.items(): + if not subnet.visited: + del self.subnets[key] + + for key, edge in self.edges.items(): + if not edge.visited: + del self.edges[key] + + for key, node in self.nodes.items(): + if not node.visited: + del self.nodes[key] + else: + for key, subnet in node.subnets.items(): + if not subnet.visited: + del node.subnets[key] + + for key, connection in self.connections.items(): + if not connection.visited: + del self.connections[key] + + def close(self): + self.sf.close() + + def disconnect(self, name): + self.sf.write('18 12 ' + name + '\r\n') + self.sf.flush() + resp = string.split(self.sf.readline()) + + def debug(self, level = -1): + self.sf.write('18 9 ' + str(level) + '\r\n') + self.sf.flush() + resp = string.split(self.sf.readline()) + return int(resp[2]) + + def __init__(self, netname = None, pidfile = None): + if platform.system == 'Windows': + try: + reg = _winreg.ConnectRegistry(None, HKEY_LOCAL_MACHINE) + key = _winreg.OpenKey(reg, "SOFTWARE\\tinc") + VPN.confdir = _winreg.QueryValue(key, None) + except WindowsError: + pass + + if netname: + self.netname = netname + self.confbase = os.path.join(VPN.confdir, netname) + else: + self.confbase = VPN.confdir + + self.tincconf = os.path.join(self.confbase, 'tinc.conf') + + if pidfile != None: + self.pidfile = pidfile + else: + if platform.system == 'Windows': + self.pidfile = os.path.join(self.confbase, 'pid') + else: + if netname: + self.pidfile = os.path.join(VPN.piddir, 'tinc.' + netname + '.pid') + else: + self.pidfile = os.path.join(VPN.piddir, 'tinc.pid') + +# GUI starts here -while len(sys.argv) >= 2: +argv0 = sys.argv[0] +del sys.argv[0] +netname = None +pidfile = None + +def usage(exitcode = 0): + print('Usage: ' + argv0 + ' [options]') + print('\nValid options are:') + print(' -n, --net=NETNAME Connect to net NETNAME.') + print(' --pidfile=FILENAME Read control cookie from FILENAME.') + print(' --help Display this help and exit.') + print('\nReport bugs to tinc@tinc-vpn.org.') + sys.exit(exitcode) + +while sys.argv: if sys.argv[0] in ('-n', '--net'): - net = sys.argv[1] - elif sys.argv[0] in ('--controlcookie'): - controlcookie = sys.argv[1] + del sys.argv[0] + netname = sys.argv[0] + elif sys.argv[0] in ('--pidfile'): + del sys.argv[0] + pidfile = sys.argv[0] + elif sys.argv[0] in ('--help'): + usage(0) else: - print('Unknown option ' + sys.argv[0]) - sys.exit(1) + print(argv0 + ': unrecognized option \'' + sys.argv[0] + '\'') + usage(1) del sys.argv[0] - del sys.argv[0] -vpn = Tinc.VPN(net, controlcookie) +if netname == None: + netname = os.getenv("NETNAME") + +if netname == ".": + netname = None + +vpn = VPN(netname, pidfile) vpn.connect() class SuperListCtrl(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin): def __init__(self, parent, style): wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES) ListCtrlAutoWidthMixin.__init__(self) - ColumnSorterMixin.__init__(self, 14) + ColumnSorterMixin.__init__(self, 16) def GetListCtrl(self): return self @@ -42,12 +317,12 @@ class SettingsPage(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) grid = wx.FlexGridSizer(cols = 2) - grid.AddGrowableCol(0, 1) + grid.AddGrowableCol(1, 1) namelabel = wx.StaticText(self, -1, 'Name:') self.name = wx.TextCtrl(self, -1, vpn.name) grid.Add(namelabel) - grid.Add(self.name) + grid.Add(self.name, 1, wx.EXPAND) portlabel = wx.StaticText(self, -1, 'Port:') self.port = wx.TextCtrl(self, -1, vpn.port) @@ -70,7 +345,7 @@ class SettingsPage(wx.Panel): class ConnectionsPage(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) - self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES) + self.list = SuperListCtrl(self, id) self.list.InsertColumn(0, 'Name') self.list.InsertColumn(1, 'Address') self.list.InsertColumn(2, 'Port') @@ -93,21 +368,19 @@ class ConnectionsPage(wx.Panel): self.Bind(wx.EVT_MENU, self.OnDisconnect, id=disconnect.GetId()) def OnDisconnect(self, event): - print('Disconnecting ' + self.item[0]) vpn.disconnect(self.item[0]) def OnContext(self, event): - print('Context menu!') i = event.GetIndex() - print(i) self.PopupMenu(self.ContextMenu(self.list.itemDataMap[event.GetIndex()]), event.GetPosition()) def refresh(self): + sortstate = self.list.GetSortState() self.list.itemDataMap = {} i = 0 for key, connection in vpn.connections.items(): - if self.list.ItemCount <= i: + if self.list.GetItemCount() <= i: self.list.InsertStringItem(i, connection.name) else: self.list.SetStringItem(i, 0, connection.name) @@ -117,11 +390,13 @@ class ConnectionsPage(wx.Panel): self.list.SetStringItem(i, 4, str(connection.weight)) self.list.itemDataMap[i] = (connection.name, connection.address, connection.port, connection.options, connection.weight) self.list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnContext) + self.list.SetItemData(i, i) i += 1 - while self.list.ItemCount > i: - self.list.DeleteItem(self.list.ItemCount - 1) + while self.list.GetItemCount() > i: + self.list.DeleteItem(self.list.GetItemCount() - 1) + self.list.SortListItems(sortstate[0], sortstate[1]) class NodesPage(wx.Panel): def __init__(self, parent, id): @@ -142,6 +417,7 @@ class NodesPage(wx.Panel): self.list.InsertColumn(12, 'PMTU') self.list.InsertColumn(13, 'Min MTU') self.list.InsertColumn(14, 'Max MTU') + self.list.InsertColumn(15, 'Since') hbox = wx.BoxSizer(wx.HORIZONTAL) hbox.Add(self.list, 1, wx.EXPAND) @@ -149,11 +425,12 @@ class NodesPage(wx.Panel): self.refresh() def refresh(self): + sortstate = self.list.GetSortState() self.list.itemDataMap = {} i = 0 for key, node in vpn.nodes.items(): - if self.list.ItemCount <= i: + if self.list.GetItemCount() <= i: self.list.InsertStringItem(i, node.name) else: self.list.SetStringItem(i, 0, node.name) @@ -163,25 +440,32 @@ class NodesPage(wx.Panel): self.list.SetStringItem(i, 4, str(node.digest)) self.list.SetStringItem(i, 5, str(node.maclength)) self.list.SetStringItem(i, 6, str(node.compression)) - self.list.SetStringItem(i, 7, str(node.options)) - self.list.SetStringItem(i, 8, str(node.status)) + self.list.SetStringItem(i, 7, format(node.options, "x")) + self.list.SetStringItem(i, 8, format(node.status, "04x")) self.list.SetStringItem(i, 9, node.nexthop) self.list.SetStringItem(i, 10, node.via) self.list.SetStringItem(i, 11, str(node.distance)) self.list.SetStringItem(i, 12, str(node.pmtu)) self.list.SetStringItem(i, 13, str(node.minmtu)) self.list.SetStringItem(i, 14, str(node.maxmtu)) - 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) + if node.last_state_change: + since = time.strftime("%Y-%m-%d %H:%M", time.localtime(node.last_state_change)) + else: + since = "never" + self.list.SetStringItem(i, 15, since) + 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) self.list.SetItemData(i, i) i += 1 - while self.list.ItemCount > i: - self.list.DeleteItem(self.list.ItemCount - 1) + while self.list.GetItemCount() > i: + self.list.DeleteItem(self.list.GetItemCount() - 1) + + self.list.SortListItems(sortstate[0], sortstate[1]) class EdgesPage(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) - self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES) + self.list = SuperListCtrl(self, id) self.list.InsertColumn(0, 'From') self.list.InsertColumn(1, 'To') self.list.InsertColumn(2, 'Address') @@ -195,24 +479,28 @@ class EdgesPage(wx.Panel): self.refresh() def refresh(self): + sortstate = self.list.GetSortState() self.list.itemDataMap = {} i = 0 for key, edge in vpn.edges.items(): - if self.list.ItemCount <= i: + if self.list.GetItemCount() <= i: self.list.InsertStringItem(i, edge.fr) else: self.list.SetStringItem(i, 0, edge.fr) self.list.SetStringItem(i, 1, edge.to) self.list.SetStringItem(i, 2, edge.address) self.list.SetStringItem(i, 3, edge.port) - self.list.SetStringItem(i, 4, str(edge.options)) + self.list.SetStringItem(i, 4, format(edge.options, "x")) self.list.SetStringItem(i, 5, str(edge.weight)) self.list.itemDataMap[i] = (edge.fr, edge.to, edge.address, edge.port, edge.options, edge.weight) + self.list.SetItemData(i, i) i += 1 - while self.list.ItemCount > i: - self.list.DeleteItem(self.list.ItemCount - 1) + while self.list.GetItemCount() > i: + self.list.DeleteItem(self.list.GetItemCount() - 1) + + self.list.SortListItems(sortstate[0], sortstate[1]) class SubnetsPage(wx.Panel): def __init__(self, parent, id): @@ -227,21 +515,25 @@ class SubnetsPage(wx.Panel): self.refresh() def refresh(self): + sortstate = self.list.GetSortState() self.list.itemDataMap = {} i = 0 for key, subnet in vpn.subnets.items(): - if self.list.ItemCount <= i: + if self.list.GetItemCount() <= i: self.list.InsertStringItem(i, subnet.address + '/' + subnet.prefixlen) else: self.list.SetStringItem(i, 0, subnet.address + '/' + subnet.prefixlen) self.list.SetStringItem(i, 1, subnet.weight) self.list.SetStringItem(i, 2, subnet.owner) self.list.itemDataMap[i] = (subnet.address + '/' + subnet.prefixlen, subnet.weight, subnet.owner) - i = i + 1 + self.list.SetItemData(i, i) + i += 1 + + while self.list.GetItemCount() > i: + self.list.DeleteItem(self.list.GetItemCount() - 1) - while self.list.ItemCount > i: - self.list.DeleteItem(self.list.ItemCount - 1) + self.list.SortListItems(sortstate[0], sortstate[1]) class StatusPage(wx.Panel): def __init__(self, parent, id): @@ -273,7 +565,7 @@ class NetPage(wx.Notebook): class MainWindow(wx.Frame): def OnQuit(self, event): - self.Close(True) + app.ExitMainLoop() def OnTimer(self, event): vpn.refresh()