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