#!/usr/bin/env ruby # # Munin plugin for the D-link DIR-655 router # # This plugin can graph # of wifi clients, # of DHCP clients, collisions & errors for all network interfaces, dropped packets for all interfaces, and both transmit and receive rates for all interfaces of the router. # # Author: David Reitz # # 1. Copy script to a Linux server on your network. # 2. Create symlink. (ln -s /path/to/dlink_dir655 /etc/munin/plugins/dlink_dir655) # 3. Edit plugin configuration for this script (vi /etc/munin/plugin-conf.d/munin-node) and add: # [dlink_dir655] # host_name dir655 # env.router_password password # env.router_ip_address 10.0.0.1 # 4. Edit munin configuration to point to itself to gather the data from your router (vi /etc/munin/munin-conf.d/munin) and add: # [dir655] # address 127.0.0.1 # use_node_name no # 5. Modify the 'password' and 'router_path' variables below to reflect the correct password for the 'User' user on your D-link DIR-655 router and to reflect the correct IP address of your router (this *MUST* be on your local subnet/LAN). # # NOTICE 1: This is the first Ruby script I've written (as well as being my first Munin plugin), so I consider this a bit of a hack. Just letting you know ahead of time. :) # # NOTICE 2: I provide this script *as-is*. Use at your own risk! I thought other people may find this script useful/interesting but have no plans to support it for anyone else. # # Many thanks go to Clarke Brunsdon for his code listed at http://clarkebrunsdon.com/2010/12/the-dlink-dir-655-hash-changes/!!! This really helped me out! # # Original Implementation: 13 Jan 2011 gem 'mechanize' require 'mechanize' require 'digest/md5' require 'nokogiri' def output nics = {} nics['LAN'] = {} nics['WAN'] = {} nics['WLAN'] = {} password = ENV['router_password'] || '' router_path = ENV['router_ip_address'] || '10.0.0.1' router_path = 'http://' + router_path agent = Mechanize.new x = agent.get(router_path) salt = x.body.match(/salt = "(.*)"/)[1] # pad the password to length 16 pad_size = (16 - password.length) padded_password = password + "\x01" * pad_size # pad it the rest of the way, length 64 for user salted_password = salt + padded_password + ("\x01" * (63 - salt.length - padded_password.length)) + 'U' login_hash = salt + Digest::MD5.hexdigest(salted_password) # authenticate against the router using the hash that we just built login_path = "#{router_path}/post_login.xml?hash=#{login_hash}" x = agent.get(login_path) # grab the statistics for all interfaces and parse it into a usable form clients_xml = agent.get("#{router_path}/interface_stats.xml").body doc = Nokogiri::XML(clients_xml.to_s) doc.xpath('//interface').each do |interface| children = interface.children name = children.search('name')[0].text nics[name]['packets_sent'] = children.search('packets_sent')[0].text nics[name]['packets_received'] = children.search('packets_received')[0].text nics[name]['tx_dropped'] = children.search('tx_dropped')[0].text begin nics[name]['tx_collisions'] = children.search('tx_collisions')[0].text rescue Exception nics[name]['tx_collisions'] = '0' end nics[name]['rx_dropped'] = children.search('rx_dropped')[0].text nics[name]['rx_errors'] = children.search('rx_errors')[0].text end # get wifi associations and print out info for munin graph puts 'multigraph clients' clients_xml = agent.get("#{router_path}/wifi_assoc.xml").body j = 0 doc = Nokogiri::XML(clients_xml.to_s) doc.xpath('//assoc').each do |_assoc| j += 1 end puts 'wifi_assoc.value ' + j.to_s # get dhcp clients and print out info for munin graph clients_xml = agent.get("#{router_path}/dhcp_clients.xml").body j = 0 doc = Nokogiri::XML(clients_xml.to_s) doc.xpath('//client').each do |_client| j += 1 end puts 'dhcp_clients.value ' + j.to_s puts 'multigraph uptime' # get uptime of connection clients_xml = agent.get("#{router_path}/wan_connection_status.xml").body doc = Nokogiri::XML(clients_xml.to_s) uptime = doc.children.search('wan_interface_up_time_0')[0].text puts 'uptime.value ' + format('%.2f', (Float(uptime) / 86_400)) # graph overall interface packets transferred per interval puts 'multigraph if_packets' %w[LAN WAN WLAN].each do |i| puts "#{i}_recv.value " + nics[i]['packets_received'] puts "#{i}_send.value " + nics[i]['packets_sent'] end # graph overall interface dropped packets per interval puts 'multigraph if_drop' %w[LAN WAN WLAN].each do |i| puts "#{i}_recv.value " + nics[i]['rx_dropped'] puts "#{i}_send.value " + nics[i]['tx_dropped'] end # graph overall interface collisions & errors per interval puts 'multigraph if_collerr' %w[LAN WAN WLAN].each do |i| puts "#{i}_coll.value " + nics[i]['tx_collisions'] puts "#{i}_err.value " + nics[i]['rx_errors'] end # graph stats for each interface %w[LAN WAN WLAN].each do |i| puts "multigraph if_packets.#{i}" puts 'send.value ' + nics[i]['packets_sent'] puts 'recv.value ' + nics[i]['packets_received'] puts "multigraph if_drop.#{i}" puts 'send.value ' + nics[i]['tx_dropped'] puts 'recv.value ' + nics[i]['rx_dropped'] puts "multigraph if_collerr.#{i}" puts 'coll.value ' + nics[i]['tx_collisions'] puts 'err.value ' + nics[i]['rx_errors'] end end def config # build the configuration for graphs puts 'multigraph if_packets' puts 'graph_title D-Link DIR-655 interface traffic' puts 'graph_category network' puts 'graph_order LAN_recv LAN_send WAN_recv WAN_send WLAN_recv WLAN_send' puts 'graph_vlabel packets in (-) / out (+) per ${graph_period}' %w[LAN WAN WLAN].each do |i| puts "#{i}_recv.type DERIVE" puts "#{i}_recv.graph no" puts "#{i}_recv.min 0" puts "#{i}_send.label #{i}" puts "#{i}_send.type DERIVE" puts "#{i}_send.negative #{i}_recv" puts "#{i}_send.min 0" end puts 'multigraph if_drop' puts 'graph_title D-Link DIR-655 interface drops' puts 'graph_category network' puts 'graph_order LAN_recv LAN_send WAN_recv WAN_send WLAN_recv WLAN_send' puts 'graph_vlabel packets / ${graph_period}' %w[LAN WAN WLAN].each do |i| puts "#{i}_recv.type DERIVE" puts "#{i}_recv.graph no" puts "#{i}_recv.min 0" puts "#{i}_send.label #{i}" puts "#{i}_send.type DERIVE" puts "#{i}_send.negative #{i}_recv" puts "#{i}_send.min 0" end puts 'multigraph if_collerr' puts 'graph_title D-Link DIR-655 interface collisions & errors' puts 'graph_category network' puts 'graph_order LAN_coll LAN_err WAN_coll WAN_err WLAN_coll WLAN_coll' puts 'graph_vlabel packets / ${graph_period}' %w[LAN WAN WLAN].each do |i| puts "#{i}_coll.label #{i} collisions" puts "#{i}_coll.type DERIVE" puts "#{i}_coll.min 0" puts "#{i}_err.label #{i} errors" puts "#{i}_err.type DERIVE" puts "#{i}_err.min 0" end puts 'multigraph clients' puts 'graph_title D-Link DIR-655 client information' puts 'graph_category system' puts 'graph_order dhcp_clients wifi_assoc' puts 'graph_vlabel number of clients' puts 'dhcp_clients.label DHCP clients' puts 'dhcp_clients.type GAUGE' puts 'dhcp_clients.min 0' puts 'wifi_assoc.label wifi clients' puts 'wifi_assoc.type GAUGE' puts 'wifi_assoc.min 0' puts 'multigraph uptime' puts 'graph_title Uptime' puts 'graph_vlabel uptime in days' puts 'graph_category system' puts 'uptime.label uptime' puts 'uptime.draw AREA' %w[LAN WAN WLAN].each do |i| puts "multigraph if_packets.#{i}" puts "graph_title D-Link DIR-655 #{i} traffic" puts 'graph_category network' puts 'graph_order recv send' puts 'graph_vlabel packets in (-) / out (+) per ${graph_period}' puts 'recv.label received' puts 'recv.type DERIVE' puts 'recv.graph no' puts 'recv.min 0' puts 'send.label packets/sec' puts 'send.type DERIVE' puts 'send.negative recv' puts 'send.min 0' puts "multigraph if_drop.#{i}" puts "graph_title D-Link DIR-655 #{i} drops" puts 'graph_category network' puts 'graph_order recv send' puts 'graph_vlabel packets / ${graph_period}' puts 'recv.label RX packets dropped' puts 'recv.type DERIVE' puts 'recv.graph no' puts 'recv.min 0' puts 'send.label TX packets dropped' puts 'send.type DERIVE' puts 'send.negative recv' puts 'send.min 0' puts "multigraph if_collerr.#{i}" puts "graph_title D-Link DIR-655 #{i} collisions & errors" puts 'graph_category network' puts 'graph_order coll err' puts 'graph_vlabel packets / ${graph_period}' puts 'coll.label collisions' puts 'coll.type DERIVE' puts 'coll.min 0' puts 'err.label errors' puts 'err.type DERIVE' puts 'err.min 0' end end # main if (ARGV.length == 1) && (ARGV[0] == 'config') config else output end