From c83d12cf79635a14f87696fa9817840cc2f6fa8d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 26 Aug 2019 13:14:35 +0200 Subject: openvpn: T1548: add 'show openvpn' command vyos@vyos:~$ show openvpn site-to-site OpenVPN status on vtun1 Client CN Remote Host Local Host TX bytes RX bytes Connected Since --------- ----------- ---------- -------- -------- --------------- None (PSK) N/A 172.18.201.10:1195 3.3 KiB 3.3 KiB N/A vyos@vyos:~$ show openvpn server OpenVPN status on vtun10 Client CN Remote Host Local Host TX bytes RX bytes Connected Since --------- ----------- ---------- -------- -------- --------------- client1 172.18.202.10:58644 172.18.201.10:1194 63.6 KiB 63.4 KiB Mon Aug 26 11:47:56 2019 client3 172.18.204.10:52641 172.18.201.10:1194 63.1 KiB 62.7 KiB Mon Aug 26 11:47:58 2019 OpenVPN status on vtun11 Client CN Remote Host Local Host TX bytes RX bytes Connected Since --------- ----------- ---------- -------- -------- --------------- client2 172.18.203.10:39472 172.18.201.10:1200 61.2 KiB 61.5 KiB Mon Aug 26 11:50:30 2019 --- op-mode-definitions/openvpn.xml | 25 ++++++ src/op_mode/show_openvpn.py | 170 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100755 src/op_mode/show_openvpn.py diff --git a/op-mode-definitions/openvpn.xml b/op-mode-definitions/openvpn.xml index 2adbfba53..368cc9115 100644 --- a/op-mode-definitions/openvpn.xml +++ b/op-mode-definitions/openvpn.xml @@ -110,6 +110,31 @@ + + + Show OpenVPN information + + + + + Show tunnel status for OpenVPN client interfaces + + sudo ${vyos_op_scripts_dir}/show_openvpn.py --mode=client + + + + Show tunnel status for OpenVPN server interfaces + + sudo ${vyos_op_scripts_dir}/show_openvpn.py --mode=server + + + + Show tunnel status for OpenVPN site-to-site interfaces + + sudo ${vyos_op_scripts_dir}/show_openvpn.py --mode=site-to-site + + + diff --git a/src/op_mode/show_openvpn.py b/src/op_mode/show_openvpn.py new file mode 100755 index 000000000..ee3729fc3 --- /dev/null +++ b/src/op_mode/show_openvpn.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# 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, see . +# + +import jinja2 +import argparse + +from vyos.config import Config + +outp_tmpl = """ +{% if clients %} +OpenVPN status on {{ intf }} + +Client CN Remote Host Local Host TX bytes RX bytes Connected Since +--------- ----------- ---------- -------- -------- --------------- +{%- for c in clients %} +{{ "%-15s"|format(c.name) }} {{ "%-21s"|format(c.remote) }} {{ "%-15s"|format(local) }} {{ "%-9s"|format(c.tx_bytes) }} {{ "%-9s"|format(c.tx_bytes) }} {{ c.online_since }} +{%- endfor %} +{% endif %} +""" + +def bytes2HR(size): + # we need to operate in integers + size = int(size) + + suff = ['B', 'KB', 'MB', 'GB', 'TB'] + suffIdx = 0 + + while size > 1024: + # incr. suffix index + suffIdx += 1 + # divide + size = size/1024.0 + + output="{0:.1f} {1}".format(size, suff[suffIdx]) + return output + +def get_status(mode, interface): + status_file = '/opt/vyatta/etc/openvpn/status/{}.status'.format(interface) + # this is an empirical value - I assume we have no more then 999999 + # current OpenVPN connections + routing_table_line = 999999 + + data = { + 'mode': mode, + 'intf': interface, + 'local': '', + 'date': '', + 'clients': [], + } + + with open(status_file, 'r') as f: + lines = f.readlines() + for line_no, line in enumerate(lines): + # remove trailing newline character first + line = line.rstrip('\n') + + # check first line header + if line_no == 0: + if mode == 'server': + if not line == 'OpenVPN CLIENT LIST': + raise NameError('Expected "OpenVPN CLIENT LIST"') + else: + if not line == 'OpenVPN STATISTICS': + raise NameError('Expected "OpenVPN STATISTICS"') + + continue + + # second line informs us when the status file has been last updated + if line_no == 1: + data['date'] = line.lstrip('Updated,').rstrip('\n') + continue + + if mode == 'server': + # followed by line3 giving output information and the actual output data + # + # Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since + # client1,172.18.202.10:55904,2880587,2882653,Fri Aug 23 16:25:48 2019 + # client3,172.18.204.10:41328,2850832,2869729,Fri Aug 23 16:25:43 2019 + # client2,172.18.203.10:48987,2856153,2871022,Fri Aug 23 16:25:45 2019 + if (line_no >= 3) and (line_no < routing_table_line): + # indicator that there are no more clients and we will continue with the + # routing table + if line == 'ROUTING TABLE': + routing_table_line = line_no + continue + + client = { + 'name': line.split(',')[0], + 'remote': line.split(',')[1], + 'rx_bytes': bytes2HR(line.split(',')[2]), + 'tx_bytes': bytes2HR(line.split(',')[3]), + 'online_since': line.split(',')[4] + } + + data['clients'].append(client) + continue + else: + if line_no == 2: + client = { + 'name': 'N/A', + 'rx_bytes': bytes2HR(line.split(',')[1]), + 'tx_bytes': '', + 'online_since': 'N/A' + } + continue + + if line_no == 3: + client['tx_bytes'] = bytes2HR(line.split(',')[1]) + data['clients'].append(client) + break + + return data + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-m', '--mode', help='OpenVPN operation mode (server, client, site-2-site)', required=True) + + args = parser.parse_args() + + # Do nothing if service is not configured + config = Config() + if len(config.list_effective_nodes('interfaces openvpn')) == 0: + print("No OpenVPN interfaces configured") + sys.exit(0) + + # search all OpenVPN interfaces and add those with a matching mode to our + # interfaces list + interfaces = [] + for intf in config.list_effective_nodes('interfaces openvpn'): + # get interface type (server, client, site-to-site) + mode = config.return_effective_value('interfaces openvpn {} mode'.format(intf)) + if args.mode == mode: + interfaces.append(intf) + + for intf in interfaces: + data = get_status(args.mode, intf) + local_host = config.return_effective_value('interfaces openvpn {} local-host'.format(intf)) + local_port = config.return_effective_value('interfaces openvpn {} local-port'.format(intf)) + data['local'] = local_host + ':' + local_port + + if args.mode in ['client', 'site-to-site']: + for client in data['clients']: + if config.exists_effective('interfaces openvpn {} shared-secret-key-file'.format(intf)): + client['name'] = "None (PSK)" + + remote_host = config.return_effective_values('interfaces openvpn {} remote-host'.format(intf)) + remote_port = config.return_effective_value('interfaces openvpn {} remote-port'.format(intf)) + if len(remote_host) >= 1: + client['remote'] = str(remote_host[0]) + ':' + remote_port + else: + client['remote'] = 'N/A' + + + tmpl = jinja2.Template(outp_tmpl) + print(tmpl.render(data)) + -- cgit v1.2.3