LEFT | RIGHT |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 | 2 |
3 import os, re, subprocess, sys, socket, struct, fcntl | 3 import os, re, subprocess, sys, socket, struct, fcntl |
4 | 4 |
5 INTERVAL = 5 | 5 INTERVAL = 5 |
6 | 6 |
7 def format_bandwidth(bits): | 7 def format_bandwidth(bits): |
8 if bits >= 1000000: | 8 if bits >= 1000000: |
9 return "%.2f Mbit/s" % (bits / 1000000) | 9 return "%.2f Mbit/s" % (bits / 1000000) |
10 elif bits >= 1000: | 10 elif bits >= 1000: |
11 return "%.2f kbit/s" % (bits / 1000) | 11 return "%.2f kbit/s" % (bits / 1000) |
12 else: | 12 else: |
13 return "%.2f bit/s" % bits | 13 return "%.2f bit/s" % bits |
14 | 14 |
15 def getmacaddress(): | 15 def getmacaddress(): |
16 # We are calling SIOCGIFHWADDR (0x8927 according to man ioctl_list) here. See | 16 # See man netdevice for the request structure: it has to start with 16 bytes |
17 # man netdevice for the request structure: it has to start with 16 bytes | |
18 # containing the interface name, the OS will write 8 bytes after that (2 bytes | 17 # containing the interface name, the OS will write 8 bytes after that (2 bytes |
19 # family name and 6 bytes actual MAC address). | 18 # family name and 6 bytes actual MAC address). |
20 s = socket.socket() | 19 s = socket.socket() |
21 return fcntl.ioctl(s.fileno(), 0x8927, struct.pack("24s", "eth0"))[18:24] | 20 SIOCGIFHWADDR = 0x8927 # see man ioctl_list |
22 | 21 return fcntl.ioctl(s.fileno(), SIOCGIFHWADDR, struct.pack("24s", "eth0"))[18:2
4] |
23 def splitport(hostport): | |
24 match = re.search(r"\.(d+)$", hostport) | |
25 if match: | |
26 return hostport[:match.start()], match.group(1) | |
27 else: | |
28 return hostport, "0" | |
29 | |
30 def readtime(time): | |
31 hour, minute, second = time.split(":") | |
32 return int(hour) * 3600 + int(minute) * 60 + float(second) | |
33 | 22 |
34 if __name__ == "__main__": | 23 if __name__ == "__main__": |
35 if len(sys.argv) != 3: | 24 if len(sys.argv) != 3: |
36 script_name = os.path.basename(sys.argv[0]) | 25 script_name = os.path.basename(sys.argv[0]) |
37 print "Usage: %s WARN CRIT" % script_name | 26 print "Usage: %s WARN CRIT" % script_name |
38 sys.exit(0) | 27 sys.exit(0) |
39 | 28 |
40 (warn, crit) = sys.argv[1:3] | 29 (warn, crit) = sys.argv[1:3] |
41 warn = int(sys.argv[1]) | 30 warn = int(sys.argv[1]) |
42 crit = int(sys.argv[2]) | 31 crit = int(sys.argv[2]) |
43 | 32 |
44 process = subprocess.Popen(["tcpdump", "-q", "-n", "-s", "64", "-w", "-"], std
out=subprocess.PIPE, stderr=subprocess.PIPE) | 33 process = subprocess.Popen( |
45 starttime = None | 34 ["sudo", "tcpdump", "-q", "-s", "64", "-G", str(INTERVAL), "-W", "1", "-w",
"-"], |
| 35 stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
46 mac = getmacaddress() | 36 mac = getmacaddress() |
47 | 37 |
48 total = {"rx": 0, "tx": 0} | 38 total = {"rx": 0, "tx": 0} |
49 http = {"rx": 0, "tx": 0} | 39 http = {"rx": 0, "tx": 0} |
50 https = {"rx": 0, "tx": 0} | 40 https = {"rx": 0, "tx": 0} |
51 ssh = {"rx": 0, "tx": 0} | 41 ssh = {"rx": 0, "tx": 0} |
52 dns = {"rx": 0, "tx": 0} | 42 dns = {"rx": 0, "tx": 0} |
53 other = {"rx": 0, "tx": 0} | 43 other = {"rx": 0, "tx": 0} |
54 other_detailed = {} | 44 other_detailed = {} |
55 | 45 |
56 # See http://wiki.wireshark.org/Development/LibpcapFileFormat for libpcap form
at description | 46 # See http://wiki.wireshark.org/Development/LibpcapFileFormat for libpcap form
at description |
57 magic_number, _, _, _, _, _, _ = struct.unpack("IHHiIII", process.stdout.read(
24)) | 47 global_header = process.stdout.read(24) |
| 48 magic_number, _, _, _, _, _, _ = struct.unpack("IHHiIII", global_header) |
58 if magic_number != 0xa1b2c3d4: | 49 if magic_number != 0xa1b2c3d4: |
59 raise Exception("Unexpected format") | 50 raise Exception("Unexpected format") |
60 while True: | 51 while True: |
61 sec, usec, incl_len, orig_len = struct.unpack("IIII", process.stdout.read(16
)) | 52 record_header = process.stdout.read(16) |
62 length = orig_len + 24 # 24 bytes Ethernet overhead not captured by tcpdum
p | 53 if record_header == "": |
| 54 break; |
| 55 _, _, incl_len, orig_len = struct.unpack("IIII", record_header) |
63 | 56 |
64 time = sec + float(usec) / 1000000 | 57 # Convert bytes to bits and normalize to seconds |
65 if starttime == None: | 58 bps = float(orig_len * 8) / INTERVAL |
66 starttime = time | |
67 if time - starttime > INTERVAL: | |
68 break | |
69 | 59 |
70 def add_other(description): | 60 def add_other(description): |
71 other[direction] += length | 61 other[direction] += bps |
72 other_detailed[description] = other_detailed.get(description, 0) + length | 62 other_detailed[description] = other_detailed.get(description, 0) + bps |
73 | 63 |
74 payload = process.stdout.read(incl_len) | 64 payload = process.stdout.read(incl_len) |
75 | 65 |
76 # Unpack Ethernet frame, http://en.wikipedia.org/wiki/Ethernet_frame#Structu
re | 66 # Unpack Ethernet frame, http://en.wikipedia.org/wiki/Ethernet_frame#Structu
re |
| 67 # Note that tcpdump doesn't capture the preamble, start of frame delimiter |
| 68 # and the interframe gap, these are handled internally by the network card. |
77 destination, source, protocol = struct.unpack("!6s6sH", payload[:14]) | 69 destination, source, protocol = struct.unpack("!6s6sH", payload[:14]) |
78 payload = payload[14:] | 70 payload = payload[14:] |
79 direction = "rx" if destination == mac else "tx" | 71 direction = "rx" if destination == mac else "tx" |
80 total[direction] += length | 72 total[direction] += bps |
81 | 73 |
82 # Check Level 3 protocol | 74 # Check Level 3 protocol |
83 if protocol == 0x0800: # IPv4, http://en.wikipedia.org/wiki/Internet_Pro
tocol_version_4#Header | 75 if protocol == 0x0800: # IPv4, http://en.wikipedia.org/wiki/Internet_Pro
tocol_version_4#Header |
84 ihl = ord(payload[0]) & 0xF | 76 ihl = ord(payload[0]) & 0xF |
85 protocol = ord(payload[9]) | 77 protocol = ord(payload[9]) |
86 payload = payload[ihl * 4:] | 78 payload = payload[ihl * 4:] |
87 elif protocol == 0x86DD: # IPv6, http://en.wikipedia.org/wiki/IPv6_packet#
Fixed_header | 79 elif protocol == 0x86DD: # IPv6, http://en.wikipedia.org/wiki/IPv6_packet#
Fixed_header |
88 protocol = ord(payload[6]) | 80 protocol = ord(payload[6]) |
89 payload = payload[40:] | 81 payload = payload[40:] |
90 else: | 82 else: |
91 add_other("L3 0x%04X" % protocol) | 83 add_other("L3 0x%04X" % protocol) |
92 continue | 84 continue |
93 | 85 |
94 # Check Level 4 protocol | 86 # Check Level 4 protocol |
95 if protocol in (0x06, 0x11): # TCP, UDP | 87 if protocol in (0x06, 0x11): # TCP, UDP |
| 88 source_port, destination_port = struct.unpack('!HH', payload[:4]) |
| 89 protocol = "TCP" if protocol == 0x06 else "UDP" |
| 90 |
96 # The lower port number should be the real port, the other one will be | 91 # The lower port number should be the real port, the other one will be |
97 # the ephemeral port. | 92 # the ephemeral port. |
98 source_port, destination_port = struct.unpack('!HH', payload[:4]) | |
99 protocol = "TCP" if protocol == 0x06 else "UDP" | |
100 port = min(source_port, destination_port) | 93 port = min(source_port, destination_port) |
101 else: | 94 else: |
102 add_other("L4 0x%02X" % protocol) | 95 add_other("L4 0x%02X" % protocol) |
103 continue | 96 continue |
104 | 97 |
105 if protocol == "TCP" and port == 80: | 98 if protocol == "TCP" and port == 80: |
106 http[direction] += length | 99 http[direction] += bps |
107 elif protocol == "TCP" and port == 443: | 100 elif protocol == "TCP" and port == 443: |
108 https[direction] += length | 101 https[direction] += bps |
109 elif protocol == "TCP" and port == 22: | 102 elif protocol == "TCP" and port == 22: |
110 ssh[direction] += length | 103 ssh[direction] += bps |
111 elif port == 53: | 104 elif port == 53: |
112 dns[direction] += length | 105 dns[direction] += bps |
113 else: | 106 else: |
114 add_other("Port %i" % port) | 107 add_other("Port %i" % port) |
115 continue | 108 continue |
116 | 109 |
117 status = [] | 110 status = [] |
118 perfdata = [] | 111 perfdata = [] |
119 def add_status(id, values): | 112 def add_status(id, values): |
120 rx = float(values["rx"]) * 8 / INTERVAL | 113 rx = values["rx"] |
121 tx = float(values["tx"]) * 8 / INTERVAL | 114 tx = values["tx"] |
122 status.append("%srx %s %stx %s" % (id, format_bandwidth(rx), id, format_band
width(tx))) | 115 status.append("%srx %s %stx %s" % (id, format_bandwidth(rx), id, format_band
width(tx))) |
123 if id == "": | 116 if id == "": |
124 perfdata.append("rx=%i;%i;%i tx=%i;%i;%i" % (rx, warn, crit, tx, warn, cri
t)) | 117 perfdata.append("rx=%i;%i;%i tx=%i;%i;%i" % (rx, warn, crit, tx, warn, cri
t)) |
125 else: | 118 else: |
126 perfdata.append("%srx=%i %stx=%i" % (id, rx, id, tx)) | 119 perfdata.append("%srx=%i %stx=%i" % (id, rx, id, tx)) |
127 | 120 |
128 add_status("", total) | 121 add_status("", total) |
129 add_status("http_", http) | 122 add_status("http_", http) |
130 add_status("https_", https) | 123 add_status("https_", https) |
131 add_status("ssh_", ssh) | 124 add_status("ssh_", ssh) |
132 add_status("dns_", dns) | 125 add_status("dns_", dns) |
133 add_status("other_", other) | 126 add_status("other_", other) |
134 for key in sorted(other_detailed.iterkeys(), key=lambda k: other_detailed[k],
reverse=True): | 127 for key in sorted(other_detailed.iterkeys(), key=lambda k: other_detailed[k],
reverse=True): |
135 status.append("%s %s" % (key, format_bandwidth(float(other_detailed[key]) /
INTERVAL))) | 128 status.append("%s %s" % (key, format_bandwidth(float(other_detailed[key]) /
INTERVAL))) |
136 | 129 |
137 output = "%s|%s" % (", ".join(status), " ".join(perfdata)) | 130 output = "%s|%s" % (", ".join(status), " ".join(perfdata)) |
138 | 131 |
139 rx = float(total["rx"]) * 8 / INTERVAL | 132 if total["rx"] >= crit or total["tx"] >= crit: |
140 tx = float(total["tx"]) * 8 / INTERVAL | |
141 if rx >= crit or tx >= crit: | |
142 print "CRITICAL - " + output | 133 print "CRITICAL - " + output |
143 sys.exit(2) | 134 sys.exit(2) |
144 | 135 |
145 if rx >= warn or tx >= warn: | 136 if total["rx"] >= warn or total["tx"] >= warn: |
146 print "WARNING - " + output | 137 print "WARNING - " + output |
147 sys.exit(1) | 138 sys.exit(1) |
148 | 139 |
149 print "OK - " + output | 140 print "OK - " + output |
LEFT | RIGHT |