]> git.ipfire.org Git - collecty.git/blob - src/collecty/plugins/conntrack.py
Allow the plugins to return the results as a tuple or list
[collecty.git] / src / collecty / plugins / conntrack.py
1 #!/usr/bin/python
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 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 self.protocol_states.has_key(layer4_protocol):
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)