]>
Commit | Line | Data |
---|---|---|
f37913e8 | 1 | #!/usr/bin/python3 |
0ec1854a MT |
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 | ||
f37913e8 | 24 | from . import base |
0ec1854a | 25 | |
03ba5630 | 26 | from ..colours import * |
0308c0f3 MT |
27 | from ..i18n import _ |
28 | ||
0ec1854a MT |
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 | |
f37913e8 | 121 | if layer4_protocol in self.protocol_states: |
0ec1854a MT |
122 | state = line[5] |
123 | ||
124 | try: | |
125 | self.protocol_states[layer4_protocol][state] += 1 | |
126 | except KeyError: | |
127 | pass | |
128 | ||
129 | ||
0308c0f3 MT |
130 | class ConntrackLayer3ProtocolsGraphTemplate(base.GraphTemplate): |
131 | name = "conntrack-layer3-protocols" | |
132 | ||
f998b6cb | 133 | _protocols = ConntrackTable._layer3_protocols |
0308c0f3 MT |
134 | |
135 | protocol_colours = { | |
03ba5630 MT |
136 | "ipv6" : COLOUR_IPV6, |
137 | "ipv4" : COLOUR_IPV4, | |
138 | "other" : COLOUR_IPVX, | |
0308c0f3 MT |
139 | } |
140 | ||
76c12e91 MT |
141 | def get_objects(self, *args): |
142 | return [ | |
143 | self.plugin.get_object("layer3-protocols"), | |
144 | ] | |
cd8bba0b | 145 | |
f998b6cb MT |
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 | ||
fa43c345 MT |
158 | @property |
159 | def protocol_descriptions(self): | |
160 | _ = self.locale.translate | |
161 | ||
162 | return { | |
163 | "ipv6" : _("IPv6"), | |
164 | "ipv4" : _("IPv4"), | |
165 | "other" : _("Other"), | |
166 | } | |
0308c0f3 MT |
167 | |
168 | @property | |
169 | def graph_title(self): | |
fa43c345 | 170 | _ = self.locale.translate |
0308c0f3 MT |
171 | return _("Connections by Layer 3 Protocols") |
172 | ||
173 | @property | |
174 | def graph_vertical_label(self): | |
fa43c345 | 175 | _ = self.locale.translate |
0308c0f3 MT |
176 | return _("Number of open connections") |
177 | ||
cd8bba0b MT |
178 | @property |
179 | def rrd_defs(self): | |
180 | return [] | |
0308c0f3 MT |
181 | |
182 | @property | |
183 | def rrd_graph(self): | |
fa43c345 | 184 | _ = self.locale.translate |
0308c0f3 MT |
185 | args = [] |
186 | ||
f998b6cb | 187 | for proto in self.protocols: |
0308c0f3 | 188 | i = { |
03ba5630 | 189 | "colour" : self.protocol_colours.get(proto, COLOUR_OTHER), |
0308c0f3 MT |
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 | ||
cd8bba0b | 200 | args += self.object.make_rrd_defs(proto) + [ |
0308c0f3 | 201 | "AREA:%(proto)s%(colour)s:%(description)-15s:STACK" % i, |
0308c0f3 | 202 | "GPRINT:%(proto)s_cur:%(legend_cur)s" % i, |
0308c0f3 | 203 | "GPRINT:%(proto)s_avg:%(legend_avg)s" % i, |
0308c0f3 | 204 | "GPRINT:%(proto)s_min:%(legend_min)s" % i, |
0308c0f3 MT |
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 = { | |
03ba5630 MT |
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, | |
0308c0f3 MT |
228 | } |
229 | ||
fa43c345 MT |
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 | } | |
0308c0f3 MT |
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 | ||
76c12e91 MT |
255 | def get_objects(self, *args): |
256 | return [ | |
257 | self.plugin.get_object("layer4-protocols"), | |
258 | ] | |
cd8bba0b | 259 | |
0308c0f3 MT |
260 | @property |
261 | def graph_title(self): | |
fa43c345 | 262 | _ = self.locale.translate |
0308c0f3 MT |
263 | return _("Connections by IP Protocols") |
264 | ||
265 | @property | |
f998b6cb | 266 | def _protocols(self): |
0308c0f3 MT |
267 | return sorted(ConntrackTable._layer4_protocols, |
268 | key=lambda x: self.protocol_sortorder.get(x, 99)) | |
269 | ||
0308c0f3 MT |
270 | |
271 | class ConntrackProtocolWithStatesGraphTemplate(base.GraphTemplate): | |
272 | name = "conntrack-protocol-states" | |
273 | ||
274 | lower_limit = 0 | |
275 | ||
0308c0f3 MT |
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): | |
fa43c345 | 321 | _ = self.locale.translate |
0308c0f3 MT |
322 | return _("Protocol States of all %s connections") % self.protocol.upper() |
323 | ||
324 | @property | |
325 | def graph_vertical_label(self): | |
fa43c345 | 326 | _ = self.locale.translate |
0308c0f3 MT |
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): | |
fa43c345 | 340 | _ = self.locale.translate |
0308c0f3 MT |
341 | args = [] |
342 | ||
343 | for state in reversed(self.states): | |
344 | i = { | |
03ba5630 | 345 | "colour" : COLOURS_PROTOCOL_STATES.get(state, BLACK), |
0308c0f3 MT |
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 | ||
cd8bba0b | 356 | args += self.object.make_rrd_defs(state) + [ |
0308c0f3 | 357 | "AREA:%(state)s%(colour)s:%(description)-15s:STACK" % i, |
0308c0f3 | 358 | "GPRINT:%(state)s_cur:%(legend_cur)s" % i, |
0308c0f3 | 359 | "GPRINT:%(state)s_avg:%(legend_avg)s" % i, |
0308c0f3 | 360 | "GPRINT:%(state)s_min:%(legend_min)s" % i, |
0308c0f3 MT |
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 | ||
0ec1854a MT |
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 | ||
f648421a | 402 | return results |
0ec1854a MT |
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 | ||
f648421a | 423 | return results |
0ec1854a MT |
424 | |
425 | ||
426 | class ConntrackProtocolWithStatesObject(ConntrackObject): | |
427 | def init(self, conntrack_table, protocol): | |
428 | ConntrackObject.init(self, conntrack_table) | |
429 | self.protocol = protocol | |
430 | ||
b54c3e02 MT |
431 | def __repr__(self): |
432 | return "<%s %s>" % (self.__class__.__name__, self.protocol) | |
433 | ||
0ec1854a MT |
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): | |
f648421a | 452 | return self.get_states() |
0ec1854a MT |
453 | |
454 | ||
455 | class ConntrackPlugin(base.Plugin): | |
456 | name = "conntrack" | |
457 | description = "Conntrack Plugin" | |
458 | ||
0308c0f3 MT |
459 | templates = [ |
460 | ConntrackLayer3ProtocolsGraphTemplate, | |
461 | ConntrackLayer4ProtocolsGraphTemplate, | |
462 | ConntrackProtocolWithStatesGraphTemplate, | |
463 | ] | |
0ec1854a MT |
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) |