Migrate to Python 3
[collecty.git] / src / collecty / plugins / conntrack.py
1 #!/usr/bin/python3
2 ###############################################################################
3 #                                                                             #
4 # collecty - A system statistics collection daemon for IPFire                 #
5 # Copyright (C) 2015 IPFire development team                                  #
6 #                                                                             #
7 # This program is free software: you can redistribute it and/or modify        #
8 # it under the terms of the GNU General Public License as published by        #
9 # the Free Software Foundation, either version 3 of the License, or           #
10 # (at your option) any later version.                                         #
11 #                                                                             #
12 # This program is distributed in the hope that it will be useful,             #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of              #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
15 # GNU General Public License for more details.                                #
16 #                                                                             #
17 # You should have received a copy of the GNU General Public License           #
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
19 #                                                                             #
20 ###############################################################################
21
22 import os
23
24 from . import base
25
26 from ..i18n import _
27
28 CONNTRACK_FILE = "/proc/net/nf_conntrack"
29
30 class ConntrackTable(object):
31         _layer3_protocols = (
32                 "ipv6",
33                 "ipv4",
34                 "other",
35         )
36
37         _layer4_protocols = (
38                 "dccp",
39                 "icmp",
40                 "igmp",
41                 "sctp",
42                 "tcp",
43                 "udp",
44                 "udplite",
45                 "other",
46         )
47
48         _stateful_layer4_protocols = {
49                 "dccp" : (
50                         "CLOSEREQ",
51                         "CLOSING",
52                         "IGNORE",
53                         "INVALID",
54                         "NONE",
55                         "OPEN",
56                         "PARTOPEN",
57                         "REQUEST",
58                         "RESPOND",
59                         "TIME_WAIT",
60                 ),
61                 "sctp" : (
62                         "CLOSED",
63                         "COOKIE_ECHOED",
64                         "COOKIE_WAIT",
65                         "ESTABLISHED",
66                         "NONE",
67                         "SHUTDOWN_ACK_SENT",
68                         "SHUTDOWN_RECD",
69                         "SHUTDOWN_SENT",
70                 ),
71                 "tcp" : (
72                         "CLOSE",
73                         "CLOSE_WAIT",
74                         "ESTABLISHED",
75                         "FIN_WAIT",
76                         "LAST_ACK",
77                         "NONE",
78                         "SYN_RECV",
79                         "SYN_SENT",
80                         "SYN_SENT2",
81                         "TIME_WAIT",
82                 ),
83         }
84
85         def __init__(self, filename):
86                 with open(filename) as f:
87                         self.layer3_protocols = {}
88                         for proto in self._layer3_protocols:
89                                 self.layer3_protocols[proto] = 0
90
91                         self.layer4_protocols = {}
92                         for proto in self._layer4_protocols:
93                                 self.layer4_protocols[proto] = 0
94
95                         self.protocol_states = {}
96                         for proto, states in self._stateful_layer4_protocols.items():
97                                 self.protocol_states[proto] = dict((state, 0) for state in states)
98
99                         for line in f.readlines():
100                                 line = line.split()
101
102                                 # Layer 3 protocol
103                                 layer3_protocol = line[0]
104
105                                 try:
106                                         self.layer3_protocols[layer3_protocol] += 1
107                                 except KeyError:
108                                         self.layer3_protocols["other"] += 1
109
110                                 # Layer 4 protocol
111                                 layer4_protocol = line[2]
112
113                                 try:
114                                         self.layer4_protocols[layer4_protocol] += 1
115                                 except KeyError:
116                                         self.layer4_protocols["other"] += 1
117                                         layer4_protocol = "other"
118
119                                 # Count connection states
120                                 if layer4_protocol in self.protocol_states:
121                                         state = line[5]
122
123                                         try:
124                                                 self.protocol_states[layer4_protocol][state] += 1
125                                         except KeyError:
126                                                 pass
127
128
129 class ConntrackLayer3ProtocolsGraphTemplate(base.GraphTemplate):
130         name = "conntrack-layer3-protocols"
131
132         protocols = ConntrackTable._layer3_protocols
133
134         protocol_colours = {
135                 "ipv6"  : "#cc0033",
136                 "ipv4"  : "#cccc33",
137         }
138
139         protocol_descriptions = {
140                 "ipv6"  : _("IPv6"),
141                 "ipv4"  : _("IPv4"),
142                 "other" : _("Other"),
143         }
144
145         @property
146         def graph_title(self):
147                 return _("Connections by Layer 3 Protocols")
148
149         @property
150         def graph_vertical_label(self):
151                 return _("Number of open connections")
152
153         def get_object_table(self, object_id):
154                 return {
155                         "file" : self.plugin.get_object("layer3-protocols"),
156                 }
157
158         @property
159         def rrd_graph(self):
160                 args = []
161
162                 for proto in reversed(self.protocols):
163                         i = {
164                                 "colour"      : self.protocol_colours.get(proto, "#000000"),
165                                 "description" : self.protocol_descriptions.get(proto, proto),
166                                 "proto"       : proto,
167                                 "type"        : type,
168
169                                 "legend_min"  : "%10s\: %%8.0lf" % _("Minimum"),
170                                 "legend_max"  : "%10s\: %%8.0lf" % _("Maximum"),
171                                 "legend_avg"  : "%10s\: %%8.0lf" % _("Average"),
172                                 "legend_cur"  : "%10s\: %%8.0lf" % _("Current"),
173                         }
174
175                         args += [
176                                 "DEF:%(proto)s=%%(file)s:%(proto)s:AVERAGE" % i,
177                                 "AREA:%(proto)s%(colour)s:%(description)-15s:STACK" % i,
178                                 "VDEF:%(proto)s_cur=%(proto)s,LAST" % i,
179                                 "GPRINT:%(proto)s_cur:%(legend_cur)s" % i,
180                                 "VDEF:%(proto)s_avg=%(proto)s,AVERAGE" % i,
181                                 "GPRINT:%(proto)s_avg:%(legend_avg)s" % i,
182                                 "VDEF:%(proto)s_min=%(proto)s,MINIMUM" % i,
183                                 "GPRINT:%(proto)s_min:%(legend_min)s" % i,
184                                 "VDEF:%(proto)s_max=%(proto)s,MAXIMUM" % i,
185                                 "GPRINT:%(proto)s_max:%(legend_max)s\\n" % i,
186                         ]
187
188                 return args
189
190         @property
191         def rrd_graph_args(self):
192                 return [
193                         "--legend-direction=bottomup",
194                 ]
195
196
197 class ConntrackLayer4ProtocolsGraphTemplate(ConntrackLayer3ProtocolsGraphTemplate):
198         name = "conntrack-layer4-protocols"
199
200         protocol_colours = {
201                 "tcp"     : "#336600",
202                 "udp"     : "#666633",
203                 "icmp"    : "#336666",
204                 "igmp"    : "#666699",
205                 "udplite" : "#3366cc",
206                 "sctp"    : "#6666ff",
207                 "dccp"    : "#33cc00",
208         }
209
210         protocol_descriptions = {
211                 "tcp"     : _("TCP"),
212                 "udp"     : _("UDP"),
213                 "icmp"    : _("ICMP"),
214                 "igmp"    : _("IGMP"),
215                 "udplite" : _("UDP Lite"),
216                 "sctp"    : _("SCTP"),
217                 "dccp"    : _("DCCP"),
218                 "other"   : _("Other"),
219         }
220
221         protocol_sortorder = {
222                 "tcp"     : 1,
223                 "udp"     : 2,
224                 "icmp"    : 3,
225                 "igmp"    : 4,
226                 "udplite" : 5,
227                 "sctp"    : 6,
228                 "dccp"    : 7,
229         }
230
231         @property
232         def graph_title(self):
233                 return _("Connections by IP Protocols")
234
235         @property
236         def protocols(self):
237                 return sorted(ConntrackTable._layer4_protocols,
238                         key=lambda x: self.protocol_sortorder.get(x, 99))
239
240         def get_object_table(self, object_id):
241                 return {
242                         "file" : self.plugin.get_object("layer4-protocols"),
243                 }
244
245
246
247 class ConntrackProtocolWithStatesGraphTemplate(base.GraphTemplate):
248         name = "conntrack-protocol-states"
249
250         lower_limit = 0
251
252         states_colours = {
253                 "dccp" : {
254                         "CLOSEREQ"          : "#000000",
255                         "CLOSING"           : "#111111",
256                         "IGNORE"            : "#222222",
257                         "INVALID"           : "#333333",
258                         "NONE"              : "#444444",
259                         "OPEN"              : "#555555",
260                         "PARTOPEN"          : "#666666",
261                         "REQUEST"           : "#777777",
262                         "RESPOND"           : "#888888",
263                         "TIME_WAIT"         : "#999999",
264                 },
265                 "sctp" : {
266                         "CLOSED"            : "#000000",
267                         "COOKIE_ECHOED"     : "#111111",
268                         "COOKIE_WAIT"       : "#222222",
269                         "ESTABLISHED"       : "#333333",
270                         "NONE"              : "#444444",
271                         "SHUTDOWN_ACK_SENT" : "#555555",
272                         "SHUTDOWN_RECD"     : "#666666",
273                         "SHUTDOWN_SENT"     : "#777777",
274                 },
275                 "tcp" : {
276                         "CLOSE"             : "#ffffff",
277                         "CLOSE_WAIT"        : "#999999",
278                         "ESTABLISHED"       : "#000000",
279                         "FIN_WAIT"          : "#888888",
280                         "LAST_ACK"          : "#777777",
281                         "NONE"              : "#000000",
282                         "SYN_RECV"          : "#111111",
283                         "SYN_SENT"          : "#222222",
284                         "SYN_SENT2"         : "#333333",
285                         "TIME_WAIT"         : "#444444",
286                 },
287         }
288
289         states_descriptions = {
290                 "dccp" : {},
291                 "sctp" : {},
292                 "tcp"  : {},
293         }
294
295         states_sortorder = {
296                 "dccp" : {
297                         "CLOSEREQ"          : 0,
298                         "CLOSING"           : 0,
299                         "IGNORE"            : 0,
300                         "INVALID"           : 0,
301                         "NONE"              : 0,
302                         "OPEN"              : 0,
303                         "PARTOPEN"          : 0,
304                         "REQUEST"           : 0,
305                         "RESPOND"           : 0,
306                         "TIME_WAIT"         : 0,
307                 },
308                 "sctp" : {
309                         "CLOSED"            : 0,
310                         "COOKIE_ECHOED"     : 0,
311                         "COOKIE_WAIT"       : 0,
312                         "ESTABLISHED"       : 0,
313                         "NONE"              : 0,
314                         "SHUTDOWN_ACK_SENT" : 0,
315                         "SHUTDOWN_RECD"     : 0,
316                         "SHUTDOWN_SENT"     : 0,
317                 },
318                 "tcp" : {
319                         "CLOSE"             : 9,
320                         "CLOSE_WAIT"        : 8,
321                         "ESTABLISHED"       : 1,
322                         "FIN_WAIT"          : 6,
323                         "LAST_ACK"          : 7,
324                         "NONE"              : 10,
325                         "SYN_RECV"          : 2,
326                         "SYN_SENT"          : 3,
327                         "SYN_SENT2"         : 4,
328                         "TIME_WAIT"         : 5,
329                 },
330         }
331
332         @property
333         def graph_title(self):
334                 return _("Protocol States of all %s connections") % self.protocol.upper()
335
336         @property
337         def graph_vertical_label(self):
338                 return _("Number of open connections")
339
340         @property
341         def protocol(self):
342                 return self.object.protocol
343
344         @property
345         def states(self):
346                 return sorted(ConntrackTable._stateful_layer4_protocols[self.protocol],
347                         key=lambda x: self.states_sortorder[self.protocol].get(x, 99))
348
349         @property
350         def rrd_graph(self):
351                 args = []
352
353                 for state in reversed(self.states):
354                         i = {
355                                 "colour"      : self.states_colours[self.protocol].get(state, "#000000"),
356                                 "description" : self.states_descriptions[self.protocol].get(state, state),
357                                 "proto"       : self.protocol,
358                                 "state"       : state,
359
360                                 "legend_min"  : "%10s\: %%8.0lf" % _("Minimum"),
361                                 "legend_max"  : "%10s\: %%8.0lf" % _("Maximum"),
362                                 "legend_avg"  : "%10s\: %%8.0lf" % _("Average"),
363                                 "legend_cur"  : "%10s\: %%8.0lf" % _("Current"),
364                         }
365
366                         args += [
367                                 "DEF:%(state)s=%%(file)s:%(state)s:AVERAGE" % i,
368                                 "AREA:%(state)s%(colour)s:%(description)-15s:STACK" % i,
369                                 "VDEF:%(state)s_cur=%(state)s,LAST" % i,
370                                 "GPRINT:%(state)s_cur:%(legend_cur)s" % i,
371                                 "VDEF:%(state)s_avg=%(state)s,AVERAGE" % i,
372                                 "GPRINT:%(state)s_avg:%(legend_avg)s" % i,
373                                 "VDEF:%(state)s_min=%(state)s,MINIMUM" % i,
374                                 "GPRINT:%(state)s_min:%(legend_min)s" % i,
375                                 "VDEF:%(state)s_max=%(state)s,MAXIMUM" % i,
376                                 "GPRINT:%(state)s_max:%(legend_max)s\\n" % i,
377                         ]
378
379                 return args
380
381         @property
382         def rrd_graph_args(self):
383                 return [
384                         "--legend-direction=bottomup",
385                 ]
386
387
388 class ConntrackObject(base.Object):
389         protocol = None
390
391         def init(self, conntrack_table):
392                 self.conntrack_table = conntrack_table
393
394         @property
395         def id(self):
396                 return self.protocol
397
398
399 class ConntrackLayer3ProtocolsObject(ConntrackObject):
400         protocols = ConntrackTable._layer3_protocols
401
402         rrd_schema = [
403                 "DS:%s:GAUGE:0:U" % p for p in protocols
404         ]
405
406         @property
407         def id(self):
408                 return "layer3-protocols"
409
410         def collect(self):
411                 results = []
412
413                 for proto in self.protocols:
414                         r = self.conntrack_table.layer3_protocols.get(proto, 0)
415                         results.append("%s" % r)
416
417                 return results
418
419
420 class ConntrackLayer4ProtocolsObject(ConntrackObject):
421         protocols = ConntrackTable._layer4_protocols
422
423         rrd_schema = [
424                 "DS:%s:GAUGE:0:U" % p for p in protocols
425         ]
426
427         @property
428         def id(self):
429                 return "layer4-protocols"
430
431         def collect(self):
432                 results = []
433
434                 for proto in self.protocols:
435                         r = self.conntrack_table.layer4_protocols.get(proto, 0)
436                         results.append("%s" % r)
437
438                 return results
439
440
441 class ConntrackProtocolWithStatesObject(ConntrackObject):
442         def init(self, conntrack_table, protocol):
443                 ConntrackObject.init(self, conntrack_table)
444                 self.protocol = protocol
445
446         def __repr__(self):
447                 return "<%s %s>" % (self.__class__.__name__, self.protocol)
448
449         @property
450         def states(self):
451                 return ConntrackTable._stateful_layer4_protocols.get(self.protocol)
452
453         @property
454         def rrd_schema(self):
455                 return ["DS:%s:GAUGE:0:U" % state for state in self.states]
456
457         def get_states(self):
458                 results = []
459
460                 for state in self.states:
461                         r = self.conntrack_table.protocol_states[self.protocol].get(state, 0)
462                         results.append("%s" % r)
463
464                 return results
465
466         def collect(self):
467                 return self.get_states()
468
469
470 class ConntrackPlugin(base.Plugin):
471         name = "conntrack"
472         description = "Conntrack Plugin"
473
474         templates = [
475                 ConntrackLayer3ProtocolsGraphTemplate,
476                 ConntrackLayer4ProtocolsGraphTemplate,
477                 ConntrackProtocolWithStatesGraphTemplate,
478         ]
479
480         @property
481         def objects(self):
482                 ct = self.get_conntrack_table()
483
484                 if ct:
485                         yield ConntrackLayer3ProtocolsObject(self, ct)
486                         yield ConntrackLayer4ProtocolsObject(self, ct)
487
488                         for protocol in ConntrackTable._stateful_layer4_protocols:
489                                 yield ConntrackProtocolWithStatesObject(self, ct, protocol)
490
491         def get_conntrack_table(self):
492                 if not os.path.exists(CONNTRACK_FILE):
493                         return
494
495                 return ConntrackTable(CONNTRACK_FILE)