diff --git a/config/rootfiles/common/aarch64/stage2 b/config/rootfiles/common/aarch64/stage2 index 82e2c20d0..77c8e97b9 100644 --- a/config/rootfiles/common/aarch64/stage2 +++ b/config/rootfiles/common/aarch64/stage2 @@ -132,6 +132,7 @@ usr/local/bin/xt_geoip_update #usr/local/share/zoneinfo #usr/local/src #usr/sbin +usr/sbin/openvpn-metrics usr/sbin/ovpn-ccd-convert usr/sbin/ovpn-collectd-convert #usr/share diff --git a/config/rootfiles/common/stage2 b/config/rootfiles/common/stage2 index 8067df39b..f56439335 100644 --- a/config/rootfiles/common/stage2 +++ b/config/rootfiles/common/stage2 @@ -132,6 +132,7 @@ usr/local/bin/xt_geoip_update #usr/local/share/zoneinfo #usr/local/src #usr/sbin +usr/sbin/openvpn-metrics usr/sbin/ovpn-ccd-convert usr/sbin/ovpn-collectd-convert #usr/share diff --git a/config/rootfiles/common/x86_64/stage2 b/config/rootfiles/common/x86_64/stage2 index 026532b8f..2197ac4ac 100644 --- a/config/rootfiles/common/x86_64/stage2 +++ b/config/rootfiles/common/x86_64/stage2 @@ -134,6 +134,7 @@ usr/local/bin/xt_geoip_update #usr/local/share/zoneinfo #usr/local/src #usr/sbin +usr/sbin/openvpn-metrics usr/sbin/ovpn-ccd-convert usr/sbin/ovpn-collectd-convert #usr/share diff --git a/html/cgi-bin/ovpnmain.cgi b/html/cgi-bin/ovpnmain.cgi index 00ecd77a0..734cc0bfa 100644 --- a/html/cgi-bin/ovpnmain.cgi +++ b/html/cgi-bin/ovpnmain.cgi @@ -372,6 +372,11 @@ sub writeserverconf { } else { print CONF "verb 3\n"; } + + print CONF "# Log clients connecting/disconnecting\n"; + print CONF "client-connect \"/usr/sbin/openvpn-metrics client-connect\"\n"; + print CONF "client-disconnect \"/usr/sbin/openvpn-metrics client-disconnect\"\n"; + # Print server.conf.local if entries exist to server.conf if ( !-z $local_serverconf && $sovpnsettings{'ADDITIONAL_CONFIGS'} eq 'on') { open (LSC, "$local_serverconf"); diff --git a/lfs/stage2 b/lfs/stage2 index d6012b85e..2863d6b69 100644 --- a/lfs/stage2 +++ b/lfs/stage2 @@ -105,6 +105,7 @@ endif done # Move script to correct place. + mv -vf /usr/local/bin/openvpn-metrics /usr/sbin/ mv -vf /usr/local/bin/ovpn-ccd-convert /usr/sbin/ mv -vf /usr/local/bin/ovpn-collectd-convert /usr/sbin/ mv -vf /usr/local/bin/captive-cleanup /usr/bin/ diff --git a/src/scripts/openvpn-metrics b/src/scripts/openvpn-metrics new file mode 100755 index 000000000..30b3932c5 --- /dev/null +++ b/src/scripts/openvpn-metrics @@ -0,0 +1,171 @@ +#!/usr/bin/python3 +############################################################################ +# # +# This file is part of the IPFire Firewall. # +# # +# IPFire 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. # +# # +# IPFire 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 IPFire; if not, write to the Free Software # +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # +# # +# Copyright (C) 2007-2020 IPFire Team . # +# # +############################################################################ + +import argparse +import logging +import logging.handlers +import os +import sqlite3 +import sys + +_ = lambda x: x + +DEFAULT_DATABASE_PATH = "/var/ipfire/ovpn/clients.db" + +def setup_logging(level=logging.INFO): + l = logging.getLogger("openvpn-metrics") + l.setLevel(level) + + # Log to console + h = logging.StreamHandler() + h.setLevel(logging.DEBUG) + l.addHandler(h) + + # Log to syslog + h = logging.handlers.SysLogHandler(address="/dev/log", + facility=logging.handlers.SysLogHandler.LOG_DAEMON) + h.setLevel(logging.INFO) + l.addHandler(h) + + # Format syslog messages + formatter = logging.Formatter("openvpn-metrics[%(process)d]: %(message)s") + h.setFormatter(formatter) + + return l + +# Initialise logging +log = setup_logging() + +class OpenVPNMetrics(object): + def __init__(self): + self.db = self._open_database() + + def parse_cli(self): + parser = argparse.ArgumentParser( + description=_("Tool that collects metrics of OpenVPN Clients"), + ) + subparsers = parser.add_subparsers() + + # client-connect + client_connect = subparsers.add_parser("client-connect", + help=_("Called when a client connects"), + ) + client_connect.add_argument("file", nargs="?", + help=_("Configuration file") + ) + client_connect.set_defaults(func=self.client_connect) + + # client-disconnect + client_disconnect = subparsers.add_parser("client-disconnect", + help=_("Called when a client disconnects"), + ) + client_disconnect.add_argument("file", nargs="?", + help=_("Configuration file") + ) + client_disconnect.set_defaults(func=self.client_disconnect) + + # Parse CLI + args = parser.parse_args() + + # Print usage if no action was given + if not "func" in args: + parser.print_usage() + sys.exit(2) + + return args + + def __call__(self): + # Parse command line arguments + args = self.parse_cli() + + # Call function + try: + ret = args.func(args) + except Exception as e: + log.critical(e) + + # Return with exit code + sys.exit(ret or 0) + + def _open_database(self, path=DEFAULT_DATABASE_PATH): + db = sqlite3.connect(path) + + # Create schema if it doesn't exist already + db.executescript(""" + CREATE TABLE IF NOT EXISTS sessions( + common_name TEXT NOT NULL, + connected_at INTEGER NOT NULL, + duration INTEGER, + bytes_received INTEGER, + bytes_sent INTEGER + ); + + -- Create index for speeding up searches + CREATE INDEX IF NOT EXISTS sessions_common_name ON sessions(common_name); + """) + + return db + + def _get_environ(self, key): + if not key in os.environ: + sys.stderr.write("%s missing from environment\n" % key) + raise SystemExit(1) + + return os.environ.get(key) + + def client_connect(self, args): + common_name = self._get_environ("common_name") + + # Time + time_ascii = self._get_environ("time_ascii") + time_unix = self._get_environ("time_unix") + + log.info("Opening session for %s at %s" % (common_name, time_ascii)) + + c = self.db.cursor() + c.execute("INSERT INTO sessions(common_name, connected_at) \ + VALUES(?, ?)", (common_name, time_unix)) + self.db.commit() + + def client_disconnect(self, args): + common_name = self._get_environ("common_name") + duration = self._get_environ("time_duration") + + # Collect some usage statistics + bytes_received = self._get_environ("bytes_received") + bytes_sent = self._get_environ("bytes_sent") + + log.info("Closing session for %s after %ss and receiving/sending %s/%s bytes" \ + % (common_name, duration, bytes_received, bytes_sent)) + + c = self.db.cursor() + c.execute("UPDATE sessions SET duration = ?, bytes_received = ?, \ + bytes_sent = ? WHERE common_name = ? AND duration IS NULL", + (duration, bytes_received, bytes_sent, common_name)) + self.db.commit() + +def main(): + m = OpenVPNMetrics() + m() + +main()