]> git.ipfire.org Git - collecty.git/blob - src/collecty/plugins/conntrack.py
conntrack: Reformat now/avg/min/max values below graph
[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 colour = self.protocol_colours.get(proto, COLOUR_OTHER)
189 description = self.protocol_descriptions.get(proto, proto)
190
191 args += [
192 "AREA:%s%s:%-15s:STACK" % (proto, colour, description),
193 "GPRINT:%s_cur:%-6s %%8.0lf" % (proto, _("Now")),
194 "GPRINT:%s_avg:%-6s %%8.0lf" % (proto, _("Avg")),
195 "GPRINT:%s_min:%-6s %%8.0lf" % (proto, _("Min")),
196 "GPRINT:%s_max:%-6s %%8.0lf\\l" % (proto, _("Max")),
197 ]
198
199 return args
200
201 @property
202 def rrd_graph_args(self):
203 return [
204 "--legend-direction=bottomup",
205 ]
206
207
208 class ConntrackLayer4ProtocolsGraphTemplate(ConntrackLayer3ProtocolsGraphTemplate):
209 name = "conntrack-layer4-protocols"
210
211 protocol_colours = {
212 "tcp" : COLOUR_TCP,
213 "udp" : COLOUR_UDP,
214 "icmp" : COLOUR_ICMP,
215 "igmp" : COLOUR_IGMP,
216 "udplite" : COLOUR_UDPLITE,
217 "sctp" : COLOUR_SCTP,
218 "dccp" : COLOUR_DCCP,
219 }
220
221 @property
222 def protocol_descriptions(self):
223 _ = self.locale.translate
224
225 return {
226 "tcp" : _("TCP"),
227 "udp" : _("UDP"),
228 "icmp" : _("ICMP"),
229 "igmp" : _("IGMP"),
230 "udplite" : _("UDP Lite"),
231 "sctp" : _("SCTP"),
232 "dccp" : _("DCCP"),
233 "other" : _("Other"),
234 }
235
236 protocol_sortorder = {
237 "tcp" : 1,
238 "udp" : 2,
239 "icmp" : 3,
240 "igmp" : 4,
241 "udplite" : 5,
242 "sctp" : 6,
243 "dccp" : 7,
244 }
245
246 def get_objects(self, *args):
247 return [
248 self.plugin.get_object("layer4-protocols"),
249 ]
250
251 @property
252 def graph_title(self):
253 _ = self.locale.translate
254 return _("Connections by IP Protocols")
255
256 @property
257 def _protocols(self):
258 return sorted(ConntrackTable._layer4_protocols,
259 key=lambda x: self.protocol_sortorder.get(x, 99))
260
261
262 class ConntrackProtocolWithStatesGraphTemplate(base.GraphTemplate):
263 name = "conntrack-protocol-states"
264
265 lower_limit = 0
266
267 states_descriptions = {
268 "dccp" : {},
269 "sctp" : {},
270 "tcp" : {},
271 }
272
273 states_sortorder = {
274 "dccp" : {
275 "CLOSEREQ" : 0,
276 "CLOSING" : 0,
277 "IGNORE" : 0,
278 "INVALID" : 0,
279 "NONE" : 0,
280 "OPEN" : 0,
281 "PARTOPEN" : 0,
282 "REQUEST" : 0,
283 "RESPOND" : 0,
284 "TIME_WAIT" : 0,
285 },
286 "sctp" : {
287 "CLOSED" : 0,
288 "COOKIE_ECHOED" : 0,
289 "COOKIE_WAIT" : 0,
290 "ESTABLISHED" : 0,
291 "NONE" : 0,
292 "SHUTDOWN_ACK_SENT" : 0,
293 "SHUTDOWN_RECD" : 0,
294 "SHUTDOWN_SENT" : 0,
295 },
296 "tcp" : {
297 "CLOSE" : 9,
298 "CLOSE_WAIT" : 8,
299 "ESTABLISHED" : 1,
300 "FIN_WAIT" : 6,
301 "LAST_ACK" : 7,
302 "NONE" : 10,
303 "SYN_RECV" : 2,
304 "SYN_SENT" : 3,
305 "SYN_SENT2" : 4,
306 "TIME_WAIT" : 5,
307 },
308 }
309
310 @property
311 def graph_title(self):
312 _ = self.locale.translate
313 return _("Protocol States of all %s connections") % self.protocol.upper()
314
315 @property
316 def graph_vertical_label(self):
317 _ = self.locale.translate
318 return _("Number of open connections")
319
320 @property
321 def protocol(self):
322 return self.object.protocol
323
324 @property
325 def states(self):
326 return sorted(ConntrackTable._stateful_layer4_protocols[self.protocol],
327 key=lambda x: self.states_sortorder[self.protocol].get(x, 99))
328
329 @property
330 def rrd_graph(self):
331 _ = self.locale.translate
332 args = []
333
334 for state in reversed(self.states):
335 i = {
336 "colour" : COLOURS_PROTOCOL_STATES.get(state, BLACK),
337 "description" : self.states_descriptions[self.protocol].get(state, state),
338 "proto" : self.protocol,
339 "state" : state,
340
341 "legend_min" : "%10s\: %%8.0lf" % _("Minimum"),
342 "legend_max" : "%10s\: %%8.0lf" % _("Maximum"),
343 "legend_avg" : "%10s\: %%8.0lf" % _("Average"),
344 "legend_cur" : "%10s\: %%8.0lf" % _("Current"),
345 }
346
347 args += self.object.make_rrd_defs(state) + [
348 "AREA:%(state)s%(colour)s:%(description)-15s:STACK" % i,
349 "GPRINT:%(state)s_cur:%(legend_cur)s" % i,
350 "GPRINT:%(state)s_avg:%(legend_avg)s" % i,
351 "GPRINT:%(state)s_min:%(legend_min)s" % i,
352 "GPRINT:%(state)s_max:%(legend_max)s\\n" % i,
353 ]
354
355 return args
356
357 @property
358 def rrd_graph_args(self):
359 return [
360 "--legend-direction=bottomup",
361 ]
362
363
364 class ConntrackObject(base.Object):
365 protocol = None
366
367 def init(self, conntrack_table):
368 self.conntrack_table = conntrack_table
369
370 @property
371 def id(self):
372 return self.protocol
373
374
375 class ConntrackLayer3ProtocolsObject(ConntrackObject):
376 protocols = ConntrackTable._layer3_protocols
377
378 rrd_schema = [
379 "DS:%s:GAUGE:0:U" % p for p in protocols
380 ]
381
382 @property
383 def id(self):
384 return "layer3-protocols"
385
386 def collect(self):
387 results = []
388
389 for proto in self.protocols:
390 r = self.conntrack_table.layer3_protocols.get(proto, 0)
391 results.append("%s" % r)
392
393 return results
394
395
396 class ConntrackLayer4ProtocolsObject(ConntrackObject):
397 protocols = ConntrackTable._layer4_protocols
398
399 rrd_schema = [
400 "DS:%s:GAUGE:0:U" % p for p in protocols
401 ]
402
403 @property
404 def id(self):
405 return "layer4-protocols"
406
407 def collect(self):
408 results = []
409
410 for proto in self.protocols:
411 r = self.conntrack_table.layer4_protocols.get(proto, 0)
412 results.append("%s" % r)
413
414 return results
415
416
417 class ConntrackProtocolWithStatesObject(ConntrackObject):
418 def init(self, conntrack_table, protocol):
419 ConntrackObject.init(self, conntrack_table)
420 self.protocol = protocol
421
422 def __repr__(self):
423 return "<%s %s>" % (self.__class__.__name__, self.protocol)
424
425 @property
426 def states(self):
427 return ConntrackTable._stateful_layer4_protocols.get(self.protocol)
428
429 @property
430 def rrd_schema(self):
431 return ["DS:%s:GAUGE:0:U" % state for state in self.states]
432
433 def get_states(self):
434 results = []
435
436 for state in self.states:
437 r = self.conntrack_table.protocol_states[self.protocol].get(state, 0)
438 results.append("%s" % r)
439
440 return results
441
442 def collect(self):
443 return self.get_states()
444
445
446 class ConntrackPlugin(base.Plugin):
447 name = "conntrack"
448 description = "Conntrack Plugin"
449
450 templates = [
451 ConntrackLayer3ProtocolsGraphTemplate,
452 ConntrackLayer4ProtocolsGraphTemplate,
453 ConntrackProtocolWithStatesGraphTemplate,
454 ]
455
456 @property
457 def objects(self):
458 ct = self.get_conntrack_table()
459
460 if ct:
461 yield ConntrackLayer3ProtocolsObject(self, ct)
462 yield ConntrackLayer4ProtocolsObject(self, ct)
463
464 for protocol in ConntrackTable._stateful_layer4_protocols:
465 yield ConntrackProtocolWithStatesObject(self, ct, protocol)
466
467 def get_conntrack_table(self):
468 if not os.path.exists(CONNTRACK_FILE):
469 return
470
471 return ConntrackTable(CONNTRACK_FILE)