Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: modules/nagios/files/check_bandwidth

Issue 12375002: Implement more detailed bandwidth monitoring (Closed)
Left Patch Set: Increased socket timeout and offloaded time calculations to tcpdump Created Oct. 9, 2013, 7:36 a.m.
Right Patch Set: Fixed comments Created Oct. 10, 2013, 9:36 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « manifests/monitoringserver.pp ('k') | modules/nagios/files/sudoers » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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
Felix Dahlke 2013/10/10 08:29:08 I'm pretty sure this will only work on Linux like
Wladimir Palant 2013/10/10 09:37:46 Yes, I've seen that. This function looks very much
21 return fcntl.ioctl(s.fileno(), SIOCGIFHWADDR, struct.pack("24s", "eth0"))[18:2 4]
22 22
23 if __name__ == "__main__": 23 if __name__ == "__main__":
24 if len(sys.argv) != 3: 24 if len(sys.argv) != 3:
25 script_name = os.path.basename(sys.argv[0]) 25 script_name = os.path.basename(sys.argv[0])
26 print "Usage: %s WARN CRIT" % script_name 26 print "Usage: %s WARN CRIT" % script_name
27 sys.exit(0) 27 sys.exit(0)
28 28
29 (warn, crit) = sys.argv[1:3] 29 (warn, crit) = sys.argv[1:3]
30 warn = int(sys.argv[1]) 30 warn = int(sys.argv[1])
31 crit = int(sys.argv[2]) 31 crit = int(sys.argv[2])
32 32
33 process = subprocess.Popen( 33 process = subprocess.Popen(
34 ["sudo", "tcpdump", "-q", "-s", "64", "-G", str(INTERVAL), "-W", "1", "-w", "-"], 34 ["sudo", "tcpdump", "-q", "-s", "64", "-G", str(INTERVAL), "-W", "1", "-w", "-"],
35 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 35 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
36 mac = getmacaddress() 36 mac = getmacaddress()
37 37
38 total = {"rx": 0, "tx": 0} 38 total = {"rx": 0, "tx": 0}
39 http = {"rx": 0, "tx": 0} 39 http = {"rx": 0, "tx": 0}
40 https = {"rx": 0, "tx": 0} 40 https = {"rx": 0, "tx": 0}
41 ssh = {"rx": 0, "tx": 0} 41 ssh = {"rx": 0, "tx": 0}
42 dns = {"rx": 0, "tx": 0} 42 dns = {"rx": 0, "tx": 0}
43 other = {"rx": 0, "tx": 0} 43 other = {"rx": 0, "tx": 0}
44 other_detailed = {} 44 other_detailed = {}
45 45
46 # 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
47 magic_number, _, _, _, _, _, _ = struct.unpack("IHHiIII", process.stdout.read( 24)) 47 global_header = process.stdout.read(24)
Felix Dahlke 2013/10/10 08:29:08 I'd find this more readable if the result of proce
48 magic_number, _, _, _, _, _, _ = struct.unpack("IHHiIII", global_header)
48 if magic_number != 0xa1b2c3d4: 49 if magic_number != 0xa1b2c3d4:
49 raise Exception("Unexpected format") 50 raise Exception("Unexpected format")
Felix Dahlke 2013/10/10 08:29:08 "Unsupported byte order" or something along those
Wladimir Palant 2013/10/10 09:37:46 No, there can be no other byte order - this script
50 while True: 51 while True:
51 header = process.stdout.read(16) 52 record_header = process.stdout.read(16)
Felix Dahlke 2013/10/10 08:29:08 header -> record_header?
52 if header == "": 53 if record_header == "":
53 break; 54 break;
54 _, _, incl_len, orig_len = struct.unpack("IIII", header) 55 _, _, incl_len, orig_len = struct.unpack("IIII", record_header)
55 56
56 # Convert bytes to bits and normalize to seconds 57 # Convert bytes to bits and normalize to seconds
57 length = float(orig_len * 8) / INTERVAL 58 bps = float(orig_len * 8) / INTERVAL
Felix Dahlke 2013/10/10 08:29:08 length -> bits_per_second?
Wladimir Palant 2013/10/10 09:37:46 bps?
Felix Dahlke 2013/10/10 09:44:34 Sure.
58 59
59 def add_other(description): 60 def add_other(description):
60 other[direction] += length 61 other[direction] += bps
61 other_detailed[description] = other_detailed.get(description, 0) + length 62 other_detailed[description] = other_detailed.get(description, 0) + bps
62 63
63 payload = process.stdout.read(incl_len) 64 payload = process.stdout.read(incl_len)
64 65
65 # 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.
66 destination, source, protocol = struct.unpack("!6s6sH", payload[:14]) 69 destination, source, protocol = struct.unpack("!6s6sH", payload[:14])
Felix Dahlke 2013/10/10 08:29:08 1. Shouldn't the offset be 8 instead of 14? What a
Wladimir Palant 2013/10/10 09:37:46 14 isn't offset but length here. The Ethernet fram
67 payload = payload[14:] 70 payload = payload[14:]
Felix Dahlke 2013/10/10 08:29:08 The payload should be the field after EtherType ac
Wladimir Palant 2013/10/10 09:37:46 As with the previous comment, preamble isn't being
68 direction = "rx" if destination == mac else "tx" 71 direction = "rx" if destination == mac else "tx"
69 total[direction] += length 72 total[direction] += bps
70 73
71 # Check Level 3 protocol 74 # Check Level 3 protocol
72 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
73 ihl = ord(payload[0]) & 0xF 76 ihl = ord(payload[0]) & 0xF
Felix Dahlke 2013/10/10 08:29:08 Shouldn't it be 0x4? 0xF would get us both version
Wladimir Palant 2013/10/10 09:37:46 No, that's correct - "& 0xF0" gives you the first
Felix Dahlke 2013/10/10 09:44:34 Um, yes, I confused something there, it's the numb
74 protocol = ord(payload[9]) 77 protocol = ord(payload[9])
75 payload = payload[ihl * 4:] 78 payload = payload[ihl * 4:]
76 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
77 protocol = ord(payload[6]) 80 protocol = ord(payload[6])
78 payload = payload[40:] 81 payload = payload[40:]
79 else: 82 else:
80 add_other("L3 0x%04X" % protocol) 83 add_other("L3 0x%04X" % protocol)
81 continue 84 continue
82 85
83 # Check Level 4 protocol 86 # Check Level 4 protocol
84 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
85 # 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
Felix Dahlke 2013/10/10 08:29:08 I think this comment should move down a bit, on to
86 # the ephemeral port. 92 # the ephemeral port.
87 source_port, destination_port = struct.unpack('!HH', payload[:4])
88 protocol = "TCP" if protocol == 0x06 else "UDP"
89 port = min(source_port, destination_port) 93 port = min(source_port, destination_port)
Felix Dahlke 2013/10/10 08:29:08 Why not do this based on the direction? port = so
Wladimir Palant 2013/10/10 09:37:46 Because our servers can open connections as well -
90 else: 94 else:
91 add_other("L4 0x%02X" % protocol) 95 add_other("L4 0x%02X" % protocol)
92 continue 96 continue
93 97
94 if protocol == "TCP" and port == 80: 98 if protocol == "TCP" and port == 80:
95 http[direction] += length 99 http[direction] += bps
96 elif protocol == "TCP" and port == 443: 100 elif protocol == "TCP" and port == 443:
97 https[direction] += length 101 https[direction] += bps
98 elif protocol == "TCP" and port == 22: 102 elif protocol == "TCP" and port == 22:
99 ssh[direction] += length 103 ssh[direction] += bps
100 elif port == 53: 104 elif port == 53:
101 dns[direction] += length 105 dns[direction] += bps
102 else: 106 else:
103 add_other("Port %i" % port) 107 add_other("Port %i" % port)
104 continue 108 continue
105 109
106 status = [] 110 status = []
107 perfdata = [] 111 perfdata = []
108 def add_status(id, values): 112 def add_status(id, values):
109 rx = values["rx"] 113 rx = values["rx"]
110 tx = values["tx"] 114 tx = values["tx"]
111 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)))
(...skipping 15 matching lines...) Expand all
127 131
128 if total["rx"] >= crit or total["tx"] >= crit: 132 if total["rx"] >= crit or total["tx"] >= crit:
129 print "CRITICAL - " + output 133 print "CRITICAL - " + output
130 sys.exit(2) 134 sys.exit(2)
131 135
132 if total["rx"] >= warn or total["tx"] >= warn: 136 if total["rx"] >= warn or total["tx"] >= warn:
133 print "WARNING - " + output 137 print "WARNING - " + output
134 sys.exit(1) 138 sys.exit(1)
135 139
136 print "OK - " + output 140 print "OK - " + output
LEFTRIGHT

Powered by Google App Engine
This is Rietveld