munin-contrib/plugins/network/shorewall-accounting_

163 lines
6.0 KiB
Python
Executable File

#!/usr/bin/env python
# shorewall_accounting v1.3
#
# A munin plugin for tracking traffic as recorded by shorewall accounting rules.
# See "man shorewall-accounting" for all possible accounting rules.
# Basically this plugin examines the output of "shorewall -x show accounting".
# See http://atlee.ca/blog/2006/01/20/munin-shorewall/ for a description of
# the original script by Chris AtLee.
#
# Copyright 2010-2012 Lars Kruse <devel@sumpralle.de>
# Copyright 2006 Chris AtLee <chris@atlee.ca>
#
# Original publication: http://atlee.ca/blog/2006/01/20/munin-shorewall/
# Released under the GPL v3 or later
#
# You can use symlinks of this script according to the following pattern:
# shorewall-accounting_prot: use only rules with a specific protocol
# shorewall-accounting_in: use only rules with a specific input interface
# shorewall-accounting_out: use only rules with a specific output interface
# shorewall-accounting_source: use only rules with a specific source IP
# shorewall-accounting_destination: use only rules with a specific destination IP
# shorewall-accounting_details: use only rules with specific details (e.g. a port)
#
# Here "specific" means: non-default (e.g. a protocol was specified).
# Combinations are allowed:
# shorewall-accounting_prot_in_source: use only rules with a specific protocol, input interface and source IP
#
# Environment variables:
# SHOREWALL_BIN - defaults to /sbin/shorewall
#
# Changelog:
# v1.3 - 2012/04/02
# * renamed plugin file from "shorewall_accounting" to "shorewall-accounting_"
# * CAUTION: rename your symlinks and plugin config section!
# * added optional SHOREWALL_BIN environment variable
# * improved labels for rules (don't mask dots)
#
#%# family=auto
#%# capabilities=autoconf
import sys
import os
import commands
import re
PLUGIN_BASE_NAME = "shorewall-accounting"
SHOREWALL_BIN = os.environ.get("SHOREWALL_BIN", "/sbin/shorewall")
ACCOUNTING_LINE_EXP = re.compile(r"^\s*\d+\s+(\d+)\s+(?P<prot>\w+)\s+(?P<opt>[\w-]+)\s+(?P<in>[\w*]+)\s+(?P<out>[\w*]+)\s+(?P<source>[\w./+-]+)\s+(?P<destination>[\w./+-]+)\s*(?P<details>.*)\s*$")
KEY_ORDER = ["prot", "in", "out", "source", "destination", "details"]
FILTER_PATTERNS = {
"prot": r"^all$",
"in": r"^\*$",
"out": r"^\*$",
"source": r"^0\.0\.0\.0/0$",
"destination": r"^0\.0\.0\.0/0$",
"details": r"^$",
}
REPLACE_PATTERNS = {
"prot": ("^all$", "allProt"),
"in": (r"^\*$", "allIn"),
"out": (r"^\*$", "allOut"),
"source": (r"^0\.0\.0\.0/0", "allSrc"),
"destination": (r"^0\.0\.0\.0/0", "allDst"),
"details": (r"^multiport\s+", ""),
}
def get_accounting_rule_fieldname_and_label(regdict):
items = []
# filter and clean all requested keys
for key in KEY_ORDER:
raw = regdict[key]
pattern, replacement = REPLACE_PATTERNS[key]
value = re.sub(pattern, replacement, raw).strip()
if value:
items.append(value)
result = "_".join(items)
# clean the fieldname: http://munin-monitoring.org/wiki/notes_on_datasource_names
result = re.sub(r"^[^A-Za-z_]", "_", result)
fieldname = re.sub(r"[^A-Za-z0-9_]", "_", result)
# keep dots (for IP addresses)
label = re.sub(r"[^A-Za-z0-9_\.]", "_", result)
return fieldname, label
def is_wanted(regdict, filter_list):
for item in filter_list:
# is the item empty?
if not regdict[item]:
return False
# is the default value (unfiltered) set?
if re.search(FILTER_PATTERNS[item], regdict[item]):
return False
return True
def get_bytes_by_chain(filter_list):
status, output = commands.getstatusoutput("'%s' -x show accounting" \
% SHOREWALL_BIN)
if status != 0:
raise OSError("Error running command (%s)[%i]: %s" % (SHOREWALL_BIN,
status, output))
chains = {}
for line in output.splitlines():
m = ACCOUNTING_LINE_EXP.match(line)
if m is not None:
# check if this line was filtered
if not is_wanted(m.groupdict(), filter_list):
continue
fieldname, label = get_accounting_rule_fieldname_and_label(m.groupdict())
bytes = int(m.group(1))
if fieldname in chains:
chains[fieldname][1] += bytes
else:
chains[fieldname] = [label, bytes]
retval = []
names = chains.keys()
names.sort()
for name in names:
retval.append((name, chains[name][0], chains[name][1]))
return retval
# extract the filters from the symlink's name
# (e.g. "shorewall-accounting_in_out_details" -> in, out, details)
call_name = os.path.basename(sys.argv[0])
if call_name.startswith(PLUGIN_BASE_NAME):
suffix = call_name[len(PLUGIN_BASE_NAME):]
suffixes = suffix.split("_")
# use only suffixes that are listed in FILTER_PATTERNS
filter_list = [item for item in suffixes if item in FILTER_PATTERNS]
else:
filter_list = []
if len(sys.argv) > 1:
if sys.argv[1] == "autoconf":
status, output = commands.getstatusoutput("'%s' -x show accounting" \
% SHOREWALL_BIN)
if (status != 0) or not output:
print "no"
else:
print "yes"
elif sys.argv[1] == "config":
if not filter_list:
title_addon = "all"
else:
title_addon = " / ".join(filter_list)
print "graph_title Shorewall accounting: %s" % title_addon
print "graph_category network"
print "graph_vlabel bits per ${graph_period}"
for chain, label, bytes in get_bytes_by_chain(filter_list):
label = " ".join([item for item in label.split("_")
if not item.lower().startswith("all")])
if not label:
label = "all"
print "%s.min 0" % chain
print "%s.type DERIVE" % chain
print "%s.label %s" % (chain, label)
print "%s.cdef %s,8,*" % (chain, chain)
sys.exit(0)
for chain, label, bytes in get_bytes_by_chain(filter_list):
print "%s.value %i" % (chain, bytes)