]> git.ipfire.org Git - collecty.git/blob - src/collecty/plugins/conntrack.py
1e9bb79819a1934077466e15cbf9ddc471dd5136
[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 def get_object(self, *args):
140 return self.plugin.get_object("layer3-protocols")
141
142 @property
143 def protocols(self):
144 # Order the protocols by standard deviation which will give us cleaner graphs
145 # http://stackoverflow.com/questions/13958409/how-to-graph-rrd-stackable-data-by-standard-deviation-to-maximize-readability
146 stddev = self.object.get_stddev()
147
148 protos = {}
149 for p in self._protocols:
150 protos[p] = stddev.get(p)
151
152 return sorted(protos, key=protos.get)
153
154 @property
155 def protocol_descriptions(self):
156 _ = self.locale.translate
157
158 return {
159 "ipv6" : _("IPv6"),
160 "ipv4" : _("IPv4"),
161 "other" : _("Other"),
162 }
163
164 @property
165 def graph_title(self):
166 _ = self.locale.translate
167 return _("Connections by Layer 3 Protocols")
168
169 @property
170 def graph_vertical_label(self):
171 _ = self.locale.translate
172 return _("Number of open connections")
173
174 @property
175 def rrd_defs(self):
176 return []
177
178 @property
179 def rrd_graph(self):
180 _ = self.locale.translate
181 args = []
182
183 for proto in self.protocols:
184 i = {
185 "colour" : self.protocol_colours.get(proto, "#000000"),
186 "description" : self.protocol_descriptions.get(proto, proto),
187 "proto" : proto,
188 "type" : type,
189
190 "legend_min" : "%10s\: %%8.0lf" % _("Minimum"),
191 "legend_max" : "%10s\: %%8.0lf" % _("Maximum"),
192 "legend_avg" : "%10s\: %%8.0lf" % _("Average"),
193 "legend_cur" : "%10s\: %%8.0lf" % _("Current"),
194 }
195
196 args += self.object.make_rrd_defs(proto) + [
197 "AREA:%(proto)s%(colour)s:%(description)-15s:STACK" % i,
198 "GPRINT:%(proto)s_cur:%(legend_cur)s" % i,
199 "GPRINT:%(proto)s_avg:%(legend_avg)s" % i,
200 "GPRINT:%(proto)s_min:%(legend_min)s" % i,
201 "GPRINT:%(proto)s_max:%(legend_max)s\\n" % i,
202 ]
203
204 return args
205
206 @property
207 def rrd_graph_args(self):
208 return [
209 "--legend-direction=bottomup",
210 ]
211
212
213 class ConntrackLayer4ProtocolsGraphTemplate(ConntrackLayer3ProtocolsGraphTemplate):
214 name = "conntrack-layer4-protocols"
215
216 protocol_colours = {
217 "tcp" : "#336600",
218 "udp" : "#666633",
219 "icmp" : "#336666",
220 "igmp" : "#666699",
221 "udplite" : "#3366cc",
222 "sctp" : "#6666ff",
223 "dccp" : "#33cc00",
224 }
225
226 @property
227 def protocol_descriptions(self):
228 _ = self.locale.translate
229
230 return {
231 "tcp" : _("TCP"),
232 "udp" : _("UDP"),
233 "icmp" : _("ICMP"),
234 "igmp" : _("IGMP"),
235 "udplite" : _("UDP Lite"),
236 "sctp" : _("SCTP"),
237 "dccp" : _("DCCP"),
238 "other" : _("Other"),
239 }
240
241 protocol_sortorder = {
242 "tcp" : 1,
243 "udp" : 2,
244 "icmp" : 3,
245 "igmp" : 4,
246 "udplite" : 5,
247 "sctp" : 6,
248 "dccp" : 7,
249 }
250
251 def get_object(self, *args):
252 return self.plugin.get_object("layer4-protocols")
253
254 @property
255 def graph_title(self):
256 _ = self.locale.translate
257 return _("Connections by IP Protocols")
258
259 @property
260 def _protocols(self):
261 return sorted(ConntrackTable._layer4_protocols,
262 key=lambda x: self.protocol_sortorder.get(x, 99))
263
264
265 class ConntrackProtocolWithStatesGraphTemplate(base.GraphTemplate):
266 name = "conntrack-protocol-states"
267
268 lower_limit = 0
269
270 states_colours = {
271 "dccp" : {
272 "CLOSEREQ" : "#000000",
273 "CLOSING" : "#111111",
274 "IGNORE" : "#222222",
275 "INVALID" : "#333333",
276 "NONE" : "#444444",
277 "OPEN" : "#555555",
278 "PARTOPEN" : "#666666",
279 "REQUEST" : "#777777",
280 "RESPOND" : "#888888",
281 "TIME_WAIT" : "#999999",
282 },
283 "sctp" : {
284 "CLOSED" : "#000000",
285 "COOKIE_ECHOED" : "#111111",
286 "COOKIE_WAIT" : "#222222",
287 "ESTABLISHED" : "#333333",
288 "NONE" : "#444444",
289 "SHUTDOWN_ACK_SENT" : "#555555",
290 "SHUTDOWN_RECD" : "#666666",
291 "SHUTDOWN_SENT" : "#777777",
292 },
293 "tcp" : {
294 "CLOSE" : "#ffffff",
295 "CLOSE_WAIT" : "#999999",
296 "ESTABLISHED" : "#000000",
297 "FIN_WAIT" : "#888888",
298 "LAST_ACK" : "#777777",
299 "NONE" : "#000000",
300 "SYN_RECV" : "#111111",
301 "SYN_SENT" : "#222222",
302 "SYN_SENT2" : "#333333",
303 "TIME_WAIT" : "#444444",
304 },
305 }
306
307 states_descriptions = {
308 "dccp" : {},
309 "sctp" : {},
310 "tcp" : {},
311 }
312
313 states_sortorder = {
314 "dccp" : {
315 "CLOSEREQ" : 0,
316 "CLOSING" : 0,
317 "IGNORE" : 0,
318 "INVALID" : 0,
319 "NONE" : 0,
320 "OPEN" : 0,
321 "PARTOPEN" : 0,
322 "REQUEST" : 0,
323 "RESPOND" : 0,
324 "TIME_WAIT" : 0,
325 },
326 "sctp" : {
327 "CLOSED" : 0,
328 "COOKIE_ECHOED" : 0,
329 "COOKIE_WAIT" : 0,
330 "ESTABLISHED" : 0,
331 "NONE" : 0,
332 "SHUTDOWN_ACK_SENT" : 0,
333 "SHUTDOWN_RECD" : 0,
334 "SHUTDOWN_SENT" : 0,
335 },
336 "tcp" : {
337 "CLOSE" : 9,
338 "CLOSE_WAIT" : 8,
339 "ESTABLISHED" : 1,
340 "FIN_WAIT" : 6,
341 "LAST_ACK" : 7,
342 "NONE" : 10,
343 "SYN_RECV" : 2,
344 "SYN_SENT" : 3,
345 "SYN_SENT2" : 4,
346 "TIME_WAIT" : 5,
347 },
348 }
349
350 @property
351 def graph_title(self):
352 _ = self.locale.translate
353 return _("Protocol States of all %s connections") % self.protocol.upper()
354
355 @property
356 def graph_vertical_label(self):
357 _ = self.locale.translate
358 return _("Number of open connections")
359
360 @property
361 def protocol(self):
362 return self.object.protocol
363
364 @property
365 def states(self):
366 return sorted(ConntrackTable._stateful_layer4_protocols[self.protocol],
367 key=lambda x: self.states_sortorder[self.protocol].get(x, 99))
368
369 @property
370 def rrd_graph(self):
371 _ = self.locale.translate
372 args = []
373
374 for state in reversed(self.states):
375 i = {
376 "colour" : self.states_colours[self.protocol].get(state, "#000000"),
377 "description" : self.states_descriptions[self.protocol].get(state, state),
378 "proto" : self.protocol,
379 "state" : state,
380
381 "legend_min" : "%10s\: %%8.0lf" % _("Minimum"),
382 "legend_max" : "%10s\: %%8.0lf" % _("Maximum"),
383 "legend_avg" : "%10s\: %%8.0lf" % _("Average"),
384 "legend_cur" : "%10s\: %%8.0lf" % _("Current"),
385 }
386
387 args += self.object.make_rrd_defs(state) + [
388 "AREA:%(state)s%(colour)s:%(description)-15s:STACK" % i,
389 "GPRINT:%(state)s_cur:%(legend_cur)s" % i,
390 "GPRINT:%(state)s_avg:%(legend_avg)s" % i,
391 "GPRINT:%(state)s_min:%(legend_min)s" % i,
392 "GPRINT:%(state)s_max:%(legend_max)s\\n" % i,
393 ]
394
395 return args
396
397 @property
398 def rrd_graph_args(self):
399 return [
400 "--legend-direction=bottomup",
401 ]
402
403
404 class ConntrackObject(base.Object):
405 protocol = None
406
407 def init(self, conntrack_table):
408 self.conntrack_table = conntrack_table
409
410 @property
411 def id(self):
412 return self.protocol
413
414
415 class ConntrackLayer3ProtocolsObject(ConntrackObject):
416 protocols = ConntrackTable._layer3_protocols
417
418 rrd_schema = [
419 "DS:%s:GAUGE:0:U" % p for p in protocols
420 ]
421
422 @property
423 def id(self):
424 return "layer3-protocols"
425
426 def collect(self):
427 results = []
428
429 for proto in self.protocols:
430 r = self.conntrack_table.layer3_protocols.get(proto, 0)
431 results.append("%s" % r)
432
433 return results
434
435
436 class ConntrackLayer4ProtocolsObject(ConntrackObject):
437 protocols = ConntrackTable._layer4_protocols
438
439 rrd_schema = [
440 "DS:%s:GAUGE:0:U" % p for p in protocols
441 ]
442
443 @property
444 def id(self):
445 return "layer4-protocols"
446
447 def collect(self):
448 results = []
449
450 for proto in self.protocols:
451 r = self.conntrack_table.layer4_protocols.get(proto, 0)
452 results.append("%s" % r)
453
454 return results
455
456
457 class ConntrackProtocolWithStatesObject(ConntrackObject):
458 def init(self, conntrack_table, protocol):
459 ConntrackObject.init(self, conntrack_table)
460 self.protocol = protocol
461
462 def __repr__(self):
463 return "<%s %s>" % (self.__class__.__name__, self.protocol)
464
465 @property
466 def states(self):
467 return ConntrackTable._stateful_layer4_protocols.get(self.protocol)
468
469 @property
470 def rrd_schema(self):
471 return ["DS:%s:GAUGE:0:U" % state for state in self.states]
472
473 def get_states(self):
474 results = []
475
476 for state in self.states:
477 r = self.conntrack_table.protocol_states[self.protocol].get(state, 0)
478 results.append("%s" % r)
479
480 return results
481
482 def collect(self):
483 return self.get_states()
484
485
486 class ConntrackPlugin(base.Plugin):
487 name = "conntrack"
488 description = "Conntrack Plugin"
489
490 templates = [
491 ConntrackLayer3ProtocolsGraphTemplate,
492 ConntrackLayer4ProtocolsGraphTemplate,
493 ConntrackProtocolWithStatesGraphTemplate,
494 ]
495
496 @property
497 def objects(self):
498 ct = self.get_conntrack_table()
499
500 if ct:
501 yield ConntrackLayer3ProtocolsObject(self, ct)
502 yield ConntrackLayer4ProtocolsObject(self, ct)
503
504 for protocol in ConntrackTable._stateful_layer4_protocols:
505 yield ConntrackProtocolWithStatesObject(self, ct, protocol)
506
507 def get_conntrack_table(self):
508 if not os.path.exists(CONNTRACK_FILE):
509 return
510
511 return ConntrackTable(CONNTRACK_FILE)