]> git.ipfire.org Git - collecty.git/blob - src/collecty/plugins/conntrack.py
efd062db645043859068eab3454bedc8447311f9
[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 ..colours import *
27 from ..i18n import _
28
29 CONNTRACK_FILE = "/proc/net/nf_conntrack"
30
31 class ConntrackTable(object):
32 _layer3_protocols = (
33 "ipv6",
34 "ipv4",
35 "other",
36 )
37
38 _layer4_protocols = (
39 "dccp",
40 "icmp",
41 "igmp",
42 "sctp",
43 "tcp",
44 "udp",
45 "udplite",
46 "other",
47 )
48
49 _stateful_layer4_protocols = {
50 "dccp" : (
51 "CLOSEREQ",
52 "CLOSING",
53 "IGNORE",
54 "INVALID",
55 "NONE",
56 "OPEN",
57 "PARTOPEN",
58 "REQUEST",
59 "RESPOND",
60 "TIME_WAIT",
61 ),
62 "sctp" : (
63 "CLOSED",
64 "COOKIE_ECHOED",
65 "COOKIE_WAIT",
66 "ESTABLISHED",
67 "NONE",
68 "SHUTDOWN_ACK_SENT",
69 "SHUTDOWN_RECD",
70 "SHUTDOWN_SENT",
71 ),
72 "tcp" : (
73 "CLOSE",
74 "CLOSE_WAIT",
75 "ESTABLISHED",
76 "FIN_WAIT",
77 "LAST_ACK",
78 "NONE",
79 "SYN_RECV",
80 "SYN_SENT",
81 "SYN_SENT2",
82 "TIME_WAIT",
83 ),
84 }
85
86 def __init__(self, filename):
87 with open(filename) as f:
88 self.layer3_protocols = {}
89 for proto in self._layer3_protocols:
90 self.layer3_protocols[proto] = 0
91
92 self.layer4_protocols = {}
93 for proto in self._layer4_protocols:
94 self.layer4_protocols[proto] = 0
95
96 self.protocol_states = {}
97 for proto, states in self._stateful_layer4_protocols.items():
98 self.protocol_states[proto] = dict((state, 0) for state in states)
99
100 for line in f.readlines():
101 line = line.split()
102
103 # Layer 3 protocol
104 layer3_protocol = line[0]
105
106 try:
107 self.layer3_protocols[layer3_protocol] += 1
108 except KeyError:
109 self.layer3_protocols["other"] += 1
110
111 # Layer 4 protocol
112 layer4_protocol = line[2]
113
114 try:
115 self.layer4_protocols[layer4_protocol] += 1
116 except KeyError:
117 self.layer4_protocols["other"] += 1
118 layer4_protocol = "other"
119
120 # Count connection states
121 if layer4_protocol in self.protocol_states:
122 state = line[5]
123
124 try:
125 self.protocol_states[layer4_protocol][state] += 1
126 except KeyError:
127 pass
128
129
130 class ConntrackLayer3ProtocolsGraphTemplate(base.GraphTemplate):
131 name = "conntrack-layer3-protocols"
132
133 _protocols = ConntrackTable._layer3_protocols
134
135 protocol_colours = {
136 "ipv6" : COLOUR_IPV6,
137 "ipv4" : COLOUR_IPV4,
138 "other" : COLOUR_IPVX,
139 }
140
141 def get_objects(self, *args):
142 return [
143 self.plugin.get_object("layer3-protocols"),
144 ]
145
146 @property
147 def protocols(self):
148 # Order the protocols by standard deviation which will give us cleaner graphs
149 # http://stackoverflow.com/questions/13958409/how-to-graph-rrd-stackable-data-by-standard-deviation-to-maximize-readability
150 stddev = self.object.get_stddev()
151
152 protos = {}
153 for p in self._protocols:
154 protos[p] = stddev.get(p)
155
156 return sorted(protos, key=protos.get)
157
158 @property
159 def protocol_descriptions(self):
160 _ = self.locale.translate
161
162 return {
163 "ipv6" : _("IPv6"),
164 "ipv4" : _("IPv4"),
165 "other" : _("Other"),
166 }
167
168 @property
169 def graph_title(self):
170 _ = self.locale.translate
171 return _("Connections by Layer 3 Protocols")
172
173 @property
174 def graph_vertical_label(self):
175 _ = self.locale.translate
176 return _("Number of open connections")
177
178 @property
179 def rrd_defs(self):
180 return []
181
182 @property
183 def rrd_graph(self):
184 _ = self.locale.translate
185 args = []
186
187 for proto in self.protocols:
188 i = {
189 "colour" : self.protocol_colours.get(proto, COLOUR_OTHER),
190 "description" : self.protocol_descriptions.get(proto, proto),
191 "proto" : proto,
192 "type" : type,
193
194 "legend_min" : "%10s\: %%8.0lf" % _("Minimum"),
195 "legend_max" : "%10s\: %%8.0lf" % _("Maximum"),
196 "legend_avg" : "%10s\: %%8.0lf" % _("Average"),
197 "legend_cur" : "%10s\: %%8.0lf" % _("Current"),
198 }
199
200 args += self.object.make_rrd_defs(proto) + [
201 "AREA:%(proto)s%(colour)s:%(description)-15s:STACK" % i,
202 "GPRINT:%(proto)s_cur:%(legend_cur)s" % i,
203 "GPRINT:%(proto)s_avg:%(legend_avg)s" % i,
204 "GPRINT:%(proto)s_min:%(legend_min)s" % i,
205 "GPRINT:%(proto)s_max:%(legend_max)s\\n" % i,
206 ]
207
208 return args
209
210 @property
211 def rrd_graph_args(self):
212 return [
213 "--legend-direction=bottomup",
214 ]
215
216
217 class ConntrackLayer4ProtocolsGraphTemplate(ConntrackLayer3ProtocolsGraphTemplate):
218 name = "conntrack-layer4-protocols"
219
220 protocol_colours = {
221 "tcp" : COLOUR_TCP,
222 "udp" : COLOUR_UDP,
223 "icmp" : COLOUR_ICMP,
224 "igmp" : COLOUR_IGMP,
225 "udplite" : COLOUR_UDPLITE,
226 "sctp" : COLOUR_SCTP,
227 "dccp" : COLOUR_DCCP,
228 }
229
230 @property
231 def protocol_descriptions(self):
232 _ = self.locale.translate
233
234 return {
235 "tcp" : _("TCP"),
236 "udp" : _("UDP"),
237 "icmp" : _("ICMP"),
238 "igmp" : _("IGMP"),
239 "udplite" : _("UDP Lite"),
240 "sctp" : _("SCTP"),
241 "dccp" : _("DCCP"),
242 "other" : _("Other"),
243 }
244
245 protocol_sortorder = {
246 "tcp" : 1,
247 "udp" : 2,
248 "icmp" : 3,
249 "igmp" : 4,
250 "udplite" : 5,
251 "sctp" : 6,
252 "dccp" : 7,
253 }
254
255 def get_objects(self, *args):
256 return [
257 self.plugin.get_object("layer4-protocols"),
258 ]
259
260 @property
261 def graph_title(self):
262 _ = self.locale.translate
263 return _("Connections by IP Protocols")
264
265 @property
266 def _protocols(self):
267 return sorted(ConntrackTable._layer4_protocols,
268 key=lambda x: self.protocol_sortorder.get(x, 99))
269
270
271 class ConntrackProtocolWithStatesGraphTemplate(base.GraphTemplate):
272 name = "conntrack-protocol-states"
273
274 lower_limit = 0
275
276 states_descriptions = {
277 "dccp" : {},
278 "sctp" : {},
279 "tcp" : {},
280 }
281
282 states_sortorder = {
283 "dccp" : {
284 "CLOSEREQ" : 0,
285 "CLOSING" : 0,
286 "IGNORE" : 0,
287 "INVALID" : 0,
288 "NONE" : 0,
289 "OPEN" : 0,
290 "PARTOPEN" : 0,
291 "REQUEST" : 0,
292 "RESPOND" : 0,
293 "TIME_WAIT" : 0,
294 },
295 "sctp" : {
296 "CLOSED" : 0,
297 "COOKIE_ECHOED" : 0,
298 "COOKIE_WAIT" : 0,
299 "ESTABLISHED" : 0,
300 "NONE" : 0,
301 "SHUTDOWN_ACK_SENT" : 0,
302 "SHUTDOWN_RECD" : 0,
303 "SHUTDOWN_SENT" : 0,
304 },
305 "tcp" : {
306 "CLOSE" : 9,
307 "CLOSE_WAIT" : 8,
308 "ESTABLISHED" : 1,
309 "FIN_WAIT" : 6,
310 "LAST_ACK" : 7,
311 "NONE" : 10,
312 "SYN_RECV" : 2,
313 "SYN_SENT" : 3,
314 "SYN_SENT2" : 4,
315 "TIME_WAIT" : 5,
316 },
317 }
318
319 @property
320 def graph_title(self):
321 _ = self.locale.translate
322 return _("Protocol States of all %s connections") % self.protocol.upper()
323
324 @property
325 def graph_vertical_label(self):
326 _ = self.locale.translate
327 return _("Number of open connections")
328
329 @property
330 def protocol(self):
331 return self.object.protocol
332
333 @property
334 def states(self):
335 return sorted(ConntrackTable._stateful_layer4_protocols[self.protocol],
336 key=lambda x: self.states_sortorder[self.protocol].get(x, 99))
337
338 @property
339 def rrd_graph(self):
340 _ = self.locale.translate
341 args = []
342
343 for state in reversed(self.states):
344 i = {
345 "colour" : COLOURS_PROTOCOL_STATES.get(state, BLACK),
346 "description" : self.states_descriptions[self.protocol].get(state, state),
347 "proto" : self.protocol,
348 "state" : state,
349
350 "legend_min" : "%10s\: %%8.0lf" % _("Minimum"),
351 "legend_max" : "%10s\: %%8.0lf" % _("Maximum"),
352 "legend_avg" : "%10s\: %%8.0lf" % _("Average"),
353 "legend_cur" : "%10s\: %%8.0lf" % _("Current"),
354 }
355
356 args += self.object.make_rrd_defs(state) + [
357 "AREA:%(state)s%(colour)s:%(description)-15s:STACK" % i,
358 "GPRINT:%(state)s_cur:%(legend_cur)s" % i,
359 "GPRINT:%(state)s_avg:%(legend_avg)s" % i,
360 "GPRINT:%(state)s_min:%(legend_min)s" % i,
361 "GPRINT:%(state)s_max:%(legend_max)s\\n" % i,
362 ]
363
364 return args
365
366 @property
367 def rrd_graph_args(self):
368 return [
369 "--legend-direction=bottomup",
370 ]
371
372
373 class ConntrackObject(base.Object):
374 protocol = None
375
376 def init(self, conntrack_table):
377 self.conntrack_table = conntrack_table
378
379 @property
380 def id(self):
381 return self.protocol
382
383
384 class ConntrackLayer3ProtocolsObject(ConntrackObject):
385 protocols = ConntrackTable._layer3_protocols
386
387 rrd_schema = [
388 "DS:%s:GAUGE:0:U" % p for p in protocols
389 ]
390
391 @property
392 def id(self):
393 return "layer3-protocols"
394
395 def collect(self):
396 results = []
397
398 for proto in self.protocols:
399 r = self.conntrack_table.layer3_protocols.get(proto, 0)
400 results.append("%s" % r)
401
402 return results
403
404
405 class ConntrackLayer4ProtocolsObject(ConntrackObject):
406 protocols = ConntrackTable._layer4_protocols
407
408 rrd_schema = [
409 "DS:%s:GAUGE:0:U" % p for p in protocols
410 ]
411
412 @property
413 def id(self):
414 return "layer4-protocols"
415
416 def collect(self):
417 results = []
418
419 for proto in self.protocols:
420 r = self.conntrack_table.layer4_protocols.get(proto, 0)
421 results.append("%s" % r)
422
423 return results
424
425
426 class ConntrackProtocolWithStatesObject(ConntrackObject):
427 def init(self, conntrack_table, protocol):
428 ConntrackObject.init(self, conntrack_table)
429 self.protocol = protocol
430
431 def __repr__(self):
432 return "<%s %s>" % (self.__class__.__name__, self.protocol)
433
434 @property
435 def states(self):
436 return ConntrackTable._stateful_layer4_protocols.get(self.protocol)
437
438 @property
439 def rrd_schema(self):
440 return ["DS:%s:GAUGE:0:U" % state for state in self.states]
441
442 def get_states(self):
443 results = []
444
445 for state in self.states:
446 r = self.conntrack_table.protocol_states[self.protocol].get(state, 0)
447 results.append("%s" % r)
448
449 return results
450
451 def collect(self):
452 return self.get_states()
453
454
455 class ConntrackPlugin(base.Plugin):
456 name = "conntrack"
457 description = "Conntrack Plugin"
458
459 templates = [
460 ConntrackLayer3ProtocolsGraphTemplate,
461 ConntrackLayer4ProtocolsGraphTemplate,
462 ConntrackProtocolWithStatesGraphTemplate,
463 ]
464
465 @property
466 def objects(self):
467 ct = self.get_conntrack_table()
468
469 if ct:
470 yield ConntrackLayer3ProtocolsObject(self, ct)
471 yield ConntrackLayer4ProtocolsObject(self, ct)
472
473 for protocol in ConntrackTable._stateful_layer4_protocols:
474 yield ConntrackProtocolWithStatesObject(self, ct, protocol)
475
476 def get_conntrack_table(self):
477 if not os.path.exists(CONNTRACK_FILE):
478 return
479
480 return ConntrackTable(CONNTRACK_FILE)