Index: modules/nagios/files/check_bandwidth |
=================================================================== |
--- a/modules/nagios/files/check_bandwidth |
+++ b/modules/nagios/files/check_bandwidth |
@@ -1,41 +1,140 @@ |
#!/usr/bin/env python |
-import os, re, subprocess, sys |
+import os, re, subprocess, sys, socket, struct, fcntl |
+ |
+INTERVAL = 5 |
def format_bandwidth(bits): |
if bits >= 1000000: |
return "%.2f Mbit/s" % (bits / 1000000) |
elif bits >= 1000: |
return "%.2f kbit/s" % (bits / 1000) |
else: |
return "%.2f bit/s" % bits |
+def getmacaddress(): |
+ # See man netdevice for the request structure: it has to start with 16 bytes |
+ # containing the interface name, the OS will write 8 bytes after that (2 bytes |
+ # family name and 6 bytes actual MAC address). |
+ s = socket.socket() |
+ SIOCGIFHWADDR = 0x8927 # see man ioctl_list |
+ return fcntl.ioctl(s.fileno(), SIOCGIFHWADDR, struct.pack("24s", "eth0"))[18:24] |
+ |
if __name__ == "__main__": |
if len(sys.argv) != 3: |
script_name = os.path.basename(sys.argv[0]) |
print "Usage: %s WARN CRIT" % script_name |
sys.exit(0) |
(warn, crit) = sys.argv[1:3] |
warn = int(sys.argv[1]) |
crit = int(sys.argv[2]) |
- process_output = subprocess.check_output(["bwm-ng", "-I", "eth0", "-t", "5000", "-c", "1", "-o", "csv"]) |
- data = process_output.splitlines()[0].split(";") |
- tx = float(data[2]) * 8 |
- rx = float(data[3]) * 8 |
- status = "rx %s tx %s" % (format_bandwidth(rx), format_bandwidth(tx)) |
+ process = subprocess.Popen( |
+ ["sudo", "tcpdump", "-q", "-s", "64", "-G", str(INTERVAL), "-W", "1", "-w", "-"], |
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
+ mac = getmacaddress() |
- perfdata = "rx=%i;%i;%i tx=%i;%i;%i" % (rx, warn, crit, tx, warn, crit) |
+ total = {"rx": 0, "tx": 0} |
+ http = {"rx": 0, "tx": 0} |
+ https = {"rx": 0, "tx": 0} |
+ ssh = {"rx": 0, "tx": 0} |
+ dns = {"rx": 0, "tx": 0} |
+ other = {"rx": 0, "tx": 0} |
+ other_detailed = {} |
- output = "%s|%s" % (status, perfdata) |
+ # See http://wiki.wireshark.org/Development/LibpcapFileFormat for libpcap format description |
+ global_header = process.stdout.read(24) |
+ magic_number, _, _, _, _, _, _ = struct.unpack("IHHiIII", global_header) |
+ if magic_number != 0xa1b2c3d4: |
+ raise Exception("Unexpected format") |
+ while True: |
+ record_header = process.stdout.read(16) |
+ if record_header == "": |
+ break; |
+ _, _, incl_len, orig_len = struct.unpack("IIII", record_header) |
- if rx >= crit or tx >= crit: |
+ # Convert bytes to bits and normalize to seconds |
+ bps = float(orig_len * 8) / INTERVAL |
+ |
+ def add_other(description): |
+ other[direction] += bps |
+ other_detailed[description] = other_detailed.get(description, 0) + bps |
+ |
+ payload = process.stdout.read(incl_len) |
+ |
+ # Unpack Ethernet frame, http://en.wikipedia.org/wiki/Ethernet_frame#Structure |
+ # Note that tcpdump doesn't capture the preamble, start of frame delimiter |
+ # and the interframe gap, these are handled internally by the network card. |
+ destination, source, protocol = struct.unpack("!6s6sH", payload[:14]) |
+ payload = payload[14:] |
+ direction = "rx" if destination == mac else "tx" |
+ total[direction] += bps |
+ |
+ # Check Level 3 protocol |
+ if protocol == 0x0800: # IPv4, http://en.wikipedia.org/wiki/Internet_Protocol_version_4#Header |
+ ihl = ord(payload[0]) & 0xF |
+ protocol = ord(payload[9]) |
+ payload = payload[ihl * 4:] |
+ elif protocol == 0x86DD: # IPv6, http://en.wikipedia.org/wiki/IPv6_packet#Fixed_header |
+ protocol = ord(payload[6]) |
+ payload = payload[40:] |
+ else: |
+ add_other("L3 0x%04X" % protocol) |
+ continue |
+ |
+ # Check Level 4 protocol |
+ if protocol in (0x06, 0x11): # TCP, UDP |
+ source_port, destination_port = struct.unpack('!HH', payload[:4]) |
+ protocol = "TCP" if protocol == 0x06 else "UDP" |
+ |
+ # The lower port number should be the real port, the other one will be |
+ # the ephemeral port. |
+ port = min(source_port, destination_port) |
+ else: |
+ add_other("L4 0x%02X" % protocol) |
+ continue |
+ |
+ if protocol == "TCP" and port == 80: |
+ http[direction] += bps |
+ elif protocol == "TCP" and port == 443: |
+ https[direction] += bps |
+ elif protocol == "TCP" and port == 22: |
+ ssh[direction] += bps |
+ elif port == 53: |
+ dns[direction] += bps |
+ else: |
+ add_other("Port %i" % port) |
+ continue |
+ |
+ status = [] |
+ perfdata = [] |
+ def add_status(id, values): |
+ rx = values["rx"] |
+ tx = values["tx"] |
+ status.append("%srx %s %stx %s" % (id, format_bandwidth(rx), id, format_bandwidth(tx))) |
+ if id == "": |
+ perfdata.append("rx=%i;%i;%i tx=%i;%i;%i" % (rx, warn, crit, tx, warn, crit)) |
+ else: |
+ perfdata.append("%srx=%i %stx=%i" % (id, rx, id, tx)) |
+ |
+ add_status("", total) |
+ add_status("http_", http) |
+ add_status("https_", https) |
+ add_status("ssh_", ssh) |
+ add_status("dns_", dns) |
+ add_status("other_", other) |
+ for key in sorted(other_detailed.iterkeys(), key=lambda k: other_detailed[k], reverse=True): |
+ status.append("%s %s" % (key, format_bandwidth(float(other_detailed[key]) / INTERVAL))) |
+ |
+ output = "%s|%s" % (", ".join(status), " ".join(perfdata)) |
+ |
+ if total["rx"] >= crit or total["tx"] >= crit: |
print "CRITICAL - " + output |
sys.exit(2) |
- if rx >= warn or tx >= warn: |
+ if total["rx"] >= warn or total["tx"] >= warn: |
print "WARNING - " + output |
sys.exit(1) |
print "OK - " + output |