]> git.ipfire.org Git - ipfire-2.x.git/commitdiff
openvpn: Add metrics script
authorMichael Tremer <michael.tremer@ipfire.org>
Mon, 13 Apr 2020 11:50:17 +0000 (11:50 +0000)
committerArne Fitzenreiter <arne_f@ipfire.org>
Fri, 1 May 2020 19:18:00 +0000 (19:18 +0000)
This script is called when an OpenVPN Roadwarrior client
connects or disconnect and logs the start and duration
of the session.

This can be used to monitor session duration and data transfer.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Signed-off-by: Arne Fitzenreiter <arne_f@ipfire.org>
config/rootfiles/common/aarch64/stage2
config/rootfiles/common/stage2
config/rootfiles/common/x86_64/stage2
html/cgi-bin/ovpnmain.cgi
lfs/stage2
src/scripts/openvpn-metrics [new file with mode: 0755]

index 82e2c20d0ded3814b5ec6c37d38ef1185da2faec..77c8e97b9a44ac139a1811f1bfe053322abcf071 100644 (file)
@@ -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
index 8067df39b3968be3934ce09e365a6942792ba180..f5643933577ad9af6d0d5755735a4714866ab6d3 100644 (file)
@@ -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
index 026532b8f1c5a23530528b6fdc80c877166139f3..2197ac4aca5131e6977d3744e662cae14ea54b97 100644 (file)
@@ -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
index 00ecd77a02352b7a93a18f355142c36966f59cab..734cc0bfac9596ba06af0f0e83d6a6581739aa42 100644 (file)
@@ -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");
index d6012b85e5dadcb86e0cdd6b009a65a4a6efacc0..2863d6b697590bce5e6755302412af0df646e3b6 100644 (file)
@@ -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 (executable)
index 0000000..30b3932
--- /dev/null
@@ -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 <info@ipfire.org>.                   #
+#                                                                          #
+############################################################################
+
+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()