1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Per-IP Bandwidth Monitoring

Discussion in 'Tomato Firmware' started by mikechml, Dec 17, 2010.

  1. mikechml

    mikechml Networkin' Nut Member

    Having search around for this, and not finding much, I decided to code it up myself. Since I found quite a few threads with people asking for it, I thought i'd post up what i've done to help anyone else.
    Note: The way i've done this is fairly hacky, and is not a simple drop-in solution. It will probably require some programming knowledge to modify for your needs.
    Also, the coding is quick and dirty. Let me know if there's any glaring errors.

    The basic process i've used is as follows: A script runs on the router every minute to read the data from iptables and clear it. This data is sent to a remote webserver which stores the data for that minute and generates a graph based on the last X data points. This graph is then saved on the router under /ext for viewing (I also included it on the Virgin Media STM Monitoring page I use).

    First off we need to get the bandwidth data for each IP - this is not stored by tomato by default. What I did was make a script to read off the connected devices and add each one to a new iptables chain to monitor the bandwidth usage:
    I put this in Administration -> Scripts -> Firewall to create the extra chains needed:
    Code:
    iptables -N bw_up
    iptables -N bw_down
    iptables -I FORWARD -j bw_up
    iptables -I FORWARD -j bw_down
    cru a BW-Monitor "* * * * * /jffs/bw.sh"
    Then this script runs every minute:
    Code:
    #!/bin/sh
    REMOTE_SCRIPT="http://nevar!"
    PASSWORD="nevar!"
    
    IFS="
    "
    IPs=`cat /proc/net/arp | grep br0 | cut -d' ' -f 1`
    
    GET="?password=$PASSWORD&qos_down="`nvram get qos_ibw`"&qos_up="`nvram get qos_obw`
    
    for line in `iptables -L bw_up -v -x | tail -n +3`; do
            name=`echo $line | awk '{ print $7 }' | sed s/[.-]/_/g`
            packets=`echo $line | awk '{ print $1 }'`
            bytes=`echo $line | awk '{ print $2 }'`
            eval data_$name=$packets:$bytes
    done
    
    for line in `iptables -L bw_down -v -x | tail -n +3`; do
            name=`echo $line | awk '{ print $8 }' | sed s/[.-]/_/g`
            packets=`echo $line | awk '{ print $1 }'`
            bytes=`echo $line | awk '{ print $2 }'`
            eval GET=\$GET"\"&data[]=\""$name:"$""data_$name":$packets:$bytes
    done
    wget "$REMOTE_SCRIPT$GET" -O /var/wwwext/bw.png
    
    # Clear the chains and add all connected IPs
    iptables -F bw_up
    iptables -F bw_down
    for IP in $IPs; do
            iptables -A bw_up -s $IP
            iptables -A bw_down -d $IP
    done
    This sends all the data to the following PHP script running on a remote server:
    Code:
    <?
        require_once ('jpgraph/jpgraph.php');
        require_once ('jpgraph/jpgraph_line.php');
        require_once ('jpgraph/jpgraph_mgraph.php');
    
        $password = "x";
        if ($_GET['password'] != $password) {
            mail('me', 'Unauthorised router_graph access', print_r($_SERVER)."\n".print_r($_GET));
            die('No.');
        }
    
        $data = array();
        foreach ($_GET['data'] as $line) {
            $split = explode(':', $line);
            $data[$split[0]] = array('packets_up' => $split[1], 'bytes_up' => $split[2], 'packets_down' => $split[3], 'bytes_down' => $split[4]);
        }
    
        $db = sqlite_open('data.db');
        if (!$db) die('DB open fail');
        @sqlite_query($db, 'CREATE TABLE bw (time INTEGER, name TEXT, packets_up INTEGER, bytes_up INTEGER, packets_down INTEGER, bytes_down INTEGER)');
        $time = time();
        foreach ($data as $name => $d) {
            $q = sprintf('INSERT INTO bw(time, name, packets_up, bytes_up, packets_down, bytes_down)
                                        VALUES(%d, \'%s\', %d, %d, %d, %d)', $time, sqlite_escape_string($name), $d['packets_up'], $d['bytes_up'], $d['packets_down'], $d['bytes_down']);
            sqlite_query($db, $q);
        }
    
        $names = array();
        $result = sqlite_query($db, 'SELECT DISTINCT name from bw');
        while ($entry = sqlite_fetch_array($result, SQLITE_ASSOC)) {
            $names[] = $entry['name'];
        }
    
        $start_time = time() - 60*60;
        $result = sqlite_query($db, sprintf('SELECT * FROM bw WHERE time > %d ORDER BY time ASC', $start_time));
        $data_up = array();
        $data_down = array();
        $i = -1;
        $prev_time = 0;
        while ($entry = sqlite_fetch_array($result, SQLITE_ASSOC)) {
            if ($prev_time == 0 || $entry['time'] != $prev_time) {
                $i++;
                foreach ($names as $name) {
                    $data_up[$name]['x'][$i] = date('H:i:s', $entry['time']);
                    $data_up[$name]['y'][$i] = 0;
    
                    $data_down[$name]['x'][$i] = date('H:i:s', $entry['time']);
                    $data_down[$name]['y'][$i] = 0;
                }
                $prev_time = $entry['time'];
            }
            $data_up[$entry['name']]['y'][$i] = $entry['bytes_up'] / 60 / 1024;
    
            $data_down[$entry['name']]['y'][$i] = $entry['bytes_down'] / 60 / 1024;
        }
    
    //  file_put_contents('log.txt', print_r($data_down, true));
        function getColor($i, $fill = false) {
            $colors = array('green', 'blue', 'red:0.5', 'orange', 'purple');
            $ret = $colors[$i % count($colors)];
    
    I used the JpGraph library to generate the graphs. Not had any other experience with it, but it seems nice. I would have used rrdtool for the whole thing, but it's a hassle to add extra data sources (ie: a new device connects to the network) without clearing the entire db.
    The graph generated by the PHP script is then saved, and should look a little something like this:
    [​IMG]
    (I was too lazy to work out how to label the x-axis nicely :))

    Someone mentioned to me Google's graph API might be a good option for this, and you could probably manage to store enough data on the jffs partition - so that could be an option for anyone without access to a webserver to do their bidding.
    It would also be possible to integrate it properly into tomato and use the built-in graphing system, but I didn't have the time to work that out.
     
  2. CBR900

    CBR900 LI Guru Member

    I guess it is the best choice...Please do it if it is possible

    10x
     
  3. dkirk

    dkirk Network Guru Member

    That is some good looking code, thanks. I have no need for it at this moment but I like what you're doing.
     
  4. dkirk

    dkirk Network Guru Member

    Another solution

    I stumbled upon another solution for per-IP monitoring. While it doesn't have the cool graphs of the OP it may offer something new:

    A small shell script designed to run on linux powered routers (OpenWRT, DD-WRT, but also other routers where shell access is available). It provides per user bandwidth monitoring capabilities and generates usage reports.

    http://code.google.com/p/wrtbwmon/wiki/Deploying
     
  5. CBR900

    CBR900 LI Guru Member

    May be victek can add it
    10x
     
  6. dkirk

    dkirk Network Guru Member

    Agreed, that would be an awesome addition to the current "Web Usage" functionality. It would be nice to have the option of real-time monitoring as well as aggregate by the day. There have been times when it would have been nice to see who was banging on the bandwidth right then and there.
     
  7. Victek

    Victek Network Guru Member

    Did you tried Tomato RAF releases? IP BW limiter is already built in and by using IMQ you can check using the normal realtime monitoring...
     
  8. dkirk

    dkirk Network Guru Member

    Your builds are the only ones I use. I don't care so much for the BW limiting as much as I do need the monitoring. I trust my users but want to see who is using it when I see a problem. Is there a way to configure the IMQ settings using the GUI or must I script it in the firewall section?
     
  9. shibby20

    shibby20 Network Guru Member

    like this

    If you use tomato k2.4 or k2.6 build52 or earlier, then use modprobe ipt_IMQ, not xt_IMQ.
     
  10. dkirk

    dkirk Network Guru Member

    Can the script be altered to show both UL and DL on one graph instead of having the two IMQ definitions, like eth0 displays, both on one graph?
     
  11. shibby20

    shibby20 Network Guru Member

    Unfortunately cant.
     
  12. dkirk

    dkirk Network Guru Member

    I enabled IP/MAC in my router running RAF, gave it three distinct IP numbers, and they do not show in the graph, should they? I'm running RAF1.28.8655 MIPSR2_RAF K26 USB VPN
     
  13. Victek

    Victek Network Guru Member

    dkirk, better use 8656 release, is hot from today and correct bugs of previous IP Range limiter.
     
  14. dkirk

    dkirk Network Guru Member

    Build 8656 loaded (thanks!) and yet I do not see anything in the bandwidth graphs. Once I define the IPs in the RAF IP/MAC bandwidth limiter do I need to still dabble with the iptables manually to see them on the bandwidth graphs?
     
  15. Victek

    Victek Network Guru Member

    dkirk, yes, no implementation of IP's in graphic bandwith
     

Share This Page