]>
Commit | Line | Data |
---|---|---|
708f2b73 MT |
1 | #!/usr/bin/python3 |
2 | ############################################################################ | |
3 | # # | |
4 | # This file is part of the IPFire Firewall. # | |
5 | # # | |
6 | # IPFire is free software; you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation; either version 2 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # IPFire is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with IPFire; if not, write to the Free Software # | |
18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # | |
19 | # # | |
20 | # Copyright (C) 2007-2020 IPFire Team <info@ipfire.org>. # | |
21 | # # | |
22 | ############################################################################ | |
23 | ||
24 | import argparse | |
25 | import logging | |
26 | import logging.handlers | |
27 | import os | |
28 | import sqlite3 | |
29 | import sys | |
30 | ||
31 | _ = lambda x: x | |
32 | ||
33 | DEFAULT_DATABASE_PATH = "/var/ipfire/ovpn/clients.db" | |
34 | ||
35 | def setup_logging(level=logging.INFO): | |
36 | l = logging.getLogger("openvpn-metrics") | |
37 | l.setLevel(level) | |
38 | ||
39 | # Log to console | |
40 | h = logging.StreamHandler() | |
41 | h.setLevel(logging.DEBUG) | |
42 | l.addHandler(h) | |
43 | ||
44 | # Log to syslog | |
45 | h = logging.handlers.SysLogHandler(address="/dev/log", | |
46 | facility=logging.handlers.SysLogHandler.LOG_DAEMON) | |
47 | h.setLevel(logging.INFO) | |
48 | l.addHandler(h) | |
49 | ||
50 | # Format syslog messages | |
51 | formatter = logging.Formatter("openvpn-metrics[%(process)d]: %(message)s") | |
52 | h.setFormatter(formatter) | |
53 | ||
54 | return l | |
55 | ||
56 | # Initialise logging | |
57 | log = setup_logging() | |
58 | ||
59 | class OpenVPNMetrics(object): | |
60 | def __init__(self): | |
61 | self.db = self._open_database() | |
62 | ||
63 | def parse_cli(self): | |
64 | parser = argparse.ArgumentParser( | |
65 | description=_("Tool that collects metrics of OpenVPN Clients"), | |
66 | ) | |
67 | subparsers = parser.add_subparsers() | |
68 | ||
69 | # client-connect | |
70 | client_connect = subparsers.add_parser("client-connect", | |
71 | help=_("Called when a client connects"), | |
72 | ) | |
73 | client_connect.add_argument("file", nargs="?", | |
74 | help=_("Configuration file") | |
75 | ) | |
76 | client_connect.set_defaults(func=self.client_connect) | |
77 | ||
78 | # client-disconnect | |
79 | client_disconnect = subparsers.add_parser("client-disconnect", | |
80 | help=_("Called when a client disconnects"), | |
81 | ) | |
82 | client_disconnect.add_argument("file", nargs="?", | |
83 | help=_("Configuration file") | |
84 | ) | |
85 | client_disconnect.set_defaults(func=self.client_disconnect) | |
86 | ||
87 | # Parse CLI | |
88 | args = parser.parse_args() | |
89 | ||
90 | # Print usage if no action was given | |
91 | if not "func" in args: | |
92 | parser.print_usage() | |
93 | sys.exit(2) | |
94 | ||
95 | return args | |
96 | ||
97 | def __call__(self): | |
98 | # Parse command line arguments | |
99 | args = self.parse_cli() | |
100 | ||
101 | # Call function | |
102 | try: | |
103 | ret = args.func(args) | |
104 | except Exception as e: | |
105 | log.critical(e) | |
106 | ||
107 | # Return with exit code | |
108 | sys.exit(ret or 0) | |
109 | ||
110 | def _open_database(self, path=DEFAULT_DATABASE_PATH): | |
111 | db = sqlite3.connect(path) | |
112 | ||
113 | # Create schema if it doesn't exist already | |
114 | db.executescript(""" | |
115 | CREATE TABLE IF NOT EXISTS sessions( | |
116 | common_name TEXT NOT NULL, | |
616de0b4 MT |
117 | connected_at TEXT NOT NULL, |
118 | disconnected_at TEXT, | |
708f2b73 MT |
119 | bytes_received INTEGER, |
120 | bytes_sent INTEGER | |
121 | ); | |
122 | ||
123 | -- Create index for speeding up searches | |
124 | CREATE INDEX IF NOT EXISTS sessions_common_name ON sessions(common_name); | |
125 | """) | |
126 | ||
127 | return db | |
128 | ||
129 | def _get_environ(self, key): | |
130 | if not key in os.environ: | |
131 | sys.stderr.write("%s missing from environment\n" % key) | |
132 | raise SystemExit(1) | |
133 | ||
134 | return os.environ.get(key) | |
135 | ||
136 | def client_connect(self, args): | |
137 | common_name = self._get_environ("common_name") | |
138 | ||
139 | # Time | |
140 | time_ascii = self._get_environ("time_ascii") | |
141 | time_unix = self._get_environ("time_unix") | |
142 | ||
143 | log.info("Opening session for %s at %s" % (common_name, time_ascii)) | |
144 | ||
145 | c = self.db.cursor() | |
146 | c.execute("INSERT INTO sessions(common_name, connected_at) \ | |
616de0b4 | 147 | VALUES(?, DATETIME(?, 'unixepoch'))", (common_name, time_unix)) |
708f2b73 MT |
148 | self.db.commit() |
149 | ||
150 | def client_disconnect(self, args): | |
151 | common_name = self._get_environ("common_name") | |
152 | duration = self._get_environ("time_duration") | |
153 | ||
154 | # Collect some usage statistics | |
155 | bytes_received = self._get_environ("bytes_received") | |
156 | bytes_sent = self._get_environ("bytes_sent") | |
157 | ||
158 | log.info("Closing session for %s after %ss and receiving/sending %s/%s bytes" \ | |
159 | % (common_name, duration, bytes_received, bytes_sent)) | |
160 | ||
161 | c = self.db.cursor() | |
616de0b4 MT |
162 | c.execute("UPDATE sessions SET disconnected_at = DATETIME(connected_at, '+' || ? || ' seconds'), \ |
163 | bytes_received = ?, bytes_sent = ? \ | |
164 | WHERE common_name = ? AND disconnected_at IS NULL", | |
708f2b73 MT |
165 | (duration, bytes_received, bytes_sent, common_name)) |
166 | self.db.commit() | |
167 | ||
168 | def main(): | |
169 | m = OpenVPNMetrics() | |
170 | m() | |
171 | ||
172 | main() |