diff --git a/src/etc/inc/plugins.inc.d/openvpn.inc b/src/etc/inc/plugins.inc.d/openvpn.inc index c8cd93d5e..f15c9c8cf 100644 --- a/src/etc/inc/plugins.inc.d/openvpn.inc +++ b/src/etc/inc/plugins.inc.d/openvpn.inc @@ -1150,223 +1150,35 @@ function openvpn_configure_do($verbose = false, $interface = '', $carp_event = f service_log("done.\n", $verbose); } -function openvpn_get_active_servers($type = 'multipoint') + +function openvpn_config() { global $config; - - $servers = array(); - - if (!empty($config['openvpn']['openvpn-server'])) { - foreach ($config['openvpn']['openvpn-server'] as $settings) { - if (empty($settings) || isset($settings['disable'])) { - continue; - } - - $prot = $settings['protocol']; - $port = $settings['local_port']; - - $server = array(); - $server['port'] = ($settings['local_port']) ? $settings['local_port'] : 1194; - $server['mode'] = $settings['mode']; - if ($settings['description']) { - $server['name'] = "{$settings['description']} {$prot}:{$port}"; - } else { - $server['name'] = "Server {$prot}:{$port}"; - } - $server['conns'] = array(); - $server['vpnid'] = $settings['vpnid']; - $server['mgmt'] = "server{$server['vpnid']}"; - $socket = "unix:///var/etc/openvpn/{$server['mgmt']}.sock"; - list($tn, $sm) = explode('/', $settings['tunnel_network']); - - if ((($server['mode'] == "p2p_shared_key") || ($sm >= 30) ) && ($type == "p2p")) { - $servers[] = openvpn_get_client_status($server, $socket); - } elseif (($server['mode'] != "p2p_shared_key") && ($type == "multipoint") && ($sm < 30)) { - $servers[] = openvpn_get_server_status($server, $socket); - } - } - } - - return $servers; -} - -function openvpn_get_server_status($server, $socket) -{ - $errval = 0; - $errstr = ''; - $fp = @stream_socket_client($socket, $errval, $errstr, 1); - if ($fp) { - stream_set_timeout($fp, 1); - - /* send our status request */ - fputs($fp, "status 3\n"); - - /* recv all response lines */ - while (!feof($fp)) { - /* read the next line */ - $line = fgets($fp, 1024); - $info = stream_get_meta_data($fp); - if ($info['timed_out']) { - break; - } - /* parse header list line */ - if (strstr($line, "HEADER")) { - continue; - } - /* parse end of output line */ - if (strstr($line, "END") || strstr($line, "ERROR")) { - break; - } - /* parse client list line */ - if (strstr($line, "CLIENT_LIST")) { - $list = explode("\t", $line); - $conn = array(); - $conn['common_name'] = $list[1]; - $conn['remote_host'] = $list[2]; - $conn['virtual_addr'] = $list[3]; - $conn['bytes_recv'] = $list[5]; - $conn['bytes_sent'] = $list[6]; - $conn['connect_time'] = date('Y-m-d H:i:s', $list[8]); - $server['conns'][] = $conn; - } - /* parse routing table lines */ - if (strstr($line, "ROUTING_TABLE")) { - $list = explode("\t", $line); - $conn = array(); - $conn['virtual_addr'] = $list[1]; - $conn['common_name'] = $list[2]; - $conn['remote_host'] = $list[3]; - $conn['last_time'] = $list[4]; - $server['routes'][] = $conn; - } - } - /* cleanup */ - fclose($fp); - } else { - $conn = array(); - $conn['common_name'] = '[error]'; // kind of a marker value now - $conn['remote_host'] = gettext('Unable to contact daemon'); - $conn['virtual_addr'] = gettext('Service not running?'); - $conn['bytes_recv'] = 0; - $conn['bytes_sent'] = 0; - $conn['connect_time'] = 0; - $server['conns'][] = $conn; - } - return $server; -} - -function openvpn_get_active_clients() -{ - global $config; - - $clients = array(); - - if (!empty($config['openvpn']['openvpn-client'])) { - foreach ($config['openvpn']['openvpn-client'] as $settings) { - if (empty($settings) || isset($settings['disable'])) { - continue; - } - - $prot = $settings['protocol']; - $port = ($settings['local_port']) ? ":{$settings['local_port']}" : ""; - - $client = array(); - $client['port'] = $settings['local_port']; - if ($settings['description']) { - $client['name'] = "{$settings['description']} {$prot}{$port}"; - } else { - $client['name'] = "Client {$prot}{$port}"; - } - - $client['vpnid'] = $settings['vpnid']; - $client['mgmt'] = "client{$client['vpnid']}"; - $socket = "unix:///var/etc/openvpn/{$client['mgmt']}.sock"; - $client['status'] = 'down'; - $clients[] = openvpn_get_client_status($client, $socket); - } - } - - return $clients; -} - -function openvpn_get_client_status($client, $socket) -{ - $errval = 0; - $errstr = ''; - $fp = @stream_socket_client($socket, $errval, $errstr, 1); - if ($fp) { - stream_set_timeout($fp, 1); - /* send our status request */ - fputs($fp, "state all\n"); - - /* recv all response lines */ - while (!feof($fp)) { - /* read the next line */ - $line = fgets($fp, 1024); - - $info = stream_get_meta_data($fp); - if ($info['timed_out']) { - break; - } - - /* Get the client state */ - $list = explode(",", $line); - if (count($list) > 1) { - $client['connect_time'] = date('Y-m-d H:i:s', $list[0]); - } - if (strstr($line, 'CONNECTED')) { - $client['status'] = 'up'; - $client['virtual_addr'] = $list[3]; - $client['remote_host'] = $list[4]; - } elseif (strstr($line, 'CONNECTING')) { - $client['status'] = 'connecting'; - } elseif (strstr($line, "ASSIGN_IP")) { - $client['status'] = "waiting"; - $client['virtual_addr'] = $list[3]; - } elseif (strstr($line, "RECONNECTING")) { - $client['status'] = "reconnecting"; - $client['status'] .= "; " . $list[2]; - } elseif (strstr($line, "END") || strstr($line, "ERROR")) { - /* parse end of output line */ - break; - } - } - - /* If up, get read/write stats */ - if (strcmp($client['status'], "up") == 0) { - fputs($fp, "status 2\n"); - /* recv all response lines */ - while (!feof($fp)) { - /* read the next line */ - $line = fgets($fp, 1024); - - $info = stream_get_meta_data($fp); - if ($info['timed_out']) { - break; + $result = []; + foreach (['openvpn-server', 'openvpn-client'] as $section) { + $result[$section] = []; + if (!empty($config['openvpn'][$section])) { + foreach ($config['openvpn'][$section] as $settings) { + if (empty($settings) || isset($settings['disable'])) { + continue; } - - $list = explode(",", $line); - if (strstr($line, "TCP/UDP read bytes")) { - $client['bytes_recv'] = $list[1]; - } elseif (strstr($line, "TCP/UDP write bytes")) { - $client['bytes_sent'] = $list[1]; - } elseif (strstr($line, "END")) { - /* parse end of output line */ - break; + $server = []; + $default_port = ($section == 'openvpn-server') ? 1194 : ''; + $server['port'] = ($settings['local_port']) ? $settings['local_port'] : $default_port; + $server['mode'] = $settings['mode']; + if (empty($settings['description'])) { + $settings['description'] = ($section == 'openvpn-server') ? 'Server' : 'Client'; } + $server['name'] = "{$settings['description']} {$settings['protocol']}:{$settings['local_port']}"; + $server['vpnid'] = $settings['vpnid']; + $result[$section][] = $server; } } - fclose($fp); - } else { - $client['remote_host'] = gettext('Unable to contact daemon'); - $client['virtual_addr'] = gettext('Service not running?'); - $client['bytes_recv'] = 0; - $client['bytes_sent'] = 0; - $client['connect_time'] = 0; } - return $client; + return $result; } + function openvpn_create_dirs() { @mkdir('/var/etc/openvpn-csc', 0750); diff --git a/src/opnsense/scripts/openvpn/ovpn_status.py b/src/opnsense/scripts/openvpn/ovpn_status.py new file mode 100755 index 000000000..37b96a5ce --- /dev/null +++ b/src/opnsense/scripts/openvpn/ovpn_status.py @@ -0,0 +1,138 @@ +#!/usr/local/bin/python3 + +""" + Copyright (c) 2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" + +import argparse +import glob +import socket +import re +import os +import ujson +socket.setdefaulttimeout(5) + + +def ovpn_cmd(filename, cmd): + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(filename) + except socket.error: + return None + + sock.send(('%s\n'%cmd).encode()) + buffer = '' + while True: + try: + buffer += sock.recv(65536).decode() + except socket.timeout: + break + eob = buffer[-20:] + if eob.find('END') > -1 or eob.find('ERROR') > -1: + break + sock.close() + return buffer + + +def ovpn_status(filename): + response = {} + buffer = ovpn_cmd(filename, 'status 3') + if buffer is None: + return {'status': 'failed'} + + header_def = [] + target_struct = None + for line in buffer.split('\n'): + if line.startswith('TCP/UDP'): + line = line.split(',') + response[line[0][8:].replace(' ', '_')] = line[1].strip() + continue + line = line.split('\t') + if line[0] == 'HEADER': + header_def = [] + for item in line[1:]: + header_def.append(re.sub('[\ \(\)]', '_', item.lower().strip())) + target_struct = header_def.pop(0).lower() if len(header_def) > 0 else None + response['status'] = 'ok' + elif target_struct is not None and line[0] in ['CLIENT_LIST', 'ROUTING_TABLE']: + line = line[1:] + record = {} + for i in range(len(header_def)): + record[header_def[i]] = line[i].strip() + if target_struct not in response: + response[target_struct] = [] + response[target_struct].append(record) + + return response + + +def ovpn_state(filename): + response = {'status': 'failed'} + buffer = ovpn_cmd(filename, 'state') + if buffer is None: + return response + + for line in buffer.split('\n'): + tmp = line.split(',') + if len(tmp) > 2 and tmp[0].isdigit(): + response['timestamp'] = int(tmp[0]) + response['status'] = tmp[1].lower() + response['virtual_addr'] = tmp[3] if len(tmp) > 3 else "" + response['remote_host'] = tmp[4] if len(tmp) > 4 else "" + + return response + + +def main(params): + response = {} + for filename in glob.glob('/var/etc/openvpn/*.sock'): + bname = os.path.basename(filename)[:-5] + this_id = bname[6:] + if bname.startswith('server') and 'server' in params.options: + if 'server' not in response: + response['server'] = {} + response['server'][this_id] = ovpn_status(filename) + if 'status' not in response['server'][this_id]: + # p2p mode, no client_list or routing_table + response['server'][this_id].update(ovpn_state(filename)) + elif bname.startswith('client') and 'client' in params.options: + if 'client' not in response: + response['client'] = {} + response['client'][this_id] = ovpn_state(filename) + if response['client'][this_id]['status'] != 'failed': + response['client'][this_id].update(ovpn_status(filename)) + + return response + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument( + '--options', + help='request status from client,server (comma separated)', + type=lambda x: x.split(','), + default='server' + ) + print(ujson.dumps(main(parser.parse_args()))) diff --git a/src/opnsense/service/conf/actions.d/actions_openvpn.conf b/src/opnsense/service/conf/actions.d/actions_openvpn.conf new file mode 100644 index 000000000..6e2e5b90e --- /dev/null +++ b/src/opnsense/service/conf/actions.d/actions_openvpn.conf @@ -0,0 +1,5 @@ +[status] +command:/usr/local/opnsense/scripts/openvpn/ovpn_status.py +parameters: --option %s +type:script_output +message:Query OpenVPN status (%s) diff --git a/src/www/status_openvpn.php b/src/www/status_openvpn.php index 125a66c7c..19d260656 100644 --- a/src/www/status_openvpn.php +++ b/src/www/status_openvpn.php @@ -1,8 +1,8 @@ - * Copyright (C) 2014-2015 Deciso B.V. * Copyright (C) 2010 Jim Pingle * Copyright (C) 2008 Shrew Soft Inc. * Copyright (C) 2005 Scott Ullrich @@ -91,12 +91,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { } } -$servers = openvpn_get_active_servers(); -legacy_html_escape_form_data($servers); -$sk_servers = openvpn_get_active_servers("p2p"); -legacy_html_escape_form_data($sk_servers); -$clients = openvpn_get_active_clients(); -legacy_html_escape_form_data($clients); +$openvpn_status = json_decode(configd_run('openvpn status client,server'), true) ?? []; +$openvpn_cfg = openvpn_config(); +legacy_html_escape_form_data($openvpn_status); +legacy_html_escape_form_data($openvpn_cfg); include("head.inc"); ?> @@ -136,7 +134,7 @@ include("head.inc"); ?>
- $server): ?> + $server): ?>
@@ -158,27 +156,42 @@ include("head.inc"); ?> - + - + + + - - - - - "> - - - - + + + + - + + + + + + + + + + + + + + + + + - + - +"> - - - + + + @@ -222,48 +235,8 @@ include("head.inc"); ?> - -
-
-
+ +
- - @@ -187,7 +200,7 @@ include("head.inc"); ?>
@@ -203,12 +216,12 @@ include("head.inc"); ?>
- - - - - - - - - - - - - - - - "> - - - - - - - - - - - -
-
- $sk_server['vpnid'])); ?> - - -
-
-
-
- - + +
@@ -281,15 +254,16 @@ include("head.inc"); ?> - + +"> - - - - - - + + + + + +
$client['vpnid'])); ?> @@ -304,7 +278,7 @@ include("head.inc"); ?>
- +
diff --git a/src/www/widgets/widgets/openvpn.widget.php b/src/www/widgets/widgets/openvpn.widget.php index f801c5749..f820945a2 100644 --- a/src/www/widgets/widgets/openvpn.widget.php +++ b/src/www/widgets/widgets/openvpn.widget.php @@ -1,7 +1,7 @@ &$ovpncfg) { + foreach ($ovpncfg as &$item) { + $opt = ($section == 'openvpn-server') ? 'server' : 'client'; + if (!empty($openvpn_status[$opt][$item['vpnid']])) { + $item = array_merge($openvpn_status[$opt][$item['vpnid']], $item); + } + } +} ?> + foreach ($openvpn_cfg['openvpn-server'] as $server) :?>
@@ -65,57 +71,39 @@ $clients = openvpn_get_active_clients(); + if (!empty($server['client_list'])): + foreach ($server['client_list'] as $conn) :?> - - + + + endforeach; + elseif (!empty($server['timestamp'])):?> + + + + + +




- '> + title='Kill client connection from '>

+ '> +

+ - - - - - - - - - - - - - - - - - - - - -


- '> -
-
- - + if (!empty($openvpn_cfg['openvpn-server'])) {?> @@ -130,12 +118,12 @@ endif; ?> +foreach ($openvpn_cfg['openvpn-client'] as $client) :?> - + ".gettext('NOTE:')." ".gettext("You need to bind each OpenVPN client to enable its management daemon: use 'Local port' setting in the OpenVPN client screen"); -} - -if ((empty($clients)) && (empty($servers)) && (empty($sk_servers))): ?> +if (empty($openvpn_cfg['openvpn-client']) && empty($openvpn_cfg['openvpn-server'])): ?>



- '> + '>